update employees chart

This commit is contained in:
MuslemRahimi 2025-02-23 17:04:56 +01:00
parent 5c30d554fa
commit b590a2b402
4 changed files with 288 additions and 326 deletions

View File

@ -147,7 +147,7 @@
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric", day: "numeric",
})}</span> <br> <span class="text-black font-normal">${abbreviateNumber(this.y)}</span>`; })}</span> <br> <span class="text-black font-normal text-sm">${abbreviateNumber(this.y)}</span>`;
}, },
}, },
@ -167,7 +167,6 @@
}, },
yAxis: [ yAxis: [
{ {
gridLineWidth: 0,
labels: { labels: {
enabled: false, enabled: false,
}, },
@ -176,7 +175,8 @@
}, },
}, },
{ {
gridLineWidth: 0, gridLineWidth: 1,
gridLineColor: "#111827",
labels: { labels: {
enabled: false, enabled: false,
}, },

View File

@ -1,16 +1,58 @@
import Highcharts from 'highcharts'; 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) => { export default (node, config) => {
const redraw = true; const redraw = true;
const oneToOne = true; const oneToOne = true;
const chart = Highcharts.chart(node, config); 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 { return {
update(config) { update(newConfig) {
chart.update(config, redraw, oneToOne); chart.update(newConfig, redraw, oneToOne);
}, },
destroy() { destroy() {
chart.destroy(); chart.destroy();
} },
}; exportChart(options = {}, chartOptions = {}) {
} chart.exportChart(options, chartOptions);
},
};
};

View File

@ -3,20 +3,18 @@
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
import { Button } from "$lib/components/shadcn/button/index.js"; import { Button } from "$lib/components/shadcn/button/index.js";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { Chart } from "svelte-echarts";
import { init, use } from "echarts/core"; import highcharts from "$lib/highcharts.ts";
import { BarChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import Infobox from "$lib/components/Infobox.svelte"; import Infobox from "$lib/components/Infobox.svelte";
import SEO from "$lib/components/SEO.svelte"; import SEO from "$lib/components/SEO.svelte";
use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]); import { abbreviateNumber, removeCompanyStrings } from "$lib/utils";
import { abbreviateNumber } from "$lib/utils";
export let data; export let data;
let config = null;
let employeeHistory = data?.getHistoryEmployee ?? []; let employeeHistory = data?.getHistoryEmployee ?? [];
let historyList = sortByDate(employeeHistory); let historyList = sortByDate(employeeHistory);
@ -24,9 +22,6 @@
let changeRate = null; let changeRate = null;
let growthRate = null; let growthRate = null;
let optionsTotal;
let optionsChange;
let optionsGrowth;
let dateDistance = false; let dateDistance = false;
function formatWithDollarSign(value) { function formatWithDollarSign(value) {
@ -47,216 +42,136 @@
}); });
} }
function plotTotal() { function plotData() {
let dateList = []; let dateList = [];
let employeeList = []; let valueList = [];
for (let i = 0; i < employeeHistory?.length; i++) { // Create a sorted copy of the employee history from oldest to newest
const current = employeeHistory[i]?.employeeCount; const sortedHistory = [...employeeHistory].sort(
dateList?.push(employeeHistory[i]?.filingDate?.slice(0, 4)); (a, b) => new Date(a.filingDate) - new Date(b.filingDate),
employeeList?.push(current); );
}
const options = { if (sortBy === "Total") {
animation: false, sortedHistory.forEach((record) => {
grid: { dateList.push(record.filingDate.slice(0, 4));
left: $screenWidth < 640 ? "5%" : "0%", valueList.push(record.employeeCount);
right: $screenWidth < 640 ? "3%" : "0%", });
top: "10%", } else if (sortBy === "Change") {
bottom: "10%", for (let i = 0; i < sortedHistory.length - 1; i++) {
containLabel: true, const current = sortedHistory[i].employeeCount;
}, const next = sortedHistory[i + 1].employeeCount;
xAxis: { const change = next - current;
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}<br/> Employees: ${params[0].value?.toLocaleString("en-US")}`;
},
},
dataZoom: [
{
type: "slider", // Adds a horizontal slider for zooming
start: 0,
end: 100,
},
],
};
return options; // Push the later date since the change happens between current and next
} dateList.push(sortedHistory[i + 1].filingDate.slice(0, 4));
valueList.push(change);
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
} }
} 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 = { const options = {
animation: false, credits: {
grid: { enabled: false,
left: "0%", },
right: "0%", chart: {
top: "10%", type: "column",
bottom: "20%", backgroundColor: "#09090B",
containLabel: true, plotBackgroundColor: "#09090B",
height: 360,
animation: false,
},
title: {
text: `<h3 class="mt-3 mb-1">${removeCompanyStrings($displayCompanyName)} Employees</h3>`,
style: {
color: "white",
},
useHTML: true,
}, },
xAxis: { xAxis: {
data: dateList, categories: dateList,
type: "category", labels: {
axisLabel: { style: {
color: "#fff", color: "#fff",
interval: 0, // Show all labels fontSize: "12px",
rotate: 45, // Rotate labels for better readability },
fontSize: 12, // Adjust font size if needed rotation: 45,
margin: 10, step: dateList.length > 12 ? 2 : 1,
}, },
}, },
yAxis: [ yAxis: {
{ gridLineWidth: 1,
type: "value", gridLineColor: "#111827",
splitLine: { labels: {
show: false, // Disable x-axis grid lines style: { color: "white" },
}, formatter: function () {
if (sortBy === "Growth") {
axisLabel: { return this.value + "%";
show: false, // Hide y-axis labels }
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 (
`<span class="m-auto text-black text-[1rem] font-[501]">${this.x}</span><br>` +
`<span class="text-black font-normal text-sm">${value}</span>`
);
},
},
series: [ series: [
{ {
name: "Growth [%]", name: sortBy,
data: growthList, data: valueList,
type: "bar", color: "#FFFFFF",
barWidth: "80%", animation: false,
smooth: true,
itemStyle: {
// Define colors based on positive/negative values
color: function (params) {
return params.data >= 0 ? "#22C55E" : "#F71F4F";
},
},
}, },
], ],
tooltip: { plotOptions: {
trigger: "axis", column: {
borderRadius: 2,
},
},
legend: {
enabled: false,
},
navigation: {
buttonOptions: {
enabled: false,
},
}, },
}; };
@ -394,15 +309,18 @@
1) * 1) *
100 100
)?.toFixed(2); )?.toFixed(2);
optionsTotal = plotTotal();
optionsChange = plotChange();
optionsGrowth = plotGrowth();
htmlOutput = generateEmployeeInfoHTML(); htmlOutput = generateEmployeeInfoHTML();
} }
} }
let htmlOutput = generateEmployeeInfoHTML(); let htmlOutput = generateEmployeeInfoHTML();
$: {
if (sortBy) {
config = plotData() || null;
}
}
</script> </script>
<SEO <SEO
@ -418,7 +336,7 @@
<div class="sm:pl-7 sm:pb-7 sm:pt-7 w-full m-auto mt-2 sm:mt-0"> <div class="sm:pl-7 sm:pb-7 sm:pt-7 w-full m-auto mt-2 sm:mt-0">
<div class="mb-6"> <div class="mb-6">
<h2 class="text-xl sm:text-2xl text-white font-bold mb-4"> <h2 class="text-xl sm:text-2xl text-white font-bold mb-4">
{$stockTicker} Employees {removeCompanyStrings($displayCompanyName)} Employees
</h2> </h2>
<Infobox text={htmlOutput} /> <Infobox text={htmlOutput} />
@ -501,14 +419,16 @@
</div> </div>
</div> </div>
<div class="flex flex-col sm:flex-row items-center w-full mt-10 mb-8"> <div
class="flex flex-col sm:flex-row items-start sm:items-center w-full mt-10 mb-4"
>
<h1 <h1
class="text-xl sm:text-2xl text-white font-bold text-start mr-auto mb-4 sm:mb-0" class="text-xl sm:text-2xl text-white font-bold text-start mr-auto mb-4 sm:mb-0"
> >
Employees Chart Employees Chart
</h1> </h1>
{#if historyList?.length > 0} {#if historyList?.length > 0}
<div class="flex flex-row items-center w-fit ml-auto"> <div class="flex flex-row items-center w-fit sm:ml-auto">
<div class="relative inline-block text-left grow"> <div class="relative inline-block text-left grow">
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder> <DropdownMenu.Trigger asChild let:builder>
@ -584,111 +504,110 @@
</div> </div>
{#if historyList?.length !== 0} {#if historyList?.length !== 0}
{#if sortBy === "Total"} <div
<div class="app w-full"> class="chart mt-5 sm:mt-0 border border-gray-800 rounded"
<Chart {init} options={optionsTotal} class="chart" /> use:highcharts={config}
</div> ></div>
{:else if sortBy === "Change"}
<div class="app w-full">
<Chart {init} options={optionsChange} class="chart" />
</div>
{:else if sortBy === "Growth"}
<div class="app w-full">
<Chart {init} options={optionsGrowth} class="chart" />
</div>
{/if}
<div class="w-full overflow-x-scroll"> <div class="mt-5">
<table <h3 class=" text-xl sm:text-2xl text-white font-bold mb-2 sm:mb-0">
class="table table-sm table-compact rounded-none sm:rounded-md w-full bg-table border border-gray-800 m-auto" Employees History
> </h3>
<thead class="bg-default">
<tr> <div class="mt-5 w-full overflow-x-scroll">
<th <table
class="text-start text-white text-sm whitespace-nowrap font-semibold" class="table table-sm table-compact rounded-none sm:rounded-md w-full bg-table border border-gray-800 m-auto"
> >
Date <thead class="bg-default">
</th> <tr>
<th <th
class="text-end text-white text-sm whitespace-nowrap font-semibold" class="text-start text-white text-sm whitespace-nowrap font-semibold"
>
Employees
</th>
<th
class="text-end text-white text-sm whitespace-nowrap font-semibold"
>
Change
</th>
<th
class="text-end text-white text-sm whitespace-nowrap font-semibold"
>
Growth
</th>
</tr>
</thead>
<tbody class="">
{#each historyList as item, index}
<tr class="text-white odd:bg-odd border-b border-gray-800">
<td
class="text-start text-sm sm:text-[1rem] whitespace-nowrap text-white"
> >
{new Date(item?.filingDate)?.toLocaleString("en-US", { Date
month: "short", </th>
day: "numeric", <th
year: "numeric", class="text-end text-white text-sm whitespace-nowrap font-semibold"
daySuffix: "2-digit",
})}
</td>
<td
class="text-end text-sm sm:text-[1rem] whitespace-nowrap text-white"
> >
{new Intl.NumberFormat("en").format(item?.employeeCount)} Employees
</td> </th>
<td <th
class="text-end text-sm sm:text-[1rem] whitespace-nowrap text-white" class="text-end text-white text-sm whitespace-nowrap font-semibold"
> >
{#if Number(item?.employeeCount - historyList[index + 1]?.employeeCount)} Change
{new Intl.NumberFormat("en").format( </th>
item?.employeeCount - <th
historyList[index + 1]?.employeeCount, class="text-end text-white text-sm whitespace-nowrap font-semibold"
)}
{:else}
n/a
{/if}
</td>
<td
class="text-end text-sm sm:text-[1rem] whitespace-nowrap text-white text-end"
> >
{#if index === historyList?.length - 1} Growth
n/a </th>
{:else if item?.employeeCount > historyList[index + 1]?.employeeCount}
<span class="text-[#00FC50]">
+{(
((item?.employeeCount -
historyList[index + 1]?.employeeCount) /
historyList[index + 1]?.employeeCount) *
100
).toFixed(2)}%
</span>
{:else if item?.employeeCount < historyList[index + 1]?.employeeCount}
<span class="text-[#FF2F1F]">
-{(
(Math.abs(
item?.employeeCount -
historyList[index + 1]?.employeeCount,
) /
historyList[index + 1]?.employeeCount) *
100
).toFixed(2)}%
</span>
{:else}
n/a
{/if}
</td>
</tr> </tr>
{/each} </thead>
</tbody> <tbody class="">
</table> {#each historyList as item, index}
<tr class="text-white odd:bg-odd border-b border-gray-800">
<td
class="text-start text-sm sm:text-[1rem] whitespace-nowrap text-white"
>
{new Date(item?.filingDate)?.toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
daySuffix: "2-digit",
})}
</td>
<td
class="text-end text-sm sm:text-[1rem] whitespace-nowrap text-white"
>
{new Intl.NumberFormat("en").format(
item?.employeeCount,
)}
</td>
<td
class="text-end text-sm sm:text-[1rem] whitespace-nowrap text-white"
>
{#if Number(item?.employeeCount - historyList[index + 1]?.employeeCount)}
{new Intl.NumberFormat("en").format(
item?.employeeCount -
historyList[index + 1]?.employeeCount,
)}
{:else}
n/a
{/if}
</td>
<td
class="text-end text-sm sm:text-[1rem] whitespace-nowrap text-white text-end"
>
{#if index === historyList?.length - 1}
n/a
{:else if item?.employeeCount > historyList[index + 1]?.employeeCount}
<span class="text-[#00FC50]">
+{(
((item?.employeeCount -
historyList[index + 1]?.employeeCount) /
historyList[index + 1]?.employeeCount) *
100
).toFixed(2)}%
</span>
{:else if item?.employeeCount < historyList[index + 1]?.employeeCount}
<span class="text-[#FF2F1F]">
-{(
(Math.abs(
item?.employeeCount -
historyList[index + 1]?.employeeCount,
) /
historyList[index + 1]?.employeeCount) *
100
).toFixed(2)}%
</span>
{:else}
n/a
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div> </div>
{:else} {:else}
<h1 <h1

View File

@ -120,7 +120,8 @@
}, },
}, },
yAxis: { yAxis: {
gridLineWidth: 0, gridLineWidth: 1,
gridLineColor: "#111827",
labels: { labels: {
style: { color: "white" }, style: { color: "white" },
}, },
@ -137,15 +138,15 @@
}, },
borderRadius: 2, borderRadius: 2,
borderWidth: 1, borderWidth: 1,
borderColor: "#ffffff", borderColor: "#fff",
formatter: function () { formatter: function () {
return `<span class="m-auto text-black text-[1rem] font-semibold">${new Date( return `<span class="m-auto text-black text-[1rem] font-[501]">${new Date(
this?.x, this?.x,
).toLocaleDateString("en-US", { ).toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric", day: "numeric",
})}</span> <br> <span class="text-black font-normal">${abbreviateNumber(this.y)}</span>`; })}</span> <br> <span class="text-black font-normal text-sm">${abbreviateNumber(this.y)}</span>`;
}, },
}, },