wiki-formant 0.57.1 → 0.58.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 +8 -5
- package/dist/chart.d.ts +4 -2
- package/dist/chart.js +10 -4
- package/dist/stats-figures.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -847,7 +847,8 @@ own subpath, because it imports `@simplewebauthn/browser`.
|
|
|
847
847
|
`Stats` (`wiki-formant/stats`) renders a `digest` as one page: the
|
|
848
848
|
headline figures, each with its change from the window before
|
|
849
849
|
(`.stats-delta[data-better]`, a share or for the bounce rate points, for the
|
|
850
|
-
site's stylesheet to colour), one of them
|
|
850
|
+
site's stylesheet to colour), one of them by day, week or month (a count's
|
|
851
|
+
total, a rate's mean), every non-empty
|
|
851
852
|
ranked list, and `?days=` links between windows, all time among them, pinned in
|
|
852
853
|
the chart's frame as its own buttons are (`TimeChart`'s `controls`). Each
|
|
853
854
|
figure is a button that charts it: visitors, agents, page views, bounce rate,
|
|
@@ -897,7 +898,7 @@ that makes 200 or fewer, so a year opens weekly and five years monthly.
|
|
|
897
898
|
|
|
898
899
|
```tsx
|
|
899
900
|
// Daily points: the chart slices and steps them itself; 30D up.
|
|
900
|
-
<TimeChart series={days} label="Transactions" aggregate="
|
|
901
|
+
<TimeChart series={days} label="Transactions" aggregate="sum" fromZero />
|
|
901
902
|
|
|
902
903
|
// A loader: asked for 'hour' (24H), '4h' (7D) or 'day' from 0 (every longer
|
|
903
904
|
// range), once each.
|
|
@@ -915,8 +916,10 @@ the slot empty, except the last, which is the price now. A price's line is
|
|
|
915
916
|
straight between closes and a count's is curved.
|
|
916
917
|
|
|
917
918
|
`aggregate` is how a week or month is made from its days: `last` for a price,
|
|
918
|
-
`
|
|
919
|
-
|
|
919
|
+
`sum` for a count, so a week reads as the week's total, and `mean` for a rate,
|
|
920
|
+
which no total makes sense of. A stepped range starts on a whole week or month,
|
|
921
|
+
so its first point is not a few days of one; the week in progress is its total
|
|
922
|
+
so far. `fromZero` starts a count's axis at zero and stops it there, with
|
|
920
923
|
no negative ticks under it. A single range draws no range buttons and shows every point.
|
|
921
924
|
`chartSteps` is exported for a chart drawn elsewhere.
|
|
922
925
|
|
|
@@ -932,7 +935,7 @@ range can make it, and `onView` hears the range and step on mount and on every
|
|
|
932
935
|
change, for a page that keeps them in its URL:
|
|
933
936
|
|
|
934
937
|
```tsx
|
|
935
|
-
<TimeChart series={days} label="Transactions" aggregate="
|
|
938
|
+
<TimeChart series={days} label="Transactions" aggregate="sum" fromZero
|
|
936
939
|
markers={[{ time: haltAt, label: 'Halt' }]} step="month" onView={v => keep(v)} />
|
|
937
940
|
```
|
|
938
941
|
`base.css` pins the buttons (`.chart-controls button[aria-pressed]`) to the
|
package/dist/chart.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export type ChartView = {
|
|
|
28
28
|
range: ChartRange;
|
|
29
29
|
step: ChartStep | null;
|
|
30
30
|
};
|
|
31
|
+
/** How a week or month is made from its days: a price's last, a count's total, a rate's mean. */
|
|
32
|
+
export type ChartAggregate = 'last' | 'sum' | 'mean';
|
|
31
33
|
/** Every range, shortest first. A chart fed daily points offers the ones from 30D up. */
|
|
32
34
|
export declare const CHART_RANGES: readonly ChartRange[];
|
|
33
35
|
/** A step is offered once it makes four points, and the default is the finest that makes 200 or fewer. */
|
|
@@ -51,8 +53,8 @@ export interface TimeChartProps {
|
|
|
51
53
|
ranges?: readonly ChartRange[];
|
|
52
54
|
/** The range shown first. */
|
|
53
55
|
range?: ChartRange;
|
|
54
|
-
/** A week or month of prices keeps its last; of counts, the
|
|
55
|
-
aggregate?:
|
|
56
|
+
/** A week or month of prices keeps its last; of counts, the total of its days; of rates, their mean. */
|
|
57
|
+
aggregate?: ChartAggregate;
|
|
56
58
|
/** The value axis starts at zero, so a quiet week does not read as a collapse. */
|
|
57
59
|
fromZero?: boolean;
|
|
58
60
|
/**
|
package/dist/chart.js
CHANGED
|
@@ -48,8 +48,8 @@ function bucketStart(time, step) {
|
|
|
48
48
|
/**
|
|
49
49
|
* Points, oldest first, one per step. A price keeps the last value in each
|
|
50
50
|
* step, as a candle does its close: the first open, the highest high, the
|
|
51
|
-
* lowest low. A count
|
|
52
|
-
*
|
|
51
|
+
* lowest low. A count is the total of its days; a rate, their mean. Volume is
|
|
52
|
+
* summed.
|
|
53
53
|
*
|
|
54
54
|
* @internal
|
|
55
55
|
*/
|
|
@@ -66,6 +66,8 @@ export function bucketChart(points, step, aggregate) {
|
|
|
66
66
|
}
|
|
67
67
|
if (aggregate === 'last')
|
|
68
68
|
last.value = p.value;
|
|
69
|
+
else if (aggregate === 'sum')
|
|
70
|
+
last.value += p.value;
|
|
69
71
|
else
|
|
70
72
|
last.value += (p.value - last.value) / ++n;
|
|
71
73
|
if (last.high !== undefined && p.high !== undefined)
|
|
@@ -256,7 +258,7 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
|
|
|
256
258
|
if (!ready || !c)
|
|
257
259
|
return;
|
|
258
260
|
c.api.priceScale('right').applyOptions({ scaleMargins: { top: MARGIN_TOP, bottom: fromZero ? 0 : 0.1 } });
|
|
259
|
-
c.area.applyOptions({ lineType: aggregate === '
|
|
261
|
+
c.area.applyOptions({ lineType: aggregate === 'last' ? c.straight : c.curved, autoscaleInfoProvider: fromZero ? fromZeroScale : ownScale });
|
|
260
262
|
}, [ready, fromZero, aggregate]);
|
|
261
263
|
// One request per resolution for the life of the loader: every range from 30D up reads the same days.
|
|
262
264
|
const cache = useRef(new Map());
|
|
@@ -298,7 +300,11 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
|
|
|
298
300
|
return drawn(windowed, [], null);
|
|
299
301
|
const { steps, fallback } = chartSteps(end - windowed[0].time);
|
|
300
302
|
const chosen = step && steps.includes(step) ? step : fallback;
|
|
301
|
-
|
|
303
|
+
if (chosen === 'day')
|
|
304
|
+
return drawn(windowed, steps, chosen);
|
|
305
|
+
// A stepped range starts on a whole week or month, so its first total is not a few days of one.
|
|
306
|
+
const from = bucketStart(windowed[0].time, chosen);
|
|
307
|
+
return drawn(bucketChart(points.filter(p => p.time >= from), chosen, aggregate), steps, chosen);
|
|
302
308
|
}, [points, range, ranges.length, resolution, step, aggregate]);
|
|
303
309
|
// While a resolution loads, the last one stays under the skeleton; an empty or failed one clears it.
|
|
304
310
|
useEffect(() => {
|
package/dist/stats-figures.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
// stats-figures.tsx — the stats page's headline figures and the chart under
|
|
4
|
-
// them. Each figure is a button that charts that figure
|
|
4
|
+
// them. Each figure is a button that charts that figure by day, week or month, so the chart
|
|
5
5
|
// is not only visitors. Its own module because it holds state; `Stats` stays a
|
|
6
6
|
// server component and hands it the digest, which is plain JSON.
|
|
7
7
|
import { useMemo, useState } from 'react';
|
|
@@ -34,6 +34,7 @@ const figuresOf = ({ previous: p, ...d }) => [
|
|
|
34
34
|
delta: change(d.bounce_rate, p.bounce_rate, { points: true, lowerIsBetter: true }),
|
|
35
35
|
day: r => (r.visits ? (100 * r.bounces) / r.visits : null),
|
|
36
36
|
format: percent,
|
|
37
|
+
aggregate: 'mean',
|
|
37
38
|
},
|
|
38
39
|
{
|
|
39
40
|
label: 'Visit length',
|
|
@@ -41,6 +42,7 @@ const figuresOf = ({ previous: p, ...d }) => [
|
|
|
41
42
|
delta: change(d.visit_duration, p.visit_duration),
|
|
42
43
|
day: r => (r.visits ? r.seconds / r.visits : null),
|
|
43
44
|
format: duration,
|
|
45
|
+
aggregate: 'mean',
|
|
44
46
|
},
|
|
45
47
|
...(d.follow_clicks
|
|
46
48
|
? [
|
|
@@ -74,5 +76,5 @@ export function StatsFigures({ digest, first, windows }) {
|
|
|
74
76
|
return value == null ? null : { time, value };
|
|
75
77
|
}).filter(p => p !== null);
|
|
76
78
|
}, [byDay, first, figure]);
|
|
77
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "stats-figures", role: "group", "aria-label": "Chart", children: figures.map((f, i) => (_jsxs("button", { type: "button", "aria-pressed": i === chosen, onClick: () => choose(i), children: [_jsx("span", { children: f.label }), _jsxs("strong", { children: [f.value, f.delta && (_jsx("small", { className: "stats-delta", "data-better": f.delta.better, children: f.delta.text }))] })] }, f.label))) }), _jsx(TimeChart, { series: series, label:
|
|
79
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "stats-figures", role: "group", "aria-label": "Chart", children: figures.map((f, i) => (_jsxs("button", { type: "button", "aria-pressed": i === chosen, onClick: () => choose(i), children: [_jsx("span", { children: f.label }), _jsxs("strong", { children: [f.value, f.delta && (_jsx("small", { className: "stats-delta", "data-better": f.delta.better, children: f.delta.text }))] })] }, f.label))) }), _jsx(TimeChart, { series: series, label: figure.label, ranges: ['all'], aggregate: figure.aggregate ?? 'sum', fromZero: true, format: figure.format, controls: windows, className: "stats-chart" })] }));
|
|
78
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.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",
|