312 lines
9.0 KiB
Svelte
312 lines
9.0 KiB
Svelte
<script lang="ts">
|
|
import { Chart } from "svelte-echarts";
|
|
import { setCache, getCache } from "$lib/store";
|
|
import { init, use } from "echarts/core";
|
|
import { LineChart, BarChart } from "echarts/charts";
|
|
import {
|
|
GridComponent,
|
|
TooltipComponent,
|
|
MarkPointComponent,
|
|
} from "echarts/components";
|
|
import { CanvasRenderer } from "echarts/renderers";
|
|
use([
|
|
LineChart,
|
|
BarChart,
|
|
GridComponent,
|
|
TooltipComponent,
|
|
MarkPointComponent,
|
|
CanvasRenderer,
|
|
]);
|
|
|
|
export let symbol;
|
|
export let ratingsList;
|
|
export let numOfRatings = 0;
|
|
|
|
let isLoaded = false;
|
|
let optionsData = null;
|
|
let historicalData = [];
|
|
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();
|
|
}
|
|
|
|
const monthNames = [
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"Apr",
|
|
"May",
|
|
"Jun",
|
|
"Jul",
|
|
"Aug",
|
|
"Sep",
|
|
"Oct",
|
|
"Nov",
|
|
"Dec",
|
|
];
|
|
|
|
// Function to plot data based on a specified time period
|
|
function plotData() {
|
|
// Extract the time (x-axis) and close values (y-axis)
|
|
const { dates, closeValues } = filterDataByPeriod(
|
|
historicalData,
|
|
timePeriod,
|
|
);
|
|
|
|
// Prepare markPoints for ratings
|
|
const markPoints = ratingsList
|
|
?.filter((rating) => {
|
|
// Ensure date format is correct and matches
|
|
return dates.includes(rating?.date) && rating?.ticker === symbol;
|
|
})
|
|
.map((rating) => ({
|
|
// Marker at the rating's date
|
|
type: "max", // 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: "white", // Set label color (can be customized)
|
|
fontSize: 14, // Set font size (increase for better visibility)
|
|
},
|
|
symbol: "rectangle", // 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",
|
|
data: closeValues,
|
|
type: "line",
|
|
smooth: true,
|
|
showSymbol: false,
|
|
areaStyle: {
|
|
color: "rgba(255, 255, 255, 0.08)",
|
|
},
|
|
lineStyle: {
|
|
color: "#fff",
|
|
width: 1,
|
|
},
|
|
markPoint: {
|
|
data: markPoints.map((point) => {
|
|
let pinColor = "#FF0000"; // Default to red (Sell, Strong Sell)
|
|
// Set the color based on the label
|
|
if (["Buy", "Strong Buy"]?.includes(point?.label?.formatter)) {
|
|
pinColor = "#00FF00"; // Green for Buy, Strong Buy
|
|
} else if (["Hold", "Neutral"]?.includes(point?.label?.formatter)) {
|
|
pinColor = "#FFA500"; // Orange for Hold
|
|
}
|
|
|
|
return {
|
|
name: point.name,
|
|
coord: point.coord,
|
|
label: {
|
|
...point.label,
|
|
fontSize: 16, // Increase font size
|
|
fontWeight: "bold", // Make label bold
|
|
color: "#fff", // Change label color to white
|
|
},
|
|
symbol: "pin", // Use pin symbol
|
|
symbolSize: 20, // Increase symbol size
|
|
itemStyle: {
|
|
color: pinColor, // Apply the dynamically set color
|
|
},
|
|
};
|
|
}),
|
|
},
|
|
},
|
|
];
|
|
|
|
// Define chart options
|
|
const options = {
|
|
animation: false,
|
|
silent: true,
|
|
grid: {
|
|
left: "2%",
|
|
right: "2%",
|
|
bottom: "10%",
|
|
top: "10%",
|
|
containLabel: true,
|
|
},
|
|
xAxis: {
|
|
type: "category",
|
|
data: dates, // Use the extracted dates
|
|
axisLabel: {
|
|
color: "#fff",
|
|
formatter: function (value) {
|
|
// Assuming dates are in the format 'yyyy-mm-dd'
|
|
const dateParts = value.split("-");
|
|
const year = dateParts[0].substring(2); // Extract last two digits of the year
|
|
const monthIndex = parseInt(dateParts[1]) - 1; // Zero-indexed months
|
|
return `${monthNames[monthIndex]} '${year}`;
|
|
},
|
|
},
|
|
},
|
|
yAxis: {
|
|
show: false, // Completely hides the y-axis
|
|
type: "value",
|
|
splitLine: {
|
|
show: false, // Disable grid lines
|
|
},
|
|
axisLabel: {
|
|
color: "#fff",
|
|
},
|
|
},
|
|
|
|
series: series, // Use the dynamically created series array
|
|
|
|
tooltip: {
|
|
trigger: "axis",
|
|
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;
|
|
}
|
|
|
|
$: {
|
|
if (symbol && typeof window !== "undefined" && timePeriod) {
|
|
isLoaded = false;
|
|
optionsData = null;
|
|
historicalData = [];
|
|
|
|
historicalPrice(symbol);
|
|
isLoaded = true;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="w-full overflow-hidden m-auto mt-5">
|
|
{#if isLoaded && optionsData !== null}
|
|
<div class="app w-full relative">
|
|
<div class="flex justify-start space-x-4 absolute left-20 top-0 z-10">
|
|
{#each ["1Y", "3Y", "5Y", "Max"] as item}
|
|
<label
|
|
on:click={() => (timePeriod = item)}
|
|
class="px-4 py-2 {timePeriod === item
|
|
? 'bg-[#27272A]'
|
|
: ''} sm:hover:bg-[#27272A] border border-gray-600 text-white rounded-md cursor-pointer"
|
|
>
|
|
{item}
|
|
</label>
|
|
{/each}
|
|
</div>
|
|
<h2
|
|
class="text-white text-xl font-semibold text-center absolute left-1/2 transform -translate-x-1/2 top-5 -translate-y-1/2"
|
|
>
|
|
{symbol} - {numOfRatings} Ratings
|
|
</h2>
|
|
<Chart {init} options={optionsData} class="chart" />
|
|
</div>
|
|
{:else}
|
|
<div class="flex justify-center items-center h-80">
|
|
<div class="relative">
|
|
<label
|
|
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 sm:loading-[1rem] text-gray-400"
|
|
></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.app {
|
|
height: 400px;
|
|
width: 100%;
|
|
}
|
|
|
|
@media (max-width: 560px) {
|
|
.app {
|
|
width: 100%;
|
|
height: 300px;
|
|
}
|
|
}
|
|
|
|
.chart {
|
|
width: 100%;
|
|
}
|
|
</style>
|