This commit is contained in:
MuslemRahimi 2025-02-23 13:53:49 +01:00
parent e116a9b24d
commit 41c0b85c99

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { displayCompanyName, stockTicker, screenWidth } from "$lib/store"; import { displayCompanyName, stockTicker, screenWidth } from "$lib/store";
import Infobox from "$lib/components/Infobox.svelte"; import Infobox from "$lib/components/Infobox.svelte";
import highcharts from "$lib/highcharts.ts";
import { Chart } from "svelte-echarts"; import { Chart } from "svelte-echarts";
import { init, use } from "echarts/core"; import { init, use } from "echarts/core";
@ -134,7 +135,7 @@
} }
function getPriceForecastChart() { function getPriceForecastChart() {
const historicalData = data?.getAnalystSummary?.pastPriceList || []; const historicalData = data?.getPriceAnalysis?.pastPriceList || [];
const forecastTargets = { const forecastTargets = {
low: lowPriceTarget, low: lowPriceTarget,
avg: avgPriceTarget, avg: avgPriceTarget,
@ -142,139 +143,171 @@
}; };
// Process historical data // Process historical data
const processedHistorical = historicalData?.map((point) => ({ const processedHistorical = historicalData?.map((point) => [
date: point?.date, new Date(point?.date).getTime(),
value: point?.close, point?.close,
})); ]);
const currentDate = new Date(); // Get the current date const currentDate = new Date();
const forecastDate = new Date( const forecastDate = new Date(
currentDate.getFullYear() + 1, currentDate.getFullYear() + 1,
currentDate.getMonth(), currentDate.getMonth(),
currentDate.getDate(), currentDate.getDate(),
); // Add one year );
const forecastDateString = forecastDate.toISOString().split("T")[0]; // Format as 'YYYY-MM-DD' const forecastTimestamp = forecastDate.getTime();
// Get the last historical data point // Get the last historical data point
const lastHistoricalDate = historicalData[historicalData.length - 1]?.date; const lastHistoricalDate = new Date(
historicalData[historicalData.length - 1]?.date,
).getTime();
const lastHistoricalClose = const lastHistoricalClose =
historicalData[historicalData.length - 1]?.close; historicalData[historicalData.length - 1]?.close;
// Create forecast points by appending them after the last historical date // Create forecast points
const forecastHigh = [ const forecastHigh = [
{ date: lastHistoricalDate, value: lastHistoricalClose }, [lastHistoricalDate, lastHistoricalClose],
{ date: forecastDateString, value: forecastTargets.high }, [forecastTimestamp, forecastTargets.high],
]; ];
const forecastAvg = [ const forecastAvg = [
{ date: lastHistoricalDate, value: lastHistoricalClose }, [lastHistoricalDate, lastHistoricalClose],
{ date: forecastDateString, value: forecastTargets.avg }, [forecastTimestamp, forecastTargets.avg],
]; ];
const forecastLow = [ const forecastLow = [
{ date: lastHistoricalDate, value: lastHistoricalClose }, [lastHistoricalDate, lastHistoricalClose],
{ date: forecastDateString, value: forecastTargets.low }, [forecastTimestamp, forecastTargets.low],
]; ];
const option = { const options = {
animation: false, tooltip: {
silent: true, enabled: false,
grid: { },
left: "2%", plotOptions: {
right: "2%", series: {
bottom: "10%", enableMouseTracking: false,
top: "5%", states: {
containLabel: true, hover: {
enabled: false,
},
},
marker: {
states: {
hover: {
enabled: false,
},
},
},
},
},
chart: {
backgroundColor: "#09090B",
plotBackgroundColor: "#09090B",
height: 360,
animation: false,
},
title: {
text: `<div class="grid grid-cols-2 w-[200px] sm:w-[500px] -mb-3.5 text-xs font-[501] text-gray-400">
<h3 class="text-left">${$screenWidth && $screenWidth < 640 ? "Past Year" : "Past 12 Months"}</h3>
<h3 class="text-right">${$screenWidth && $screenWidth < 640 ? "Next Year" : "12 Month Forecast"}</h3>
</div>`,
style: {
color: "white",
width: "100%",
},
verticalAlign: "top",
useHTML: true,
}, },
xAxis: { xAxis: {
type: "time", gridLineWidth: 1,
axisLabel: { gridLineColor: "#111827",
type: "datetime",
endOnTick: false,
labels: {
style: {
color: "#fff", color: "#fff",
formatter: (value) => { },
const date = new Date(value); formatter: function () {
const isMobile = $screenWidth < 640; // Define your breakpoint for mobile const date = new Date(this.value);
return date.toLocaleDateString("en-US", {
// Use a different date format for mobile screens
return isMobile
? date.toLocaleDateString("en-US", { month: "short" }) // Show only the month for mobile
: date.toLocaleDateString("en-US", {
month: "short", month: "short",
year: "numeric", year: "numeric",
}); // Full format for larger screens });
},
},
axisPointer: {
type: "line", // Can enhance interaction on mobile
lineStyle: {
color: "#fff", // Customize pointer color if needed
}, },
}, },
}, },
yAxis: { yAxis: {
type: "value", title: {
axisLabel: { text: "",
},
labels: {
style: {
color: "#fff", color: "#fff",
formatter: (value) => `$${value.toFixed(0)}`, },
formatter: function () {
return `$${this.value.toFixed(0)}`;
},
},
gridLineWidth: 1,
gridLineColor: "#111827",
tickInterval: 20, // Adjust this value to reduce step size
}, },
splitLine: {
show: false,
},
},
series: [ series: [
{ {
animation: false,
name: "Historical", name: "Historical",
type: "line", data: processedHistorical,
data: processedHistorical?.map((point) => [point.date, point.value]), color: "#fff",
marker: {
symbol: "circle", symbol: "circle",
symbolSize: 6, radius: 4,
itemStyle: {
color: "#fff",
},
lineStyle: {
width: 2,
}, },
lineWidth: 2,
}, },
{ {
animation: false,
name: "High", name: "High",
type: "line", data: forecastHigh,
data: forecastHigh?.map((point) => [point.date, point.value]),
symbol: "none",
lineStyle: {
type: "dashed",
color: "#31B800", color: "#31B800",
dashStyle: "Dash",
marker: {
enabled: false,
}, },
}, },
{ {
animation: false,
name: "Average", name: "Average",
type: "line", data: forecastAvg,
data: forecastAvg?.map((point) => [point.date, point.value]),
symbol: "none",
lineStyle: {
type: "dashed",
color: "#fff", color: "#fff",
dashStyle: "Dash",
marker: {
enabled: false,
}, },
}, },
{ {
animation: false,
name: "Low", name: "Low",
type: "line", data: forecastLow,
data: forecastLow?.map((point) => [point.date, point.value]),
symbol: "none",
lineStyle: {
type: "dashed",
color: "#D9220E", color: "#D9220E",
dashStyle: "Dash",
marker: {
enabled: false,
}, },
}, },
], ],
legend: {
enabled: false,
},
credits: {
enabled: false,
},
}; };
return options;
return option;
} }
let optionsPieChart = getPieChart() || null; let optionsPieChart = getPieChart() || null;
let optionsPriceForecast = getPriceForecastChart() || null; let config = getPriceForecastChart() || null;
</script> </script>
<SEO <SEO
@ -316,6 +349,7 @@
price of {price}. price of {price}.
</p> </p>
</div> </div>
<div>
<div> <div>
<div class="app h-[160px]"> <div class="app h-[160px]">
{#if optionsPieChart !== null} {#if optionsPieChart !== null}
@ -323,7 +357,7 @@
{/if} {/if}
</div> </div>
<div class="-mt-36 text-center text-xl font-semibold"> <div class="-mt-36 text-center text-xl font-semibold">
AI Consensus: <span Analyst Consensus: <span
class="font-bold {['Strong Buy', 'Buy']?.includes( class="font-bold {['Strong Buy', 'Buy']?.includes(
consensusRating, consensusRating,
) )
@ -335,40 +369,12 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="grow pt-2 md:pt-4 lg:pl-4 lg:pt-0"> <div class="grow pt-2 md:pt-4 lg:pl-4 lg:pt-0">
<div class="app h-[250px] xs:h-[275px]">
{#if data?.user?.tier !== "Pro"}
<div <div
class="flex flex-row items-center justify-center m-auto h-full text-center font-bold" class="chart mt-5 sm:mt-0 border border-gray-800 rounded"
> use:highcharts={config}
<a ></div>
href="/pricing"
class="flex flex-row items-center sm:hover:text-blue-400 text-white"
>
<span class="m-auto"> Chart Forecast</span>
<svg
class="ml-1 size-5"
viewBox="0 0 20 20"
fill="currentColor"
style="max-width: 40px;"
>
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
>
</path>
</svg>
</a>
</div>
{:else if optionsPriceForecast !== null}
<Chart
{init}
options={optionsPriceForecast}
class="chart"
/>
{/if}
</div>
<div <div
class="hide-scroll mb-1 mt-2 overflow-x-auto px-1.5 text-center md:mb-0 md:px-0 lg:mt-2" class="hide-scroll mb-1 mt-2 overflow-x-auto px-1.5 text-center md:mb-0 md:px-0 lg:mt-2"
> >
@ -397,112 +403,24 @@
<tr class="text-sm sm:text-[1rem]" <tr class="text-sm sm:text-[1rem]"
><td class="py-[3px] text-left lg:py-0.5">Change</td> ><td class="py-[3px] text-left lg:py-0.5">Change</td>
<td <td
class={lowChange > 0 && data?.user?.tier === "Pro" class={lowChange > 0
? "before:content-['+'] text-[#00FC50]" ? "before:content-['+'] text-[#00FC50]"
: "text-[#FF2F1F]"} : "text-[#FF2F1F]"}>{lowChange}%</td
> >
{#if data?.user?.tier !== "Pro"}
<a
href="/pricing"
class="text-white sm:hover:text-blue-400"
>
<svg
class="size-4 sm:size-5 text-end ml-auto"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
>
</path>
</svg>
</a>
{:else}
{lowChange}%
{/if}
</td>
<td <td
class={avgChange > 0 && data?.user?.tier === "Pro" class={avgChange > 0
? "before:content-['+'] text-[#00FC50]" ? "before:content-['+'] text-[#00FC50]"
: "text-[#FF2F1F]"} : "text-[#FF2F1F]"}>{avgChange}%</td
> >
{#if data?.user?.tier !== "Pro"}
<a
href="/pricing"
class="text-white sm:hover:text-blue-400"
>
<svg
class="size-4 sm:size-5 text-end ml-auto"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
>
</path>
</svg>
</a>
{:else}
{avgChange}%
{/if}
</td>
<td <td
class={medianChange > 0 && data?.user?.tier === "Pro" class={medianChange > 0
? "before:content-['+'] text-[#00FC50]" ? "before:content-['+'] text-[#00FC50]"
: "text-[#FF2F1F]"} : "text-[#FF2F1F]"}>{medianChange}%</td
> >
{#if data?.user?.tier !== "Pro"}
<a
href="/pricing"
class="text-white sm:hover:text-blue-400"
>
<svg
class="size-4 sm:size-5 text-end ml-auto"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
>
</path>
</svg>
</a>
{:else}
{medianChange}%
{/if}
</td>
<td <td
class={highChange > 0 && data?.user?.tier === "Pro" class={highChange > 0
? "before:content-['+'] text-[#00FC50]" ? "before:content-['+'] text-[#00FC50]"
: "text-[#FF2F1F]"} : "text-[#FF2F1F]"}>{highChange}%</td
>
{#if data?.user?.tier !== "Pro"}
<a
href="/pricing"
class="text-white sm:hover:text-blue-400"
>
<svg
class="size-4 sm:size-5 text-end ml-auto"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
>
</path>
</svg>
</a>
{:else}
{highChange}%
{/if}</td
></tr ></tr
></tbody ></tbody
> >