bugfixing
This commit is contained in:
parent
681d9cc423
commit
80e83b9e14
11
package-lock.json
generated
11
package-lock.json
generated
@ -75,7 +75,7 @@
|
|||||||
"svelte-intersection-observer": "^1.0.0",
|
"svelte-intersection-observer": "^1.0.0",
|
||||||
"svelte-intersection-observer-action": "^0.0.5",
|
"svelte-intersection-observer-action": "^0.0.5",
|
||||||
"svelte-inview": "^4.0.2",
|
"svelte-inview": "^4.0.2",
|
||||||
"svelte-lazy": "^1.2.7",
|
"svelte-lazy": "^1.2.11",
|
||||||
"svelte-lightweight-charts": "^2.2.0",
|
"svelte-lightweight-charts": "^2.2.0",
|
||||||
"svelte-loading-spinners": "^0.3.6",
|
"svelte-loading-spinners": "^0.3.6",
|
||||||
"svelte-preprocess": "^5.1.4",
|
"svelte-preprocess": "^5.1.4",
|
||||||
@ -8205,12 +8205,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-lazy": {
|
"node_modules/svelte-lazy": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-lazy/-/svelte-lazy-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-lazy/-/svelte-lazy-1.2.11.tgz",
|
||||||
"integrity": "sha512-TvOP22pNsSKlCRsQIZ9mv1IAaEhSZFhezzhMO/kYuKUvX9VQALJvWHV5MOymn/pjeTeVuT4lE9g9y40uf0bNSw==",
|
"integrity": "sha512-VVrkn5eMLgAHSZlZrApR675MqQACqlA1pWEI7rDi9kQ3GTsyXEaMjwFpzddnNsNWV4B0Tvd1pZHplQgFiBqPfg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "UNLICENSE",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": "^3.0.0 || ^4.0.0"
|
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-lightweight-charts": {
|
"node_modules/svelte-lightweight-charts": {
|
||||||
|
|||||||
@ -74,7 +74,7 @@
|
|||||||
"svelte-intersection-observer": "^1.0.0",
|
"svelte-intersection-observer": "^1.0.0",
|
||||||
"svelte-intersection-observer-action": "^0.0.5",
|
"svelte-intersection-observer-action": "^0.0.5",
|
||||||
"svelte-inview": "^4.0.2",
|
"svelte-inview": "^4.0.2",
|
||||||
"svelte-lazy": "^1.2.7",
|
"svelte-lazy": "^1.2.11",
|
||||||
"svelte-lightweight-charts": "^2.2.0",
|
"svelte-lightweight-charts": "^2.2.0",
|
||||||
"svelte-loading-spinners": "^0.3.6",
|
"svelte-loading-spinners": "^0.3.6",
|
||||||
"svelte-preprocess": "^5.1.4",
|
"svelte-preprocess": "^5.1.4",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { analystEstimateComponent, stockTicker } from "$lib/store";
|
import { analystEstimateComponent, stockTicker } from "$lib/store";
|
||||||
import { abbreviateNumber } from "$lib/utils";
|
import { abbreviateNumber, computeGrowthSingleList } from "$lib/utils";
|
||||||
import EstimationGraph from "$lib/components/EstimationGraph.svelte";
|
import EstimationGraph from "$lib/components/EstimationGraph.svelte";
|
||||||
import Lazy from "svelte-lazy";
|
import Lazy from "svelte-lazy";
|
||||||
|
|
||||||
@ -12,19 +12,29 @@
|
|||||||
let xData = [];
|
let xData = [];
|
||||||
let optionsRevenue = null;
|
let optionsRevenue = null;
|
||||||
let optionsEPS = null;
|
let optionsEPS = null;
|
||||||
|
let optionsNetIncome = null;
|
||||||
let optionsRevenueGrowth = null;
|
let optionsRevenueGrowth = null;
|
||||||
let optionsEPSGrowth = null;
|
let optionsEPSGrowth = null;
|
||||||
|
let optionsNetIncomeGrowth = null;
|
||||||
|
|
||||||
let revenueDateList = [];
|
let revenueDateList = [];
|
||||||
let avgRevenueList = [];
|
let avgRevenueList = [];
|
||||||
let lowRevenueList = [];
|
let lowRevenueList = [];
|
||||||
let highRevenueList = [];
|
let highRevenueList = [];
|
||||||
|
|
||||||
let epsDateList = [];
|
let epsDateList = [];
|
||||||
let avgEPSList = [];
|
let avgEPSList = [];
|
||||||
let lowEPSList = [];
|
let lowEPSList = [];
|
||||||
let highEPSList = [];
|
let highEPSList = [];
|
||||||
|
|
||||||
|
let netIncomeDateList = [];
|
||||||
|
let avgNetIncomeList = [];
|
||||||
|
let lowNetIncomeList = [];
|
||||||
|
let highNetIncomeList = [];
|
||||||
|
|
||||||
let revenueAvgGrowthList = [];
|
let revenueAvgGrowthList = [];
|
||||||
let epsAvgGrowthList = [];
|
let epsAvgGrowthList = [];
|
||||||
|
let netIncomeAvgGrowthList = [];
|
||||||
|
|
||||||
let displayData = "Revenue";
|
let displayData = "Revenue";
|
||||||
|
|
||||||
@ -56,42 +66,6 @@
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function computeGrowthSingleList(data, actualList) {
|
|
||||||
// Initialize the result list
|
|
||||||
let resultList = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < data?.length; i++) {
|
|
||||||
const currentData = data[i];
|
|
||||||
|
|
||||||
// Find the corresponding actual data from one FY back
|
|
||||||
const correspondingActual = actualList?.find(
|
|
||||||
(entry) => Number(entry.FY) === Number(currentData.FY) - 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Calculate growth if a matching entry exists in actualList
|
|
||||||
let growth = null;
|
|
||||||
if (
|
|
||||||
correspondingActual &&
|
|
||||||
correspondingActual?.val !== null &&
|
|
||||||
currentData.val !== null
|
|
||||||
) {
|
|
||||||
growth = (
|
|
||||||
((currentData?.val - correspondingActual?.val) /
|
|
||||||
Math.abs(correspondingActual?.val)) *
|
|
||||||
100
|
|
||||||
)?.toFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the result for this FY
|
|
||||||
resultList.push({
|
|
||||||
FY: currentData.FY,
|
|
||||||
val: currentData.val,
|
|
||||||
growth: growth !== null ? Number(growth) : null, // Convert growth to number or leave as null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeGrowthList(tableActualRevenue, tableForecastRevenue) {
|
function computeGrowthList(tableActualRevenue, tableForecastRevenue) {
|
||||||
return tableActualRevenue?.map((item, index) => {
|
return tableActualRevenue?.map((item, index) => {
|
||||||
@ -162,9 +136,14 @@
|
|||||||
|
|
||||||
let tableForecastRevenue = [];
|
let tableForecastRevenue = [];
|
||||||
let tableForecastEPS = [];
|
let tableForecastEPS = [];
|
||||||
|
let tableForecastNetIncome = [];
|
||||||
|
|
||||||
let tableCombinedRevenue = [];
|
let tableCombinedRevenue = [];
|
||||||
let tableCombinedEPS = [];
|
let tableCombinedEPS = [];
|
||||||
|
|
||||||
|
let tableActualNetIncome = [];
|
||||||
|
let tableCombinedNetIncome = [];
|
||||||
|
|
||||||
function getPlotOptions(dataType: string) {
|
function getPlotOptions(dataType: string) {
|
||||||
let dates = [];
|
let dates = [];
|
||||||
let valueList = [];
|
let valueList = [];
|
||||||
@ -182,17 +161,25 @@
|
|||||||
dates.push(`FY${date}`);
|
dates.push(`FY${date}`);
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case "Revenue":
|
case "Revenue":
|
||||||
valueList.push(item.revenue);
|
valueList.push(item?.revenue);
|
||||||
avgList.push(isAfterStartIndex ? item.estimatedRevenueAvg : null);
|
avgList.push(isAfterStartIndex ? item.estimatedRevenueAvg : null);
|
||||||
lowList.push(isAfterStartIndex ? item.estimatedRevenueLow : null);
|
lowList.push(isAfterStartIndex ? item.estimatedRevenueLow : null);
|
||||||
highList.push(isAfterStartIndex ? item.estimatedRevenueHigh : null);
|
highList.push(isAfterStartIndex ? item.estimatedRevenueHigh : null);
|
||||||
break;
|
break;
|
||||||
case "EPS":
|
case "EPS":
|
||||||
valueList.push(item.eps);
|
valueList.push(item?.eps);
|
||||||
avgList.push(isAfterStartIndex ? item.estimatedEpsAvg : null);
|
avgList.push(isAfterStartIndex ? item.estimatedEpsAvg : null);
|
||||||
lowList.push(isAfterStartIndex ? item.estimatedEpsLow : null);
|
lowList.push(isAfterStartIndex ? item.estimatedEpsLow : null);
|
||||||
highList.push(isAfterStartIndex ? item.estimatedEpsHigh : null);
|
highList.push(isAfterStartIndex ? item.estimatedEpsHigh : null);
|
||||||
break;
|
break;
|
||||||
|
case "NetIncome":
|
||||||
|
valueList.push(item?.netIncome);
|
||||||
|
avgList.push(isAfterStartIndex ? item.estimatedNetIncomeAvg : null);
|
||||||
|
lowList.push(isAfterStartIndex ? item.estimatedNetIncomeLow : null);
|
||||||
|
highList.push(
|
||||||
|
isAfterStartIndex ? item.estimatedNetIncomeHigh : null,
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -249,12 +236,33 @@
|
|||||||
FY: epsDateList[index]?.slice(2),
|
FY: epsDateList[index]?.slice(2),
|
||||||
val: val,
|
val: val,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
} else if (dataType === "NetIncome") {
|
||||||
|
netIncomeDateList = dates?.slice(currentYearIndex) || [];
|
||||||
|
avgNetIncomeList =
|
||||||
|
avgList?.slice(currentYearIndex)?.map((val, index) => ({
|
||||||
|
FY: netIncomeDateList[index]?.slice(2),
|
||||||
|
val: val,
|
||||||
|
})) || [];
|
||||||
|
lowNetIncomeList =
|
||||||
|
lowList?.slice(currentYearIndex)?.map((val, index) => ({
|
||||||
|
FY: netIncomeDateList[index]?.slice(2),
|
||||||
|
val: val,
|
||||||
|
})) || [];
|
||||||
|
highNetIncomeList =
|
||||||
|
highList?.slice(currentYearIndex)?.map((val, index) => ({
|
||||||
|
FY: netIncomeDateList[index]?.slice(2),
|
||||||
|
val: val,
|
||||||
|
})) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const growthList = dates?.map((date) => {
|
const growthList = dates?.map((date) => {
|
||||||
const fy = parseInt(date.replace("FY", ""), 10); // Extract numeric FY value
|
const fy = parseInt(date.replace("FY", ""), 10); // Extract numeric FY value
|
||||||
const listToUse =
|
const listToUse =
|
||||||
dataType === "Revenue" ? revenueAvgGrowthList : epsAvgGrowthList; // Select the correct growth list
|
dataType === "Revenue"
|
||||||
|
? revenueAvgGrowthList
|
||||||
|
: dataType === "EPS"
|
||||||
|
? epsAvgGrowthList
|
||||||
|
: netIncomeAvgGrowthList; // Select the correct growth list
|
||||||
const growth = listToUse?.find((r) => r.FY === fy); // Find matching FY
|
const growth = listToUse?.find((r) => r.FY === fy); // Find matching FY
|
||||||
return growth ? growth?.growth : null; // Return growth or null if not found
|
return growth ? growth?.growth : null; // Return growth or null if not found
|
||||||
});
|
});
|
||||||
@ -350,10 +358,20 @@
|
|||||||
if (dataType === "Revenue") {
|
if (dataType === "Revenue") {
|
||||||
highGrowthList = computeGrowthSingleList(highRevenueList, avgRevenueList);
|
highGrowthList = computeGrowthSingleList(highRevenueList, avgRevenueList);
|
||||||
lowGrowthList = computeGrowthSingleList(lowRevenueList, avgRevenueList);
|
lowGrowthList = computeGrowthSingleList(lowRevenueList, avgRevenueList);
|
||||||
} else {
|
} else if (dataType === "EPS") {
|
||||||
highGrowthList = computeGrowthSingleList(highEPSList, avgEPSList);
|
highGrowthList = computeGrowthSingleList(highEPSList, avgEPSList);
|
||||||
lowGrowthList = computeGrowthSingleList(lowEPSList, avgEPSList);
|
lowGrowthList = computeGrowthSingleList(lowEPSList, avgEPSList);
|
||||||
|
} else if (dataType === "NetIncome") {
|
||||||
|
highGrowthList = computeGrowthSingleList(
|
||||||
|
highNetIncomeList,
|
||||||
|
avgNetIncomeList,
|
||||||
|
);
|
||||||
|
lowGrowthList = computeGrowthSingleList(
|
||||||
|
lowNetIncomeList,
|
||||||
|
avgNetIncomeList,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
highGrowthList = fillMissingDates(dates, highGrowthList)?.map(
|
highGrowthList = fillMissingDates(dates, highGrowthList)?.map(
|
||||||
(item) => item?.growth,
|
(item) => item?.growth,
|
||||||
);
|
);
|
||||||
@ -392,7 +410,12 @@
|
|||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: dataType === "Revenue" ? "Revenue Growth" : "EPS Growth",
|
name:
|
||||||
|
dataType === "Revenue"
|
||||||
|
? "Revenue Growth"
|
||||||
|
: dataType === "EPS"
|
||||||
|
? "EPS Growth"
|
||||||
|
: "Net Income Growth",
|
||||||
data: growthList?.map((value) => ({
|
data: growthList?.map((value) => ({
|
||||||
value,
|
value,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
@ -518,6 +541,9 @@
|
|||||||
} else if (dataType === "EPS") {
|
} else if (dataType === "EPS") {
|
||||||
optionsEPS = option;
|
optionsEPS = option;
|
||||||
optionsEPSGrowth = optionsGrowth;
|
optionsEPSGrowth = optionsGrowth;
|
||||||
|
} else if (dataType === "NetIncome") {
|
||||||
|
optionsNetIncome = option;
|
||||||
|
optionsNetIncomeGrowth = optionsGrowth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,18 +552,24 @@
|
|||||||
tableActualRevenue = [];
|
tableActualRevenue = [];
|
||||||
tableForecastRevenue = [];
|
tableForecastRevenue = [];
|
||||||
tableCombinedRevenue = [];
|
tableCombinedRevenue = [];
|
||||||
tableCombinedEPS = [];
|
|
||||||
|
|
||||||
tableActualEPS = [];
|
tableActualEPS = [];
|
||||||
|
tableCombinedEPS = [];
|
||||||
tableForecastEPS = [];
|
tableForecastEPS = [];
|
||||||
|
|
||||||
|
tableActualNetIncome = [];
|
||||||
|
tableCombinedNetIncome = [];
|
||||||
|
tableForecastNetIncome = [];
|
||||||
|
|
||||||
revenueAvgGrowthList = [];
|
revenueAvgGrowthList = [];
|
||||||
epsAvgGrowthList = [];
|
epsAvgGrowthList = [];
|
||||||
|
netIncomeAvgGrowthList = [];
|
||||||
|
|
||||||
let filteredData =
|
let filteredData =
|
||||||
analystEstimateList?.filter((item) => item.date >= 2015) ?? [];
|
analystEstimateList?.filter((item) => item.date >= 2015) ?? [];
|
||||||
|
|
||||||
xData = filteredData?.map(({ date }) => Number(String(date)?.slice(-2)));
|
xData = filteredData?.map(({ date }) => Number(String(date)?.slice(-2)));
|
||||||
|
//============================//
|
||||||
//Revenue Data
|
//Revenue Data
|
||||||
filteredData?.forEach((item) => {
|
filteredData?.forEach((item) => {
|
||||||
tableActualRevenue?.push({
|
tableActualRevenue?.push({
|
||||||
@ -565,6 +597,33 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//============================//
|
||||||
|
//NetIncome Data
|
||||||
|
filteredData?.forEach((item) => {
|
||||||
|
tableActualNetIncome?.push({
|
||||||
|
FY: Number(String(item?.date)?.slice(-2)),
|
||||||
|
val: item?.netIncome,
|
||||||
|
});
|
||||||
|
tableForecastNetIncome?.push({
|
||||||
|
FY: Number(String(item?.date)?.slice(-2)),
|
||||||
|
val: item?.estimatedNetIncomeAvg,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tableCombinedNetIncome = tableActualNetIncome?.map((item1) => {
|
||||||
|
// Find the corresponding item in data2 based on "FY"
|
||||||
|
const item2 = tableForecastNetIncome?.find(
|
||||||
|
(item2) => item2?.FY === item1?.FY,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the value in data1 is null, replace it with the value from data2
|
||||||
|
return {
|
||||||
|
FY: item1.FY,
|
||||||
|
val: item1.val === null ? item2.val : item1.val,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//============================//
|
||||||
//EPS Data
|
//EPS Data
|
||||||
filteredData?.forEach((item) => {
|
filteredData?.forEach((item) => {
|
||||||
tableActualEPS?.push({
|
tableActualEPS?.push({
|
||||||
@ -576,11 +635,6 @@
|
|||||||
val: item?.estimatedEpsAvg,
|
val: item?.estimatedEpsAvg,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
//Values coincide with table values for crosscheck
|
|
||||||
revenueAvgGrowthList = computeGrowthList(
|
|
||||||
tableActualRevenue,
|
|
||||||
tableCombinedRevenue,
|
|
||||||
);
|
|
||||||
|
|
||||||
tableCombinedEPS = tableActualEPS?.map((item1) => {
|
tableCombinedEPS = tableActualEPS?.map((item1) => {
|
||||||
// Find the corresponding item in data2 based on "FY"
|
// Find the corresponding item in data2 based on "FY"
|
||||||
@ -593,6 +647,15 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Values coincide with table values for crosscheck
|
||||||
|
revenueAvgGrowthList = computeGrowthList(
|
||||||
|
tableActualRevenue,
|
||||||
|
tableCombinedRevenue,
|
||||||
|
);
|
||||||
|
netIncomeAvgGrowthList = computeGrowthList(
|
||||||
|
tableActualNetIncome,
|
||||||
|
tableCombinedNetIncome,
|
||||||
|
);
|
||||||
epsAvgGrowthList = computeGrowthList(tableActualEPS, tableCombinedEPS);
|
epsAvgGrowthList = computeGrowthList(tableActualEPS, tableCombinedEPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,6 +669,7 @@
|
|||||||
$analystEstimateComponent = true;
|
$analystEstimateComponent = true;
|
||||||
getPlotOptions("Revenue");
|
getPlotOptions("Revenue");
|
||||||
getPlotOptions("EPS");
|
getPlotOptions("EPS");
|
||||||
|
getPlotOptions("NetIncome");
|
||||||
} else {
|
} else {
|
||||||
$analystEstimateComponent = false;
|
$analystEstimateComponent = false;
|
||||||
}
|
}
|
||||||
@ -749,25 +813,59 @@
|
|||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
<!--
|
<tr class="bg-[#27272A] border-b-[#27272A]">
|
||||||
<tr class="bg-[#09090B] border-b-[#09090B]">
|
|
||||||
<th
|
<th
|
||||||
class="bg-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap text-white text-start font-medium border-b border-[#09090B]"
|
class="text-white whitespace-nowrap text-sm sm:text-[1rem] text-start font-medium bg-[#27272A] border-b border-[#27272A]"
|
||||||
>Forward PE</th
|
|
||||||
>
|
>
|
||||||
{#each tableForecastEPS as item}
|
Net Income
|
||||||
|
</th>
|
||||||
|
{#each tableCombinedNetIncome as item}
|
||||||
<td
|
<td
|
||||||
class="text-white text-sm sm:text-[1rem] text-end font-medium bg-[#09090B]"
|
class="text-white text-sm sm:text-[1rem] text-end font-medium border-b border-[#27272A] bg-[#09090B]"
|
||||||
>
|
>
|
||||||
{item?.forwardPe === "0.00" ||
|
{item?.val === "0.00" ||
|
||||||
item?.forwardPe === null ||
|
item?.val === null ||
|
||||||
item?.forwardPe === 0
|
item?.val === 0
|
||||||
? "-"
|
? "n/a"
|
||||||
: abbreviateNumber(item.forwardPe)}
|
: abbreviateNumber(item?.val.toFixed(2))}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr class="bg-[#27272A] border-b-[#27272A]">
|
||||||
|
<th
|
||||||
|
class="bg-[#27272A] whitespace-nowrap text-sm sm:text-[1rem] text-white text-start font-medium border-b border-[#27272A]"
|
||||||
|
>
|
||||||
|
Net Income Growth
|
||||||
|
</th>
|
||||||
|
{#each computeGrowthList(tableActualNetIncome, tableCombinedNetIncome) as item, index}
|
||||||
|
<td
|
||||||
|
class="text-white text-sm sm:text-[1rem] text-end font-medium bg-[#09090B]"
|
||||||
|
>
|
||||||
|
{#if index === 0 || item?.growth === null}
|
||||||
|
n/a
|
||||||
|
{:else if tableActualNetIncome[index]?.val === null}
|
||||||
|
<span
|
||||||
|
class="text-orange-400 {item?.growth > 0
|
||||||
|
? "before:content-['+']"
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
|
{item?.growth}%*
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class={item?.growth > 0
|
||||||
|
? "text-[#00FC50] before:content-['+']"
|
||||||
|
: item?.growth < 0
|
||||||
|
? "text-[#FF2F1F]"
|
||||||
|
: ""}
|
||||||
|
>
|
||||||
|
{item?.growth}%
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
-->
|
|
||||||
|
|
||||||
<tr class="bg-[#27272A] border-b-[#27272A]">
|
<tr class="bg-[#27272A] border-b-[#27272A]">
|
||||||
<th
|
<th
|
||||||
@ -818,7 +916,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="space-y-6 lg:grid lg:grid-cols-2 lg:gap-6 lg:space-y-0 mt-10">
|
<div class="space-y-6 lg:grid lg:grid-cols-2 lg:gap-6 lg:space-y-0 mt-10">
|
||||||
<Lazy>
|
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
|
||||||
<EstimationGraph
|
<EstimationGraph
|
||||||
userTier={data?.user?.tier}
|
userTier={data?.user?.tier}
|
||||||
title="Revenue"
|
title="Revenue"
|
||||||
@ -830,7 +928,7 @@
|
|||||||
/>
|
/>
|
||||||
</Lazy>
|
</Lazy>
|
||||||
|
|
||||||
<Lazy>
|
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
|
||||||
<EstimationGraph
|
<EstimationGraph
|
||||||
userTier={data?.user?.tier}
|
userTier={data?.user?.tier}
|
||||||
title="Revenue Growth"
|
title="Revenue Growth"
|
||||||
@ -839,10 +937,12 @@
|
|||||||
highDataList={highRevenueList}
|
highDataList={highRevenueList}
|
||||||
avgDataList={avgRevenueList}
|
avgDataList={avgRevenueList}
|
||||||
lowDataList={lowRevenueList}
|
lowDataList={lowRevenueList}
|
||||||
|
avgGrowthList={revenueAvgGrowthList}
|
||||||
|
graphType="growth"
|
||||||
/>
|
/>
|
||||||
</Lazy>
|
</Lazy>
|
||||||
|
|
||||||
<Lazy>
|
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
|
||||||
<EstimationGraph
|
<EstimationGraph
|
||||||
userTier={data?.user?.tier}
|
userTier={data?.user?.tier}
|
||||||
title="EPS"
|
title="EPS"
|
||||||
@ -854,7 +954,7 @@
|
|||||||
/>
|
/>
|
||||||
</Lazy>
|
</Lazy>
|
||||||
|
|
||||||
<Lazy>
|
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
|
||||||
<EstimationGraph
|
<EstimationGraph
|
||||||
userTier={data?.user?.tier}
|
userTier={data?.user?.tier}
|
||||||
title="EPS Growth"
|
title="EPS Growth"
|
||||||
@ -863,6 +963,34 @@
|
|||||||
highDataList={highEPSList}
|
highDataList={highEPSList}
|
||||||
avgDataList={avgEPSList}
|
avgDataList={avgEPSList}
|
||||||
lowDataList={lowEPSList}
|
lowDataList={lowEPSList}
|
||||||
|
avgGrowthList={epsAvgGrowthList}
|
||||||
|
graphType="growth"
|
||||||
|
/>
|
||||||
|
</Lazy>
|
||||||
|
|
||||||
|
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
|
||||||
|
<EstimationGraph
|
||||||
|
userTier={data?.user?.tier}
|
||||||
|
title="Net Income"
|
||||||
|
options={optionsNetIncome}
|
||||||
|
tableDataList={netIncomeDateList}
|
||||||
|
highDataList={highNetIncomeList}
|
||||||
|
avgDataList={avgNetIncomeList}
|
||||||
|
lowDataList={lowNetIncomeList}
|
||||||
|
/>
|
||||||
|
</Lazy>
|
||||||
|
|
||||||
|
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
|
||||||
|
<EstimationGraph
|
||||||
|
userTier={data?.user?.tier}
|
||||||
|
title="Net Income Growth"
|
||||||
|
options={optionsNetIncomeGrowth}
|
||||||
|
tableDataList={netIncomeDateList}
|
||||||
|
highDataList={highNetIncomeList}
|
||||||
|
avgDataList={avgNetIncomeList}
|
||||||
|
lowDataList={lowNetIncomeList}
|
||||||
|
avgGrowthList={netIncomeAvgGrowthList}
|
||||||
|
graphType="growth"
|
||||||
/>
|
/>
|
||||||
</Lazy>
|
</Lazy>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
import { LineChart, CustomChart } from "echarts/charts";
|
import { LineChart, CustomChart } from "echarts/charts";
|
||||||
import { GridComponent, TooltipComponent } from "echarts/components";
|
import { GridComponent, TooltipComponent } from "echarts/components";
|
||||||
import { CanvasRenderer } from "echarts/renderers";
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
import { abbreviateNumber } from "$lib/utils";
|
import { abbreviateNumber, computeGrowthSingleList } from "$lib/utils";
|
||||||
use([
|
use([
|
||||||
LineChart,
|
LineChart,
|
||||||
CustomChart,
|
CustomChart,
|
||||||
@ -20,8 +20,12 @@
|
|||||||
export let highDataList;
|
export let highDataList;
|
||||||
export let avgDataList;
|
export let avgDataList;
|
||||||
export let lowDataList;
|
export let lowDataList;
|
||||||
|
|
||||||
|
export let avgGrowthList = [];
|
||||||
|
export let graphType = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if graphType !== "growth"}
|
||||||
<div>
|
<div>
|
||||||
<h2 class="mb-2 text-xl font-bold">{title} Forecast</h2>
|
<h2 class="mb-2 text-xl font-bold">{title} Forecast</h2>
|
||||||
<div class="rounded-sm border p-2 border-gray-600">
|
<div class="rounded-sm border p-2 border-gray-600">
|
||||||
@ -121,6 +125,137 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-2 text-xl font-bold">{title}</h2>
|
||||||
|
<div class="rounded-sm border p-2 border-gray-600">
|
||||||
|
<div class="app h-[275px] w-full">
|
||||||
|
{#if options !== null}
|
||||||
|
<Chart {init} {options} class="chart" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 overflow-x-auto p-0 text-center sm:p-0.5 lg:mt-3.5">
|
||||||
|
<table class="w-full text-right">
|
||||||
|
<thead
|
||||||
|
><tr
|
||||||
|
class="border-b border-gray-600 align-bottom text-white font-normal whitespace-nowrap"
|
||||||
|
><th class="p-1 text-left font-semibold text-sm sm:text-[1rem]"
|
||||||
|
>{title}</th
|
||||||
|
>
|
||||||
|
{#each tableDataList as date, index}
|
||||||
|
<th class="p-1 font-semibold text-sm sm:text-[1rem]"
|
||||||
|
>{#if index !== 0}{date}{/if}</th
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</tr></thead
|
||||||
|
>
|
||||||
|
<tbody
|
||||||
|
><tr class="border-b border-gray-600 last:border-0"
|
||||||
|
><td class="whitespace-nowrap px-1 py-[3px] text-left">High</td>
|
||||||
|
{#each computeGrowthSingleList(highDataList, avgDataList) as item, index}
|
||||||
|
<td class="px-1 py-[3px] text-sm sm:text-[1rem]">
|
||||||
|
{#if index !== 0}
|
||||||
|
{#if userTier !== "Pro" && index >= highDataList?.length - 2}
|
||||||
|
<a class="inline-block ml-0.5 text-white" href="/pricing"
|
||||||
|
>Pro<svg
|
||||||
|
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
|
||||||
|
/></svg
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class={item?.growth !== null && item?.growth > 0
|
||||||
|
? "text-[#00FC50] before:content-['+']"
|
||||||
|
: item?.growth < 0
|
||||||
|
? "text-[#FF2F1F]"
|
||||||
|
: "text-white"}
|
||||||
|
>
|
||||||
|
{item?.growth !== null && Math.abs(item?.growth - 0) > 0
|
||||||
|
? abbreviateNumber(item?.growth) + "%"
|
||||||
|
: "-"}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr><tr class="border-b border-gray-600 last:border-0"
|
||||||
|
><td class="whitespace-nowrap px-1 py-[3px] text-left">Avg</td>
|
||||||
|
{#each avgGrowthList?.filter((item) => item.FY >= 24) as item, index}
|
||||||
|
<td class="px-1 py-[3px] text-sm sm:text-[1rem]">
|
||||||
|
{#if index !== 0}
|
||||||
|
{#if userTier !== "Pro" && index >= avgDataList?.length - 2}
|
||||||
|
<a class="inline-block ml-0.5 text-white" href="/pricing"
|
||||||
|
>Pro<svg
|
||||||
|
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
|
||||||
|
/></svg
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class={item?.growth !== null && item?.growth > 0
|
||||||
|
? "text-[#00FC50] before:content-['+']"
|
||||||
|
: item?.growth < 0
|
||||||
|
? "text-[#FF2F1F]"
|
||||||
|
: "text-white"}
|
||||||
|
>
|
||||||
|
{item?.growth !== null && Math.abs(item?.growth - 0) > 0
|
||||||
|
? abbreviateNumber(item?.growth) + "%"
|
||||||
|
: "-"}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr><tr class="border-b border-gray-600 last:border-0"
|
||||||
|
><td class="whitespace-nowrap px-1 py-[3px] text-left">Low</td>
|
||||||
|
{#each computeGrowthSingleList(lowDataList, avgDataList) as item, index}
|
||||||
|
<td class="px-1 py-[3px] text-sm sm:text-[1rem]">
|
||||||
|
{#if index !== 0}
|
||||||
|
{#if userTier !== "Pro" && index >= lowDataList?.length - 2}
|
||||||
|
<a class="inline-block ml-0.5 text-white" href="/pricing"
|
||||||
|
>Pro<svg
|
||||||
|
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
|
||||||
|
/></svg
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class={item?.growth !== null && item?.growth > 0
|
||||||
|
? "text-[#00FC50] before:content-['+']"
|
||||||
|
: item?.growth < 0
|
||||||
|
? "text-[#FF2F1F]"
|
||||||
|
: "text-white"}
|
||||||
|
>
|
||||||
|
{item?.growth !== null && Math.abs(item?.growth - 0) > 0
|
||||||
|
? abbreviateNumber(item?.growth) + "%"
|
||||||
|
: "-"}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr></tbody
|
||||||
|
>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.app {
|
.app {
|
||||||
|
|||||||
@ -20,6 +20,43 @@ type FlyAndScaleParams = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const computeGrowthSingleList = (data, actualList) => {
|
||||||
|
// Initialize the result list
|
||||||
|
let resultList = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < data?.length; i++) {
|
||||||
|
const currentData = data[i];
|
||||||
|
|
||||||
|
// Find the corresponding actual data from one FY back
|
||||||
|
const correspondingActual = actualList?.find(
|
||||||
|
(entry) => Number(entry.FY) === Number(currentData.FY) - 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate growth if a matching entry exists in actualList
|
||||||
|
let growth = null;
|
||||||
|
if (
|
||||||
|
correspondingActual &&
|
||||||
|
correspondingActual?.val !== null &&
|
||||||
|
currentData.val !== null
|
||||||
|
) {
|
||||||
|
growth = (
|
||||||
|
((currentData?.val - correspondingActual?.val) /
|
||||||
|
Math.abs(correspondingActual?.val)) *
|
||||||
|
100
|
||||||
|
)?.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the result for this FY
|
||||||
|
resultList.push({
|
||||||
|
FY: currentData.FY,
|
||||||
|
val: currentData.val,
|
||||||
|
growth: growth !== null ? Number(growth) : null, // Convert growth to number or leave as null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const compareTimes = (time1, time2) => {
|
export const compareTimes = (time1, time2) => {
|
||||||
const [hours1, minutes1] = time1.split(":").map(Number);
|
const [hours1, minutes1] = time1.split(":").map(Number);
|
||||||
|
|||||||
@ -33,7 +33,6 @@ export const load = async ({ locals, params }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const output = await response.json();
|
const output = await response.json();
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user