update chart
This commit is contained in:
parent
9ed0af466b
commit
b92dd0eaed
@ -1,61 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import highcharts from "$lib/highcharts.ts";
|
import highcharts from "$lib/highcharts.ts";
|
||||||
import { abbreviateNumber } from "$lib/utils";
|
import { abbreviateNumber } from "$lib/utils";
|
||||||
|
import Lazy from "svelte-lazy";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let displayStatement;
|
export let displayStatement;
|
||||||
export let filterRule = "annual";
|
export let filterRule = "annual";
|
||||||
export let tableList = [];
|
|
||||||
export let statementConfig;
|
export let statementConfig;
|
||||||
|
export let processedData = null; // Accept pre-processed data
|
||||||
|
|
||||||
let config = null;
|
let config = null;
|
||||||
|
let isLoaded = false;
|
||||||
|
let chartElement;
|
||||||
|
|
||||||
function plotData() {
|
// Memoize chart options to prevent unnecessary recalculations
|
||||||
let labelName = "-";
|
function getChartOptions(xList, valueList, labelName) {
|
||||||
let xList = [];
|
return {
|
||||||
let valueList = [];
|
|
||||||
tableList = []; // Assuming tableList is a global variable
|
|
||||||
|
|
||||||
const index = statementConfig?.findIndex(
|
|
||||||
(item) => item?.propertyName === displayStatement,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Loop through the income array in reverse order
|
|
||||||
for (let i = data?.length - 1; i >= 0; i--) {
|
|
||||||
const statement = data[i];
|
|
||||||
const year = statement?.calendarYear?.slice(-2);
|
|
||||||
const quarter = statement?.period;
|
|
||||||
|
|
||||||
// Determine label based on filterRule
|
|
||||||
if (filterRule === "annual") {
|
|
||||||
xList.push("FY" + year);
|
|
||||||
} else {
|
|
||||||
xList.push("FY" + year + " " + quarter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the value, converting to a number
|
|
||||||
const rawValue = Number(statement[statementConfig[index]?.propertyName]);
|
|
||||||
const value = parseFloat(rawValue.toFixed(2));
|
|
||||||
valueList.push(value);
|
|
||||||
|
|
||||||
// Populate tableList
|
|
||||||
tableList.push({
|
|
||||||
date: statement?.date,
|
|
||||||
value: value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort tableList by date (newest first)
|
|
||||||
tableList?.sort((a, b) => new Date(b?.date) - new Date(a?.date));
|
|
||||||
|
|
||||||
labelName = statementConfig[index]?.label;
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
chart: {
|
chart: {
|
||||||
type: "column",
|
type: "column",
|
||||||
backgroundColor: "#09090B",
|
backgroundColor: "#09090B",
|
||||||
plotBackgroundColor: "#09090B",
|
plotBackgroundColor: "#09090B",
|
||||||
height: 360, // Set the maximum height for the chart
|
height: 360,
|
||||||
animation: false,
|
animation: false,
|
||||||
},
|
},
|
||||||
credits: { enabled: false },
|
credits: { enabled: false },
|
||||||
@ -83,12 +49,19 @@
|
|||||||
style: { color: "white" },
|
style: { color: "white" },
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
|
endOnTick: false,
|
||||||
categories: xList,
|
categories: xList,
|
||||||
labels: {
|
crosshair: {
|
||||||
style: { color: "#fff" },
|
color: "#fff", // Set the color of the crosshair line
|
||||||
|
width: 1, // Adjust the line width as needed
|
||||||
|
dashStyle: "Solid",
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
distance: 10, // Increases space between label and axis
|
||||||
},
|
},
|
||||||
lineColor: "#fff",
|
|
||||||
tickColor: "#fff",
|
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
gridLineWidth: 1,
|
gridLineWidth: 1,
|
||||||
@ -105,8 +78,8 @@
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
shared: true,
|
shared: true,
|
||||||
useHTML: true,
|
useHTML: true,
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.8)", // Semi-transparent black
|
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
||||||
borderColor: "rgba(255, 255, 255, 0.2)", // Slightly visible white border
|
borderColor: "rgba(255, 255, 255, 0.2)",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
style: {
|
style: {
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
@ -115,17 +88,13 @@
|
|||||||
},
|
},
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
formatter: function () {
|
formatter: function () {
|
||||||
// Format the x value to display time in hh:mm format
|
|
||||||
let tooltipContent = `<span class="text-white m-auto text-black text-[1rem] font-[501]">${this?.x}</span><br>`;
|
let tooltipContent = `<span class="text-white m-auto text-black text-[1rem] font-[501]">${this?.x}</span><br>`;
|
||||||
|
|
||||||
// Loop through each point in the shared tooltip
|
|
||||||
this.points.forEach((point) => {
|
this.points.forEach((point) => {
|
||||||
tooltipContent += `<span class="text-white font-semibold text-sm">${point.series.name}:</span>
|
tooltipContent += `<span class="text-white font-semibold text-sm">${point.series.name}:</span>
|
||||||
<span class="text-white font-normal text-sm" style="color:${point.color}">${abbreviateNumber(
|
<span class="text-white font-normal text-sm" style="color:${point.color}">${abbreviateNumber(
|
||||||
point.y,
|
point.y,
|
||||||
)}</span><br>`;
|
)}</span><br>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return tooltipContent;
|
return tooltipContent;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -134,21 +103,67 @@
|
|||||||
name: labelName,
|
name: labelName,
|
||||||
data: valueList,
|
data: valueList,
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
|
borderRadius: "1px",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
async function plotData() {
|
||||||
if (filterRule || displayStatement || data) {
|
isLoaded = false;
|
||||||
config = plotData() || null;
|
|
||||||
|
// Simulate some processing time to ensure loading state is visible
|
||||||
|
// This can be removed in production if the actual data processing takes enough time
|
||||||
|
if (!processedData) {
|
||||||
|
// If no processed data, just show loading
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add a small delay to ensure the loading state is visible
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
|
||||||
|
const { xList, valueList, labelName } = processedData[displayStatement];
|
||||||
|
return getChartOptions(xList, valueList, labelName);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing chart data:", error);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
// Don't set isLoaded here - we'll set it after the chart is rendered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function will be called whenever the relevant props change
|
||||||
|
async function updateChart() {
|
||||||
|
config = await plotData();
|
||||||
|
// Only set isLoaded to true after a small delay to ensure the chart is fully rendered
|
||||||
|
setTimeout(() => {
|
||||||
|
isLoaded = true;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
updateChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for changes in props and update chart accordingly
|
||||||
|
$: if (filterRule || displayStatement || data || processedData) {
|
||||||
|
updateChart();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if !isLoaded}
|
||||||
|
<div
|
||||||
|
class="w-full h-[360px] flex justify-center items-center m-auto border border-gray-800 rounded"
|
||||||
|
>
|
||||||
|
<span class="loading loading-bars loading-sm"></span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Lazy fadeOption={{ delay: 50, duration: 100 }} keep={true}>
|
||||||
<div
|
<div
|
||||||
class="border border-gray-800 rounded w-full"
|
class="border border-gray-800 rounded w-full"
|
||||||
use:highcharts={config}
|
use:highcharts={config}
|
||||||
|
bind:this={chartElement}
|
||||||
></div>
|
></div>
|
||||||
|
</Lazy>
|
||||||
|
{/if}
|
||||||
|
|||||||
@ -17,6 +17,9 @@ export default (node, config) => {
|
|||||||
const createChart = () => {
|
const createChart = () => {
|
||||||
chart = Highcharts.chart(node, {
|
chart = Highcharts.chart(node, {
|
||||||
...config,
|
...config,
|
||||||
|
accessibility: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
chart: {
|
chart: {
|
||||||
...(config.chart || {}),
|
...(config.chart || {}),
|
||||||
events: {
|
events: {
|
||||||
|
|||||||
@ -5,21 +5,20 @@
|
|||||||
coolMode,
|
coolMode,
|
||||||
timeFrame,
|
timeFrame,
|
||||||
} from "$lib/store";
|
} from "$lib/store";
|
||||||
import { abbreviateNumber, removeCompanyStrings } from "$lib/utils";
|
import { removeCompanyStrings } from "$lib/utils";
|
||||||
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 * as XLSX from 'xlsx';
|
//import * as XLSX from 'xlsx';
|
||||||
import FinancialTable from "$lib/components/FinancialTable.svelte";
|
import FinancialTable from "$lib/components/FinancialTable.svelte";
|
||||||
import FinancialChart from "$lib/components/FinancialChart.svelte";
|
import FinancialChart from "$lib/components/FinancialChart.svelte";
|
||||||
|
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import SEO from "$lib/components/SEO.svelte";
|
import SEO from "$lib/components/SEO.svelte";
|
||||||
import Lazy from "svelte-lazy";
|
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
let isLoaded = true;
|
let isLoaded = true;
|
||||||
let tableList = [];
|
let tableList = [];
|
||||||
|
let processedData = {};
|
||||||
|
|
||||||
let income = [];
|
let income = [];
|
||||||
let fullStatement = [];
|
let fullStatement = [];
|
||||||
@ -250,6 +249,61 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pre-process all data once instead of in each component
|
||||||
|
function preprocessFinancialData() {
|
||||||
|
processedData = {};
|
||||||
|
|
||||||
|
// Precompute mapping from propertyName to config for quick lookup
|
||||||
|
const configMap = {};
|
||||||
|
statementConfig.forEach((item) => {
|
||||||
|
if (item && item.propertyName) {
|
||||||
|
configMap[item.propertyName] = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Precompute xList from income (reverse order)
|
||||||
|
const xList = [];
|
||||||
|
for (let i = income.length - 1; i >= 0; i--) {
|
||||||
|
const statement = income[i];
|
||||||
|
const year = statement.calendarYear.slice(-2);
|
||||||
|
const quarter = statement.period;
|
||||||
|
xList.push(
|
||||||
|
filterRule === "annual" ? "FY" + year : "FY" + year + " " + quarter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each field using precomputed config and xList
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const statementKey = field.key;
|
||||||
|
const config = configMap[statementKey];
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
const valueList = [];
|
||||||
|
// Loop through income in reverse to match xList order
|
||||||
|
for (let i = income.length - 1; i >= 0; i--) {
|
||||||
|
const statement = income[i];
|
||||||
|
const rawValue = Number(statement[config.propertyName]);
|
||||||
|
// Round to two decimals
|
||||||
|
const value = parseFloat(rawValue.toFixed(2));
|
||||||
|
valueList.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
processedData[statementKey] = {
|
||||||
|
xList, // re-use the precomputed labels
|
||||||
|
valueList,
|
||||||
|
labelName: config.label,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build tableList once for all charts and sort by date (newest first)
|
||||||
|
tableList = income.map((statement) => ({
|
||||||
|
date: statement.date,
|
||||||
|
// Add more properties if needed
|
||||||
|
}));
|
||||||
|
|
||||||
|
tableList.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($timeFrame || activeIdx) {
|
if ($timeFrame || activeIdx) {
|
||||||
if (activeIdx === 0) {
|
if (activeIdx === 0) {
|
||||||
@ -260,6 +314,7 @@
|
|||||||
fullStatement = data?.getIncomeStatement?.quarter;
|
fullStatement = data?.getIncomeStatement?.quarter;
|
||||||
}
|
}
|
||||||
income = filterStatement(fullStatement, $timeFrame);
|
income = filterStatement(fullStatement, $timeFrame);
|
||||||
|
preprocessFinancialData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -279,19 +334,14 @@
|
|||||||
{#if isLoaded}
|
{#if isLoaded}
|
||||||
<main class="w-full">
|
<main class="w-full">
|
||||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
||||||
<div class="mb-3">
|
<div
|
||||||
|
class="mb-3 flex flex-col sm:flex-row items-center justify-between"
|
||||||
|
>
|
||||||
<h1 class="text-xl sm:text-2xl text-white font-bold">
|
<h1 class="text-xl sm:text-2xl text-white font-bold">
|
||||||
{removeCompanyStrings($displayCompanyName)} Income Statement
|
{removeCompanyStrings($displayCompanyName)} Income Statement
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-2">
|
|
||||||
{#if income?.length > 0}
|
|
||||||
<div
|
<div
|
||||||
class="inline-flex justify-center w-full rounded-md sm:w-auto sm:ml-auto mt-3 mb-6"
|
class="mt-3 sm:mt-0 mb-2 sm:mb-0 bg-secondary w-full min-w-24 sm:w-fit relative flex flex-wrap items-center justify-center rounded-md p-1"
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bg-secondary w-full min-w-24 sm:w-fit relative flex flex-wrap items-center justify-center rounded-md p-1 mt-4"
|
|
||||||
>
|
>
|
||||||
{#each tabs as item, i}
|
{#each tabs as item, i}
|
||||||
{#if data?.user?.tier !== "Pro" && i > 0}
|
{#if data?.user?.tier !== "Pro" && i > 0}
|
||||||
@ -339,6 +389,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-2">
|
||||||
|
{#if income?.length > 0}
|
||||||
<div
|
<div
|
||||||
class="mb-2 flex flex-row items-center w-full justify-end sm:justify-center"
|
class="mb-2 flex flex-row items-center w-full justify-end sm:justify-center"
|
||||||
>
|
>
|
||||||
@ -443,19 +495,14 @@
|
|||||||
|
|
||||||
{#if $coolMode}
|
{#if $coolMode}
|
||||||
<div class="grid gap-5 xs:gap-6 lg:grid-cols-3 lg:gap-3">
|
<div class="grid gap-5 xs:gap-6 lg:grid-cols-3 lg:gap-3">
|
||||||
{#each fields as item}
|
{#each fields as item, i}
|
||||||
<Lazy
|
|
||||||
fadeOption={{ delay: 100, duration: 100 }}
|
|
||||||
keep={true}
|
|
||||||
>
|
|
||||||
<FinancialChart
|
<FinancialChart
|
||||||
data={income}
|
data={income}
|
||||||
{tableList}
|
|
||||||
{statementConfig}
|
{statementConfig}
|
||||||
displayStatement={item?.key}
|
displayStatement={item?.key}
|
||||||
{filterRule}
|
{filterRule}
|
||||||
|
{processedData}
|
||||||
/>
|
/>
|
||||||
</Lazy>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user