wiki-formant 0.47.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/chart.d.ts +23 -1
- package/dist/chart.js +49 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -820,6 +820,17 @@ straight between closes and a count's is curved.
|
|
|
820
820
|
`mean` for a count, so the week in progress keeps its level instead of reading
|
|
821
821
|
as a collapse. A single range draws no range buttons and shows every point.
|
|
822
822
|
`bucketChart` and `chartSteps` are exported for a chart drawn elsewhere.
|
|
823
|
+
|
|
824
|
+
`markers` are events, `{ time, label }`, each drawn as an arrow on the last
|
|
825
|
+
point at or before it, so one mid-week lands on its week and two on one point
|
|
826
|
+
share it (`placeMarkers` does the placing). `step` opens on a step where the
|
|
827
|
+
range can make it, and `onView` hears the range and step on mount and on every
|
|
828
|
+
change, for a page that keeps them in its URL:
|
|
829
|
+
|
|
830
|
+
```tsx
|
|
831
|
+
<TimeChart series={days} label="Transactions" aggregate="mean" fromZero
|
|
832
|
+
markers={[{ time: haltAt, label: 'Halt' }]} step="month" onView={v => keep(v)} />
|
|
833
|
+
```
|
|
823
834
|
`base.css` pins the buttons (`.chart-controls button[aria-pressed]`) to the
|
|
824
835
|
canvas's top-left corner, rules a line between groups and dims a disabled step;
|
|
825
836
|
the site's stylesheet gives them and the box a look.
|
package/dist/chart.d.ts
CHANGED
|
@@ -18,6 +18,16 @@ export type ChartResolution = 'hour' | '4h' | 'day';
|
|
|
18
18
|
export type ChartStep = 'day' | 'week' | 'month';
|
|
19
19
|
/** Points by resolution, from `from` (Unix seconds, 0 for all time), oldest first. */
|
|
20
20
|
export type ChartLoader = (resolution: ChartResolution, from: number) => Promise<readonly ChartPoint[]>;
|
|
21
|
+
/** Something that happened at a time, in Unix seconds, drawn on the point that holds it. */
|
|
22
|
+
export type ChartMarker = {
|
|
23
|
+
time: number;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
26
|
+
/** The range and step a reader has picked, for a page that keeps them in its URL. */
|
|
27
|
+
export type ChartView = {
|
|
28
|
+
range: ChartRange;
|
|
29
|
+
step: ChartStep | null;
|
|
30
|
+
};
|
|
21
31
|
/** Every range, shortest first. A chart fed daily points offers the ones from 30D up. */
|
|
22
32
|
export declare const CHART_RANGES: readonly ChartRange[];
|
|
23
33
|
/** A step is offered once it makes four points, and the default is the finest that makes 200 or fewer. */
|
|
@@ -32,6 +42,12 @@ export declare function chartSteps(span: number): {
|
|
|
32
42
|
* not read as a collapse. Volume is summed.
|
|
33
43
|
*/
|
|
34
44
|
export declare function bucketChart(points: readonly ChartPoint[], step: ChartStep, aggregate: 'last' | 'mean'): ChartPoint[];
|
|
45
|
+
/**
|
|
46
|
+
* Markers on the points drawn: each on the last point at or before it, so one
|
|
47
|
+
* mid-week sits on its week. Markers on one point share it, labels joined; one
|
|
48
|
+
* before the first point or after the last point's step is left off.
|
|
49
|
+
*/
|
|
50
|
+
export declare function placeMarkers(markers: readonly ChartMarker[], shown: readonly ChartPoint[], step: ChartStep | null): ChartMarker[];
|
|
35
51
|
/** Compact from a thousand up, four significant figures below. */
|
|
36
52
|
export declare const formatChartValue: (n: number) => string;
|
|
37
53
|
export interface TimeChartProps {
|
|
@@ -58,6 +74,12 @@ export interface TimeChartProps {
|
|
|
58
74
|
* window links, which load another digest rather than slice this one.
|
|
59
75
|
*/
|
|
60
76
|
controls?: ReactNode;
|
|
77
|
+
/** Events drawn on the points that hold them. */
|
|
78
|
+
markers?: readonly ChartMarker[];
|
|
79
|
+
/** The step shown first, where the range can make it. */
|
|
80
|
+
step?: ChartStep;
|
|
81
|
+
/** Called with the range and step on mount and on every change. */
|
|
82
|
+
onView?: (view: ChartView) => void;
|
|
61
83
|
className?: string;
|
|
62
84
|
}
|
|
63
85
|
/**
|
|
@@ -65,4 +87,4 @@ export interface TimeChartProps {
|
|
|
65
87
|
* `.chart-controls button[aria-pressed]`, and a site's stylesheet gives them
|
|
66
88
|
* and the box their look; the line and fill are the canvas box's `color`.
|
|
67
89
|
*/
|
|
68
|
-
export declare function TimeChart({ series, label, ranges, range: initial, aggregate, fromZero, format, candles: initialCandles, height, controls, className, }: TimeChartProps): import("react").JSX.Element;
|
|
90
|
+
export declare function TimeChart({ series, label, ranges, range: initial, aggregate, fromZero, format, candles: initialCandles, height, controls, markers, step: initialStep, onView, className, }: TimeChartProps): import("react").JSX.Element;
|
package/dist/chart.js
CHANGED
|
@@ -8,7 +8,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
// loaded on mount, so the page paints without it. Every colour is the canvas
|
|
9
9
|
// box's own `color`, its axis `--chart-axis`, and a candle `--chart-up` or
|
|
10
10
|
// `--chart-down`, each if set. A count's line is curved; a price's is straight,
|
|
11
|
-
// since a curve between two closes draws prices nobody paid.
|
|
11
|
+
// since a curve between two closes draws prices nobody paid. Events are markers
|
|
12
|
+
// on the point they fall in, so a spike carries the reason for it.
|
|
12
13
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
13
14
|
const DAY = 86_400;
|
|
14
15
|
const RANGE = {
|
|
@@ -74,6 +75,29 @@ export function bucketChart(points, step, aggregate) {
|
|
|
74
75
|
}
|
|
75
76
|
return out;
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Markers on the points drawn: each on the last point at or before it, so one
|
|
80
|
+
* mid-week sits on its week. Markers on one point share it, labels joined; one
|
|
81
|
+
* before the first point or after the last point's step is left off.
|
|
82
|
+
*/
|
|
83
|
+
export function placeMarkers(markers, shown, step) {
|
|
84
|
+
const first = shown[0];
|
|
85
|
+
const last = shown.at(-1);
|
|
86
|
+
if (!first || !last)
|
|
87
|
+
return [];
|
|
88
|
+
const end = last.time + (step ? STEP[step].span : DAY);
|
|
89
|
+
const placed = new Map();
|
|
90
|
+
for (const m of [...markers].sort((a, b) => a.time - b.time)) {
|
|
91
|
+
if (m.time < first.time || m.time >= end)
|
|
92
|
+
continue;
|
|
93
|
+
let i = shown.length - 1;
|
|
94
|
+
while (i > 0 && shown[i].time > m.time)
|
|
95
|
+
i--;
|
|
96
|
+
const time = shown[i].time;
|
|
97
|
+
placed.set(time, [...(placed.get(time) ?? []), m.label]);
|
|
98
|
+
}
|
|
99
|
+
return [...placed].map(([time, labels]) => ({ time, label: labels.join(' · ') }));
|
|
100
|
+
}
|
|
77
101
|
const isCandle = (p) => p.open !== undefined && p.high !== undefined && p.low !== undefined;
|
|
78
102
|
const compact = new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 });
|
|
79
103
|
const precise = new Intl.NumberFormat('en', { maximumSignificantDigits: 4 });
|
|
@@ -102,14 +126,16 @@ const fromZeroScale = (base) => {
|
|
|
102
126
|
* `.chart-controls button[aria-pressed]`, and a site's stylesheet gives them
|
|
103
127
|
* and the box their look; the line and fill are the canvas box's `color`.
|
|
104
128
|
*/
|
|
105
|
-
export function TimeChart({ series, label, ranges = typeof series === 'function' ? CHART_RANGES : DAILY_RANGES, range: initial = ranges.includes('30d') ? '30d' : ranges[0], aggregate = 'last', fromZero = false, format = formatChartValue, candles: initialCandles = false, height, controls, className, }) {
|
|
129
|
+
export function TimeChart({ series, label, ranges = typeof series === 'function' ? CHART_RANGES : DAILY_RANGES, range: initial = ranges.includes('30d') ? '30d' : ranges[0], aggregate = 'last', fromZero = false, format = formatChartValue, candles: initialCandles = false, height, controls, markers, step: initialStep, onView, className, }) {
|
|
106
130
|
const box = useRef(null);
|
|
107
131
|
const chart = useRef(null);
|
|
108
132
|
const formatRef = useRef(format);
|
|
109
133
|
formatRef.current = format;
|
|
134
|
+
const onViewRef = useRef(onView);
|
|
135
|
+
onViewRef.current = onView;
|
|
110
136
|
const [ready, setReady] = useState(false);
|
|
111
137
|
const [range, setRange] = useState(initial);
|
|
112
|
-
const [step, setStep] = useState(null);
|
|
138
|
+
const [step, setStep] = useState(initialStep ?? null);
|
|
113
139
|
const [candles, setCandles] = useState(initialCandles);
|
|
114
140
|
const [loaded, setLoaded] = useState(null);
|
|
115
141
|
const resolution = RANGE[range].resolution;
|
|
@@ -118,7 +144,7 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
|
|
|
118
144
|
if (!el)
|
|
119
145
|
return;
|
|
120
146
|
let disposed = false;
|
|
121
|
-
import('lightweight-charts').then(({ createChart, AreaSeries, CandlestickSeries, HistogramSeries, ColorType, CrosshairMode, LineStyle, LineType }) => {
|
|
147
|
+
import('lightweight-charts').then(({ createChart, createSeriesMarkers, AreaSeries, CandlestickSeries, HistogramSeries, ColorType, CrosshairMode, LineStyle, LineType }) => {
|
|
122
148
|
if (disposed)
|
|
123
149
|
return;
|
|
124
150
|
const style = getComputedStyle(el);
|
|
@@ -177,7 +203,11 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
|
|
|
177
203
|
api.panes()[1]?.setStretchFactor(1);
|
|
178
204
|
return volume;
|
|
179
205
|
};
|
|
180
|
-
chart.current = {
|
|
206
|
+
chart.current = {
|
|
207
|
+
api, area, candle, volume: null, addVolume, up, down, ink,
|
|
208
|
+
areaMarkers: createSeriesMarkers(area, []),
|
|
209
|
+
candleMarkers: createSeriesMarkers(candle, []),
|
|
210
|
+
};
|
|
181
211
|
setReady(true);
|
|
182
212
|
});
|
|
183
213
|
return () => {
|
|
@@ -259,8 +289,21 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
|
|
|
259
289
|
const rising = p.value >= (p.open ?? shown[i - 1]?.value ?? p.value);
|
|
260
290
|
return { time: time(p), value: p.volume ?? 0, color: (rising ? c.up : c.down).replace(/[\d.]+\)$/, '0.5)') };
|
|
261
291
|
}));
|
|
292
|
+
const drawn = placeMarkers(markers ?? [], shown, view?.step ?? null).map(m => ({
|
|
293
|
+
time: time(m),
|
|
294
|
+
position: 'aboveBar',
|
|
295
|
+
shape: 'arrowDown',
|
|
296
|
+
color: c.ink,
|
|
297
|
+
text: m.label,
|
|
298
|
+
}));
|
|
299
|
+
c.areaMarkers.setMarkers(asCandles ? [] : drawn);
|
|
300
|
+
c.candleMarkers.setMarkers(asCandles ? drawn : []);
|
|
262
301
|
c.api.timeScale().fitContent();
|
|
263
|
-
}, [ready, source, view, candles]);
|
|
302
|
+
}, [ready, source, view, candles, markers]);
|
|
303
|
+
const shownStep = view?.step ?? null;
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
onViewRef.current?.({ range, step: shownStep });
|
|
306
|
+
}, [range, shownStep]);
|
|
264
307
|
// The steps always show where a range is drawn by day, so the buttons do not jump about; one
|
|
265
308
|
// the range cannot make (any, under 24H and 7D) is disabled.
|
|
266
309
|
const daily = ranges.some(r => RANGE[r].resolution === 'day');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|