update chart
This commit is contained in:
parent
72fa6c3394
commit
f618bb8271
@ -1,65 +1,178 @@
|
|||||||
<script lang="ts" context="module">
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
|
|
||||||
export { echarts }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type EChartsOptions = echarts.EChartsOption
|
|
||||||
export type EChartsTheme = string | object
|
|
||||||
export type EChartsRenderer = 'canvas' | 'svg'
|
|
||||||
|
|
||||||
export type ChartOptions = {
|
|
||||||
theme?: EChartsTheme
|
|
||||||
renderer?: EChartsRenderer
|
|
||||||
options: EChartsOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_OPTIONS: Partial<ChartOptions> = {
|
|
||||||
theme: undefined,
|
|
||||||
renderer: 'canvas',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chartable(element: HTMLElement, echartOptions: ChartOptions) {
|
|
||||||
const { theme, renderer, options } = {
|
|
||||||
...DEFAULT_OPTIONS,
|
|
||||||
...echartOptions,
|
|
||||||
}
|
|
||||||
const echartsInstance = echarts.init(element, theme, { renderer })
|
|
||||||
echartsInstance.setOption(options)
|
|
||||||
|
|
||||||
function handleResize() {
|
|
||||||
echartsInstance.resize()
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
echartsInstance.dispose()
|
|
||||||
window.removeEventListener('resize', handleResize)
|
|
||||||
},
|
|
||||||
update(newOptions: ChartOptions) {
|
|
||||||
echartsInstance.setOption({
|
|
||||||
...echartOptions.options,
|
|
||||||
...newOptions.options,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let options: echarts.EChartsOption
|
import highcharts from "$lib/highcharts.ts";
|
||||||
export let { theme, renderer } = DEFAULT_OPTIONS
|
|
||||||
|
|
||||||
|
export let ticker: string;
|
||||||
|
export let initialData: any[];
|
||||||
|
|
||||||
|
let chart: any = null;
|
||||||
|
let selectedInterval = "1D";
|
||||||
|
const intervals = ["1D", "1W", "1M", "6M", "1Y", "MAX"];
|
||||||
|
let historicalData: { [key: string]: any[] } = {};
|
||||||
|
let loading = false;
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
chart: {
|
||||||
|
type: "area",
|
||||||
|
backgroundColor: "#09090B",
|
||||||
|
height: "400px",
|
||||||
|
},
|
||||||
|
title: { text: null },
|
||||||
|
xAxis: {
|
||||||
|
type: "datetime",
|
||||||
|
labels: {
|
||||||
|
format: "{value:%b %Y}",
|
||||||
|
style: { color: "#FFF" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
title: { text: null },
|
||||||
|
opposite: true,
|
||||||
|
labels: { style: { color: "#FFF" } },
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
shared: true,
|
||||||
|
formatter: function () {
|
||||||
|
return `<b>${Highcharts.dateFormat("%b %e, %Y", this.x)}</b><br>
|
||||||
|
Price: $${Highcharts.numberFormat(this.points[0].y, 2)}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Price",
|
||||||
|
data: [],
|
||||||
|
color: "#00FC50",
|
||||||
|
fillColor: {
|
||||||
|
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
|
||||||
|
stops: [
|
||||||
|
[0, "rgba(0, 252, 80, 0.2)"],
|
||||||
|
[1, "rgba(0, 252, 80, 0.01)"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchHistoricalData(timePeriod: string) {
|
||||||
|
if (historicalData[timePeriod]) return;
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/historical-price`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ ticker, timePeriod }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
historicalData[timePeriod] = data.map((point: any) => ({
|
||||||
|
x: new Date(point.date).getTime(),
|
||||||
|
y: point.close,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch historical data:", error);
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChart(timePeriod: string) {
|
||||||
|
if (!chart || !historicalData[timePeriod]) return;
|
||||||
|
|
||||||
|
chart.update(
|
||||||
|
{
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: historicalData[timePeriod],
|
||||||
|
type: timePeriod === "1D" ? "line" : "area",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleIntervalChange(timePeriod: string) {
|
||||||
|
selectedInterval = timePeriod;
|
||||||
|
await fetchHistoricalData(timePeriod);
|
||||||
|
updateChart(timePeriod);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="chart" use:chartable={{ renderer, theme, options }} />
|
<div class="chart-container">
|
||||||
|
<div
|
||||||
|
class="chart border border-gray-800 rounded"
|
||||||
|
use:highcharts={chartConfig}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div class="interval-selector">
|
||||||
|
{#each intervals as interval}
|
||||||
|
<button
|
||||||
|
class:active={selectedInterval === interval}
|
||||||
|
on:click={() => handleIntervalChange(interval)}
|
||||||
|
>
|
||||||
|
{interval}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="loading-indicator">
|
||||||
|
<span class="spinner" /> Loading...
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chart {
|
.chart-container {
|
||||||
height: 100%;
|
position: relative;
|
||||||
width: 100%;
|
background: #09090b;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interval-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
background: #222;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
background: #00fc50;
|
||||||
|
color: #000;
|
||||||
|
border-color: #00fc50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: transparent;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AreaSeries, Chart, PriceLine } from "svelte-lightweight-charts";
|
import highcharts from "$lib/highcharts.ts";
|
||||||
import { TrackingModeExitMode, ColorType } from "lightweight-charts";
|
|
||||||
import {
|
import {
|
||||||
getCache,
|
getCache,
|
||||||
setCache,
|
setCache,
|
||||||
@ -14,8 +14,7 @@
|
|||||||
shouldUpdatePriceChart,
|
shouldUpdatePriceChart,
|
||||||
priceChartData,
|
priceChartData,
|
||||||
} from "$lib/store";
|
} from "$lib/store";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
//import BullBearSay from "$lib/components/BullBearSay.svelte";
|
|
||||||
import WIIM from "$lib/components/WIIM.svelte";
|
import WIIM from "$lib/components/WIIM.svelte";
|
||||||
import SEO from "$lib/components/SEO.svelte";
|
import SEO from "$lib/components/SEO.svelte";
|
||||||
|
|
||||||
@ -35,12 +34,173 @@
|
|||||||
//============================================//
|
//============================================//
|
||||||
const intervals = ["1D", "1W", "1M", "6M", "1Y", "MAX"];
|
const intervals = ["1D", "1W", "1M", "6M", "1Y", "MAX"];
|
||||||
|
|
||||||
let chart = null;
|
let config = null;
|
||||||
async function checkChart() {
|
let output = null;
|
||||||
if (chart) {
|
let displayData = "1D";
|
||||||
clearInterval(intervalId);
|
let lastValue;
|
||||||
fitContentChart();
|
|
||||||
|
function plotData(priceData) {
|
||||||
|
const rawData = priceData || [];
|
||||||
|
|
||||||
|
const priceList = rawData?.map((item) => item?.close);
|
||||||
|
const dateList = rawData?.map((item) => {
|
||||||
|
const parsedDate = new Date(item?.time);
|
||||||
|
if (isNaN(parsedDate)) {
|
||||||
|
console.error("Invalid date:", item?.time);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
return parsedDate.getTime(); // Use timestamp directly
|
||||||
|
});
|
||||||
|
|
||||||
|
const seriesData = rawData?.map((item) => [
|
||||||
|
Date.UTC(
|
||||||
|
new Date(item?.time).getUTCFullYear(),
|
||||||
|
new Date(item?.time).getUTCMonth(),
|
||||||
|
new Date(item?.time).getUTCDate(),
|
||||||
|
new Date(item?.time).getUTCHours(),
|
||||||
|
new Date(item?.time).getUTCMinutes(),
|
||||||
|
new Date(item?.time).getUTCSeconds(),
|
||||||
|
),
|
||||||
|
item?.close,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Find the lowest & highest close values
|
||||||
|
const minValue = Math.min(...rawData?.map((item) => item?.close));
|
||||||
|
const maxValue = Math.max(...rawData?.map((item) => item?.close));
|
||||||
|
|
||||||
|
const padding = 0.015; // 1.5%
|
||||||
|
const yMin = minValue * (1 - padding);
|
||||||
|
const yMax = maxValue * (1 + padding);
|
||||||
|
|
||||||
|
const baseDate =
|
||||||
|
rawData && rawData?.length ? new Date(rawData?.at(0)?.time) : new Date();
|
||||||
|
|
||||||
|
// Set the fixed start and end times (9:30 and 16:10)
|
||||||
|
const startTime = new Date(
|
||||||
|
baseDate.getFullYear(),
|
||||||
|
baseDate.getMonth(),
|
||||||
|
baseDate.getDate(),
|
||||||
|
9,
|
||||||
|
30,
|
||||||
|
).getTime();
|
||||||
|
const endTime = new Date(
|
||||||
|
baseDate.getFullYear(),
|
||||||
|
baseDate.getMonth(),
|
||||||
|
baseDate.getDate(),
|
||||||
|
16,
|
||||||
|
0,
|
||||||
|
).getTime();
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
chart: {
|
||||||
|
backgroundColor: "#09090B",
|
||||||
|
height: 360,
|
||||||
|
},
|
||||||
|
credits: { enabled: false },
|
||||||
|
title: { text: null },
|
||||||
|
xAxis: {
|
||||||
|
type: "datetime",
|
||||||
|
min: displayData === "1D" ? startTime : null,
|
||||||
|
max: displayData === "1D" ? endTime : null,
|
||||||
|
tickLength: 0,
|
||||||
|
data: displayData === "1D" ? null : dateList,
|
||||||
|
crosshair: {
|
||||||
|
color: "#fff",
|
||||||
|
width: 1,
|
||||||
|
dashStyle: "Solid",
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { color: "#fff" },
|
||||||
|
distance: 20,
|
||||||
|
formatter: function () {
|
||||||
|
const date = new Date(this?.value);
|
||||||
|
if (displayData === "1D") {
|
||||||
|
return date?.toLocaleTimeString("en-US", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return date?.toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
timeZone: "UTC",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
yAxis: {
|
||||||
|
// Force y‑axis to stay near the actual data range
|
||||||
|
min: yMin,
|
||||||
|
max: yMax,
|
||||||
|
startOnTick: false,
|
||||||
|
endOnTick: false,
|
||||||
|
gridLineWidth: 1,
|
||||||
|
gridLineColor: "#111827",
|
||||||
|
title: { text: null },
|
||||||
|
labels: {
|
||||||
|
style: { color: "white" },
|
||||||
|
},
|
||||||
|
opposite: true,
|
||||||
|
// Add a dashed plot line at the previous close value
|
||||||
|
plotLines: [
|
||||||
|
{
|
||||||
|
value:
|
||||||
|
displayData === "1D"
|
||||||
|
? data?.getStockQuote?.previousClose
|
||||||
|
: priceData?.at(0)?.close,
|
||||||
|
dashStyle: "Dash",
|
||||||
|
color: "#fff", // Choose a contrasting color if needed
|
||||||
|
width: 0.8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
shared: true,
|
||||||
|
useHTML: true,
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
||||||
|
borderColor: "rgba(255, 255, 255, 0.2)",
|
||||||
|
borderWidth: 1,
|
||||||
|
style: {
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: "16px",
|
||||||
|
padding: "10px",
|
||||||
|
},
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
series: {
|
||||||
|
animation: false,
|
||||||
|
marker: { enabled: false },
|
||||||
|
states: { hover: { enabled: false } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: { enabled: false },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Price",
|
||||||
|
type: "area",
|
||||||
|
data: displayData === "1D" ? seriesData : priceList,
|
||||||
|
animation: false,
|
||||||
|
color: "#fff",
|
||||||
|
lineWidth: 1,
|
||||||
|
marker: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
fillColor: {
|
||||||
|
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
|
||||||
|
stops: [
|
||||||
|
[0, "rgba(255, 255, 255, 0.03)"],
|
||||||
|
[1, "rgba(255, 255, 255, 0.001)"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
//const startTimeTracking = performance.now();
|
//const startTimeTracking = performance.now();
|
||||||
@ -56,43 +216,43 @@
|
|||||||
let baseClose = previousClose;
|
let baseClose = previousClose;
|
||||||
let graphBaseClose;
|
let graphBaseClose;
|
||||||
|
|
||||||
const length = oneDayPrice?.length;
|
currentDataRowOneDay = oneDayPrice?.at(-1);
|
||||||
for (let i = length - 1; i >= 0; i--) {
|
|
||||||
if (!isNaN(oneDayPrice[i]?.close)) {
|
|
||||||
currentDataRowOneDay = oneDayPrice[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine current data row and base close price based on displayData
|
|
||||||
switch (displayData) {
|
switch (displayData) {
|
||||||
|
case "1D":
|
||||||
|
config = plotData(oneDayPrice) || null;
|
||||||
|
break;
|
||||||
case "1W":
|
case "1W":
|
||||||
currentDataRow = oneWeekPrice?.at(-1); // Latest entry for 1 week
|
currentDataRow = oneWeekPrice?.at(-1);
|
||||||
graphBaseClose = oneWeekPrice?.at(0)?.close;
|
graphBaseClose = oneWeekPrice?.at(0)?.close;
|
||||||
|
config = plotData(oneWeekPrice) || null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "1M":
|
case "1M":
|
||||||
currentDataRow = oneMonthPrice?.at(-1); // Latest entry for 1 month
|
currentDataRow = oneMonthPrice?.at(-1);
|
||||||
graphBaseClose = oneMonthPrice?.at(0)?.close;
|
graphBaseClose = oneMonthPrice?.at(0)?.close;
|
||||||
|
config = plotData(oneMonthPrice) || null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "6M":
|
case "6M":
|
||||||
currentDataRow = sixMonthPrice?.at(-1); // Latest entry for 6 months
|
currentDataRow = sixMonthPrice?.at(-1);
|
||||||
graphBaseClose = sixMonthPrice?.at(0)?.close;
|
graphBaseClose = sixMonthPrice?.at(0)?.close;
|
||||||
|
config = plotData(sixMonthPrice) || null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "1Y":
|
case "1Y":
|
||||||
currentDataRow = oneYearPrice?.at(-1); // Latest entry for 1 year
|
currentDataRow = oneYearPrice?.at(-1);
|
||||||
graphBaseClose = oneYearPrice?.at(0)?.close;
|
graphBaseClose = oneYearPrice?.at(0)?.close;
|
||||||
|
config = plotData(oneYearPrice) || null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "MAX":
|
case "MAX":
|
||||||
currentDataRow = maxPrice?.at(-1); // Latest entry for MAX range
|
currentDataRow = maxPrice?.at(-1);
|
||||||
graphBaseClose = maxPrice?.at(0)?.close;
|
graphBaseClose = maxPrice?.at(0)?.close;
|
||||||
|
config = plotData(maxPrice) || null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate percentage change if baseClose and currentDataRow are valid
|
|
||||||
const closeValue =
|
const closeValue =
|
||||||
$realtimePrice !== null && $realtimePrice !== undefined
|
$realtimePrice !== null && $realtimePrice !== undefined
|
||||||
? $realtimePrice
|
? $realtimePrice
|
||||||
@ -155,36 +315,18 @@
|
|||||||
$realtimePrice = data?.getStockQuote?.price;
|
$realtimePrice = data?.getStockQuote?.price;
|
||||||
$currentPortfolioPrice = $realtimePrice;
|
$currentPortfolioPrice = $realtimePrice;
|
||||||
} else if (oneDayPrice?.length !== 0) {
|
} else if (oneDayPrice?.length !== 0) {
|
||||||
const length = oneDayPrice?.length;
|
$currentPortfolioPrice = oneDayPrice?.at(-1)?.close;
|
||||||
for (let i = length - 1; i >= 0; i--) {
|
|
||||||
if (!isNaN(oneDayPrice[i]?.close)) {
|
|
||||||
$currentPortfolioPrice = oneDayPrice[i]?.close;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let displayData = "1D";
|
|
||||||
let colorChange;
|
|
||||||
let topColorChange;
|
|
||||||
let bottomColorChange;
|
|
||||||
|
|
||||||
let lastValue;
|
|
||||||
async function changeData(state) {
|
async function changeData(state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "1D":
|
case "1D":
|
||||||
displayData = "1D";
|
displayData = "1D";
|
||||||
if (oneDayPrice?.length !== 0) {
|
if (oneDayPrice?.length !== 0) {
|
||||||
displayLastLogicalRangeValue = oneDayPrice?.at(0)?.close; //previousClose
|
displayLastLogicalRangeValue = oneDayPrice?.at(0)?.close; //previousClose
|
||||||
const length = oneDayPrice?.length;
|
lastValue = oneDayPrice?.at(-1)?.close;
|
||||||
for (let i = length - 1; i >= 0; i--) {
|
|
||||||
if (!isNaN(oneDayPrice[i]?.close)) {
|
|
||||||
lastValue = oneDayPrice[i]?.close;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
displayLastLogicalRangeValue = null;
|
displayLastLogicalRangeValue = null;
|
||||||
lastValue = null;
|
lastValue = null;
|
||||||
@ -253,27 +395,8 @@
|
|||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
colorChange =
|
|
||||||
lastValue < displayLastLogicalRangeValue ? "#FF2F1F" : "#00FC50";
|
|
||||||
topColorChange =
|
|
||||||
lastValue < displayLastLogicalRangeValue
|
|
||||||
? "rgb(255, 47, 31, 0.2)"
|
|
||||||
: "rgb(16, 219, 6, 0.2)";
|
|
||||||
bottomColorChange =
|
|
||||||
lastValue < displayLastLogicalRangeValue
|
|
||||||
? "rgb(255, 47, 31, 0.001)"
|
|
||||||
: "rgb(16, 219, 6, 0.001)";
|
|
||||||
|
|
||||||
fitContentChart();
|
|
||||||
|
|
||||||
//trackButtonClick('Time Period: '+ state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = null;
|
|
||||||
|
|
||||||
//====================================//
|
|
||||||
|
|
||||||
let intervalId = null;
|
|
||||||
let oneDayPrice = [];
|
let oneDayPrice = [];
|
||||||
let oneWeekPrice = [];
|
let oneWeekPrice = [];
|
||||||
let oneMonthPrice = [];
|
let oneMonthPrice = [];
|
||||||
@ -306,6 +429,7 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output = null;
|
output = null;
|
||||||
|
config = null;
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
ticker: $stockTicker,
|
ticker: $stockTicker,
|
||||||
@ -322,39 +446,28 @@
|
|||||||
|
|
||||||
output = (await response?.json()) ?? [];
|
output = (await response?.json()) ?? [];
|
||||||
|
|
||||||
const mapData = (data) =>
|
|
||||||
data?.map(({ time, open, high, low, close }) => ({
|
|
||||||
time: ["1D", "1W", "1M"]?.includes(displayData)
|
|
||||||
? Date?.parse(time + "Z") / 1000
|
|
||||||
: time,
|
|
||||||
open,
|
|
||||||
high,
|
|
||||||
low,
|
|
||||||
close,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mappedData = mapData(output);
|
|
||||||
try {
|
try {
|
||||||
switch (timePeriod) {
|
switch (timePeriod) {
|
||||||
case "one-week":
|
case "one-week":
|
||||||
oneWeekPrice = mappedData;
|
oneWeekPrice = output;
|
||||||
break;
|
break;
|
||||||
case "one-month":
|
case "one-month":
|
||||||
oneMonthPrice = mappedData;
|
oneMonthPrice = output;
|
||||||
break;
|
break;
|
||||||
case "six-months":
|
case "six-months":
|
||||||
sixMonthPrice = mappedData;
|
sixMonthPrice = output;
|
||||||
break;
|
break;
|
||||||
case "one-year":
|
case "one-year":
|
||||||
oneYearPrice = mappedData;
|
oneYearPrice = output;
|
||||||
break;
|
break;
|
||||||
case "max":
|
case "max":
|
||||||
maxPrice = mappedData;
|
maxPrice = output;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log(`Unsupported time period: ${timePeriod}`);
|
console.log(`Unsupported time period: ${timePeriod}`);
|
||||||
}
|
}
|
||||||
setCache($stockTicker, mappedData, "historicalPrice" + timePeriod);
|
|
||||||
|
setCache($stockTicker, output, "historicalPrice" + timePeriod);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
@ -363,48 +476,26 @@
|
|||||||
|
|
||||||
async function initializePrice() {
|
async function initializePrice() {
|
||||||
output = null;
|
output = null;
|
||||||
if (intervalId) {
|
config = null;
|
||||||
clearInterval(intervalId);
|
|
||||||
}
|
|
||||||
intervalId = setInterval(checkChart, 0);
|
|
||||||
try {
|
try {
|
||||||
output = [...data?.getOneDayPrice] ?? [];
|
oneDayPrice = [...data?.getOneDayPrice] ?? [];
|
||||||
oneDayPrice = output?.map((item) => ({
|
config = plotData(oneDayPrice) || null;
|
||||||
time: Date?.parse(item?.time + "Z") / 1000,
|
|
||||||
open: item?.open !== null ? item?.open : NaN,
|
output = [...oneDayPrice];
|
||||||
high: item?.high !== null ? item?.high : NaN,
|
|
||||||
low: item?.low !== null ? item?.low : NaN,
|
|
||||||
close: item?.close !== null ? item?.close : NaN,
|
|
||||||
}));
|
|
||||||
displayData =
|
displayData =
|
||||||
oneDayPrice?.length === 0 && sixMonthPrice?.length !== 0 ? "6M" : "1D";
|
oneDayPrice?.length === 0 && sixMonthPrice?.length !== 0 ? "6M" : "1D";
|
||||||
if (displayData === "1D") {
|
if (displayData === "1D") {
|
||||||
const length = oneDayPrice?.length;
|
lastValue = oneDayPrice?.at(-0)?.close;
|
||||||
for (let i = length - 1; i >= 0; i--) {
|
|
||||||
if (!isNaN(oneDayPrice[i]?.close)) {
|
|
||||||
lastValue = oneDayPrice[i]?.close;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (displayData === "6M") {
|
} else if (displayData === "6M") {
|
||||||
lastValue = sixMonthPrice?.slice(-1)?.at(0)?.close;
|
lastValue = sixMonthPrice?.at(-1)?.close;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayLastLogicalRangeValue =
|
displayLastLogicalRangeValue =
|
||||||
oneDayPrice?.length === 0 && sixMonthPrice?.length !== 0
|
oneDayPrice?.length === 0 && sixMonthPrice?.length !== 0
|
||||||
? sixMonthPrice?.at(0)?.close
|
? sixMonthPrice?.at(0)?.close
|
||||||
: oneDayPrice?.at(0)?.close; //previousClose;
|
: oneDayPrice?.at(0)?.close;
|
||||||
|
|
||||||
colorChange =
|
|
||||||
lastValue < displayLastLogicalRangeValue ? "#FF2F1F" : "#00FC50";
|
|
||||||
topColorChange =
|
|
||||||
lastValue < displayLastLogicalRangeValue
|
|
||||||
? "rgb(255, 47, 31, 0.2)"
|
|
||||||
: "rgb(16, 219, 6, 0.2)";
|
|
||||||
bottomColorChange =
|
|
||||||
lastValue < displayLastLogicalRangeValue
|
|
||||||
? "rgb(255, 47, 31, 0.001)"
|
|
||||||
: "rgb(16, 219, 6, 0.001)";
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
@ -422,161 +513,6 @@
|
|||||||
|
|
||||||
let displayLastLogicalRangeValue;
|
let displayLastLogicalRangeValue;
|
||||||
|
|
||||||
const fitContentChart = async () => {
|
|
||||||
if (displayData === "1Y" && oneYearPrice?.length === 0) {
|
|
||||||
} else if (chart !== null && typeof window !== "undefined") {
|
|
||||||
chart?.timeScale().fitContent();
|
|
||||||
|
|
||||||
chart?.applyOptions({
|
|
||||||
trackingMode: {
|
|
||||||
exitMode: TrackingModeExitMode.OnTouchEnd,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let width = 580;
|
|
||||||
//Initial height of graph
|
|
||||||
let height = 350;
|
|
||||||
|
|
||||||
let observer;
|
|
||||||
let ref;
|
|
||||||
|
|
||||||
ref = (element) => {
|
|
||||||
if (observer) {
|
|
||||||
observer?.disconnect();
|
|
||||||
}
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
observer = new ResizeObserver(([entry]) => {
|
|
||||||
width = entry.contentRect.width;
|
|
||||||
height = entry.contentRect.height;
|
|
||||||
});
|
|
||||||
observer.observe(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
//===============================================//
|
|
||||||
function defaultTickMarkFormatter(timePoint, tickMarkType, locale) {
|
|
||||||
const formatOptions = {
|
|
||||||
timeZone: "UTC",
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (tickMarkType) {
|
|
||||||
case 0: // TickMarkType.Year:
|
|
||||||
formatOptions.year = "numeric";
|
|
||||||
break;
|
|
||||||
case 1: // TickMarkType.Month:
|
|
||||||
formatOptions.month = "short";
|
|
||||||
break;
|
|
||||||
case 2: // TickMarkType.DayOfMonth:
|
|
||||||
formatOptions.day = "numeric";
|
|
||||||
break;
|
|
||||||
case 3: // TickMarkType.Time:
|
|
||||||
formatOptions.hour12 = true; // Use 12-hour clock
|
|
||||||
formatOptions.hour = "numeric"; // Use numeric hour without leading zero
|
|
||||||
break;
|
|
||||||
case 4: // TickMarkType.TimeWithSeconds:
|
|
||||||
formatOptions.hour12 = true; // Use 12-hour clock
|
|
||||||
formatOptions.hour = "numeric"; // Use numeric hour without leading zero
|
|
||||||
formatOptions.minute = "2-digit"; // Always show minutes with leading zero
|
|
||||||
formatOptions.second = "2-digit"; // Always show seconds with leading zero
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Ensure this default case handles unexpected tickMarkType values
|
|
||||||
}
|
|
||||||
if ([3, 4]?.includes(tickMarkType)) {
|
|
||||||
const date = new Date(timePoint?.timestamp * 1000);
|
|
||||||
return new Intl.DateTimeFormat(locale, formatOptions)?.format(date);
|
|
||||||
} else {
|
|
||||||
const date = new Date(timePoint?.timestamp);
|
|
||||||
return new Intl.DateTimeFormat(locale, formatOptions)?.format(date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: options = {
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
layout: {
|
|
||||||
background: {
|
|
||||||
type: ColorType.Solid,
|
|
||||||
color: "#09090B",
|
|
||||||
},
|
|
||||||
textColor: "#d1d4dc",
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
vertLines: {
|
|
||||||
color: "#09090B",
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
horzLines: {
|
|
||||||
color: "#09090B",
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
crosshair: {
|
|
||||||
horzLine: {
|
|
||||||
visible: true,
|
|
||||||
labelBackgroundColor: "#fff",
|
|
||||||
},
|
|
||||||
vertLine: {
|
|
||||||
labelVisible: true,
|
|
||||||
labelBackgroundColor: "#fff",
|
|
||||||
style: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
priceScale: {
|
|
||||||
autoScale: true,
|
|
||||||
scaleMargins: {
|
|
||||||
top: 0.3,
|
|
||||||
bottom: 0.25,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rightPriceScale: {
|
|
||||||
scaleMargins: {
|
|
||||||
top: 0.3,
|
|
||||||
bottom: 0.25,
|
|
||||||
borderVisible: false,
|
|
||||||
},
|
|
||||||
visible: true,
|
|
||||||
borderVisible: false,
|
|
||||||
mode: 1, // Keeps price scale fixed
|
|
||||||
},
|
|
||||||
leftPriceScale: {
|
|
||||||
visible: false,
|
|
||||||
borderColor: "rgba(197, 203, 206, 0.8)",
|
|
||||||
},
|
|
||||||
handleScale: {
|
|
||||||
mouseWheel: false,
|
|
||||||
pinch: false, // Disables scaling via pinch gestures
|
|
||||||
axisPressedMouseMove: false, // Disables scaling by dragging the axis with the mouse
|
|
||||||
},
|
|
||||||
handleScroll: {
|
|
||||||
mouseWheel: false,
|
|
||||||
horzTouchDrag: false,
|
|
||||||
vertTouchDrag: false,
|
|
||||||
pressedMouseMove: false,
|
|
||||||
},
|
|
||||||
timeScale: {
|
|
||||||
borderColor: "#fff",
|
|
||||||
textColor: "#fff",
|
|
||||||
borderVisible: false,
|
|
||||||
visible: true,
|
|
||||||
fixLeftEdge: true,
|
|
||||||
fixRightEdge: true,
|
|
||||||
timeVisible: ["1D", "1W", "1M"].includes(displayData),
|
|
||||||
secondsVisible: false,
|
|
||||||
tickMarkFormatter: (time, tickMarkType, locale) => {
|
|
||||||
return defaultTickMarkFormatter(
|
|
||||||
{ timestamp: time },
|
|
||||||
tickMarkType,
|
|
||||||
locale,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
onDestroy(async () => {
|
onDestroy(async () => {
|
||||||
$priceIncrease = null;
|
$priceIncrease = null;
|
||||||
$globalForm = [];
|
$globalForm = [];
|
||||||
@ -632,32 +568,8 @@
|
|||||||
return data; // Return updated data
|
return data; // Return updated data
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
shouldUpdatePriceChart.subscribe(async (value) => {
|
|
||||||
if (
|
|
||||||
value &&
|
|
||||||
chart !== null &&
|
|
||||||
$realtimePrice !== null &&
|
|
||||||
oneDayPrice?.length > 0 &&
|
|
||||||
$priceChartData?.time !== null &&
|
|
||||||
$priceChartData?.price !== null
|
|
||||||
) {
|
|
||||||
// Create a new array and update oneDayPrice to trigger reactivity
|
|
||||||
const updatedPrice = updateClosePrice(oneDayPrice, $priceChartData);
|
|
||||||
oneDayPrice = [...updatedPrice]; // Reassign the updated array to trigger reactivity
|
|
||||||
try {
|
|
||||||
chart?.update(oneDayPrice); // Update the chart with the new prices
|
|
||||||
} catch (e) {
|
|
||||||
// Handle error if chart update fails
|
|
||||||
//console.error("Chart update error:", e);
|
|
||||||
}
|
|
||||||
shouldUpdatePriceChart.set(false); // Reset the update flag
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($stockTicker && typeof window !== "undefined") {
|
if ($stockTicker) {
|
||||||
// add a check to see if running on client-side
|
// add a check to see if running on client-side
|
||||||
shouldUpdatePriceChart.set(false);
|
shouldUpdatePriceChart.set(false);
|
||||||
oneDayPrice = [];
|
oneDayPrice = [];
|
||||||
@ -666,6 +578,7 @@
|
|||||||
oneYearPrice = [];
|
oneYearPrice = [];
|
||||||
maxPrice = [];
|
maxPrice = [];
|
||||||
output = null;
|
output = null;
|
||||||
|
config = null;
|
||||||
|
|
||||||
stockDeck = data?.getStockDeck; // Essential otherwise chart will not be updated since we wait until #layout.server.ts server response is finished
|
stockDeck = data?.getStockDeck; // Essential otherwise chart will not be updated since we wait until #layout.server.ts server response is finished
|
||||||
|
|
||||||
@ -713,7 +626,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="{displayData === interval
|
class="{displayData === interval
|
||||||
? `bg-[${colorChange}] `
|
? `bg-[${lastValue < displayLastLogicalRangeValue ? '#FF2F1F' : '#00FC50'}] `
|
||||||
: 'bg-default'} mt-1 h-[3px] w-[1.5rem] m-auto rounded-full"
|
: 'bg-default'} mt-1 h-[3px] w-[1.5rem] m-auto rounded-full"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@ -756,7 +669,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="{displayData === interval
|
class="{displayData === interval
|
||||||
? `bg-[${colorChange}] `
|
? `bg-[${lastValue < displayLastLogicalRangeValue ? '#FF2F1F' : '#00FC50'}] `
|
||||||
: 'bg-default'} mt-1 h-[3px] w-[1.5rem] m-auto rounded-full"
|
: 'bg-default'} mt-1 h-[3px] w-[1.5rem] m-auto rounded-full"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@ -781,129 +694,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if output !== null && dataMapping[displayData]?.length !== 0}
|
{#if output !== null && config !== null && dataMapping[displayData]?.length !== 0}
|
||||||
<Chart
|
<div
|
||||||
{...options}
|
class="border border-gray-800 rounded"
|
||||||
autoSize={true}
|
use:highcharts={config}
|
||||||
ref={(api) => (chart = api)}
|
></div>
|
||||||
>
|
|
||||||
{#if displayData === "1D"}
|
|
||||||
<AreaSeries
|
|
||||||
reactive={true}
|
|
||||||
data={oneDayPrice?.map(({ time, close }) => ({
|
|
||||||
time,
|
|
||||||
value: close,
|
|
||||||
}))}
|
|
||||||
lineWidth={1.5}
|
|
||||||
priceScaleId="right"
|
|
||||||
lineColor={colorChange}
|
|
||||||
topColor={topColorChange}
|
|
||||||
bottomColor={bottomColorChange}
|
|
||||||
priceLineVisible={false}
|
|
||||||
>
|
|
||||||
<PriceLine
|
|
||||||
price={oneDayPrice?.at(0)?.close}
|
|
||||||
lineWidth={1}
|
|
||||||
color="#fff"
|
|
||||||
/>
|
|
||||||
</AreaSeries>
|
|
||||||
{:else if displayData === "1W"}
|
|
||||||
<AreaSeries
|
|
||||||
data={oneWeekPrice?.map(({ time, close }) => ({
|
|
||||||
time,
|
|
||||||
value: close,
|
|
||||||
}))}
|
|
||||||
lineWidth={1.5}
|
|
||||||
priceScaleId="right"
|
|
||||||
lineColor={colorChange}
|
|
||||||
topColor={topColorChange}
|
|
||||||
bottomColor={bottomColorChange}
|
|
||||||
priceLineVisible={false}
|
|
||||||
>
|
|
||||||
<PriceLine
|
|
||||||
price={oneWeekPrice?.at(0)?.close}
|
|
||||||
lineWidth={1}
|
|
||||||
color="#fff"
|
|
||||||
/>
|
|
||||||
</AreaSeries>
|
|
||||||
{:else if displayData === "1M"}
|
|
||||||
<AreaSeries
|
|
||||||
data={oneMonthPrice?.map(({ time, close }) => ({
|
|
||||||
time: time,
|
|
||||||
value: close,
|
|
||||||
}))}
|
|
||||||
lineWidth={1.5}
|
|
||||||
priceScaleId="right"
|
|
||||||
lineColor={colorChange}
|
|
||||||
topColor={topColorChange}
|
|
||||||
bottomColor={bottomColorChange}
|
|
||||||
priceLineVisible={false}
|
|
||||||
>
|
|
||||||
<PriceLine
|
|
||||||
price={oneMonthPrice?.at(0)?.close}
|
|
||||||
lineWidth={1}
|
|
||||||
color="#fff"
|
|
||||||
/>
|
|
||||||
</AreaSeries>
|
|
||||||
{:else if displayData === "6M"}
|
|
||||||
<AreaSeries
|
|
||||||
data={sixMonthPrice?.map(({ time, close }) => ({
|
|
||||||
time,
|
|
||||||
value: close,
|
|
||||||
}))}
|
|
||||||
lineWidth={1.5}
|
|
||||||
priceScaleId="right"
|
|
||||||
lineColor={colorChange}
|
|
||||||
topColor={topColorChange}
|
|
||||||
bottomColor={bottomColorChange}
|
|
||||||
priceLineVisible={false}
|
|
||||||
>
|
|
||||||
<PriceLine
|
|
||||||
price={sixMonthPrice?.at(0)?.close}
|
|
||||||
lineWidth={1}
|
|
||||||
color="#fff"
|
|
||||||
/>
|
|
||||||
</AreaSeries>
|
|
||||||
{:else if displayData === "1Y"}
|
|
||||||
<AreaSeries
|
|
||||||
data={oneYearPrice?.map(({ time, close }) => ({
|
|
||||||
time,
|
|
||||||
value: close,
|
|
||||||
}))}
|
|
||||||
lineWidth={1.5}
|
|
||||||
priceScaleId="right"
|
|
||||||
lineColor={colorChange}
|
|
||||||
topColor={topColorChange}
|
|
||||||
bottomColor={bottomColorChange}
|
|
||||||
priceLineVisible={false}
|
|
||||||
>
|
|
||||||
<PriceLine
|
|
||||||
price={oneYearPrice?.at(0)?.close}
|
|
||||||
lineWidth={1}
|
|
||||||
color="#fff"
|
|
||||||
/>
|
|
||||||
</AreaSeries>
|
|
||||||
{:else if displayData === "MAX"}
|
|
||||||
<AreaSeries
|
|
||||||
data={maxPrice?.map(({ time, close }) => ({
|
|
||||||
time,
|
|
||||||
value: close,
|
|
||||||
}))}
|
|
||||||
lineWidth={1.5}
|
|
||||||
priceScaleId="right"
|
|
||||||
lineColor={colorChange}
|
|
||||||
topColor={topColorChange}
|
|
||||||
bottomColor={bottomColorChange}
|
|
||||||
priceLineVisible={false}
|
|
||||||
>
|
|
||||||
<PriceLine
|
|
||||||
price={maxPrice?.at(0)?.close}
|
|
||||||
lineWidth={1}
|
|
||||||
color="#fff"
|
|
||||||
/>
|
|
||||||
</AreaSeries>
|
|
||||||
{/if}
|
|
||||||
</Chart>
|
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="flex justify-center w-full sm:w-[650px] h-[350px] items-center"
|
class="flex justify-center w-full sm:w-[650px] h-[350px] items-center"
|
||||||
@ -1210,35 +1005,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!--End-Indicator-Modal-->
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
max-height: 450px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pulse {
|
|
||||||
position: relative;
|
|
||||||
animation: pulse-animation 1s forwards cubic-bezier(0.5, 0, 0.5, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse-animation {
|
|
||||||
0% {
|
|
||||||
transform: scale(0.9);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(0.9);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--date-picker-background: #09090b;
|
|
||||||
--date-picker-foreground: #f7f7f7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user