add ratings chart

This commit is contained in:
MuslemRahimi 2024-12-02 14:58:55 +01:00
parent cbe718ab6b
commit 67c19ec5d6
2 changed files with 234 additions and 466 deletions

View File

@ -1,25 +1,98 @@
<script lang="ts"> <script lang="ts">
import { numberOfUnreadNotification } from "$lib/store";
import ArrowLogo from "lucide-svelte/icons/move-up-right";
import { Chart } from "svelte-echarts"; import { Chart } from "svelte-echarts";
import Lazy from "$lib/components/Lazy.svelte"; import { setCache, getCache } from "$lib/store";
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, TooltipComponent } from "echarts/components"; import {
GridComponent,
TooltipComponent,
MarkPointComponent,
MarkLineComponent,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers"; import { CanvasRenderer } from "echarts/renderers";
import { onMount } from "svelte"; use([
import { goto } from "$app/navigation"; LineChart,
use([LineChart, BarChart, GridComponent, TooltipComponent, CanvasRenderer]); BarChart,
GridComponent,
TooltipComponent,
MarkPointComponent,
MarkLineComponent,
CanvasRenderer,
]);
export let data; export let symbol;
export let ratingsList;
export let numOfRatings = 0;
let isLoaded = false; let isLoaded = false;
let rawData; let optionsData = null;
let tableList = []; let historicalData = [];
let optionsData; let timePeriod = "1Y";
function filterDataByPeriod(historicalData, period = "1Y") {
const currentDate = new Date();
let startDate;
// Calculate the start date based on the period input
switch (period) {
case "1Y":
startDate = new Date();
startDate.setFullYear(currentDate.getFullYear() - 1);
break;
case "3Y":
startDate = new Date();
startDate.setFullYear(currentDate.getFullYear() - 3);
break;
case "5Y":
startDate = new Date();
startDate.setFullYear(currentDate.getFullYear() - 5);
break;
case "Max":
// For 'Max', assume we want to start from the earliest possible date
startDate = new Date(0); // This will set the date to January 1, 1970
break;
default:
throw new Error(`Unsupported period: ${period}`);
}
// Filter the data based on the calculated start date
const filteredData = historicalData.filter((item) => {
const itemDate = new Date(item.time);
return itemDate >= startDate && itemDate <= currentDate;
});
// Extract the dates and close values from the filtered data
const dates = filteredData.map((item) => item.time);
const closeValues = filteredData.map((item) => item.close);
return { dates, closeValues };
}
async function historicalPrice(symbol) {
const cachedData = getCache(symbol, "ratingsChart");
if (cachedData) {
historicalData = cachedData;
} else {
const postData = {
ticker: symbol,
timePeriod: "max",
};
const response = await fetch("/api/historical-price", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
historicalData = (await response?.json()) ?? [];
setCache(symbol, historicalData, "ratingsChart");
}
optionsData = plotData();
}
let timePeriod = "threeYears";
const monthNames = [ const monthNames = [
"Jan", "Jan",
"Feb", "Feb",
@ -35,495 +108,149 @@
"Dec", "Dec",
]; ];
async function changeStatement(event) {
timePeriod = event.target.value;
optionsData = await plotData();
}
// Function to plot data based on a specified time period // Function to plot data based on a specified time period
async function plotData() { function plotData() {
// Get the filtered data based on the selected time period // Extract the time (x-axis) and close values (y-axis)
const { dates, seriesData } = filterDataByTimePeriod(rawData, timePeriod); const { dates, closeValues } = filterDataByPeriod(
historicalData,
timePeriod,
);
// Convert seriesData to the format required by the series property // Prepare markPoints for ratings
let series = Object?.keys(seriesData).map((key) => { const markPoints = ratingsList
return { .filter((rating) => {
name: treasuryLabel[key], // Ensure date format is correct and matches
data: seriesData[key], return dates.includes(rating.date);
type: "line", })
smooth: true, .map((rating) => ({
showSymbol: false, // Marker at the rating's date
}; type: "1Y", // Marking the rating date
}); name: rating.rating_current,
coord: [
rating.date,
closeValues[dates.indexOf(rating.date)], // Find the close value corresponding to the rating date
],
label: {
formatter: rating.rating_current, // Display the rating_current text
position: "top", // Position the label above the point
color: "yellow", // Set label color (can be customized)
fontSize: 14, // Set font size (increase for better visibility)
},
symbol: "circle", // Symbol type (can be customized)
symbolSize: 12, // Increase symbol size for better visibility
itemStyle: {
color: "red", // Set symbol color to red for better visibility
},
}));
const series = [
{
name: "Price", // Name of the series
data: closeValues, // Use close values
type: "line", // Line chart
smooth: true, // Smooth lines
showSymbol: false, // Hide symbols on data points
areaStyle: {
color: "rgba(255, 255, 255, 0.08)", // Set the area color to a white color with opacity
},
lineStyle: {
color: "#fff", // Set the line color to white
},
},
];
// Define chart options
const options = { const options = {
animation: false, animation: false,
silent: true,
grid: { grid: {
left: "2%", left: "2%",
right: "2%", right: "2%",
bottom: "2%", bottom: "10%",
top: "10%", top: "10%",
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
type: "category",
data: dates, // Use the extracted dates
axisLabel: { axisLabel: {
color: "#fff", color: "#fff",
formatter: function (value) { formatter: function (value) {
// Assuming dates are in the format 'yyyy-mm-dd' // Assuming dates are in the format 'yyyy-mm-dd'
// Extract the month and day from the date string and convert the month to its abbreviated name
const dateParts = value.split("-"); const dateParts = value.split("-");
const year = dateParts[0].substring(2); // Extracting the last two digits of the year const year = dateParts[0].substring(2); // Extract last two digits of the year
const monthIndex = parseInt(dateParts[1]) - 1; // Months are zero-indexed in JavaScript Date objects const monthIndex = parseInt(dateParts[1]) - 1; // Zero-indexed months
return `${monthNames[monthIndex]} '${year}`; return `${monthNames[monthIndex]} '${year}`;
}, },
}, },
data: dates,
type: "category",
}, },
yAxis: [ yAxis: {
{
type: "value", type: "value",
splitLine: { splitLine: {
show: false, // Disable x-axis grid lines show: false, // Disable grid lines
}, },
axisLabel: { axisLabel: {
show: false, color: "#fff",
}, },
}, },
{
type: "value",
splitLine: {
show: false, // Disable x-axis grid lines
},
},
],
series: series, // Use the dynamically created series array series: series, // Use the dynamically created series array
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
hideDelay: 100, hideDelay: 100,
formatter: function (params) {
const date = params[0].name; // Get the date from the x-axis value
const dateParts = date.split("-");
const year = dateParts[0];
const monthIndex = parseInt(dateParts[1]) - 1;
const day = dateParts[2];
const formattedDate = `${monthNames[monthIndex]} ${day}, ${year}`;
// Return the tooltip content
return `${formattedDate}<br/> ${params[0].value}`;
},
}, },
}; };
return options; return options;
} }
onMount(async () => { $: {
rawData = data?.getEconomicIndicator?.treasury ?? {}; if (symbol && typeof window !== "undefined") {
isLoaded = false;
optionsData = null;
historicalData = [];
optionsData = await plotData(); historicalPrice(symbol);
isLoaded = true; isLoaded = true;
}); }
}
</script> </script>
<section <div class="w-full overflow-hidden m-auto mt-5">
class="w-full max-w-screen-2xl overflow-hidden min-h-screen pb-20 pt-5 px-4 lg:px-3" {#if isLoaded && optionsData !== null}
> <div class="app w-full relative">
<div class="text-sm sm:text-[1rem] breadcrumbs">
<ul>
<li><a href="/" class="text-gray-300">Home</a></li>
<li class="text-gray-300">US Economic Indicator</li>
</ul>
</div>
<div class="w-full overflow-hidden m-auto mt-5">
<div class="sm:p-0 flex justify-center w-full m-auto overflow-hidden">
<div
class="relative flex justify-center items-start overflow-hidden w-full"
>
<main class="w-full lg:w-3/4 lg:pr-5">
<div class="mb-6 border-b-[2px]">
<h1 class="mb-1 text-white text-2xl sm:text-3xl font-bold">
Economic Indicators
</h1>
</div>
{#if isLoaded}
<div
class="mb-8 w-full text-start sm:flex sm:flex-row sm:items-center m-auto text-gray-100 border border-gray-800 sm:rounded-md h-auto p-5"
>
<svg
class="w-5 h-5 inline-block sm:mr-2 flex-shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
><path
fill="#fff"
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
/></svg
>
Real-time and historical data on key economic indicators like GDP,
unemployment, and inflation, essential for tracking economic performance
and growth trends.
</div>
<div class="w-full m-auto mt-10">
<h2 class="text-xl sm:text-2xl text-gray-200 font-bold">
Federal Fund Rate
</h2>
<div class="text-[1rem] text-white mt-2 mb-2">
The federal funds rate is the interest rate at which banks lend
to each other overnight to maintain reserve balances. It's a
critical tool for U.S. monetary policy, influencing borrowing
costs for consumers and businesses, economic growth, and
inflation. Changes in the federal funds rate affect everything
from loan interest rates to stock market performance, making it
a key indicator of economic health.
</div>
<Lazy>
<div class="app w-full">
<Chart {init} options={optionsFedFundRate} class="chart" />
</div>
</Lazy>
<h2 <h2
class="text-xl sm:text-2xl text-gray-200 font-bold mt-20 sm:mt-10" class="text-white text-xl font-semibold text-center absolute left-1/2 transform -translate-x-1/2 top-5 -translate-y-1/2"
> >
Consumer Price Index (CPI) {symbol} - {numOfRatings} Ratings
</h2> </h2>
<div class="text-[1rem] text-white mt-2 mb-2">
The CPI measures the average change in prices for a typical
basket of goods. It's key for tracking inflation, affecting
interest rates, wages, and business decisions. Rising CPI
indicates inflation, impacting purchasing power and the overall
economy.
</div>
<Lazy>
<div class="app w-full">
<Chart {init} options={optionsCPI} class="chart" />
</div>
</Lazy>
<h2
class="text-xl sm:text-2xl text-gray-200 font-bold mt-20 sm:mt-10"
>
Gross Domestic Product (GDP)
</h2>
<div class="text-[1rem] text-white mt-2 mb-2">
The GDP measures a country's economic performance, representing
the total value of all goods and services produced within a
specific period. It's a key indicator of economic health, used
to compare the economic output of different nations and track
growth or decline over time.
</div>
<Lazy>
<div class="app w-full">
<Chart {init} options={optionsGDP} class="chart" />
</div>
</Lazy>
<h2
class="text-xl sm:text-2xl text-gray-200 font-bold mt-20 sm:mt-10"
>
Unemployment Rate vs Inflation Rate
</h2>
<div class="text-[1rem] text-white mt-2 mb-2">
The unemployment rate measures the jobless percentage in the
labor force, impacting spending and growth. Low unemployment
boosts wages and activity, while high unemployment slows them.
The inflation rate tracks price increases, with moderate
inflation (~2%) being healthy. These rates are often inversely
related and crucial for economic stability, influencing
spending, savings, and investment.
</div>
<Lazy>
<div class="app w-full">
<Chart {init} options={optionsInflation} class="chart" />
</div>
</Lazy>
<h2
class="text-xl sm:text-2xl text-gray-200 font-bold mt-20 sm:mt-10"
>
Treasury Rates
</h2>
<div class="text-[1rem] text-white mt-2 mb-2">
Treasury rates are the interest rates that the US government
pays on its debt obligations, and they are a key benchmark for
interest rates across the economy.
</div>
<div class="w-full text-white">
<div class="relative flex justify-end">
<select
class="w-24 select select-bordered select-sm p-0 pl-5 bg-[#313131]"
on:change={changeStatement}
>
<option disabled>Choose a Time Period</option>
<option value="oneMonth">1M</option>
<option value="sixMonths">6M</option>
<option value="oneYear">1Y</option>
<option value="threeYears" selected>3Y</option>
<option value="fiveYears">5Y</option>
<option value="max">Max</option>
</select>
</div>
</div>
<Lazy>
<div class="app w-full">
<Chart {init} options={optionsData} class="chart" /> <Chart {init} options={optionsData} class="chart" />
</div> </div>
</Lazy>
<div
class="mt-10 mb-4 bg-[#313131] w-fit relative flex flex-wrap items-center justify-center rounded-md p-1 flex justify-center sm:justify-end items-center ml-auto"
>
{#each tabs as item, i}
<button
on:click={() => changeTablePeriod(i)}
class="group relative z-[1] rounded-full px-6 py-1 {activeIdx ===
i
? 'z-0'
: ''} "
>
{#if activeIdx === i}
<div class="absolute inset-0 rounded-md bg-[#fff]"></div>
{/if}
<span
class="relative text-sm block font-semibold {activeIdx ===
i
? 'text-black'
: 'text-white'}"
>
{item.title}
<svg
class="{data?.user?.tier !== 'Pro' && i === 1
? ''
: 'hidden'} inline-block ml-0.5 -mt-1 w-3.5 h-3.5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="#fff"
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
>
</span>
</button>
{/each}
</div>
<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-gray-600">
<th
class="text-white font-semibold text-start text-sm sm:text-[1rem]"
>Date</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>1-Month</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>2-Month</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>3-Month</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>6-Month</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>1-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>2-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>3-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>5-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>7-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>10-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>20-Year</th
>
<th
class="text-white font-semibold text-end text-sm sm:text-[1rem]"
>30-Year</th
>
</tr>
</thead>
<tbody>
{#each tableList as item}
<!-- 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 font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.month1}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.month2 !== null ? item?.month2 : "-"}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.month3}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.month6}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year1}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year2}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year3}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year5}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year7}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year10}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year20}
</td>
<td
class="text-white font-medium text-sm sm:text-[1rem] text-end whitespace-nowrap border-b-[#09090B]"
>
{item?.year30}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
{:else} {:else}
<div class="flex justify-center items-center h-80"> <div class="flex justify-center items-center h-80">
<div class="relative"> <div class="relative">
<label <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" class="bg-[#27272A] rounded-md 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 text-gray-400" <span
class="loading loading-spinner loading-md sm:loading-[1rem] text-gray-400"
></span> ></span>
</label> </label>
</div> </div>
</div> </div>
{/if} {/if}
</main> </div>
<aside class="hidden lg:block relative fixed w-1/4 ml-4">
{#if data?.user?.tier !== "Pro" || data?.user?.freeTrial}
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href="/pricing"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Pro Subscription
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Upgrade now for unlimited access to all data and tools.
</span>
</a>
</div>
{/if}
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href="/economic-calendar"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Economic Events
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Stay updated on upcoming Economic Events worldwide.
</span>
</a>
</div>
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href="/earnings-calendar"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Earnings Calendar
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Get the latest Earnings of companies.
</span>
</a>
</div>
</aside>
</div>
</div>
</div>
</section>
<style> <style>
.app { .app {

View File

@ -4,6 +4,7 @@
import TableHeader from "$lib/components/Table/TableHeader.svelte"; import TableHeader from "$lib/components/Table/TableHeader.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import HoverStockChart from "$lib/components/HoverStockChart.svelte"; import HoverStockChart from "$lib/components/HoverStockChart.svelte";
import RatingsChart from "$lib/components/RatingsChart.svelte";
export let data; export let data;
let analystStats = data?.getAnalystStats; let analystStats = data?.getAnalystStats;
@ -72,6 +73,15 @@
}; };
}); });
$: checkedSymbol = "";
function openGraph(symbol) {
// Clear all existing symbols
if (checkedSymbol === symbol) {
checkedSymbol = "";
} else {
checkedSymbol = symbol;
}
}
$: charNumber = $screenWidth < 640 ? 20 : 40; $: charNumber = $screenWidth < 640 ? 20 : 40;
let columns = [ let columns = [
@ -379,11 +389,16 @@
class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A]" class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A]"
> >
<td <td
><button class="h-full pl-2 pr-2 align-middle lg:pl-3" ><button
on:click={() => openGraph(item?.ticker)}
class="h-full pl-2 pr-2 align-middle lg:pl-3"
><svg ><svg
class="w-5 h-5 text-icon" class="w-5 h-5 text-icon {checkedSymbol ===
item?.ticker
? 'rotate-180'
: ''}"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor" fill="white"
style="max-width:40px" style="max-width:40px"
><path ><path
fill-rule="evenodd" fill-rule="evenodd"
@ -542,6 +557,32 @@
})} })}
</td> </td>
</tr> </tr>
{#if checkedSymbol === item?.ticker}
<tr
><td colspan="8" class="px-0" style=""
><div class="-mt-0.5 px-0 pb-2">
<div class="relative h-[400px]">
<div class="absolute top-0 w-full">
<div
class="h-[250px] w-full border-gray-600 xs:h-[300px] sm:h-[400px] md:border"
style="overflow: hidden;"
>
<div
style="position: relative; height: 0px; z-index: 1;"
>
<RatingsChart
ratingsList={rawData}
symbol={item?.ticker}
numOfRatings={item?.ratings}
/>
</div>
</div>
</div>
</div>
</div></td
>
</tr>
{/if}
{/each} {/each}
</tbody> </tbody>
</table> </table>