@pushwoosh/kit-charts 0.0.15 → 0.0.17
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/ai-documentation.md +13 -0
- package/core/model/build-model.js +20 -8
- package/core/options/normalize.d.ts +5 -1
- package/core/options/normalize.js +5 -1
- package/core/scale/time.js +6 -1
- package/package.json +1 -1
- package/series/area/area.js +2 -1
- package/series/line/line.js +2 -1
- package/series/shared/Markers.js +2 -1
package/ai-documentation.md
CHANGED
|
@@ -113,6 +113,19 @@ it, exactly as `pick(labels.x, distance)` does there.
|
|
|
113
113
|
The x axis line is drawn after the series, so a bar or an area sitting on zero
|
|
114
114
|
cannot paint over the line it stands on.
|
|
115
115
|
|
|
116
|
+
A y axis with `labels.enabled: false` reserves no gutter at all — only a rotated
|
|
117
|
+
title still takes room — so the plot starts at `chart.spacing`, where Highcharts
|
|
118
|
+
starts it, rather than a label column in from there.
|
|
119
|
+
|
|
120
|
+
An end the axis leaves unpadded puts its extreme point on the plot edge, where
|
|
121
|
+
half of the point's marker would fall outside the SVG and be cut. Such an end
|
|
122
|
+
keeps `MARKER_REACH` of room: the plot edge moves in to 5px from the canvas
|
|
123
|
+
unless the gutter, the spacing or `chart.marginLeft`/`marginRight` already leaves
|
|
124
|
+
at least that much, so a chart with `spacing: 0` and no gutter is 5px in rather
|
|
125
|
+
than flush. Only an end that can draw a marker pays it: `marker.enabled: false`
|
|
126
|
+
does not release the room — the hover dot is drawn regardless, as in Highcharts —
|
|
127
|
+
but `marker.states.hover.enabled: false` does, and then no dot is drawn either.
|
|
128
|
+
|
|
116
129
|
`labels.allowOverlap` defaults to false on both axes and on data labels. A label
|
|
117
130
|
whose box would run into one already placed loses its text — Highcharts hides
|
|
118
131
|
the label and keeps the tick, and so do we.
|
|
@@ -7,7 +7,7 @@ import { AXIS_LABEL_GAP, AXIS_LABEL_DISTANCE, AXIS_TITLE_GAP, MARKER_REACH, VALU
|
|
|
7
7
|
import { nodeText } from '../layout/node-text';
|
|
8
8
|
import { computeSlots } from '../layout/point-width';
|
|
9
9
|
import { layoutTitles } from '../layout/title-layout';
|
|
10
|
-
import { resolveAxes, resolveSeries, isSeriesVisible } from '../options/normalize';
|
|
10
|
+
import { resolveAxes, resolveSeries, isSeriesVisible, showsHoverMarker, showsMarkers } from '../options/normalize';
|
|
11
11
|
import { createBandScale } from '../scale/band';
|
|
12
12
|
import { createColorScale } from '../scale/color';
|
|
13
13
|
import { createLinearScale } from '../scale/linear';
|
|
@@ -475,7 +475,7 @@ export function buildChartModel({
|
|
|
475
475
|
const yLabelsShown = !axisless && yAxes[0]?.visible !== false && yAxes[0]?.labels?.enabled !== false;
|
|
476
476
|
const markerReach = resolved.some(({
|
|
477
477
|
options: series
|
|
478
|
-
}) => series.marker
|
|
478
|
+
}) => showsMarkers(series.marker)) ? MARKER_REACH : 0;
|
|
479
479
|
// The topmost tick label is centred on the plot's top edge, so half of it hangs
|
|
480
480
|
// above the plot: without this the highest grid line and its label sit on the
|
|
481
481
|
// chart border.
|
|
@@ -499,10 +499,13 @@ export function buildChartModel({
|
|
|
499
499
|
})();
|
|
500
500
|
const marginTop = spaceTop + titleHeight + (legendAtTop ? legendHeight : 0) + (labelsOnTop ? xLabelHeight : 0) + Math.max(labelHeadroom, topOverflow);
|
|
501
501
|
const marginBottom = spaceBottom + (labelsOnTop ? 0 : xLabelHeight) + (legendAtTop ? 0 : legendHeight);
|
|
502
|
-
const measureGutter =
|
|
502
|
+
const measureGutter = () => {
|
|
503
503
|
const axis = yAxes[0] ?? {};
|
|
504
|
+
const title = axis.title?.text && (axis.title.rotation ?? -90) !== 0 ? textHeight(axis.title.style) + AXIS_TITLE_GAP : 0;
|
|
505
|
+
// Highcharts measures the label column and nothing else, so an axis without
|
|
506
|
+
// labels leaves the plot flush with the chart edge rather than indented.
|
|
504
507
|
if (axis.labels?.enabled === false) {
|
|
505
|
-
return
|
|
508
|
+
return title;
|
|
506
509
|
}
|
|
507
510
|
const labels = axis.categories?.length ? [...axis.categories] : axisTicks(axis, createLinearScale({
|
|
508
511
|
domain: (() => {
|
|
@@ -515,17 +518,26 @@ export function buildChartModel({
|
|
|
515
518
|
allowDecimals: axis.allowDecimals
|
|
516
519
|
}), axis.tickAmount ?? 5, undefined, 'y', numberFormat).map(tick => tick.label);
|
|
517
520
|
const widest = labels.reduce((widest_, label) => Math.max(widest_, measureText(label, axis.labels?.style)), 0);
|
|
518
|
-
const title = axis.title?.text && (axis.title.rotation ?? -90) !== 0 ? textHeight(axis.title.style) + AXIS_TITLE_GAP : 0;
|
|
519
521
|
// Highcharts reserves the widest label plus the distance it is drawn at; the two
|
|
520
522
|
// must be one number or the plot edge and the label column drift apart.
|
|
521
523
|
return Math.ceil(widest) + yLabelGap(axis) + title;
|
|
522
524
|
};
|
|
523
|
-
const gutter = axisless || yAxes[0]?.visible === false ? 0 : measureGutter(
|
|
525
|
+
const gutter = axisless || yAxes[0]?.visible === false ? 0 : measureGutter();
|
|
524
526
|
const legendAlign = options.legend?.align ?? 'left';
|
|
525
527
|
const legendBesideLeft = legendMetrics.beside && legendAlign === 'left';
|
|
528
|
+
// A point on the plot edge draws half its marker outside the canvas, where the
|
|
529
|
+
// svg cuts it; an end the axis pads keeps that room already.
|
|
530
|
+
const edgeMarkers = !axisless && xKind !== 'band' && resolved.some(({
|
|
531
|
+
renderer,
|
|
532
|
+
options: series
|
|
533
|
+
}) => renderer.scales.x !== 'band' && (showsMarkers(series.marker) || showsHoverMarker(series.marker)));
|
|
534
|
+
const hoverReach = edgeMarkers ? MARKER_REACH : 0;
|
|
535
|
+
const domainMin = xAxisOptions.min ?? xDomain.min;
|
|
536
|
+
const domainMax = xAxisOptions.max ?? xDomain.max;
|
|
537
|
+
const edgeRoom = (margin, flush) => flush ? Math.max(margin, hoverReach) : margin;
|
|
526
538
|
// Highcharts margins replace the computed edge distance rather than adding to it.
|
|
527
|
-
const left = options.chart?.marginLeft ?? spaceLeft + gutter + (legendBesideLeft ? legendMetrics.width : 0);
|
|
528
|
-
const right = options.chart?.marginRight ?? spaceRight + (legendMetrics.beside && !legendBesideLeft ? legendMetrics.width : 0);
|
|
539
|
+
const left = options.chart?.marginLeft ?? edgeRoom(spaceLeft + gutter + (legendBesideLeft ? legendMetrics.width : 0), domainMin === acc.x.min);
|
|
540
|
+
const right = options.chart?.marginRight ?? edgeRoom(spaceRight + (legendMetrics.beside && !legendBesideLeft ? legendMetrics.width : 0), domainMax === acc.x.max);
|
|
529
541
|
const top = options.chart?.marginTop ?? marginTop;
|
|
530
542
|
const bottom = options.chart?.marginBottom ?? marginBottom;
|
|
531
543
|
const plot = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AxisOptions, type ChartOptions, type SeriesOptionsBase } from '../types/options';
|
|
1
|
+
import { type AxisOptions, type ChartOptions, type MarkerOptions, type SeriesOptionsBase } from '../types/options';
|
|
2
2
|
export declare const toAxisArray: (axis: AxisOptions | readonly AxisOptions[] | undefined) => AxisOptions[];
|
|
3
3
|
export declare function resolveSeries(options: ChartOptions): SeriesOptionsBase[];
|
|
4
4
|
export declare function resolveAxes(options: ChartOptions): {
|
|
@@ -6,3 +6,7 @@ export declare function resolveAxes(options: ChartOptions): {
|
|
|
6
6
|
yAxes: AxisOptions[];
|
|
7
7
|
};
|
|
8
8
|
export declare const isSeriesVisible: (series: SeriesOptionsBase) => boolean;
|
|
9
|
+
/** A marker at every point of the series. */
|
|
10
|
+
export declare const showsMarkers: (marker: MarkerOptions | undefined) => boolean;
|
|
11
|
+
/** The dot under the pointer, which Highcharts draws whatever `marker.enabled` says. */
|
|
12
|
+
export declare const showsHoverMarker: (marker: MarkerOptions | undefined) => boolean;
|
|
@@ -40,4 +40,8 @@ export function resolveAxes(options) {
|
|
|
40
40
|
yAxes: toAxisArray(options.yAxis)
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
export const isSeriesVisible = series => series.visible !== false;
|
|
43
|
+
export const isSeriesVisible = series => series.visible !== false;
|
|
44
|
+
/** A marker at every point of the series. */
|
|
45
|
+
export const showsMarkers = marker => marker?.enabled !== false && marker?.symbol !== 'none';
|
|
46
|
+
/** The dot under the pointer, which Highcharts draws whatever `marker.enabled` says. */
|
|
47
|
+
export const showsHoverMarker = marker => marker?.states?.hover?.enabled !== false;
|
package/core/scale/time.js
CHANGED
|
@@ -90,7 +90,12 @@ const LADDER = [{
|
|
|
90
90
|
}];
|
|
91
91
|
const pickRungIndex = (span, count) => {
|
|
92
92
|
const target = span / Math.max(1, count);
|
|
93
|
-
|
|
93
|
+
// Highcharts keeps a rung until the midpoint to the next one, so a month of data read at
|
|
94
|
+
// three days a tick still steps by two days instead of jumping to whole weeks.
|
|
95
|
+
const found = LADDER.findIndex((rung, index) => {
|
|
96
|
+
const next = LADDER[index + 1];
|
|
97
|
+
return !next || target <= (rung.approx + next.approx) / 2;
|
|
98
|
+
});
|
|
94
99
|
return found === -1 ? LADDER.length - 1 : found;
|
|
95
100
|
};
|
|
96
101
|
const alignUp = (from, rung) => {
|
package/package.json
CHANGED
package/series/area/area.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { drawable, projectSeries, toAreaPath, toLinePath } from '../../core/geometry/path';
|
|
3
|
+
import { showsHoverMarker } from '../../core/options/normalize';
|
|
3
4
|
import { includeZero } from '../shared/domain';
|
|
4
5
|
import { nearestByX } from '../shared/hit';
|
|
5
6
|
import { seriesLegend } from '../shared/legend';
|
|
@@ -41,7 +42,7 @@ export const area = {
|
|
|
41
42
|
marker: ctx.series.marker,
|
|
42
43
|
projected: projected,
|
|
43
44
|
color: color
|
|
44
|
-
}), hovered ? _jsx("circle", {
|
|
45
|
+
}), hovered && showsHoverMarker(ctx.series.marker) ? _jsx("circle", {
|
|
45
46
|
cx: hovered.px,
|
|
46
47
|
cy: hovered.py,
|
|
47
48
|
r: ctx.series.marker?.states?.hover?.radius ?? 4,
|
package/series/line/line.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { drawable, projectSeries, toLinePath } from '../../core/geometry/path';
|
|
3
|
+
import { showsHoverMarker } from '../../core/options/normalize';
|
|
3
4
|
import { nearestByX } from '../shared/hit';
|
|
4
5
|
import { seriesLegend } from '../shared/legend';
|
|
5
6
|
import { Markers } from '../shared/Markers';
|
|
@@ -33,7 +34,7 @@ export const line = {
|
|
|
33
34
|
marker: ctx.series.marker,
|
|
34
35
|
projected: projected,
|
|
35
36
|
color: color
|
|
36
|
-
}), hovered ? _jsx("circle", {
|
|
37
|
+
}), hovered && showsHoverMarker(ctx.series.marker) ? _jsx("circle", {
|
|
37
38
|
cx: hovered.px,
|
|
38
39
|
cy: hovered.py,
|
|
39
40
|
r: ctx.series.marker?.states?.hover?.radius ?? 4,
|
package/series/shared/Markers.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { showsMarkers } from '../../core/options/normalize';
|
|
2
3
|
const markerRadius = marker => marker?.radius ?? (marker?.width ? marker.width / 2 : 3);
|
|
3
4
|
export function Markers({
|
|
4
5
|
marker,
|
|
5
6
|
projected,
|
|
6
7
|
color
|
|
7
8
|
}) {
|
|
8
|
-
if (marker
|
|
9
|
+
if (!showsMarkers(marker)) {
|
|
9
10
|
return null;
|
|
10
11
|
}
|
|
11
12
|
const radius = markerRadius(marker);
|