@voila.dev/chart 6.0.125 → 6.0.127
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axes.d.ts","sourceRoot":"","sources":["../../../src/core/guides/axes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EACX,kBAAkB,EAClB,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,SAAS,EACT,MAAM,iBAAiB,CAAC;AAgBzB,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;CACxC;AAED,wBAAgB,SAAS,CACxB,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACnC,aAAa,CAAC,UAAU,CAAC,CAM3B;AAED,wBAAgB,SAAS,CACxB,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,KAAK,EAAE,UAAU,GACf,MAAM,CAER;
|
|
1
|
+
{"version":3,"file":"axes.d.ts","sourceRoot":"","sources":["../../../src/core/guides/axes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EACX,kBAAkB,EAClB,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,SAAS,EACT,MAAM,iBAAiB,CAAC;AAgBzB,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;CACxC;AAED,wBAAgB,SAAS,CACxB,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACnC,aAAa,CAAC,UAAU,CAAC,CAM3B;AAED,wBAAgB,SAAS,CACxB,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,KAAK,EAAE,UAAU,GACf,MAAM,CAER;AAOD,8DAA8D;AAC9D,wBAAgB,QAAQ,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,iBAAiB,GAC5B,MAAM,CASR;AAMD,+EAA+E;AAC/E,wBAAgB,UAAU,CACzB,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,GAChB,MAAM,CAgBR;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAW5D;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAa3D;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CAC1C;AAgCD,wBAAgB,UAAU,CACzB,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,OAAO,GACX,SAAS,CAwDX;AAED,wBAAgB,QAAQ,CACvB,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAChB,SAAS,CAmEX"}
|
package/dist/core/guides/axes.js
CHANGED
|
@@ -14,6 +14,9 @@ function axisTicks(scale, options) {
|
|
|
14
14
|
function tickLabel(scale, options, value) {
|
|
15
15
|
return options?.tickFormat?.(value) ?? scale.format(value);
|
|
16
16
|
}
|
|
17
|
+
function tickKey(value) {
|
|
18
|
+
return value instanceof Date ? String(value.getTime()) : String(value);
|
|
19
|
+
}
|
|
17
20
|
function truncate(text, maxWidth, fontSize, measureText) {
|
|
18
21
|
if (measureText(text, fontSize) <= maxWidth) {
|
|
19
22
|
return text;
|
|
@@ -63,14 +66,19 @@ function rightMargin(input) {
|
|
|
63
66
|
const overhang = half - (scale.range[1] - scale.map(last));
|
|
64
67
|
return Math.max(EDGE, overhang + 2);
|
|
65
68
|
}
|
|
66
|
-
function thinned(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
function thinned(texts, widths, positions) {
|
|
70
|
+
const distinct = texts.map((_text, index) => index).filter((index) => index === 0 || texts[index] !== texts[index - 1]);
|
|
71
|
+
if (distinct.length < 2) {
|
|
72
|
+
return distinct;
|
|
69
73
|
}
|
|
70
|
-
const spacing = Math.
|
|
71
|
-
|
|
74
|
+
const spacing = Math.min(
|
|
75
|
+
...distinct.slice(1).map(
|
|
76
|
+
(index, at) => Math.abs(positions[index] - positions[distinct[at]])
|
|
77
|
+
)
|
|
78
|
+
) || 1;
|
|
79
|
+
const widest = Math.max(...distinct.map((index) => widths[index]));
|
|
72
80
|
const step = Math.max(1, Math.ceil((widest + LABEL_SPACING) / spacing));
|
|
73
|
-
return
|
|
81
|
+
return distinct.filter((_index, at) => at % step === 0);
|
|
74
82
|
}
|
|
75
83
|
function bottomAxis(input, plot, grid) {
|
|
76
84
|
const { scale, options, title, theme, measureText } = input;
|
|
@@ -78,7 +86,7 @@ function bottomAxis(input, plot, grid) {
|
|
|
78
86
|
const positions = ticks.map((tick) => scale.center(tick));
|
|
79
87
|
const gridNodes = grid ? ticks.map((tick, index) => ({
|
|
80
88
|
kind: "line",
|
|
81
|
-
key: `grid-x:${
|
|
89
|
+
key: `grid-x:${tickKey(tick)}`,
|
|
82
90
|
role: "grid",
|
|
83
91
|
x1: positions[index],
|
|
84
92
|
y1: plot.y,
|
|
@@ -92,10 +100,10 @@ function bottomAxis(input, plot, grid) {
|
|
|
92
100
|
const texts = ticks.map((tick) => tickLabel(scale, options, tick));
|
|
93
101
|
const widths = texts.map((text) => measureText(text, theme.fontSize));
|
|
94
102
|
const baseline = plot.y + plot.height + TICK_GAP;
|
|
95
|
-
const labels = thinned(
|
|
103
|
+
const labels = thinned(texts, widths, positions).map(
|
|
96
104
|
(index) => ({
|
|
97
105
|
kind: "text",
|
|
98
|
-
key: `tick-x:${
|
|
106
|
+
key: `tick-x:${tickKey(ticks[index])}`,
|
|
99
107
|
role: "axis",
|
|
100
108
|
x: positions[index],
|
|
101
109
|
y: baseline,
|
|
@@ -133,7 +141,7 @@ function leftAxis(input, plot, grid, chartWidth) {
|
|
|
133
141
|
const positions = ticks.map((tick) => scale.center(tick));
|
|
134
142
|
const gridNodes = grid ? ticks.map((tick, index) => ({
|
|
135
143
|
kind: "line",
|
|
136
|
-
key: `grid-y:${
|
|
144
|
+
key: `grid-y:${tickKey(tick)}`,
|
|
137
145
|
role: "grid",
|
|
138
146
|
x1: plot.x,
|
|
139
147
|
y1: positions[index],
|
|
@@ -155,10 +163,10 @@ function leftAxis(input, plot, grid, chartWidth) {
|
|
|
155
163
|
)
|
|
156
164
|
);
|
|
157
165
|
const heights = texts.map(() => theme.fontSize);
|
|
158
|
-
const labels = thinned(
|
|
166
|
+
const labels = thinned(texts, heights, positions).map(
|
|
159
167
|
(index) => ({
|
|
160
168
|
kind: "text",
|
|
161
|
-
key: `tick-y:${
|
|
169
|
+
key: `tick-y:${tickKey(ticks[index])}`,
|
|
162
170
|
role: "axis",
|
|
163
171
|
x: plot.x - TICK_GAP,
|
|
164
172
|
y: positions[index],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/guides/axes.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ChartAxisOptions } from \"#/core/define-chart.ts\";\nimport type {\n\tChartPositionScale,\n\tChartRect,\n\tChartTextMeasurer,\n\tChartTheme,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * The axes as scene nodes: grid lines behind the marks, tick labels and\n * titles in front. They are measured before the plot exists, so the margins\n * fit the widest label instead of a guessed constant.\n */\n\nconst TICK_GAP = 8;\nconst TITLE_GAP = 6;\nconst LABEL_SPACING = 8;\nconst EDGE = 8;\n/** A category label on a horizontal bar chart never takes more of the width than this. */\nconst MAX_CATEGORY_SHARE = 0.35;\nconst ELLIPSIS = \"\u2026\";\n\nexport interface AxisMeasureInput {\n\treadonly scale: ChartPositionScale;\n\treadonly options: ChartAxisOptions | undefined;\n\treadonly title: string | undefined;\n\treadonly theme: ChartTheme;\n\treadonly measureText: ChartTextMeasurer;\n}\n\nexport function axisTicks(\n\tscale: ChartPositionScale,\n\toptions: ChartAxisOptions | undefined,\n): ReadonlyArray<ChartValue> {\n\tconst ticks = options?.ticks;\n\tif (Array.isArray(ticks)) {\n\t\treturn ticks;\n\t}\n\treturn scale.ticks(typeof ticks === \"number\" ? ticks : undefined);\n}\n\nexport function tickLabel(\n\tscale: ChartPositionScale,\n\toptions: ChartAxisOptions | undefined,\n\tvalue: ChartValue,\n): string {\n\treturn options?.tickFormat?.(value) ?? scale.format(value);\n}\n\n/** Cuts a label down to `maxWidth`, ending on an ellipsis. */\nexport function truncate(\n\ttext: string,\n\tmaxWidth: number,\n\tfontSize: number,\n\tmeasureText: ChartTextMeasurer,\n): string {\n\tif (measureText(text, fontSize) <= maxWidth) {\n\t\treturn text;\n\t}\n\tlet kept = text;\n\twhile (kept.length > 1 && measureText(kept + ELLIPSIS, fontSize) > maxWidth) {\n\t\tkept = kept.slice(0, -1);\n\t}\n\treturn kept + ELLIPSIS;\n}\n\nfunction hidden(options: ChartAxisOptions | undefined): boolean {\n\treturn options?.axis === false;\n}\n\n/** Room the left axis needs: its widest tick label, the gap, and the title. */\nexport function leftMargin(\n\tinput: AxisMeasureInput,\n\tchartWidth: number,\n): number {\n\tconst { scale, options, title, theme, measureText } = input;\n\tif (hidden(options)) {\n\t\treturn EDGE;\n\t}\n\tconst discrete = scale.kind === \"band\" || scale.kind === \"point\";\n\tconst widest = Math.max(\n\t\t0,\n\t\t...axisTicks(scale, options).map((tick) =>\n\t\t\tmeasureText(tickLabel(scale, options, tick), theme.fontSize),\n\t\t),\n\t);\n\tconst labels = discrete\n\t\t? Math.min(widest, chartWidth * MAX_CATEGORY_SHARE)\n\t\t: widest;\n\treturn EDGE + labels + TICK_GAP + (title ? theme.fontSize + TITLE_GAP : 0);\n}\n\nexport function bottomMargin(input: AxisMeasureInput): number {\n\tconst { options, title, theme } = input;\n\tif (hidden(options)) {\n\t\treturn EDGE;\n\t}\n\treturn (\n\t\tTICK_GAP +\n\t\ttheme.fontSize +\n\t\tEDGE / 2 +\n\t\t(title ? theme.fontSize + TITLE_GAP : 0)\n\t);\n}\n\n/** Half the last x label hangs past the plot's right edge; leave it room. */\nexport function rightMargin(input: AxisMeasureInput): number {\n\tconst { scale, options, theme, measureText } = input;\n\tif (hidden(options) || scale.kind === \"band\" || scale.kind === \"point\") {\n\t\treturn EDGE;\n\t}\n\tconst ticks = axisTicks(scale, options);\n\tconst last = ticks[ticks.length - 1];\n\tif (last === undefined) {\n\t\treturn EDGE;\n\t}\n\tconst half = measureText(tickLabel(scale, options, last), theme.fontSize) / 2;\n\tconst overhang = half - (scale.range[1] - scale.map(last));\n\treturn Math.max(EDGE, overhang + 2);\n}\n\nexport interface AxisNodes {\n\treadonly grid: ReadonlyArray<SceneNode>;\n\treadonly labels: ReadonlyArray<SceneNode>;\n}\n\n/**\n *
|
|
5
|
-
"mappings": "AAgBA,MAAM,WAAW;AACjB,MAAM,YAAY;AAClB,MAAM,gBAAgB;AACtB,MAAM,OAAO;AAEb,MAAM,qBAAqB;AAC3B,MAAM,WAAW;AAUV,SAAS,UACf,OACA,SAC4B;AAC5B,QAAM,QAAQ,SAAS;AACvB,MAAI,MAAM,QAAQ,KAAK,GAAG;AACzB,WAAO;AAAA,EACR;AACA,SAAO,MAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,MAAS;AACjE;AAEO,SAAS,UACf,OACA,SACA,OACS;AACT,SAAO,SAAS,aAAa,KAAK,KAAK,MAAM,OAAO,KAAK;AAC1D;AAGO,SAAS,SACf,MACA,UACA,UACA,aACS;AACT,MAAI,YAAY,MAAM,QAAQ,KAAK,UAAU;AAC5C,WAAO;AAAA,EACR;AACA,MAAI,OAAO;AACX,SAAO,KAAK,SAAS,KAAK,YAAY,OAAO,UAAU,QAAQ,IAAI,UAAU;AAC5E,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACxB;AACA,SAAO,OAAO;AACf;AAEA,SAAS,OAAO,SAAgD;AAC/D,SAAO,SAAS,SAAS;AAC1B;AAGO,SAAS,WACf,OACA,YACS;AACT,QAAM,EAAE,OAAO,SAAS,OAAO,OAAO,YAAY,IAAI;AACtD,MAAI,OAAO,OAAO,GAAG;AACpB,WAAO;AAAA,EACR;AACA,QAAM,WAAW,MAAM,SAAS,UAAU,MAAM,SAAS;AACzD,QAAM,SAAS,KAAK;AAAA,IACnB;AAAA,IACA,GAAG,UAAU,OAAO,OAAO,EAAE;AAAA,MAAI,CAAC,SACjC,YAAY,UAAU,OAAO,SAAS,IAAI,GAAG,MAAM,QAAQ;AAAA,IAC5D;AAAA,EACD;AACA,QAAM,SAAS,WACZ,KAAK,IAAI,QAAQ,aAAa,kBAAkB,IAChD;AACH,SAAO,OAAO,SAAS,YAAY,QAAQ,MAAM,WAAW,YAAY;AACzE;AAEO,SAAS,aAAa,OAAiC;AAC7D,QAAM,EAAE,SAAS,OAAO,MAAM,IAAI;AAClC,MAAI,OAAO,OAAO,GAAG;AACpB,WAAO;AAAA,EACR;AACA,SACC,WACA,MAAM,WACN,OAAO,KACN,QAAQ,MAAM,WAAW,YAAY;AAExC;AAGO,SAAS,YAAY,OAAiC;AAC5D,QAAM,EAAE,OAAO,SAAS,OAAO,YAAY,IAAI;AAC/C,MAAI,OAAO,OAAO,KAAK,MAAM,SAAS,UAAU,MAAM,SAAS,SAAS;AACvE,WAAO;AAAA,EACR;AACA,QAAM,QAAQ,UAAU,OAAO,OAAO;AACtC,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,QAAW;AACvB,WAAO;AAAA,EACR;AACA,QAAM,OAAO,YAAY,UAAU,OAAO,SAAS,IAAI,GAAG,MAAM,QAAQ,IAAI;AAC5E,QAAM,WAAW,QAAQ,MAAM,MAAM,CAAC,IAAI,MAAM,IAAI,IAAI;AACxD,SAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AACnC;
|
|
4
|
+
"sourcesContent": ["import type { ChartAxisOptions } from \"#/core/define-chart.ts\";\nimport type {\n\tChartPositionScale,\n\tChartRect,\n\tChartTextMeasurer,\n\tChartTheme,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * The axes as scene nodes: grid lines behind the marks, tick labels and\n * titles in front. They are measured before the plot exists, so the margins\n * fit the widest label instead of a guessed constant.\n */\n\nconst TICK_GAP = 8;\nconst TITLE_GAP = 6;\nconst LABEL_SPACING = 8;\nconst EDGE = 8;\n/** A category label on a horizontal bar chart never takes more of the width than this. */\nconst MAX_CATEGORY_SHARE = 0.35;\nconst ELLIPSIS = \"\u2026\";\n\nexport interface AxisMeasureInput {\n\treadonly scale: ChartPositionScale;\n\treadonly options: ChartAxisOptions | undefined;\n\treadonly title: string | undefined;\n\treadonly theme: ChartTheme;\n\treadonly measureText: ChartTextMeasurer;\n}\n\nexport function axisTicks(\n\tscale: ChartPositionScale,\n\toptions: ChartAxisOptions | undefined,\n): ReadonlyArray<ChartValue> {\n\tconst ticks = options?.ticks;\n\tif (Array.isArray(ticks)) {\n\t\treturn ticks;\n\t}\n\treturn scale.ticks(typeof ticks === \"number\" ? ticks : undefined);\n}\n\nexport function tickLabel(\n\tscale: ChartPositionScale,\n\toptions: ChartAxisOptions | undefined,\n\tvalue: ChartValue,\n): string {\n\treturn options?.tickFormat?.(value) ?? scale.format(value);\n}\n\n/** Two ticks can share a label (\"11:00\" on two days), never a value. */\nfunction tickKey(value: ChartValue): string {\n\treturn value instanceof Date ? String(value.getTime()) : String(value);\n}\n\n/** Cuts a label down to `maxWidth`, ending on an ellipsis. */\nexport function truncate(\n\ttext: string,\n\tmaxWidth: number,\n\tfontSize: number,\n\tmeasureText: ChartTextMeasurer,\n): string {\n\tif (measureText(text, fontSize) <= maxWidth) {\n\t\treturn text;\n\t}\n\tlet kept = text;\n\twhile (kept.length > 1 && measureText(kept + ELLIPSIS, fontSize) > maxWidth) {\n\t\tkept = kept.slice(0, -1);\n\t}\n\treturn kept + ELLIPSIS;\n}\n\nfunction hidden(options: ChartAxisOptions | undefined): boolean {\n\treturn options?.axis === false;\n}\n\n/** Room the left axis needs: its widest tick label, the gap, and the title. */\nexport function leftMargin(\n\tinput: AxisMeasureInput,\n\tchartWidth: number,\n): number {\n\tconst { scale, options, title, theme, measureText } = input;\n\tif (hidden(options)) {\n\t\treturn EDGE;\n\t}\n\tconst discrete = scale.kind === \"band\" || scale.kind === \"point\";\n\tconst widest = Math.max(\n\t\t0,\n\t\t...axisTicks(scale, options).map((tick) =>\n\t\t\tmeasureText(tickLabel(scale, options, tick), theme.fontSize),\n\t\t),\n\t);\n\tconst labels = discrete\n\t\t? Math.min(widest, chartWidth * MAX_CATEGORY_SHARE)\n\t\t: widest;\n\treturn EDGE + labels + TICK_GAP + (title ? theme.fontSize + TITLE_GAP : 0);\n}\n\nexport function bottomMargin(input: AxisMeasureInput): number {\n\tconst { options, title, theme } = input;\n\tif (hidden(options)) {\n\t\treturn EDGE;\n\t}\n\treturn (\n\t\tTICK_GAP +\n\t\ttheme.fontSize +\n\t\tEDGE / 2 +\n\t\t(title ? theme.fontSize + TITLE_GAP : 0)\n\t);\n}\n\n/** Half the last x label hangs past the plot's right edge; leave it room. */\nexport function rightMargin(input: AxisMeasureInput): number {\n\tconst { scale, options, theme, measureText } = input;\n\tif (hidden(options) || scale.kind === \"band\" || scale.kind === \"point\") {\n\t\treturn EDGE;\n\t}\n\tconst ticks = axisTicks(scale, options);\n\tconst last = ticks[ticks.length - 1];\n\tif (last === undefined) {\n\t\treturn EDGE;\n\t}\n\tconst half = measureText(tickLabel(scale, options, last), theme.fontSize) / 2;\n\tconst overhang = half - (scale.range[1] - scale.map(last));\n\treturn Math.max(EDGE, overhang + 2);\n}\n\nexport interface AxisNodes {\n\treadonly grid: ReadonlyArray<SceneNode>;\n\treadonly labels: ReadonlyArray<SceneNode>;\n}\n\n/**\n * The first tick of each run that reads the same (\"9 oct.\" on four ticks six\n * hours apart says the day once), then every `step`-th of those, so neighbours\n * never overlap. The step is the same across the axis: thinning by skipping\n * single labels reads as noise.\n */\nfunction thinned(\n\ttexts: ReadonlyArray<string>,\n\twidths: ReadonlyArray<number>,\n\tpositions: ReadonlyArray<number>,\n): ReadonlyArray<number> {\n\tconst distinct = texts\n\t\t.map((_text, index) => index)\n\t\t.filter((index) => index === 0 || texts[index] !== texts[index - 1]);\n\tif (distinct.length < 2) {\n\t\treturn distinct;\n\t}\n\tconst spacing =\n\t\tMath.min(\n\t\t\t...distinct\n\t\t\t\t.slice(1)\n\t\t\t\t.map((index, at) =>\n\t\t\t\t\tMath.abs(positions[index] - positions[distinct[at]]),\n\t\t\t\t),\n\t\t) || 1;\n\tconst widest = Math.max(...distinct.map((index) => widths[index]));\n\tconst step = Math.max(1, Math.ceil((widest + LABEL_SPACING) / spacing));\n\treturn distinct.filter((_index, at) => at % step === 0);\n}\n\nexport function bottomAxis(\n\tinput: AxisMeasureInput,\n\tplot: ChartRect,\n\tgrid: boolean,\n): AxisNodes {\n\tconst { scale, options, title, theme, measureText } = input;\n\tconst ticks = axisTicks(scale, options);\n\tconst positions = ticks.map((tick) => scale.center(tick));\n\tconst gridNodes: SceneNode[] = grid\n\t\t? ticks.map((tick, index) => ({\n\t\t\t\tkind: \"line\",\n\t\t\t\tkey: `grid-x:${tickKey(tick)}`,\n\t\t\t\trole: \"grid\",\n\t\t\t\tx1: positions[index],\n\t\t\t\ty1: plot.y,\n\t\t\t\tx2: positions[index],\n\t\t\t\ty2: plot.y + plot.height,\n\t\t\t\tpaint: { stroke: theme.grid, strokeWidth: 1 },\n\t\t\t}))\n\t\t: [];\n\tif (hidden(options)) {\n\t\treturn { grid: gridNodes, labels: [] };\n\t}\n\tconst texts = ticks.map((tick) => tickLabel(scale, options, tick));\n\tconst widths = texts.map((text) => measureText(text, theme.fontSize));\n\tconst baseline = plot.y + plot.height + TICK_GAP;\n\tconst labels: SceneNode[] = thinned(texts, widths, positions).map(\n\t\t(index) => ({\n\t\t\tkind: \"text\",\n\t\t\tkey: `tick-x:${tickKey(ticks[index])}`,\n\t\t\trole: \"axis\",\n\t\t\tx: positions[index],\n\t\t\ty: baseline,\n\t\t\ttext: texts[index],\n\t\t\tpaint: {\n\t\t\t\tfill: theme.muted,\n\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\ttextAnchor: \"middle\",\n\t\t\t\tbaseline: \"top\",\n\t\t\t},\n\t\t}),\n\t);\n\tif (title) {\n\t\tlabels.push({\n\t\t\tkind: \"text\",\n\t\t\tkey: \"title-x\",\n\t\t\trole: \"axis-title\",\n\t\t\tx: plot.x + plot.width / 2,\n\t\t\ty: baseline + theme.fontSize + TITLE_GAP,\n\t\t\ttext: title,\n\t\t\tpaint: {\n\t\t\t\tfill: theme.muted,\n\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\tfontWeight: 500,\n\t\t\t\ttextAnchor: \"middle\",\n\t\t\t\tbaseline: \"top\",\n\t\t\t},\n\t\t});\n\t}\n\treturn { grid: gridNodes, labels };\n}\n\nexport function leftAxis(\n\tinput: AxisMeasureInput,\n\tplot: ChartRect,\n\tgrid: boolean,\n\tchartWidth: number,\n): AxisNodes {\n\tconst { scale, options, title, theme, measureText } = input;\n\tconst ticks = axisTicks(scale, options);\n\tconst positions = ticks.map((tick) => scale.center(tick));\n\tconst gridNodes: SceneNode[] = grid\n\t\t? ticks.map((tick, index) => ({\n\t\t\t\tkind: \"line\",\n\t\t\t\tkey: `grid-y:${tickKey(tick)}`,\n\t\t\t\trole: \"grid\",\n\t\t\t\tx1: plot.x,\n\t\t\t\ty1: positions[index],\n\t\t\t\tx2: plot.x + plot.width,\n\t\t\t\ty2: positions[index],\n\t\t\t\tpaint: { stroke: theme.grid, strokeWidth: 1 },\n\t\t\t}))\n\t\t: [];\n\tif (hidden(options)) {\n\t\treturn { grid: gridNodes, labels: [] };\n\t}\n\tconst discrete = scale.kind === \"band\" || scale.kind === \"point\";\n\tconst maxWidth = discrete\n\t\t? chartWidth * MAX_CATEGORY_SHARE\n\t\t: Number.POSITIVE_INFINITY;\n\tconst texts = ticks.map((tick) =>\n\t\ttruncate(\n\t\t\ttickLabel(scale, options, tick),\n\t\t\tmaxWidth,\n\t\t\ttheme.fontSize,\n\t\t\tmeasureText,\n\t\t),\n\t);\n\tconst heights = texts.map(() => theme.fontSize);\n\tconst labels: SceneNode[] = thinned(texts, heights, positions).map(\n\t\t(index) => ({\n\t\t\tkind: \"text\",\n\t\t\tkey: `tick-y:${tickKey(ticks[index])}`,\n\t\t\trole: \"axis\",\n\t\t\tx: plot.x - TICK_GAP,\n\t\t\ty: positions[index],\n\t\t\ttext: texts[index],\n\t\t\tpaint: {\n\t\t\t\tfill: theme.muted,\n\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\ttextAnchor: \"end\",\n\t\t\t\tbaseline: \"middle\",\n\t\t\t},\n\t\t}),\n\t);\n\tif (title) {\n\t\tlabels.push({\n\t\t\tkind: \"text\",\n\t\t\tkey: \"title-y\",\n\t\t\trole: \"axis-title\",\n\t\t\tx: EDGE / 2 + theme.fontSize / 2,\n\t\t\ty: plot.y + plot.height / 2,\n\t\t\ttext: title,\n\t\t\trotate: -90,\n\t\t\tpaint: {\n\t\t\t\tfill: theme.muted,\n\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\tfontWeight: 500,\n\t\t\t\ttextAnchor: \"middle\",\n\t\t\t\tbaseline: \"middle\",\n\t\t\t},\n\t\t});\n\t}\n\treturn { grid: gridNodes, labels };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voila.dev/chart",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.127",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Charts as data: a definition of marks, channels and scales, drawn to SVG or Canvas, keyboard and screen-reader accessible. The core has no dependencies; the heavy layouts (geo, hierarchy, sankey…) sit behind their own subpaths.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"sideEffects": false,
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@voila.dev/motion": "6.0.
|
|
67
|
+
"@voila.dev/motion": "6.0.127",
|
|
68
68
|
"d3-contour": "4.0.2",
|
|
69
69
|
"d3-delaunay": "6.0.4",
|
|
70
70
|
"d3-force": "3.0.0",
|
package/src/core/guides/axes.ts
CHANGED
|
@@ -49,6 +49,11 @@ export function tickLabel(
|
|
|
49
49
|
return options?.tickFormat?.(value) ?? scale.format(value);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Two ticks can share a label ("11:00" on two days), never a value. */
|
|
53
|
+
function tickKey(value: ChartValue): string {
|
|
54
|
+
return value instanceof Date ? String(value.getTime()) : String(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
/** Cuts a label down to `maxWidth`, ending on an ellipsis. */
|
|
53
58
|
export function truncate(
|
|
54
59
|
text: string,
|
|
@@ -127,23 +132,33 @@ export interface AxisNodes {
|
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
135
|
+
* The first tick of each run that reads the same ("9 oct." on four ticks six
|
|
136
|
+
* hours apart says the day once), then every `step`-th of those, so neighbours
|
|
137
|
+
* never overlap. The step is the same across the axis: thinning by skipping
|
|
138
|
+
* single labels reads as noise.
|
|
132
139
|
*/
|
|
133
140
|
function thinned(
|
|
134
|
-
|
|
141
|
+
texts: ReadonlyArray<string>,
|
|
135
142
|
widths: ReadonlyArray<number>,
|
|
136
143
|
positions: ReadonlyArray<number>,
|
|
137
144
|
): ReadonlyArray<number> {
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
const distinct = texts
|
|
146
|
+
.map((_text, index) => index)
|
|
147
|
+
.filter((index) => index === 0 || texts[index] !== texts[index - 1]);
|
|
148
|
+
if (distinct.length < 2) {
|
|
149
|
+
return distinct;
|
|
140
150
|
}
|
|
141
|
-
const spacing =
|
|
142
|
-
|
|
151
|
+
const spacing =
|
|
152
|
+
Math.min(
|
|
153
|
+
...distinct
|
|
154
|
+
.slice(1)
|
|
155
|
+
.map((index, at) =>
|
|
156
|
+
Math.abs(positions[index] - positions[distinct[at]]),
|
|
157
|
+
),
|
|
158
|
+
) || 1;
|
|
159
|
+
const widest = Math.max(...distinct.map((index) => widths[index]));
|
|
143
160
|
const step = Math.max(1, Math.ceil((widest + LABEL_SPACING) / spacing));
|
|
144
|
-
return
|
|
145
|
-
.map((_tick, index) => index)
|
|
146
|
-
.filter((index) => index % step === 0);
|
|
161
|
+
return distinct.filter((_index, at) => at % step === 0);
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
export function bottomAxis(
|
|
@@ -157,7 +172,7 @@ export function bottomAxis(
|
|
|
157
172
|
const gridNodes: SceneNode[] = grid
|
|
158
173
|
? ticks.map((tick, index) => ({
|
|
159
174
|
kind: "line",
|
|
160
|
-
key: `grid-x:${
|
|
175
|
+
key: `grid-x:${tickKey(tick)}`,
|
|
161
176
|
role: "grid",
|
|
162
177
|
x1: positions[index],
|
|
163
178
|
y1: plot.y,
|
|
@@ -172,10 +187,10 @@ export function bottomAxis(
|
|
|
172
187
|
const texts = ticks.map((tick) => tickLabel(scale, options, tick));
|
|
173
188
|
const widths = texts.map((text) => measureText(text, theme.fontSize));
|
|
174
189
|
const baseline = plot.y + plot.height + TICK_GAP;
|
|
175
|
-
const labels: SceneNode[] = thinned(
|
|
190
|
+
const labels: SceneNode[] = thinned(texts, widths, positions).map(
|
|
176
191
|
(index) => ({
|
|
177
192
|
kind: "text",
|
|
178
|
-
key: `tick-x:${
|
|
193
|
+
key: `tick-x:${tickKey(ticks[index])}`,
|
|
179
194
|
role: "axis",
|
|
180
195
|
x: positions[index],
|
|
181
196
|
y: baseline,
|
|
@@ -220,7 +235,7 @@ export function leftAxis(
|
|
|
220
235
|
const gridNodes: SceneNode[] = grid
|
|
221
236
|
? ticks.map((tick, index) => ({
|
|
222
237
|
kind: "line",
|
|
223
|
-
key: `grid-y:${
|
|
238
|
+
key: `grid-y:${tickKey(tick)}`,
|
|
224
239
|
role: "grid",
|
|
225
240
|
x1: plot.x,
|
|
226
241
|
y1: positions[index],
|
|
@@ -245,10 +260,10 @@ export function leftAxis(
|
|
|
245
260
|
),
|
|
246
261
|
);
|
|
247
262
|
const heights = texts.map(() => theme.fontSize);
|
|
248
|
-
const labels: SceneNode[] = thinned(
|
|
263
|
+
const labels: SceneNode[] = thinned(texts, heights, positions).map(
|
|
249
264
|
(index) => ({
|
|
250
265
|
kind: "text",
|
|
251
|
-
key: `tick-y:${
|
|
266
|
+
key: `tick-y:${tickKey(ticks[index])}`,
|
|
252
267
|
role: "axis",
|
|
253
268
|
x: plot.x - TICK_GAP,
|
|
254
269
|
y: positions[index],
|