@pushwoosh/kit-charts 0.0.10 → 0.0.12

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.
@@ -92,6 +92,12 @@ own `formatter` or `format` wins over it.
92
92
  the step is chosen from the candidates that stay integral at its magnitude, and
93
93
  never falls below 1.
94
94
 
95
+ `tickAmount` is Highcharts' demand and not a hint: the y axis stretches the end
96
+ the caller left open until exactly that many ticks fit, so a count of 2 on a
97
+ `tickAmount: 5` axis still draws 0..4 and reads against a stable grid rather than
98
+ filling the plot. An end pinned with `max` cannot be stretched, so a tick amount
99
+ alongside one is ignored.
100
+
95
101
  `labels.allowOverlap` defaults to false on both axes and on data labels. A label
96
102
  whose box would run into one already placed loses its text — Highcharts hides
97
103
  the label and keeps the tick, and so do we.
@@ -14,6 +14,8 @@ const ANCHOR = {
14
14
  center: 'middle',
15
15
  right: 'end'
16
16
  };
17
+ /** `style.textAlign` wins over the anchor the axis derives from its own geometry. */
18
+ const anchorFor = (style, fallback) => style?.textAlign && ANCHOR[style.textAlign] || fallback;
17
19
  /** What a tick draws: its own node if a formatter made one, its text otherwise. */
18
20
  const tickContent = tick => {
19
21
  if (tick.node !== undefined && tick.node !== null) {
@@ -33,7 +35,7 @@ function TickLabel({
33
35
  const text = _jsx("text", {
34
36
  x: x,
35
37
  y: y,
36
- textAnchor: anchor,
38
+ textAnchor: anchorFor(style, anchor),
37
39
  ...textProps(style),
38
40
  children: children
39
41
  });
@@ -89,7 +91,7 @@ function AxisTitle({
89
91
  return _jsx("text", {
90
92
  x: at.x,
91
93
  y: at.y,
92
- textAnchor: rotation === 0 ? anchor : 'middle',
94
+ textAnchor: rotation === 0 ? anchorFor(title.style, anchor) : 'middle',
93
95
  transform: rotation === 0 ? undefined : `rotate(${rotation} ${at.x} ${at.y})`,
94
96
  ...textProps(title.style),
95
97
  children: title.text
@@ -139,6 +139,7 @@ function buildYScale(axis, extent, range, hint, linked) {
139
139
  max: axis.max === undefined
140
140
  },
141
141
  tickHint: hint,
142
+ tickCount: axis.tickAmount,
142
143
  allowDecimals: axis.allowDecimals
143
144
  });
144
145
  }
@@ -337,7 +338,9 @@ export function buildChartModel({
337
338
  }) => {
338
339
  points.forEach(point => {
339
340
  if (typeof point.x === 'string') {
340
- point.band = point.x;
341
+ // Highcharts reads a string in `[name, value]` as the point's name and
342
+ // places it by index; we key by category, and fall back where it misses.
343
+ point.band = !explicitCategories || categories.includes(point.x) ? point.x : categories[point.index] ?? point.x;
341
344
  return;
342
345
  }
343
346
  if (!explicitCategories) {
@@ -347,6 +350,8 @@ export function buildChartModel({
347
350
  point.band = categories[Number(point.x)] ?? categories[point.index] ?? String(point.x);
348
351
  });
349
352
  });
353
+ // A band still outside the list extends the axis: a surplus point is data, and
354
+ // dropping it would lose it without a word.
350
355
  resolved.forEach(({
351
356
  points
352
357
  }) => {
@@ -9,6 +9,8 @@ export interface LinearScaleOptions {
9
9
  range: readonly [number, number];
10
10
  nice?: boolean | NiceEnds;
11
11
  tickHint?: number;
12
+ /** Highcharts' `tickAmount`: stretch the open end so exactly this many ticks fit. */
13
+ tickCount?: number;
12
14
  /** False keeps every tick a whole number, as a count axis needs. */
13
15
  allowDecimals?: boolean;
14
16
  }
@@ -31,18 +31,32 @@ export function createLinearScale(options) {
31
31
  range,
32
32
  nice = false,
33
33
  tickHint = 5,
34
+ tickCount,
34
35
  allowDecimals = true
35
36
  } = options;
36
37
  let [min, max] = options.domain;
38
+ const niceMin = nice === true || typeof nice === 'object' && nice.min === true;
39
+ const niceMax = nice === true || typeof nice === 'object' && nice.max === true;
37
40
  if (!Number.isFinite(min) || !Number.isFinite(max)) {
38
41
  [min, max] = [0, 1];
39
42
  } else if (min === max) {
40
43
  const pad = Math.abs(min) || 1;
41
- [min, max] = [min - pad, max + pad];
44
+ // An end the caller pinned stays put: padding both would take a `min: 0`
45
+ // count axis below zero on a day where every value is zero.
46
+ [min, max] = [min - (niceMin || !niceMax ? pad : 0), max + (niceMax || !niceMin ? pad : 0)];
42
47
  }
43
- const niceMin = nice === true || typeof nice === 'object' && nice.min === true;
44
- const niceMax = nice === true || typeof nice === 'object' && nice.max === true;
45
- if (niceMin || niceMax) {
48
+ if (tickCount !== undefined && tickCount > 1 && niceMax) {
49
+ let step = niceStep((max - min) / (tickCount - 1), allowDecimals);
50
+ if (niceMin) {
51
+ min = Math.floor(min / step) * step;
52
+ }
53
+ // Rounding the low end down lengthens the run, which can leave the last tick
54
+ // short of the data; widen the step until the fixed number of them covers it.
55
+ while (min + (tickCount - 1) * step < max) {
56
+ step = niceStep(step * 1.5, allowDecimals);
57
+ }
58
+ max = min + (tickCount - 1) * step;
59
+ } else if (niceMin || niceMax) {
46
60
  const step = niceStep((max - min) / tickHint, allowDecimals);
47
61
  if (niceMin) {
48
62
  min = Math.floor(min / step) * step;
@@ -86,10 +86,8 @@ export interface AxisLabelsOptions {
86
86
  hint?: (ctx: AxisLabelContext) => string | undefined;
87
87
  allowOverlap?: boolean;
88
88
  align?: 'left' | 'center' | 'right';
89
- rotation?: number;
90
89
  x?: number;
91
90
  y?: number;
92
- padding?: number;
93
91
  style?: TextStyle;
94
92
  }
95
93
  /** `zIndex` at or above this draws over the series instead of under it. */
@@ -168,7 +166,6 @@ export interface LegendOptions<TSeriesMeta = unknown> {
168
166
  align?: 'left' | 'center' | 'right';
169
167
  verticalAlign?: 'top' | 'middle' | 'bottom';
170
168
  layout?: 'horizontal' | 'vertical';
171
- margin?: number;
172
169
  x?: number;
173
170
  y?: number;
174
171
  itemStyle?: TextStyle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/kit-charts",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Small React chart library on SVG, with a Highcharts-shaped configuration model",
5
5
  "license": "MIT",
6
6
  "repository": {