fine 2
This commit is contained in:
parent
9e4e65fa2a
commit
ca771a7edf
32 changed files with 1467 additions and 109 deletions
|
|
@ -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)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue