From cc6387ff7eedab63928ff6d6526168e5b2281fe9 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Thu, 22 Aug 2024 17:53:38 +0200 Subject: [PATCH] update AnalystEstimate --- src/lib/components/AnalystEstimate.svelte | 318 ++++++++++++++-------- 1 file changed, 202 insertions(+), 116 deletions(-) diff --git a/src/lib/components/AnalystEstimate.svelte b/src/lib/components/AnalystEstimate.svelte index c547d8c8..794be7ae 100644 --- a/src/lib/components/AnalystEstimate.svelte +++ b/src/lib/components/AnalystEstimate.svelte @@ -2,28 +2,26 @@ import {analystEstimateComponent, stockTicker, screenWidth, getCache, setCache} from '$lib/store'; import InfoModal from '$lib/components/InfoModal.svelte'; -import { LayerCake, Html } from 'layercake'; - -import Scatter from '$lib/components/Scatter//Scatter.html.svelte'; -import AxisX from '$lib/components/Scatter//AxisX.html.svelte'; -import AxisY from '$lib/components/Scatter//AxisY.html.svelte'; - +import { Chart } from 'svelte-echarts' +import { init, use } from 'echarts/core' +import { ScatterChart } from 'echarts/charts' +import { GridComponent, TooltipComponent } from 'echarts/components' +import { CanvasRenderer } from 'echarts/renderers' + import { abbreviateNumber } from '$lib/utils'; export let data; +use([ScatterChart, GridComponent, TooltipComponent, CanvasRenderer]) + let analystEstimateList = []; let isLoaded = false; let deactivateContent = data?.user?.tier === 'Pro' ? false : true; -let dataset = []; let xData = []; +let optionsData; let displayData = 'Revenue'; -let displayRevenueUnit = 'Billions'; -let displayNetIncomeUnit = 'Billions'; -let displayEBITDAUnit = 'Billions'; - function changeStatement(event) { @@ -31,24 +29,23 @@ function changeStatement(event) } -function determineDisplayUnit(value) { - if (Math?.abs(value) >= 1e10) { - return { unit: '100 Billions', denominator: 10e10 }; +function normalizer(value) { + if (Math?.abs(value) >= 1e18) { + return { unit: 'Q', denominator: 1e18 }; + } else if (Math?.abs(value) >= 1e12) { + return { unit: 'T', denominator: 1e12 }; } else if (Math?.abs(value) >= 1e9) { - return { unit: '10 Billions', denominator: 1e10 }; - } else if (Math?.abs(value) >= 1e7) { - return { unit: 'Billions', denominator: 1e9 }; + return { unit: 'B', denominator: 1e9 }; + } else if (Math?.abs(value) >= 1e6) { + return { unit: 'M', denominator: 1e6 }; } else if (Math?.abs(value) >= 1e5) { - return { unit: 'Millions', denominator: 1e6 }; + return { unit: 'K', denominator: 1e5 }; } else { return { unit: '', denominator: 1 }; } -} + } + -const xKey = 'FY'; -const yKey = 'val'; -const r = 5; -const padding = 2.5; let tableDataActual = []; let tableDataForecast = [] @@ -85,75 +82,154 @@ const getAnalystEstimate = async (ticker) => { }; -//To-do: Optimize this piece of shit -function prepareData(analystEstimateList) { +function getPlotOptions() { + let dates = []; + let valueList = []; + let estimatedValueList = []; + + // Iterate over the data and extract required information + if( displayData === 'Revenue') { + analystEstimateList?.slice(-10)?.forEach(item => { + dates.push(item?.date); + valueList.push(item?.revenue); // Handle null values by using 0 or any placeholder value + estimatedValueList.push(item?.estimatedRevenueAvg); + }); + } else if ( displayData === 'Net Income') { + analystEstimateList?.slice(-10)?.forEach(item => { + dates.push(item?.date); + valueList.push(item?.netIncome); // Handle null values by using 0 or any placeholder value + estimatedValueList.push(item?.estimatedNetIncomeAvg); + }); + } else if ( displayData === 'EBITDA') { + analystEstimateList?.slice(-10)?.forEach(item => { + dates.push(item?.date); + valueList.push(item?.ebitda); // Handle null values by using 0 or any placeholder value + estimatedValueList.push(item?.estimatedEbitdaAvg); + }); + } else if ( displayData === 'EPS') { + analystEstimateList?.slice(-10)?.forEach(item => { + dates.push(item?.date); + valueList.push(item?.eps); // Handle null values by using 0 or any placeholder value + estimatedValueList.push(item?.estimatedEpsAvg); + }); + } + + + // Normalize the data if needed (not required in this case, but leaving it here for reference) + const { unit, denominator } = normalizer(Math.max(...valueList, ...estimatedValueList) ?? 0); + + const option = { + silent: true, + tooltip: { + trigger: 'axis', + hideDelay: 100, // Set the delay in milliseconds + }, + animation: false, + grid: { + left: '2%', + right: '2%', + bottom: '2%', + top: '5%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: dates, + axisLabel: { + color: '#fff', + } + }, + yAxis: { + type: 'value', + splitLine: { + show: false, // Disable x-axis grid lines + }, + axisLabel: { + color: '#fff', // Change label color to white + formatter: function (value, index) { + // Display every second tick + if (index % 2 === 0) { + return (value / denominator)?.toFixed(0) + unit; // Format value in millions + } else { + return ''; // Hide this tick + } + } + }, + }, + series: [ + { + name: 'Actual', + data: valueList, + type: 'scatter', + itemStyle: { + color: '#fff' // Change scatter plot color to white + }, + showSymbol: true // Show symbols for scatter plot points + }, + { + name: 'Forecast', + data: estimatedValueList, + type: 'scatter', + itemStyle: { + color: '#E11D48' // Change scatter plot color to green + }, + showSymbol: true // Show symbols for scatter plot points + }, + ] + }; + + return option; +} + + + +//To-do: Optimize this piece of shit +function prepareData() { - dataset = []; tableDataActual = []; tableDataForecast = []; - xData = analystEstimateList?.slice(-7)?.map(({ date }) => Number(String(date)?.slice(-2))); + xData = analystEstimateList?.slice(-10)?.map(({ date }) => Number(String(date)?.slice(-2))); if(displayData === 'Revenue') { - const { unit, denominator } = determineDisplayUnit(analystEstimateList?.at(-1)?.estimatedRevenueAvg); - displayRevenueUnit = unit; - analystEstimateList?.slice(-7)?.forEach(item => { + analystEstimateList?.slice(-10)?.forEach(item => { - tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.revenue/ denominator)?.toFixed(2)}); - tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.estimatedRevenueAvg/denominator)?.toFixed(2)}); - - if (item?.revenue !== null) { - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.revenue/denominator)?.toFixed(2), 'dataset': 'actual' }); - } - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.estimatedRevenueAvg !== null ? (item?.estimatedRevenueAvg / denominator)?.toFixed(2) : null, 'dataset': 'forecast' }); - }); + tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.revenue)}); + tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.estimatedRevenueAvg)}); + }) } else if(displayData === 'Net Income') { - const { unit, denominator } = analystEstimateList?.at(-2)?.estimatedNetIncomeAvg !== 0 ? determineDisplayUnit(analystEstimateList?.at(-2)?.estimatedNetIncomeAvg) : determineDisplayUnit(analystEstimateList?.at(-1)?.netIncome); - displayNetIncomeUnit = unit; - analystEstimateList?.slice(-7)?.forEach(item => { + analystEstimateList?.slice(-10)?.forEach(item => { - tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.netIncome / denominator)?.toFixed(2)}); - tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.estimatedNetIncomeAvg / denominator)?.toFixed(2)}); + tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.netIncome )}); + tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.estimatedNetIncomeAvg )}); - if (item?.netIncome !== null) { - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.netIncome / denominator)?.toFixed(2), 'dataset': 'actual' }); - } - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.estimatedNetIncomeAvg !== null ? (item?.estimatedNetIncomeAvg / denominator)?.toFixed(2) : null, 'dataset': 'forecast' }); - }); + }); } else if(displayData === 'EPS') { - analystEstimateList?.slice(-7)?.forEach(item => { + analystEstimateList?.slice(-10)?.forEach(item => { + + tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.eps ?? null}); + tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.estimatedEpsAvg}); - tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.eps?.toFixed(2) ?? null}); - tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.estimatedEpsAvg?.toFixed(2)}); - if (item?.eps !== null) { - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.eps, 'dataset': 'actual' }); - } - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.estimatedEpsAvg !== null ? item?.estimatedEpsAvg : null, 'dataset': 'forecast' }); }); } else if(displayData === 'EBITDA') { - const { unit, denominator } = determineDisplayUnit(analystEstimateList?.at(-1)?.estimatedEbitdaAvg); - displayEBITDAUnit = unit; - analystEstimateList?.slice(-7)?.forEach(item => { + analystEstimateList?.slice(-10)?.forEach(item => { - tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.ebitda/ denominator)?.toFixed(2)}); - tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.estimatedEbitdaAvg/ denominator)?.toFixed(2)}); + tableDataActual?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.ebitda)}); + tableDataForecast?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.estimatedEbitdaAvg)}); - if (item?.ebitda !== null) { - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': (item?.ebitda / denominator)?.toFixed(2), 'dataset': 'actual' }); - } - dataset?.push({ 'FY': Number(String(item?.date)?.slice(-2)), 'val': item?.estimatedEbitdaAvg !== null ? (item?.estimatedEbitdaAvg / denominator)?.toFixed(2) : null, 'dataset': 'forecast' }); }); } @@ -170,7 +246,9 @@ $: { ]; Promise.all(asyncFunctions) .then((results) => { - prepareData(analystEstimateList) + //prepareData(analystEstimateList) + optionsData = getPlotOptions(); + prepareData() }) .catch((error) => { @@ -222,45 +300,23 @@ $: { EPS - + -
- - -
- +
+ + +
+ +
- - - - - - -
- - -
@@ -288,9 +344,9 @@ $: { - + {#each ($screenWidth >= 640 ? xData?.slice(-8) : xData) as item} - + {/each} @@ -298,24 +354,52 @@ $: { - {#each ($screenWidth >= 640 ? tableDataForecast?.slice(-8) : tableDataForecast) as item} + {/each} + + + + + + {#each ($screenWidth >= 640 ? tableDataActual?.slice(-8) : tableDataActual) as item} + {/each} - - {#each ($screenWidth >= 640 ? tableDataActual?.slice(-8) : tableDataActual) as item} + {#each ($screenWidth >= 640 ? tableDataActual?.slice(-8) : tableDataActual) as item, index} {/each} @@ -327,7 +411,8 @@ $: {
YearYear{'FY'+item}{'FY'+item}
+ Forecast - {(item?.val === '0.00' || item?.val === null) ? '-' : item?.val} + {(item?.val === '0.00' || item?.val === null) ? '-' : abbreviateNumber(item?.val)} +
+ Actual + + {(item?.val === '0.00' || item?.val === null) ? '-' : abbreviateNumber(item?.val)}
- Actual + + % Change - {(item?.val === '0.00' || item?.val === null) ? '-' : item?.val} + {#if index+1-tableDataActual?.length === 0} + - + {:else} + {#if item?.val === null} + - + {:else if (item?.val- tableDataActual[index-1]?.val) > 0} + + +{(((item?.val-tableDataActual[index-1]?.val) / tableDataActual[index-1]?.val) * 100 )?.toFixed(2)}% + + {:else if (item?.val - tableDataActual[index-1]?.val ) < 0} + + {(((item?.val - tableDataActual[index-1]?.val ) / tableDataActual[index-1]?.val) * 100 )?.toFixed(2)}% + + {:else} + 0.00% + {/if} + {/if}
-
+
+
@@ -362,21 +447,22 @@ $: { - \ No newline at end of file