updating income statement page

This commit is contained in:
MuslemRahimi 2024-08-13 12:43:22 +02:00
parent 8f31a46d01
commit c74b4ec4af
3 changed files with 183 additions and 101 deletions

View File

@ -138,12 +138,12 @@ export const validateData = async (formData, schema) => {
export function sumQuarterlyResultsByYear(quarterlyResults, namingList) { export function sumQuarterlyResultsByYear(quarterlyResults, namingList) {
const yearlySummaries = {}; const yearlySummaries = {};
const quarterCounts = {}; const quarterCounts = {};
//FMP sucks since these keys are up to date only by the last quarter value // FMP sucks since these keys are up to date only by the last quarter value
const lastQuarterKeys = new Set([namingList]); // Keys that need last quarter values const lastQuarterKeys = new Set([namingList]); // Keys that need last quarter values
// Define a Set of keys to exclude from summing // Define a Set of keys to exclude from summing
//FMP sucks since these keys are up to date for every quarter hence no summation required // FMP sucks since these keys are up to date for every quarter hence no summation required
const excludeKeys = new Set(['priceToEarnings','weightedAverageShsOut', 'weightedAverageShsOutDil']); const excludeKeys = new Set(['priceToEarnings', 'weightedAverageShsOut', 'weightedAverageShsOutDil']);
// Function to get the quarter number from the period string // Function to get the quarter number from the period string
function getQuarterNumber(period) { function getQuarterNumber(period) {
@ -166,7 +166,8 @@ export function sumQuarterlyResultsByYear(quarterlyResults, namingList) {
if (!yearlySummaries[year]) { if (!yearlySummaries[year]) {
yearlySummaries[year] = { yearlySummaries[year] = {
calendarYear: `${year}`, // Use end of the year date calendarYear: `${year}`, // Use end of the year date
lastQuarterProcessed: 0 // Keep track of the last quarter processed lastQuarterProcessed: 0, // Keep track of the last quarter processed
date: quarter?.date // Copy the 'date' field unchanged
}; };
quarterCounts[year] = 0; quarterCounts[year] = 0;
} }
@ -174,9 +175,11 @@ export function sumQuarterlyResultsByYear(quarterlyResults, namingList) {
// Increment the quarter count for the year // Increment the quarter count for the year
quarterCounts[year]++; quarterCounts[year]++;
// Update last quarter processed if current quarter is greater // Update last quarter processed if the current quarter is greater
if (quarterNum > yearlySummaries[year].lastQuarterProcessed) { if (quarterNum > yearlySummaries[year].lastQuarterProcessed) {
yearlySummaries[year].lastQuarterProcessed = quarterNum; yearlySummaries[year].lastQuarterProcessed = quarterNum;
// Update the date to the latest quarter's date if applicable
yearlySummaries[year].date = quarter?.date;
} }
// Sum up the numeric fields for the year, excluding specific keys // Sum up the numeric fields for the year, excluding specific keys

View File

@ -64,7 +64,7 @@ function selectSortingMethod(state:string) {
const options = { const options = {
grid: { grid: {
left: '0%', left: '0%',
right: '2%', right: '0%',
top: '10%', top: '10%',
bottom: '20%', bottom: '20%',
containLabel: true, containLabel: true,
@ -72,11 +72,15 @@ function selectSortingMethod(state:string) {
xAxis: { xAxis: {
data: dateList, data: dateList,
type: 'category', type: 'category',
axisLabel: {
color: '#fff',
}
}, },
yAxis: [ yAxis: [
{ {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
color: '#fff',
formatter: '{value}', formatter: '{value}',
}, },
splitLine: { splitLine: {
@ -94,12 +98,7 @@ function selectSortingMethod(state:string) {
type: 'bar', type: 'bar',
barWidth: '80%', barWidth: '80%',
smooth: true, smooth: true,
itemStyle: {
// Define colors based on positive/negative values
color: function(params) {
return '#F8901E';
}
},
}, },
], ],
@ -142,11 +141,15 @@ function selectSortingMethod(state:string) {
xAxis: { xAxis: {
data: dateList, data: dateList,
type: 'category', type: 'category',
axisLabel: {
color: '#fff'
}
}, },
yAxis: [ yAxis: [
{ {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
color: '#fff',
formatter: '{value}', formatter: '{value}',
}, },
splitLine: { splitLine: {
@ -219,11 +222,15 @@ function plotGrowth() {
xAxis: { xAxis: {
data: dateList, data: dateList,
type: 'category', type: 'category',
axisLabel: {
color: '#fff'
}
}, },
yAxis: [ yAxis: [
{ {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
color:'#fff',
formatter: '{value} %', formatter: '{value} %',
}, },
splitLine: { splitLine: {
@ -452,20 +459,20 @@ optionsGrowth = plotGrowth();
</div> </div>
{/if} {/if}
<div class="flex justify-start items-center w-full m-auto pr-1 pl-1 p-3"> <div class="w-full overflow-x-scroll">
<table class="table table-sm table-compact flex justify-start items-center w-full px-3 m-auto"> <table class="table table-sm table-compact rounded-none sm:rounded-md w-full border-bg-[#09090B] m-auto mt-4 ">
<thead> <thead>
<tr> <tr>
<th class="text-start border-b border-[#09090B] bg-[#09090B] text-white text-sm font-semibiold"> <th class="text-start border-b border-[#09090B] bg-[#09090B] text-white text-[1rem] whitespace-nowrap font-semibiold">
Date Date
</th> </th>
<th class="text-end border-b border-[#09090B] bg-[#09090B] text-white text-sm font-semibiold"> <th class="text-end border-b border-[#09090B] bg-[#09090B] text-white text-[1rem] whitespace-nowrap font-semibiold">
Employees Employees
</th> </th>
<th class="text-end border-b border-[#09090B] bg-[#09090B] hidden sm:table-cell text-white text-sm font-semibiold"> <th class="text-end border-b border-[#09090B] bg-[#09090B] text-white text-[1rem] whitespace-nowrap font-semibiold">
Change Change
</th> </th>
<th class="text-end border-b border-[#09090B] bg-[#09090B] text-white text-sm font-semibiold"> <th class="text-end border-b border-[#09090B] bg-[#09090B] text-white text-[1rem] whitespace-nowrap font-semibiold">
Growth Growth
</th> </th>
</tr> </tr>
@ -473,28 +480,26 @@ optionsGrowth = plotGrowth();
<tbody class=""> <tbody class="">
{#each historyList as item, index} {#each historyList as item, index}
<tr class="text-gray-200 odd:bg-[#27272A]"> <tr class="text-gray-200 odd:bg-[#27272A]">
<td class="text-start border-b border-[#09090B] text-xs sm:text-sm text-white"> <td class="text-start border-b border-[#09090B] 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' })} {new Date(item?.filingDate)?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' })}
</td> </td>
<td class="text-end border-b border-[#09090B] text-xs sm:text-sm text-white"> <td class="text-end border-b border-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap text-white">
{new Intl.NumberFormat("en").format(item?.employeeCount)} {new Intl.NumberFormat("en").format(item?.employeeCount)}
</td> </td>
<td class="text-end border-b border-[#09090B] text-xs sm:text-sm hidden sm:table-cell text-white"> <td class="text-end border-b border-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap text-white">
{abbreviateNumber(item?.employeeCount-historyList[index+1]?.employeeCount)} {abbreviateNumber(item?.employeeCount-historyList[index+1]?.employeeCount)}
</td> </td>
<td class="text-end border-b border-[#09090B] text-xs sm:text-sm text-white text-end"> <td class="text-end border-b border-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap text-white text-end">
{#if index+1-historyList?.length == 0} {#if index+1-historyList?.length === 0}
0.00% 0.00%
{:else} {:else}
{#if (item?.employeeCount- historyList[index+1]?.employeeCount) > 0} {#if (item?.employeeCount- historyList[index+1]?.employeeCount) > 0}
<svg class="w-5 h-5 -mr-1 inline-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g id="evaArrowUpFill0"><g id="evaArrowUpFill1"><path id="evaArrowUpFill2" fill="#10db06" d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"/></g></g></svg>
<span class="text-[#10DB06]"> <span class="text-[#10DB06]">
+{(((item?.employeeCount-historyList[index+1]?.employeeCount) / item?.employeeCount) * 100 )?.toFixed(2)}% +{(((item?.employeeCount-historyList[index+1]?.employeeCount) / item?.employeeCount) * 100 )?.toFixed(2)}%
</span> </span>
{:else if (item?.employeeCount - historyList[index+1]?.employeeCount ) < 0} {:else if (item?.employeeCount - historyList[index+1]?.employeeCount ) < 0}
<span class="text-[#FF2F1F]"> <span class="text-[#FF2F1F]">
<svg class="w-5 h-5 rotate-180 inline-block -mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g id="evaArrowUpFill0"><g id="evaArrowUpFill1"><path id="evaArrowUpFill2" fill="#FF2F1F" d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"/></g></g></svg> -{(((historyList[index+1]?.employeeCount - item?.employeeCount) / item?.employeeCount) * 100 )?.toFixed(2)}%
{(((historyList[index+1]?.employeeCount - item?.employeeCount) / item?.employeeCount) * 100 )?.toFixed(2)}%
</span> </span>
{:else} {:else}
0.00% 0.00%
@ -625,21 +630,20 @@ optionsGrowth = plotGrowth();
</dialog> </dialog>
<!--End Sort By Modal--> <!--End Sort By Modal-->
<style>
<style>
.app { .app {
height: 450px; height: 400px;
max-width: 1500px; width: 100%;
} }
@media (max-width: 560px) { @media (max-width: 560px) {
.app { .app {
max-width: 520px; width: 100%;
height: 450px; height: 300px;
} }
} }
.chart { .chart {
width: 100%; width: 100%;
} }
</style> </style>

View File

@ -6,14 +6,17 @@ import { Chart } from 'svelte-echarts'
import { init, use } from 'echarts/core' import { init, use } from 'echarts/core'
import { LineChart, BarChart } from 'echarts/charts' import { LineChart, BarChart } from 'echarts/charts'
import { GridComponent } from 'echarts/components' import { GridComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers' import { CanvasRenderer } from 'echarts/renderers'
use([LineChart, BarChart, GridComponent, CanvasRenderer]) import { onMount } from 'svelte';
use([LineChart, BarChart, GridComponent,TooltipComponent, CanvasRenderer])
export let data; export let data;
let isLoaded = false;
let optionsData; let optionsData;
let tableList = [];
let income = []; let income = [];
let fullStatement = []; let fullStatement = [];
@ -21,9 +24,12 @@ import { init, use } from 'echarts/core'
let displayStatement = 'revenue'; let displayStatement = 'revenue';
let mode = false; let mode = true;
let timeFrame = '10Y'; let timeFrame = '10Y';
onMount(async () => {
isLoaded = true;
})
const statementConfig = [ const statementConfig = [
@ -163,14 +169,14 @@ function normalizer(value) {
} }
} }
function plotData() function plotData()
{ {
let labelName = '-'; let labelName = '-';
let xList = []; let xList = [];
let valueList = []; let valueList = [];
let growthList = []; let growthList = [];
tableList = [];
const index = statementConfig?.findIndex((item) => item?.propertyName === displayStatement); const index = statementConfig?.findIndex((item) => item?.propertyName === displayStatement);
@ -180,23 +186,31 @@ function normalizer(value) {
const year = statement?.calendarYear?.slice(-2); const year = statement?.calendarYear?.slice(-2);
const quarter = statement?.period; const quarter = statement?.period;
// Determine the label based on filterRule
if (filterRule === 'annual') { if (filterRule === 'annual') {
xList?.push('FY'+year); xList.push('FY' + year);
} else { } else {
xList?.push('FY'+year+' '+quarter); xList.push('FY' + year + ' ' + quarter);
} }
// Calculate the value and growth
const value = (Number(statement[statementConfig[index]?.propertyName]))?.toFixed(2);
const growth = (Number(statement[statementConfig[index]?.growthPropertyName]) * 100)?.toFixed(2);
if (!['eps', 'epsdiluted']?.includes(statementConfig[index]?.propertyName)) valueList.push(value);
{ growthList.push(growth);
valueList.push((Number(statement[statementConfig[index]?.propertyName]) )?.toFixed(2));
} // Add the entry to tableList
else { tableList.push({
valueList.push((Number(statement[statementConfig[index]?.propertyName]))?.toFixed(2)); 'date': statement?.date,
} 'value': value,
growthList.push((Number(statement[statementConfig[index]?.growthPropertyName]) * 100)?.toFixed(2)); });
} }
//sort tableList by date
tableList?.sort((a, b) => new Date(b?.date) - new Date(a?.date));
if(['growthSellingGeneralAndAdministrativeExpenses']?.includes(statementConfig[index]?.growthPropertyName)) if(['growthSellingGeneralAndAdministrativeExpenses']?.includes(statementConfig[index]?.growthPropertyName))
{ growthList = []; { growthList = [];
// Calculate growth percentage and append to growthList // Calculate growth percentage and append to growthList
@ -213,7 +227,18 @@ function normalizer(value) {
const {unit, denominator } = normalizer(Math.max(...valueList) ?? 0) const {unit, denominator } = normalizer(Math.max(...valueList) ?? 0)
const options = { const options = {
animation: false,
grid: {
left: '0%',
right: '0%',
bottom: '2%',
top: '10%',
containLabel: true
},
xAxis: { xAxis: {
axisLabel: {
color: '#fff',
},
data: xList, data: xList,
type: 'category', type: 'category',
}, },
@ -224,7 +249,7 @@ function normalizer(value) {
show: false, // Disable x-axis grid lines show: false, // Disable x-axis grid lines
}, },
axisLabel: { axisLabel: {
color: '#6E7079', // Change label color to white color: '#fff', // Change label color to white
formatter: function (value) { formatter: function (value) {
value = Math.max(value, 0); value = Math.max(value, 0);
return '$'+(value / denominator)?.toFixed(1) + unit; // Format value in millions return '$'+(value / denominator)?.toFixed(1) + unit; // Format value in millions
@ -237,40 +262,18 @@ function normalizer(value) {
show: false, // Disable x-axis grid lines show: false, // Disable x-axis grid lines
}, },
}, },
{
type: 'value',
axisLabel: {
formatter: '{value} %',
},
splitLine: {
show: false, // Disable x-axis grid lines
},
},
], ],
series: [ series: [
{ {
name: labelName, name: labelName,
data: valueList, data: valueList,
type: 'line',
smooth: true,
},
{
name: 'Growth Rate [%]',
data: growthList,
type: 'bar', type: 'bar',
smooth: true, smooth: true,
yAxisIndex: 1,
itemStyle: {
color: (params) => {
// Set color based on positive or negative value
return params.data >= 0 ? '#10DB06' : '#FF2F1F';
},
},
}, },
], ],
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
hideDelay: 100, // Set the delay in milliseconds hideDelay: 100,
}, },
}; };
@ -387,9 +390,11 @@ const exportData = (format = 'csv') => {
</svelte:head> </svelte:head>
<section class="bg-[#09090B] overflow-hidden text-white h-full pb-40 sm:mb-0"> <section class="bg-[#09090B] w-full overflow-hidden text-white h-full pb-40 sm:mb-0">
<div class="flex justify-center w-full sm-auto h-full overflow-hidden"> <div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
<div class="relative flex justify-center items-center overflow-hidden"> <div class="w-full relative flex justify-center items-center overflow-hidden">
{#if isLoaded}
<main> <main>
<div class="sm:p-7 m-auto mt-2 sm:mt-0"> <div class="sm:p-7 m-auto mt-2 sm:mt-0">
<div class="mb-3"> <div class="mb-3">
@ -508,10 +513,71 @@ const exportData = (format = 'csv') => {
</div> </div>
</div> </div>
<div class="app w-full h-[300px] m-auto"> <div class="app w-full ">
<Chart {init} options={optionsData} class="chart" /> <Chart {init} options={optionsData} class="chart" />
</div> </div>
<h1 class="mt-5 text-2xl text-gray-200 font-semibold">
{statementConfig?.find((item) => item?.propertyName === displayStatement)?.label} History
</h1>
<div class="w-full overflow-x-scroll">
<table class="table table-sm table-compact rounded-none sm:rounded-md w-full border-bg-[#09090B] m-auto mt-4 ">
<thead>
<tr class="border border-slate-800">
<th class="text-white font-semibold text-start text-[1rem]">{filterRule === 'annual' ? 'Fiscal Year End' : 'Quarter Ends'}</th>
<th class="text-white font-semibold text-[1rem]">Revenue</th>
<th class="text-white font-semibold text-center text-[1rem]">Change</th>
<th class="text-white font-semibold text-end text-[1rem]">Growth</th>
</tr>
</thead>
<tbody>
{#each tableList as item, index}
<!-- row -->
<tr class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A] border-b-[#09090B] shake-ticker cursor-pointer">
<td class="text-white font-medium text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]">
{item?.date}
</td>
<td class="text-white text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]">
{abbreviateNumber(item?.value,true)}
</td>
<td class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-center border-b-[#09090B]">
{item?.value-tableList[index+1]?.value !== 0 ? abbreviateNumber(item?.value-tableList[index+1]?.value) : '-'}
</td>
<td class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-end border-b-[#09090B]">
{#if index+1-tableList?.length === 0}
-
{:else}
{#if (item?.value- tableList[index+1]?.value) > 0}
<span class="text-[#10DB06]">
+{(((item?.value-tableList[index+1]?.value) / item?.value) * 100 )?.toFixed(2)}%
</span>
{:else if (item?.value - tableList[index+1]?.value ) < 0}
<span class="text-[#FF2F1F]">
-{(((tableList[index+1]?.value - item?.value) / item?.value) * 100 )?.toFixed(2)}%
</span>
{:else}
-
{/if}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{:else} {:else}
@ -653,6 +719,15 @@ const exportData = (format = 'csv') => {
</div> </div>
</main> </main>
{:else}
<div class="w-full flex justify-center items-center h-80">
<div class="relative">
<label class="bg-[#09090B] rounded-xl h-14 w-14 flex justify-center items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<span class="loading loading-spinner loading-md"></span>
</label>
</div>
</div>
{/if}
</div> </div>
</div> </div>
</section> </section>
@ -826,14 +901,14 @@ const exportData = (format = 'csv') => {
<style> <style>
.app { .app {
height: 500px; height: 400px;
max-width: 1500px; width: 100%;
} }
@media (Max-width: 560px) { @media (max-width: 560px) {
.app { .app {
Max-width: 520px; width: 100%;
height: 500px; height: 300px;
} }
} }