Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mokelgit committed Jun 11, 2024
2 parents 9190ce3 + c1fae3d commit aaea6a7
Show file tree
Hide file tree
Showing 25 changed files with 3,236 additions and 1,199 deletions.
120 changes: 105 additions & 15 deletions app/(labels)/labels/CanvasSparkline.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef } from "react";
import { useEffect, useRef, useCallback, useState, createContext, useContext } from "react";

type CanvasSparklineProps = {
chainKey: string;
Expand All @@ -23,16 +23,20 @@ const GradientStops = {
positive: [3.33 * 23, 0, 100, 20],
}

export default function CanvasSparkline({ chainKey, data, change }: CanvasSparklineProps) {
export default function CanvasSparkline({ chainKey }: CanvasSparklineProps) {
const todayUTCStart = new Date().setUTCHours(0, 0, 0, 0);

const { data, change, value, hoverValue, setHoverValue } = useCanvasSparkline();
// creates a canvas element and draws the sparkline on it
const canvasRef = useRef<HTMLCanvasElement>(null);
const hoverCanvasRef = useRef<HTMLCanvasElement>(null);

const isNegative = change < 0;

const dataMin = Math.min(...data.map(([, y]) => y));
const dataMax = Math.max(...data.map(([, y]) => y));

const drawSparkline = (ctx: CanvasRenderingContext2D) => {
const drawSparkline = useCallback((ctx: CanvasRenderingContext2D) => {
const [x1, y1, x2, y2] = GradientStops[isNegative ? "negative" : "positive"];
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
const [color1, color2] = GradientColors[isNegative ? "negative" : "positive"];
Expand Down Expand Up @@ -79,30 +83,116 @@ export default function CanvasSparkline({ chainKey, data, change }: CanvasSparkl
break;
}
});
}, [data, dataMax, dataMin, isNegative]);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext("2d");
if (!ctx) return;

// ctx.strokeStyle = gradient;
drawSparkline(ctx);
}, [canvasRef, data, drawSparkline]);

// // ctx.lineWidth = 1;
// // ctx.stroke();
// // ctx.closePath();
}

// const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);

useEffect(() => {
const canvas = canvasRef.current;
const canvas = hoverCanvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext("2d");
if (!ctx) return;

drawSparkline(ctx);
}, [canvasRef, data]);
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;

//find closest timestamp
const xTimestamp = todayUTCStart - (30 - Math.floor((x / 100) * 30)) * 86400000;

// calculate the x and y coordinates of the hovered point based on the timestamp
const xCoord = 100 - ((todayUTCStart - xTimestamp) / 86400000) * 100 / 30;
const yCoord = data.find(([timestamp]) => timestamp === xTimestamp)?.[1];

if (!yCoord) return;

// setHoveredIndex(xCoord);
setHoverValue(data.find(([timestamp]) => timestamp === xTimestamp)?.[1] ?? null);

ctx.clearRect(0, 0, 100, 20);
ctx.beginPath();
// cursor line
ctx.fillStyle = "#ffffff33";
ctx.fillRect(x, 0, 1, 20);

// place transparent circle on the sparkline to indicate the hovered point
ctx.beginPath();

ctx.fillStyle = x > 100 / 30 * 23 ? isNegative ? GradientColors.negative[1] + "66" : GradientColors.positive[1] + "66" : "#CDD8D366";
ctx.arc(x, yCoord ? 20 - ((yCoord - dataMin) / (dataMax - dataMin)) * 20 : 0, 3, 0, 2 * Math.PI);
ctx.fill();

});

canvas.addEventListener("mouseleave", () => {
// setHoveredIndex(null);
setHoverValue(null);
ctx.clearRect(0, 0, 100, 20);
});

return () => {
canvas.removeEventListener("mousemove", () => { });
canvas.removeEventListener("mouseleave", () => { });
};

}, [hoverCanvasRef, data, dataMax, dataMin, isNegative, setHoverValue]);



return (
<>
<div className="w-[100px] h-[20px] relative">
<canvas ref={hoverCanvasRef} width={100} height={20} className="absolute inset-0" />
<canvas ref={canvasRef} width={100} height={20} />
</div>
{/* <div className="flex justify-between">
<span>{hoveredIndex}</span>
<span>{data[hoveredIndex]?.[1]}</span>
</div> */}
</>
);
}


type CanvasSparklineContextType = {
data: [number, number][];
change: number;
value: number;
hoverValue: number | null;
setHoverValue: (value: number | null) => void;
};

const CanvasSparklineContext = createContext<CanvasSparklineContextType | null>(null);

export const CanvasSparklineProvider = ({ data, change, value, children }: CanvasSparklineContextType & { children: React.ReactNode }) => {
const [hoverValue, setHoverValue] = useState<number | null>(null);

return (
<div className="w-[100px] h-[20px] relative">
<canvas ref={canvasRef} width={100} height={20} />
</div>
<CanvasSparklineContext.Provider value={{ data, change, value, hoverValue, setHoverValue }}>
{children}
</CanvasSparklineContext.Provider>
);
}
};

export const useCanvasSparkline = () => {
const ctx = useContext(CanvasSparklineContext);

if (!ctx) {
throw new Error("useCanvasSparkline must be used within a CanvasSparklineProvider");
}

return ctx;
};
Loading

0 comments on commit aaea6a7

Please sign in to comment.