diff --git a/src/lib/components/FailToDeliver.svelte b/src/lib/components/FailToDeliver.svelte index 95dc61dc..9140675c 100644 --- a/src/lib/components/FailToDeliver.svelte +++ b/src/lib/components/FailToDeliver.svelte @@ -147,7 +147,7 @@ year: "numeric", month: "short", day: "numeric", - })}
${abbreviateNumber(this.y)}`; + })}
${abbreviateNumber(this.y)}`; }, }, @@ -167,7 +167,6 @@ }, yAxis: [ { - gridLineWidth: 0, labels: { enabled: false, }, @@ -176,7 +175,8 @@ }, }, { - gridLineWidth: 0, + gridLineWidth: 1, + gridLineColor: "#111827", labels: { enabled: false, }, diff --git a/src/lib/highcharts.ts b/src/lib/highcharts.ts index 972946b5..79d69c28 100644 --- a/src/lib/highcharts.ts +++ b/src/lib/highcharts.ts @@ -1,16 +1,58 @@ import Highcharts from 'highcharts'; +//import { browser } from '$app/environment'; + +/* +if (browser) { + import('highcharts/modules/exporting').then(module => { + module.default(Highcharts); + }); + + import('highcharts/modules/export-data').then(module => { + module.default(Highcharts); + }); +} + */ export default (node, config) => { - const redraw = true; - const oneToOne = true; - const chart = Highcharts.chart(node, config); + const redraw = true; + const oneToOne = true; + const chart = Highcharts.chart(node, { + ...config, + /* + exporting: { + filename: 'event-id-metadata-graph', + fetchOptions:{ + credentials: "omit", // omit, same-origin, include + mode: "cors" // cors, no-cors, same-origin + }, + buttons: { + customButton: { + menuItems: [ + 'viewFullscreen', + 'printChart', + 'separator', + 'downloadPNG', + 'downloadJPEG', + 'downloadPDF', + 'downloadSVG', + ], + className: 'bg-gray-500', + text: 'Custom button', + }, + }, + }, + */ + }); - return { - update(config) { - chart.update(config, redraw, oneToOne); - }, - destroy() { - chart.destroy(); - } - }; -} \ No newline at end of file + return { + update(newConfig) { + chart.update(newConfig, redraw, oneToOne); + }, + destroy() { + chart.destroy(); + }, + exportChart(options = {}, chartOptions = {}) { + chart.exportChart(options, chartOptions); + }, + }; +}; diff --git a/src/routes/stocks/[tickerID]/profile/employees/+page.svelte b/src/routes/stocks/[tickerID]/profile/employees/+page.svelte index f3c3dd11..b5b00f01 100644 --- a/src/routes/stocks/[tickerID]/profile/employees/+page.svelte +++ b/src/routes/stocks/[tickerID]/profile/employees/+page.svelte @@ -3,20 +3,18 @@ import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import { Button } from "$lib/components/shadcn/button/index.js"; import { goto } from "$app/navigation"; - import { Chart } from "svelte-echarts"; - import { init, use } from "echarts/core"; - import { BarChart } from "echarts/charts"; - import { GridComponent, TooltipComponent } from "echarts/components"; - import { CanvasRenderer } from "echarts/renderers"; + + import highcharts from "$lib/highcharts.ts"; + import Infobox from "$lib/components/Infobox.svelte"; import SEO from "$lib/components/SEO.svelte"; - use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]); - - import { abbreviateNumber } from "$lib/utils"; + import { abbreviateNumber, removeCompanyStrings } from "$lib/utils"; export let data; + let config = null; + let employeeHistory = data?.getHistoryEmployee ?? []; let historyList = sortByDate(employeeHistory); @@ -24,9 +22,6 @@ let changeRate = null; let growthRate = null; - let optionsTotal; - let optionsChange; - let optionsGrowth; let dateDistance = false; function formatWithDollarSign(value) { @@ -47,216 +42,136 @@ }); } - function plotTotal() { + function plotData() { let dateList = []; - let employeeList = []; + let valueList = []; - for (let i = 0; i < employeeHistory?.length; i++) { - const current = employeeHistory[i]?.employeeCount; - dateList?.push(employeeHistory[i]?.filingDate?.slice(0, 4)); - employeeList?.push(current); - } + // Create a sorted copy of the employee history from oldest to newest + const sortedHistory = [...employeeHistory].sort( + (a, b) => new Date(a.filingDate) - new Date(b.filingDate), + ); - const options = { - animation: false, - grid: { - left: $screenWidth < 640 ? "5%" : "0%", - right: $screenWidth < 640 ? "3%" : "0%", - top: "10%", - bottom: "10%", - containLabel: true, - }, - xAxis: { - data: dateList, - type: "category", - axisLabel: { - color: "#fff", - interval: (index, value) => - dateList.length > 12 ? index % 2 === 0 : 0, // Show every second label if there are too many - rotate: 45, // Rotate labels for better readability - fontSize: 12, - margin: 10, - }, - }, - yAxis: [ - { - type: "value", - splitLine: { - show: false, - }, - axisLabel: { - show: false, - }, - }, - ], - series: [ - { - name: "Total Employees", - data: employeeList, - type: "bar", - smooth: true, - itemStyle: { - color: "#fff", - }, - }, - ], - tooltip: { - trigger: "axis", - hideDelay: 100, - borderColor: "#969696", - borderWidth: 1, - backgroundColor: "#313131", - textStyle: { - color: "#fff", - }, - formatter: function (params) { - const date = params[0].name; - return `${date}
Employees: ${params[0].value?.toLocaleString("en-US")}`; - }, - }, - dataZoom: [ - { - type: "slider", // Adds a horizontal slider for zooming - start: 0, - end: 100, - }, - ], - }; + if (sortBy === "Total") { + sortedHistory.forEach((record) => { + dateList.push(record.filingDate.slice(0, 4)); + valueList.push(record.employeeCount); + }); + } else if (sortBy === "Change") { + for (let i = 0; i < sortedHistory.length - 1; i++) { + const current = sortedHistory[i].employeeCount; + const next = sortedHistory[i + 1].employeeCount; + const change = next - current; - return options; - } - - function plotChange() { - let dateList = []; - let changeList = []; - - for (let i = 0; i < employeeHistory?.length; i++) { - const current = employeeHistory[i]?.employeeCount; - const previous = i === 0 ? 0 : employeeHistory[i - 1]?.employeeCount; - const change = current - previous; - dateList?.push(employeeHistory[i]?.filingDate?.slice(0, 4)); - changeList?.push(change); - } - - const options = { - animation: false, - grid: { - left: "0%", - right: "0%", - top: "10%", - bottom: "20%", - containLabel: true, - }, - xAxis: { - data: dateList, - type: "category", - axisLabel: { - color: "#fff", - interval: 0, // Show all labels - rotate: 45, // Rotate labels for better readability - fontSize: 12, // Adjust font size if needed - }, - }, - yAxis: [ - { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines - }, - axisLabel: { - show: false, // Hide y-axis labels - }, - }, - ], - series: [ - { - name: "Change of Numbers", - data: changeList, - type: "bar", - barWidth: "80%", - smooth: true, - itemStyle: { - color: function (params) { - return params.data >= 0 ? "#22C55E" : "#F71F4F"; - }, - }, - }, - ], - tooltip: { - trigger: "axis", - }, - }; - - return options; - } - - function plotGrowth() { - let dateList = []; - let growthList = []; - - for (let i = 0; i < employeeHistory?.length; i++) { - const current = employeeHistory[i]?.employeeCount; - const previous = i === 0 ? 0 : employeeHistory[i - 1]?.employeeCount; - - if (current !== null && previous !== null && previous !== 0) { - const growth = (((current - previous) / previous) * 100)?.toFixed(2); - growthList?.push(growth); - } else { - growthList?.push(0); // Pushing null if the growth calculation is not possible + // Push the later date since the change happens between current and next + dateList.push(sortedHistory[i + 1].filingDate.slice(0, 4)); + valueList.push(change); } + } else if (sortBy === "Growth") { + for (let i = 0; i < sortedHistory.length - 1; i++) { + const current = sortedHistory[i].employeeCount; + const next = sortedHistory[i + 1].employeeCount; - dateList?.push(employeeHistory[i]?.filingDate?.slice(0, 4)); + if (current !== null && current !== 0) { + const growth = ((next - current) / current) * 100; + valueList.push(Number(growth.toFixed(2))); + } else { + valueList.push(0); + } + dateList.push(sortedHistory[i + 1].filingDate.slice(0, 4)); + } } const options = { - animation: false, - grid: { - left: "0%", - right: "0%", - top: "10%", - bottom: "20%", - containLabel: true, + credits: { + enabled: false, + }, + chart: { + type: "column", + backgroundColor: "#09090B", + plotBackgroundColor: "#09090B", + height: 360, + animation: false, + }, + title: { + text: `

${removeCompanyStrings($displayCompanyName)} Employees

`, + style: { + color: "white", + }, + useHTML: true, }, xAxis: { - data: dateList, - type: "category", - axisLabel: { - color: "#fff", - interval: 0, // Show all labels - rotate: 45, // Rotate labels for better readability - fontSize: 12, // Adjust font size if needed - margin: 10, + categories: dateList, + labels: { + style: { + color: "#fff", + fontSize: "12px", + }, + rotation: 45, + step: dateList.length > 12 ? 2 : 1, }, }, - yAxis: [ - { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines - }, - - axisLabel: { - show: false, // Hide y-axis labels + yAxis: { + gridLineWidth: 1, + gridLineColor: "#111827", + labels: { + style: { color: "white" }, + formatter: function () { + if (sortBy === "Growth") { + return this.value + "%"; + } + return this.value.toLocaleString(); }, }, - ], + title: { text: null }, + opposite: true, + }, + tooltip: { + useHTML: true, + backgroundColor: "#fff", + style: { + color: "black", + fontSize: "16px", + padding: "10px", + }, + borderRadius: 2, + borderWidth: 1, + borderColor: "#fff", + formatter: function () { + let value; + if (sortBy === "Growth") { + value = `${this.y >= 0 ? "+" : ""}${this.y.toFixed(2)}%`; + } else if (sortBy === "Change") { + value = (this.y >= 0 ? "+" : "") + this.y.toLocaleString(); + } else { + value = this.y.toLocaleString(); + } + + return ( + `${this.x}
` + + `${value}` + ); + }, + }, series: [ { - name: "Growth [%]", - data: growthList, - type: "bar", - barWidth: "80%", - smooth: true, - itemStyle: { - // Define colors based on positive/negative values - color: function (params) { - return params.data >= 0 ? "#22C55E" : "#F71F4F"; - }, - }, + name: sortBy, + data: valueList, + color: "#FFFFFF", + animation: false, }, ], - tooltip: { - trigger: "axis", + plotOptions: { + column: { + borderRadius: 2, + }, + }, + legend: { + enabled: false, + }, + navigation: { + buttonOptions: { + enabled: false, + }, }, }; @@ -394,15 +309,18 @@ 1) * 100 )?.toFixed(2); - optionsTotal = plotTotal(); - optionsChange = plotChange(); - optionsGrowth = plotGrowth(); htmlOutput = generateEmployeeInfoHTML(); } } let htmlOutput = generateEmployeeInfoHTML(); + + $: { + if (sortBy) { + config = plotData() || null; + } + }

- {$stockTicker} Employees + {removeCompanyStrings($displayCompanyName)} Employees

@@ -501,14 +419,16 @@
-
+

Employees Chart

{#if historyList?.length > 0} -
+
@@ -584,111 +504,110 @@
{#if historyList?.length !== 0} - {#if sortBy === "Total"} -
- -
- {:else if sortBy === "Change"} -
- -
- {:else if sortBy === "Growth"} -
- -
- {/if} +
-
- - - - - - - - - - - {#each historyList as item, index} - - + {#each historyList as item, index} + + + + + + + {/each} + +
- Date - - Employees - - Change - - Growth -
+

+ Employees History +

+ +
+ + + + - {/each} - -
- {new Date(item?.filingDate)?.toLocaleString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - daySuffix: "2-digit", - })} - - + - {new Intl.NumberFormat("en").format(item?.employeeCount)} - - + - {#if Number(item?.employeeCount - historyList[index + 1]?.employeeCount)} - {new Intl.NumberFormat("en").format( - item?.employeeCount - - historyList[index + 1]?.employeeCount, - )} - {:else} - n/a - {/if} - - + - {#if index === historyList?.length - 1} - n/a - {:else if item?.employeeCount > historyList[index + 1]?.employeeCount} - - +{( - ((item?.employeeCount - - historyList[index + 1]?.employeeCount) / - historyList[index + 1]?.employeeCount) * - 100 - ).toFixed(2)}% - - {:else if item?.employeeCount < historyList[index + 1]?.employeeCount} - - -{( - (Math.abs( - item?.employeeCount - - historyList[index + 1]?.employeeCount, - ) / - historyList[index + 1]?.employeeCount) * - 100 - ).toFixed(2)}% - - {:else} - n/a - {/if} - + Growth +
+ +
+ {new Date(item?.filingDate)?.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + daySuffix: "2-digit", + })} + + {new Intl.NumberFormat("en").format( + item?.employeeCount, + )} + + {#if Number(item?.employeeCount - historyList[index + 1]?.employeeCount)} + {new Intl.NumberFormat("en").format( + item?.employeeCount - + historyList[index + 1]?.employeeCount, + )} + {:else} + n/a + {/if} + + {#if index === historyList?.length - 1} + n/a + {:else if item?.employeeCount > historyList[index + 1]?.employeeCount} + + +{( + ((item?.employeeCount - + historyList[index + 1]?.employeeCount) / + historyList[index + 1]?.employeeCount) * + 100 + ).toFixed(2)}% + + {:else if item?.employeeCount < historyList[index + 1]?.employeeCount} + + -{( + (Math.abs( + item?.employeeCount - + historyList[index + 1]?.employeeCount, + ) / + historyList[index + 1]?.employeeCount) * + 100 + ).toFixed(2)}% + + {:else} + n/a + {/if} +
+
{:else}

${new Date( + return `${new Date( this?.x, ).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", - })}
${abbreviateNumber(this.y)}`; + })}
${abbreviateNumber(this.y)}`; }, },