feat: add source-grounded evolution and Ollama assistant
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import type { TemporalObservation } from '../../types'
|
||||
|
||||
interface TemporalTrendChartProps {
|
||||
timeline: TemporalObservation[]
|
||||
metricKey: string
|
||||
}
|
||||
|
||||
const WIDTH = 560
|
||||
const HEIGHT = 150
|
||||
const PADDING_X = 34
|
||||
const PADDING_Y = 24
|
||||
|
||||
function formatValue(value: number, unit: string): string {
|
||||
const maximumFractionDigits = unit === 'inwoners' || unit === 'objecten' ? 0 : 2
|
||||
return `${value.toLocaleString('nl-BE', { maximumFractionDigits })} ${unit}`
|
||||
}
|
||||
|
||||
export function TemporalTrendChart({ timeline, metricKey }: TemporalTrendChartProps): JSX.Element | null {
|
||||
const observations = timeline.flatMap((observation) => {
|
||||
const metric = observation.metrics.find((item) => item.metric_key === metricKey)
|
||||
return metric ? [{ observation, metric }] : []
|
||||
})
|
||||
if (observations.length < 2) {
|
||||
return null
|
||||
}
|
||||
|
||||
const values = observations.map((item) => item.metric.value)
|
||||
const minimum = Math.min(...values)
|
||||
const maximum = Math.max(...values)
|
||||
const range = maximum - minimum
|
||||
const innerWidth = WIDTH - PADDING_X * 2
|
||||
const innerHeight = HEIGHT - PADDING_Y * 2
|
||||
const points = observations.map((item, index) => {
|
||||
const x = PADDING_X + (index / (observations.length - 1)) * innerWidth
|
||||
const normalized = range === 0 ? 0.5 : (item.metric.value - minimum) / range
|
||||
const y = HEIGHT - PADDING_Y - normalized * innerHeight
|
||||
return { ...item, x, y }
|
||||
})
|
||||
const pointString = points.map((point) => `${point.x},${point.y}`).join(' ')
|
||||
const label = `${points[0].metric.label}: ${formatValue(points[0].metric.value, points[0].metric.unit)} tot ${formatValue(
|
||||
points[points.length - 1].metric.value,
|
||||
points[points.length - 1].metric.unit,
|
||||
)}`
|
||||
|
||||
return (
|
||||
<div className="geo-temporal-chart" aria-label={label}>
|
||||
<div className="geo-temporal-chart-heading">
|
||||
<div>
|
||||
<span>Volledige tijdreeks</span>
|
||||
<strong>{points[0].metric.label}</strong>
|
||||
</div>
|
||||
<span>{points.length} meetmomenten</span>
|
||||
</div>
|
||||
<svg viewBox={`0 0 ${WIDTH} ${HEIGHT}`} role="img" aria-label={label} preserveAspectRatio="none">
|
||||
<line x1={PADDING_X} x2={WIDTH - PADDING_X} y1={HEIGHT - PADDING_Y} y2={HEIGHT - PADDING_Y} />
|
||||
<polyline points={pointString} />
|
||||
{points.map((point) => (
|
||||
<g key={point.observation.dataset.id}>
|
||||
<circle cx={point.x} cy={point.y} r="4" />
|
||||
<text x={point.x} y={HEIGHT - 6} textAnchor="middle">
|
||||
{new Date(point.observation.dataset.observed_at).getUTCFullYear()}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
<div className="geo-temporal-chart-values">
|
||||
{points.map((point) => (
|
||||
<div key={point.observation.dataset.id}>
|
||||
<span>{new Date(point.observation.dataset.observed_at).getUTCFullYear()}</span>
|
||||
<strong>{formatValue(point.metric.value, point.metric.unit)}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user