wip 20
This commit is contained in:
@@ -9,9 +9,6 @@ export interface SVGPoint {
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates bezier control points for smooth curves
|
||||
*/
|
||||
function getSVGBezierOpposedLine(
|
||||
pointA: SVGPoint,
|
||||
pointB: SVGPoint
|
||||
@@ -19,7 +16,6 @@ function getSVGBezierOpposedLine(
|
||||
const lengthX = pointB.x - pointA.x;
|
||||
const lengthY = pointB.y - pointA.y;
|
||||
|
||||
// Go uses int() which truncates toward zero, JavaScript Math.trunc matches this
|
||||
return {
|
||||
Length: Math.floor(Math.sqrt(lengthX * lengthX + lengthY * lengthY)),
|
||||
Angle: Math.trunc(Math.atan2(lengthY, lengthX)),
|
||||
@@ -32,7 +28,6 @@ function getBezierControlPoint(
|
||||
nextPoint: SVGPoint | null,
|
||||
isReverse: boolean
|
||||
): SVGPoint {
|
||||
// First / Last Point
|
||||
let pPrev = prevPoint;
|
||||
let pNext = nextPoint;
|
||||
if (!pPrev) {
|
||||
@@ -42,57 +37,49 @@ function getBezierControlPoint(
|
||||
pNext = currentPoint;
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
const smoothingRatio: number = 0.2;
|
||||
const directionModifier: number = isReverse ? Math.PI : 0;
|
||||
const smoothingRatio = 0.2;
|
||||
const directionModifier = isReverse ? Math.PI : 0;
|
||||
|
||||
const opposingLine = getSVGBezierOpposedLine(pPrev, pNext);
|
||||
const lineAngle: number = opposingLine.Angle + directionModifier;
|
||||
const lineLength: number = opposingLine.Length * smoothingRatio;
|
||||
const lineAngle = opposingLine.Angle + directionModifier;
|
||||
const lineLength = opposingLine.Length * smoothingRatio;
|
||||
|
||||
// Calculate Control Point - Go converts everything to int
|
||||
// Note: int(math.Cos(...) * lineLength) means truncate product, not truncate then multiply
|
||||
return {
|
||||
x: Math.floor(currentPoint.x + Math.trunc(Math.cos(lineAngle) * lineLength)),
|
||||
y: Math.floor(currentPoint.y + Math.trunc(Math.sin(lineAngle) * lineLength)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the bezier path for the graph
|
||||
*/
|
||||
function getSVGBezierPath(points: SVGPoint[]): string {
|
||||
if (points.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let bezierSVGPath: string = '';
|
||||
let bezierSVGPath = '';
|
||||
|
||||
for (let index = 0; index < points.length; index++) {
|
||||
const point = points[index];
|
||||
if (!point) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
bezierSVGPath += `M ${point.x},${point.y}`;
|
||||
} else {
|
||||
const pointPlusOne = points[index + 1];
|
||||
const pointMinusOne = points[index - 1];
|
||||
const pointMinusTwo: SVGPoint | null = index - 2 >= 0 ? points[index - 2] : null;
|
||||
|
||||
const startControlPoint: SVGPoint = getBezierControlPoint(
|
||||
pointMinusOne,
|
||||
pointMinusTwo,
|
||||
point,
|
||||
false
|
||||
);
|
||||
const endControlPoint: SVGPoint = getBezierControlPoint(
|
||||
point,
|
||||
pointMinusOne,
|
||||
pointPlusOne || point,
|
||||
true
|
||||
);
|
||||
|
||||
// Go converts all coordinates to int
|
||||
bezierSVGPath += ` C${startControlPoint.x},${startControlPoint.y} ${endControlPoint.x},${endControlPoint.y} ${point.x},${point.y}`;
|
||||
continue;
|
||||
}
|
||||
|
||||
const pointMinusOne = points[index - 1];
|
||||
if (!pointMinusOne) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pointPlusOne = points[index + 1] ?? point;
|
||||
const pointMinusTwo = index - 2 >= 0 ? (points[index - 2] ?? null) : null;
|
||||
|
||||
const startControlPoint = getBezierControlPoint(pointMinusOne, pointMinusTwo, point, false);
|
||||
const endControlPoint = getBezierControlPoint(point, pointMinusOne, pointPlusOne, true);
|
||||
|
||||
bezierSVGPath += ` C${startControlPoint.x},${startControlPoint.y} ${endControlPoint.x},${endControlPoint.y} ${point.x},${point.y}`;
|
||||
}
|
||||
|
||||
return bezierSVGPath;
|
||||
@@ -105,42 +92,35 @@ export interface SVGGraphData {
|
||||
Offset: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SVG Graph Data
|
||||
*/
|
||||
export function getSVGGraphData(
|
||||
inputData: GraphDataPoint[],
|
||||
svgWidth: number,
|
||||
svgHeight: number
|
||||
): SVGGraphData {
|
||||
// Derive Height
|
||||
let maxHeight: number = 0;
|
||||
let maxHeight = 0;
|
||||
for (const item of inputData) {
|
||||
if (item.minutes_read > maxHeight) {
|
||||
maxHeight = item.minutes_read;
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical Graph Real Estate
|
||||
const sizePercentage: number = 0.5;
|
||||
const sizePercentage = 0.5;
|
||||
const sizeRatio = maxHeight > 0 ? (svgHeight * sizePercentage) / maxHeight : 0;
|
||||
const blockOffset = inputData.length > 0 ? Math.floor(svgWidth / inputData.length) : 0;
|
||||
|
||||
// Scale Ratio -> Desired Height
|
||||
const sizeRatio: number = (svgHeight * sizePercentage) / maxHeight;
|
||||
|
||||
// Point Block Offset
|
||||
const blockOffset: number = Math.floor(svgWidth / inputData.length);
|
||||
|
||||
// Line & Bar Points
|
||||
const linePoints: SVGPoint[] = [];
|
||||
|
||||
// Bezier Fill Coordinates (Max X, Min X, Max Y)
|
||||
let maxBX: number = 0;
|
||||
let maxBY: number = 0;
|
||||
let minBX: number = 0;
|
||||
let maxBX = 0;
|
||||
let maxBY = 0;
|
||||
let minBX = 0;
|
||||
|
||||
for (let idx = 0; idx < inputData.length; idx++) {
|
||||
// Go uses int conversion
|
||||
const itemSize = Math.floor(inputData[idx].minutes_read * sizeRatio);
|
||||
const item = inputData[idx];
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const itemSize = Math.floor(item.minutes_read * sizeRatio);
|
||||
const itemY = svgHeight - itemSize;
|
||||
const lineX = (idx + 1) * blockOffset;
|
||||
|
||||
@@ -162,7 +142,6 @@ export function getSVGGraphData(
|
||||
}
|
||||
}
|
||||
|
||||
// Return Data
|
||||
return {
|
||||
LinePoints: linePoints,
|
||||
BezierPath: getSVGBezierPath(linePoints),
|
||||
@@ -171,27 +150,14 @@ export function getSVGGraphData(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a date string to YYYY-MM-DD format (ISO-like)
|
||||
* Note: The date string from the API is already in YYYY-MM-DD format,
|
||||
* but since JavaScript Date parsing can add timezone offsets, we use UTC
|
||||
* methods to ensure we get the correct date.
|
||||
*/
|
||||
function formatDate(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
// Use UTC methods to avoid timezone offset issues
|
||||
const year = date.getUTCFullYear();
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getUTCDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* ReadingHistoryGraph component
|
||||
*
|
||||
* Displays a bezier curve graph of daily reading totals with hover tooltips.
|
||||
* Exact copy of Go template implementation.
|
||||
*/
|
||||
export default function ReadingHistoryGraph({ data }: ReadingHistoryGraphProps) {
|
||||
const svgWidth = 800;
|
||||
const svgHeight = 70;
|
||||
@@ -204,11 +170,7 @@ export default function ReadingHistoryGraph({ data }: ReadingHistoryGraphProps)
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
BezierPath,
|
||||
BezierFill,
|
||||
LinePoints: _linePoints,
|
||||
} = getSVGGraphData(data, svgWidth, svgHeight);
|
||||
const { BezierPath, BezierFill } = getSVGGraphData(data, svgWidth, svgHeight);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
@@ -227,7 +189,6 @@ export default function ReadingHistoryGraph({ data }: ReadingHistoryGraphProps)
|
||||
{data.map((point, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick
|
||||
className="w-full opacity-0 hover:opacity-100"
|
||||
style={{
|
||||
background:
|
||||
|
||||
Reference in New Issue
Block a user