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) {
const yearlySummaries = {};
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
// 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
const excludeKeys = new Set(['priceToEarnings','weightedAverageShsOut', 'weightedAverageShsOutDil']);
// FMP sucks since these keys are up to date for every quarter hence no summation required
const excludeKeys = new Set(['priceToEarnings', 'weightedAverageShsOut', 'weightedAverageShsOutDil']);
// Function to get the quarter number from the period string
function getQuarterNumber(period) {
@ -166,19 +166,22 @@ export function sumQuarterlyResultsByYear(quarterlyResults, namingList) {
if (!yearlySummaries[year]) {
yearlySummaries[year] = {
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;
}
// Increment the quarter count for the 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) {
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
Object?.keys(quarter)?.forEach(key => {
if (typeof quarter[key] === 'number' && !excludeKeys?.has(key) && !lastQuarterKeys.has(key)) {
@ -192,14 +195,14 @@ export function sumQuarterlyResultsByYear(quarterlyResults, namingList) {
}
});
});
// Filter out years with less than 4 quarters
const validYears = Object?.keys(quarterCounts)?.filter(year => quarterCounts[year] === 4);
const annualResults = validYears?.map(year => yearlySummaries[year]);
// Sort the results by year in descending order
annualResults.sort((a, b) => b?.calendarYear?.localeCompare(a?.calendarYear));
return annualResults;
}

View File

@ -64,7 +64,7 @@ function selectSortingMethod(state:string) {
const options = {
grid: {
left: '0%',
right: '2%',
right: '0%',
top: '10%',
bottom: '20%',
containLabel: true,
@ -72,11 +72,15 @@ function selectSortingMethod(state:string) {
xAxis: {
data: dateList,
type: 'category',
axisLabel: {
color: '#fff',
}
},
yAxis: [
{
type: 'value',
axisLabel: {
color: '#fff',
formatter: '{value}',
},
splitLine: {
@ -94,12 +98,7 @@ function selectSortingMethod(state:string) {
type: 'bar',
barWidth: '80%',
smooth: true,
itemStyle: {
// Define colors based on positive/negative values
color: function(params) {
return '#F8901E';
}
},
},
],
@ -142,11 +141,15 @@ function selectSortingMethod(state:string) {
xAxis: {
data: dateList,
type: 'category',
axisLabel: {
color: '#fff'
}
},
yAxis: [
{
type: 'value',
axisLabel: {
color: '#fff',
formatter: '{value}',
},
splitLine: {
@ -219,11 +222,15 @@ function plotGrowth() {
xAxis: {
data: dateList,
type: 'category',
axisLabel: {
color: '#fff'
}
},
yAxis: [
{
type: 'value',
axisLabel: {
color:'#fff',
formatter: '{value} %',
},
splitLine: {
@ -452,20 +459,20 @@ optionsGrowth = plotGrowth();
</div>
{/if}
<div class="flex justify-start items-center w-full m-auto pr-1 pl-1 p-3">
<table class="table table-sm table-compact flex justify-start items-center w-full px-3 m-auto">
<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>
<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
</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
</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
</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
</th>
</tr>
@ -473,28 +480,26 @@ optionsGrowth = plotGrowth();
<tbody class="">
{#each historyList as item, index}
<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' })}
</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)}
</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)}
</td>
<td class="text-end border-b border-[#09090B] text-xs sm:text-sm text-white text-end">
{#if index+1-historyList?.length == 0}
<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}
0.00%
{:else}
{#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]">
+{(((item?.employeeCount-historyList[index+1]?.employeeCount) / item?.employeeCount) * 100 )?.toFixed(2)}%
</span>
{:else if (item?.employeeCount - historyList[index+1]?.employeeCount ) < 0}
<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>
{:else}
0.00%
@ -625,21 +630,20 @@ optionsGrowth = plotGrowth();
</dialog>
<!--End Sort By Modal-->
<style>
.app {
height: 450px;
max-width: 1500px;
}
@media (max-width: 560px) {
.app {
max-width: 520px;
height: 450px;
}
}
<style>
.app {
height: 400px;
width: 100%;
}
.chart {
width: 100%;
}
</style>
@media (max-width: 560px) {
.app {
width: 100%;
height: 300px;
}
}
.chart {
width: 100%;
}
</style>

View File

@ -6,24 +6,30 @@ import { Chart } from 'svelte-echarts'
import { init, use } from 'echarts/core'
import { LineChart, BarChart } from 'echarts/charts'
import { GridComponent } from 'echarts/components'
import { GridComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
use([LineChart, BarChart, GridComponent, CanvasRenderer])
import { onMount } from 'svelte';
use([LineChart, BarChart, GridComponent,TooltipComponent, CanvasRenderer])
export let data;
let isLoaded = false;
let optionsData;
let tableList = [];
let income = [];
let fullStatement = [];
let filterRule = 'annual';
let displayStatement = 'revenue';
let mode = false;
let mode = true;
let timeFrame = '10Y';
onMount(async () => {
isLoaded = true;
})
const statementConfig = [
@ -163,14 +169,14 @@ function normalizer(value) {
}
}
function plotData()
function plotData()
{
let labelName = '-';
let xList = [];
let valueList = [];
let growthList = [];
tableList = [];
const index = statementConfig?.findIndex((item) => item?.propertyName === displayStatement);
@ -180,22 +186,30 @@ function normalizer(value) {
const year = statement?.calendarYear?.slice(-2);
const quarter = statement?.period;
// Determine the label based on filterRule
if (filterRule === 'annual') {
xList?.push('FY'+year);
xList.push('FY' + year);
} else {
xList?.push('FY'+year+' '+quarter);
xList.push('FY' + year + ' ' + quarter);
}
if (!['eps', 'epsdiluted']?.includes(statementConfig[index]?.propertyName))
{
valueList.push((Number(statement[statementConfig[index]?.propertyName]) )?.toFixed(2));
}
else {
valueList.push((Number(statement[statementConfig[index]?.propertyName]))?.toFixed(2));
}
growthList.push((Number(statement[statementConfig[index]?.growthPropertyName]) * 100)?.toFixed(2));
}
// Calculate the value and growth
const value = (Number(statement[statementConfig[index]?.propertyName]))?.toFixed(2);
const growth = (Number(statement[statementConfig[index]?.growthPropertyName]) * 100)?.toFixed(2);
valueList.push(value);
growthList.push(growth);
// Add the entry to tableList
tableList.push({
'date': statement?.date,
'value': value,
});
}
//sort tableList by date
tableList?.sort((a, b) => new Date(b?.date) - new Date(a?.date));
if(['growthSellingGeneralAndAdministrativeExpenses']?.includes(statementConfig[index]?.growthPropertyName))
{ growthList = [];
@ -213,7 +227,18 @@ function normalizer(value) {
const {unit, denominator } = normalizer(Math.max(...valueList) ?? 0)
const options = {
animation: false,
grid: {
left: '0%',
right: '0%',
bottom: '2%',
top: '10%',
containLabel: true
},
xAxis: {
axisLabel: {
color: '#fff',
},
data: xList,
type: 'category',
},
@ -224,7 +249,7 @@ function normalizer(value) {
show: false, // Disable x-axis grid lines
},
axisLabel: {
color: '#6E7079', // Change label color to white
color: '#fff', // Change label color to white
formatter: function (value) {
value = Math.max(value, 0);
return '$'+(value / denominator)?.toFixed(1) + unit; // Format value in millions
@ -237,40 +262,18 @@ function normalizer(value) {
show: false, // Disable x-axis grid lines
},
},
{
type: 'value',
axisLabel: {
formatter: '{value} %',
},
splitLine: {
show: false, // Disable x-axis grid lines
},
},
],
series: [
{
name: labelName,
data: valueList,
type: 'line',
smooth: true,
},
{
name: 'Growth Rate [%]',
data: growthList,
type: 'bar',
smooth: true,
yAxisIndex: 1,
itemStyle: {
color: (params) => {
// Set color based on positive or negative value
return params.data >= 0 ? '#10DB06' : '#FF2F1F';
},
},
},
],
tooltip: {
trigger: 'axis',
hideDelay: 100, // Set the delay in milliseconds
hideDelay: 100,
},
};
@ -329,8 +332,8 @@ const exportData = (format = 'csv') => {
timeFrame = '10Y';
income = fullStatement?.slice(0,10);
displayStatement = 'revenue';
$: {
if (timeFrame || displayStatement || filterRule)
{
@ -387,9 +390,11 @@ const exportData = (format = 'csv') => {
</svelte:head>
<section class="bg-[#09090B] 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="relative flex justify-center items-center overflow-hidden">
<section class="bg-[#09090B] w-full overflow-hidden text-white h-full pb-40 sm:mb-0">
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
<div class="w-full relative flex justify-center items-center overflow-hidden">
{#if isLoaded}
<main>
<div class="sm:p-7 m-auto mt-2 sm:mt-0">
<div class="mb-3">
@ -508,9 +513,70 @@ const exportData = (format = 'csv') => {
</div>
</div>
<div class="app w-full h-[300px] m-auto">
<div class="app w-full ">
<Chart {init} options={optionsData} class="chart" />
</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}
@ -653,6 +719,15 @@ const exportData = (format = 'csv') => {
</div>
</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>
</section>
@ -826,14 +901,14 @@ const exportData = (format = 'csv') => {
<style>
.app {
height: 500px;
max-width: 1500px;
height: 400px;
width: 100%;
}
@media (Max-width: 560px) {
@media (max-width: 560px) {
.app {
Max-width: 520px;
height: 500px;
width: 100%;
height: 300px;
}
}