This commit is contained in:
Andras Schmelczer 2026-07-12 20:37:39 +01:00
parent 9e4e65fa2a
commit ca771a7edf
32 changed files with 1467 additions and 109 deletions

View file

@ -1,9 +1,11 @@
import { useMemo, useRef, useState, useEffect } from 'react';
import type { PricePoint } from '../../types';
import type { PriceMetric, PricePoint } from '../../types';
import { formatValue } from '../../lib/format';
interface PriceHistoryChartProps {
points: PricePoint[];
/** Which value to plot. Defaults to the absolute sale price. */
metric?: PriceMetric;
}
const PADDING = { top: 8, right: 24, bottom: 20, left: 48 };
@ -22,7 +24,7 @@ interface PriceScale {
ticks: number[];
}
export default function PriceHistoryChart({ points }: PriceHistoryChartProps) {
export default function PriceHistoryChart({ points, metric = 'price' }: PriceHistoryChartProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(0);
@ -37,7 +39,18 @@ export default function PriceHistoryChart({ points }: PriceHistoryChartProps) {
return () => observer.disconnect();
}, []);
// Project each point onto the chosen metric as a plain {year, price} so the
// rest of the chart is metric-agnostic. In per-m² mode, points without a
// recorded floor area drop out.
const plotPoints = useMemo<PricePoint[]>(() => {
if (metric === 'price') return points;
return points
.filter((p) => Number.isFinite(p.price_per_sqm))
.map((p) => ({ year: p.year, price: p.price_per_sqm as number }));
}, [points, metric]);
const { yearMin, yearMax, priceScale, medians } = useMemo(() => {
const points = plotPoints;
let yMin = Infinity,
yMax = -Infinity;
for (const p of points) {
@ -83,7 +96,7 @@ export default function PriceHistoryChart({ points }: PriceHistoryChartProps) {
priceScale: getPriceScale(points),
medians: meds,
};
}, [points]);
}, [plotPoints]);
const plotW = width - PADDING.left - PADDING.right;
const plotH = HEIGHT - PADDING.top - PADDING.bottom;
@ -104,7 +117,7 @@ export default function PriceHistoryChart({ points }: PriceHistoryChartProps) {
return (
<div ref={containerRef} style={{ height: HEIGHT }}>
{width > 0 && (
{width > 0 && plotPoints.length > 0 && (
<svg width={width} height={HEIGHT}>
{/* Grid lines */}
{priceScale.ticks.map((tick) => (
@ -120,7 +133,7 @@ export default function PriceHistoryChart({ points }: PriceHistoryChartProps) {
))}
{/* Dots */}
{points.map((p, i) => (
{plotPoints.map((p, i) => (
<circle
key={i}
cx={scaleX(p.year)}