adding market heatmap
This commit is contained in:
parent
126c40f806
commit
902b734302
45
package-lock.json
generated
45
package-lock.json
generated
@ -40,6 +40,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"flowbite-svelte": "^0.46.15",
|
||||
"got": "^14.4.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"katex": "^0.16.11",
|
||||
"layercake": "^8.4.0-beta.1",
|
||||
@ -3732,6 +3733,16 @@
|
||||
"integrity": "sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz",
|
||||
@ -5580,6 +5591,20 @@
|
||||
"integrity": "sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
@ -9307,6 +9332,16 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
@ -9628,6 +9663,16 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"flowbite-svelte": "^0.46.15",
|
||||
"got": "^14.4.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"katex": "^0.16.11",
|
||||
"layercake": "^8.4.0-beta.1",
|
||||
|
||||
@ -409,19 +409,19 @@
|
||||
>Market Mover</a
|
||||
>
|
||||
</Button>
|
||||
<!--
|
||||
|
||||
<Button
|
||||
builders={[builder]}
|
||||
type="submit"
|
||||
class="w-full bg-[#141417] hover:bg-[#141417]"
|
||||
>
|
||||
<a
|
||||
href="/heatmaps"
|
||||
href="/heatmap"
|
||||
class="text-start w-full text-[1rem] text-white ml-4 mt-4"
|
||||
>Heatmaps</a
|
||||
>Market Heatmap</a
|
||||
>
|
||||
</Button>
|
||||
-->
|
||||
|
||||
<Button
|
||||
builders={[builder]}
|
||||
type="submit"
|
||||
@ -969,12 +969,13 @@
|
||||
class="text-[1rem] text-white ml-4 mt-4"
|
||||
>Market Mover</a
|
||||
>
|
||||
<!--
|
||||
|
||||
<a
|
||||
href="/heatmaps"
|
||||
class="text-[1rem] text-white ml-4 mt-4">Heatmaps</a
|
||||
href="/heatmap"
|
||||
class="text-[1rem] text-white ml-4 mt-4"
|
||||
>Market Heatmap</a
|
||||
>
|
||||
-->
|
||||
|
||||
<a
|
||||
href="/list"
|
||||
class="text-[1rem] text-white ml-4 mt-4"
|
||||
|
||||
24
src/routes/api/heatmap/+server.ts
Normal file
24
src/routes/api/heatmap/+server.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const data = await request.json();
|
||||
const { apiURL, apiKey } = locals;
|
||||
|
||||
const postData = { params: data?.params };
|
||||
|
||||
|
||||
|
||||
const response = await fetch(apiURL + "/heatmap", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
const output = await response.text();
|
||||
return new Response(JSON.stringify(output));
|
||||
};
|
||||
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
export const load = async ({ locals }) => {
|
||||
const getData = async () => {
|
||||
const { apiURL, apiKey } = locals;
|
||||
|
||||
const response = await fetch(apiURL + "/heatmap", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
const output = await response.text();
|
||||
return output;
|
||||
};
|
||||
|
||||
// Make sure to return a promise
|
||||
return {
|
||||
getData: await getData(),
|
||||
};
|
||||
};
|
||||
@ -1,47 +1,223 @@
|
||||
<script>
|
||||
import Plot from "svelte-plotly.js";
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
|
||||
// Treemap data configuration
|
||||
const data = [
|
||||
{
|
||||
type: "treemap",
|
||||
labels: [
|
||||
"Eve",
|
||||
"Cain",
|
||||
"Seth",
|
||||
"Enos",
|
||||
"Noam",
|
||||
"Abel",
|
||||
"Awan",
|
||||
"Enoch",
|
||||
"Azura",
|
||||
],
|
||||
parents: ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"],
|
||||
textinfo: "label+parent",
|
||||
marker: { line: { width: 2 } },
|
||||
},
|
||||
];
|
||||
export let data;
|
||||
let rawData = data?.getData;
|
||||
let iframe: HTMLIFrameElement;
|
||||
let iframeLoaded = false;
|
||||
let selectedFormat: "png" | "jpeg" | "svg" = "png";
|
||||
let selectedTimePeriod = "1D";
|
||||
let isLoaded = false;
|
||||
|
||||
// Layout configuration with black background
|
||||
const layout = {
|
||||
title: "Family Tree Treemap",
|
||||
margin: { l: 0, r: 0, b: 20, t: 40 },
|
||||
width: 800,
|
||||
height: 600,
|
||||
paper_bgcolor: "#09090b", // Sets the outer background to black
|
||||
font: { color: "#FFFFFF" }, // Optional: Change text color to white for better contrast
|
||||
};
|
||||
async function getHeatMap() {
|
||||
const postData = { params: selectedTimePeriod };
|
||||
const response = await fetch("/api/heatmap", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
rawData = await response.json();
|
||||
}
|
||||
|
||||
async function downloadPlot(item) {
|
||||
if (item === "PNG") {
|
||||
selectedFormat = "png";
|
||||
} else if (item === "JPG") {
|
||||
selectedFormat = "jpeg";
|
||||
} else {
|
||||
selectedFormat = "svg";
|
||||
}
|
||||
|
||||
if (!iframe || !iframeLoaded) return;
|
||||
|
||||
try {
|
||||
const iframeWindow = iframe.contentWindow;
|
||||
if (!iframeWindow) return;
|
||||
|
||||
const Plotly = (iframeWindow as any).Plotly;
|
||||
if (!Plotly) throw new Error("Plotly not found in iframe");
|
||||
|
||||
const plotDiv =
|
||||
iframe.contentDocument?.querySelector(".plotly-graph-div");
|
||||
if (!plotDiv) throw new Error("Plotly div not found");
|
||||
|
||||
// Configure image options based on format
|
||||
const options = {
|
||||
format: selectedFormat,
|
||||
width: 1200,
|
||||
height: 800,
|
||||
};
|
||||
|
||||
// Get image data URL
|
||||
const imageData = await Plotly.toImage(plotDiv, options);
|
||||
|
||||
// Create download link
|
||||
const link = document.createElement("a");
|
||||
link.href = imageData;
|
||||
link.download = `sp500-heatmap-${selectedTimePeriod}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (error) {
|
||||
console.error("Download failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (selectedTimePeriod && typeof window !== "undefined") {
|
||||
isLoaded = false;
|
||||
getHeatMap();
|
||||
isLoaded = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full min-h-screen">
|
||||
<div class="treemap-container">
|
||||
<Plot {data} {layout} />
|
||||
</div>
|
||||
</div>
|
||||
<SEO
|
||||
title="S&P 500 Stock Market Heatmap"
|
||||
description="A stock market heatmap showing the performance of the individual stocks, sectors and industries in the S&P500."
|
||||
/>
|
||||
|
||||
<style>
|
||||
.treemap-container {
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
<section
|
||||
class="w-full max-w-3xl sm:max-w-[1400px] overflow-hidden min-h-screen pb-20 pt-5 px-4 lg:px-3"
|
||||
>
|
||||
<div class="text-sm sm:text-[1rem] breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/" class="text-gray-300">Home</a></li>
|
||||
<li class="text-gray-300">Heatmap</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">
|
||||
<div class="border-b-[2px] flex justify-between items-center gap-4">
|
||||
<h1 class="mb-1 text-white text-2xl sm:text-3xl font-bold">
|
||||
S&P 500 - {selectedTimePeriod} Performance
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center w-fit">
|
||||
<div
|
||||
class="grid grid-cols-2 sm:grid-cols-3 gap-y-3 sm:gap-y-0 gap-x-2.5 lg:grid-cols-3 w-full mt-5"
|
||||
>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
builders={[builder]}
|
||||
class="border-gray-600 border bg-default sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-2 text-white rounded-md truncate"
|
||||
>
|
||||
<span class="truncate text-white">Time Period</span>
|
||||
<svg
|
||||
class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
style="max-width:40px"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-auto h-fit max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
<div
|
||||
class="relative sticky z-40 focus:outline-none -top-1"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
style=""
|
||||
></div>
|
||||
<DropdownMenu.Group>
|
||||
{#each ["1D", "1W", "1M", "3M", "6M", "1Y", "3Y"] as item}
|
||||
<DropdownMenu.Item class="sm:hover:bg-primary">
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
on:click={() => (selectedTimePeriod = item)}
|
||||
class="cursor-pointer text-white"
|
||||
>
|
||||
<span class="mr-8">{item}</span>
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
builders={[builder]}
|
||||
class="border-gray-600 border bg-default sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-2 text-white rounded-md truncate"
|
||||
>
|
||||
<span class="truncate text-white">Download</span>
|
||||
<svg
|
||||
class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
style="max-width:40px"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-auto h-fit max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
<div
|
||||
class="relative sticky z-40 focus:outline-none -top-1"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
style=""
|
||||
></div>
|
||||
<DropdownMenu.Group>
|
||||
{#each ["PNG", "JPG", "SVG"] as item}
|
||||
<DropdownMenu.Item class="sm:hover:bg-primary">
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
on:click={() => downloadPlot(item)}
|
||||
disabled={!iframeLoaded}
|
||||
class="cursor-pointer text-white"
|
||||
>
|
||||
<span class="mr-8">Download {item}</span>
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full min-h-screen bg-[#09090B] overflow-hidden">
|
||||
{#if rawData}
|
||||
<iframe
|
||||
bind:this={iframe}
|
||||
srcdoc={rawData}
|
||||
class="w-full h-screen border-none"
|
||||
on:load={() => (iframeLoaded = true)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -65,6 +65,7 @@ const pages = [
|
||||
{ title: "/politicians/flow-data" },
|
||||
{ title: "/analysts" },
|
||||
{ title: "/analysts/top-stocks" },
|
||||
{ title: "/heatmap" },
|
||||
];
|
||||
|
||||
const website = "https://stocknear.com";
|
||||
|
||||
@ -124,6 +124,10 @@
|
||||
title: "Stock Screener",
|
||||
link: "/stock-screener",
|
||||
},
|
||||
{
|
||||
title: "Market Heatmap",
|
||||
link: "/heatmap",
|
||||
},
|
||||
{
|
||||
title: "Stock Lists",
|
||||
link: "/list",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user