@voila.dev/chart 4.0.108 → 5.0.110
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/dist/core/marks/rule.d.ts +5 -0
- package/dist/core/marks/rule.d.ts.map +1 -1
- package/dist/core/marks/rule.js +15 -1
- package/dist/core/marks/rule.js.map +2 -2
- package/dist/core/scales/resolve.d.ts.map +1 -1
- package/dist/core/scales/resolve.js +6 -1
- package/dist/core/scales/resolve.js.map +2 -2
- package/package.json +1 -1
- package/src/core/marks/rule.ts +26 -1
- package/src/core/scales/resolve.ts +11 -1
|
@@ -9,6 +9,11 @@ export interface RuleOptions<TDatum> {
|
|
|
9
9
|
readonly opacity?: number;
|
|
10
10
|
/** Text written at the end of the rule: "Objectif", "Moyenne". */
|
|
11
11
|
readonly label?: string;
|
|
12
|
+
/**
|
|
13
|
+
* On a band or point axis, `before` draws the rule between the value's
|
|
14
|
+
* column and the one before it: where a period starts, not its middle.
|
|
15
|
+
*/
|
|
16
|
+
readonly position?: "center" | "before";
|
|
12
17
|
}
|
|
13
18
|
/** Horizontal rules at y values: a baseline, a target, an average. */
|
|
14
19
|
export declare function ruleY<TDatum>(data: ReadonlyArray<TDatum>, options?: RuleOptions<TDatum>): ChartMark;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../../src/core/marks/rule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../../src/core/marks/rule.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EAET,UAAU,EAEV,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,WAAW,CAAC,MAAM;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CACxC;AAwFD,sEAAsE;AACtE,wBAAgB,KAAK,CAAC,MAAM,EAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,GAAE,WAAW,CAAC,MAAM,CAAM,GAC/B,SAAS,CAEX;AAED,uDAAuD;AACvD,wBAAgB,KAAK,CAAC,MAAM,EAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,GAAE,WAAW,CAAC,MAAM,CAAM,GAC/B,SAAS,CAEX"}
|
package/dist/core/marks/rule.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { isPlaceable, readChannel } from "../channel.js";
|
|
2
2
|
import { markId, paint, valueOn } from "./shared.js";
|
|
3
|
+
import { categoryKey } from "../scales/discrete.js";
|
|
3
4
|
const LABEL_GAP = 4;
|
|
5
|
+
function before(scale, value) {
|
|
6
|
+
if (scale.kind !== "band" && scale.kind !== "point") {
|
|
7
|
+
return scale.center(value);
|
|
8
|
+
}
|
|
9
|
+
const index = scale.domain.findIndex(
|
|
10
|
+
(candidate) => categoryKey(candidate) === categoryKey(value)
|
|
11
|
+
);
|
|
12
|
+
const previous = scale.domain[index - 1];
|
|
13
|
+
if (index <= 0 || previous === void 0) {
|
|
14
|
+
return scale.map(value);
|
|
15
|
+
}
|
|
16
|
+
return (scale.map(previous) + scale.bandwidth + scale.map(value)) / 2;
|
|
17
|
+
}
|
|
4
18
|
function ruleMark(kind, data, options) {
|
|
5
19
|
const horizontal = kind === "ruleY";
|
|
6
20
|
const values = readChannel(
|
|
@@ -26,7 +40,7 @@ function ruleMark(kind, data, options) {
|
|
|
26
40
|
if (value === void 0) {
|
|
27
41
|
continue;
|
|
28
42
|
}
|
|
29
|
-
const at = scale.center(value);
|
|
43
|
+
const at = options.position === "before" ? before(scale, value) : scale.center(value);
|
|
30
44
|
nodes.push({
|
|
31
45
|
kind: "line",
|
|
32
46
|
key: `${id}:${index}`,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/marks/rule.ts"],
|
|
4
|
-
"sourcesContent": ["import { isPlaceable, readChannel } from \"#/core/channel.ts\";\nimport { markId, paint, valueOn } from \"#/core/marks/shared.ts\";\nimport type {\n\tChartAccessor,\n\tChartMark,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nexport interface RuleOptions<TDatum> {\n\treadonly id?: string;\n\t/** The value of each rule. Defaults to the datum itself, so `ruleY([0])` works. */\n\treadonly value?: ChartAccessor<TDatum, ChartValue>;\n\treadonly stroke?: string;\n\treadonly strokeWidth?: number;\n\treadonly strokeDasharray?: string;\n\treadonly opacity?: number;\n\t/** Text written at the end of the rule: \"Objectif\", \"Moyenne\". */\n\treadonly label?: string;\n}\n\nconst LABEL_GAP = 4;\n\nfunction ruleMark<TDatum>(\n\tkind: \"ruleY\" | \"ruleX\",\n\tdata: ReadonlyArray<TDatum>,\n\toptions: RuleOptions<TDatum>,\n): ChartMark {\n\tconst horizontal = kind === \"ruleY\";\n\tconst values = readChannel(\n\t\tdata,\n\t\toptions.value ?? ((datum: TDatum) => datum as unknown as ChartValue),\n\t);\n\tconst channel = { values: values.filter(isPlaceable) };\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"cartesian\",\n\t\tchannels: horizontal ? { y: channel } : { x: channel },\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, kind, context);\n\t\t\tconst scale = horizontal ? context.scales.y : context.scales.x;\n\t\t\tif (scale === undefined) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst { plot, theme } = context;\n\t\t\tconst stroke = options.stroke ?? theme.foreground;\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tfor (const [index, raw] of values.entries()) {\n\t\t\t\tconst value = valueOn(scale, raw);\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst at
|
|
5
|
-
"mappings": "AAAA,SAAS,aAAa,mBAAmB;AACzC,SAAS,QAAQ,OAAO,eAAe;
|
|
4
|
+
"sourcesContent": ["import { isPlaceable, readChannel } from \"#/core/channel.ts\";\nimport { markId, paint, valueOn } from \"#/core/marks/shared.ts\";\nimport { categoryKey } from \"#/core/scales/discrete.ts\";\nimport type {\n\tChartAccessor,\n\tChartMark,\n\tChartPositionScale,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nexport interface RuleOptions<TDatum> {\n\treadonly id?: string;\n\t/** The value of each rule. Defaults to the datum itself, so `ruleY([0])` works. */\n\treadonly value?: ChartAccessor<TDatum, ChartValue>;\n\treadonly stroke?: string;\n\treadonly strokeWidth?: number;\n\treadonly strokeDasharray?: string;\n\treadonly opacity?: number;\n\t/** Text written at the end of the rule: \"Objectif\", \"Moyenne\". */\n\treadonly label?: string;\n\t/**\n\t * On a band or point axis, `before` draws the rule between the value's\n\t * column and the one before it: where a period starts, not its middle.\n\t */\n\treadonly position?: \"center\" | \"before\";\n}\n\nconst LABEL_GAP = 4;\n\n/** Halfway between a category's slot and the previous one's, or its own start for the first. */\nfunction before(scale: ChartPositionScale, value: ChartValue): number {\n\tif (scale.kind !== \"band\" && scale.kind !== \"point\") {\n\t\treturn scale.center(value);\n\t}\n\tconst index = scale.domain.findIndex(\n\t\t(candidate) => categoryKey(candidate) === categoryKey(value),\n\t);\n\tconst previous = scale.domain[index - 1];\n\tif (index <= 0 || previous === undefined) {\n\t\treturn scale.map(value);\n\t}\n\treturn (scale.map(previous) + scale.bandwidth + scale.map(value)) / 2;\n}\n\nfunction ruleMark<TDatum>(\n\tkind: \"ruleY\" | \"ruleX\",\n\tdata: ReadonlyArray<TDatum>,\n\toptions: RuleOptions<TDatum>,\n): ChartMark {\n\tconst horizontal = kind === \"ruleY\";\n\tconst values = readChannel(\n\t\tdata,\n\t\toptions.value ?? ((datum: TDatum) => datum as unknown as ChartValue),\n\t);\n\tconst channel = { values: values.filter(isPlaceable) };\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"cartesian\",\n\t\tchannels: horizontal ? { y: channel } : { x: channel },\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, kind, context);\n\t\t\tconst scale = horizontal ? context.scales.y : context.scales.x;\n\t\t\tif (scale === undefined) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst { plot, theme } = context;\n\t\t\tconst stroke = options.stroke ?? theme.foreground;\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tfor (const [index, raw] of values.entries()) {\n\t\t\t\tconst value = valueOn(scale, raw);\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst at =\n\t\t\t\t\toptions.position === \"before\"\n\t\t\t\t\t\t? before(scale, value)\n\t\t\t\t\t\t: scale.center(value);\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"line\",\n\t\t\t\t\tkey: `${id}:${index}`,\n\t\t\t\t\trole: \"rule\",\n\t\t\t\t\t...(horizontal\n\t\t\t\t\t\t? { x1: plot.x, y1: at, x2: plot.x + plot.width, y2: at }\n\t\t\t\t\t\t: { x1: at, y1: plot.y, x2: at, y2: plot.y + plot.height }),\n\t\t\t\t\tpaint: paint({\n\t\t\t\t\t\tstroke,\n\t\t\t\t\t\tstrokeWidth: options.strokeWidth ?? 1,\n\t\t\t\t\t\tstrokeDasharray: options.strokeDasharray,\n\t\t\t\t\t\topacity: options.opacity,\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t\tif (options.label !== undefined) {\n\t\t\t\t\tnodes.push({\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tkey: `${id}:label:${index}`,\n\t\t\t\t\t\trole: \"label\",\n\t\t\t\t\t\tx: horizontal ? plot.x + plot.width : at + LABEL_GAP,\n\t\t\t\t\t\ty: horizontal ? at - LABEL_GAP : plot.y,\n\t\t\t\t\t\ttext: options.label,\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: stroke,\n\t\t\t\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\t\t\t\ttextAnchor: horizontal ? \"end\" : \"start\",\n\t\t\t\t\t\t\tbaseline: horizontal ? \"bottom\" : \"top\",\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes };\n\t\t},\n\t};\n}\n\n/** Horizontal rules at y values: a baseline, a target, an average. */\nexport function ruleY<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: RuleOptions<TDatum> = {},\n): ChartMark {\n\treturn ruleMark(\"ruleY\", data, options);\n}\n\n/** Vertical rules at x values: a date, a threshold. */\nexport function ruleX<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: RuleOptions<TDatum> = {},\n): ChartMark {\n\treturn ruleMark(\"ruleX\", data, options);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/core/scales/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAKxE,OAAO,KAAK,EACX,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,UAAU,EACV,MAAM,iBAAiB,CAAC;AAEzB;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AA6DD,wBAAgB,SAAS,CACxB,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EACrC,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAC9C,IAAI,EAAE,GAAG,GAAG,GAAG,GACb,SAAS,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/core/scales/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAKxE,OAAO,KAAK,EACX,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,UAAU,EACV,MAAM,iBAAiB,CAAC;AAEzB;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AA6DD,wBAAgB,SAAS,CACxB,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EACrC,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAC9C,IAAI,EAAE,GAAG,GAAG,GAAG,GACb,SAAS,GAAG,SAAS,CA8CvB;AAED,wBAAgB,UAAU,CACzB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAC9C,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GACf,kBAAkB,CAwBpB"}
|
|
@@ -72,7 +72,12 @@ function planScale(channels, options, axis) {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
if (options?.domain !== void 0) {
|
|
75
|
-
|
|
75
|
+
const [low, high] = options.domain.map(toNumber);
|
|
76
|
+
return {
|
|
77
|
+
kind,
|
|
78
|
+
domain: options.nice === true && kind === "linear" ? niceDomain(low, high) : [low, high],
|
|
79
|
+
label
|
|
80
|
+
};
|
|
76
81
|
}
|
|
77
82
|
const zero = options?.zero ?? channels.some((channel) => channel.includeZero === true);
|
|
78
83
|
const nice = options?.nice ?? (axis === "y" || zero);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/scales/resolve.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ChartPositionScaleOptions } from \"#/core/define-chart.ts\";\nimport { linearScale, logScale, toNumber } from \"#/core/scales/continuous.ts\";\nimport { bandScale, categoryKey, pointScale } from \"#/core/scales/discrete.ts\";\nimport { timeScale } from \"#/core/scales/time.ts\";\nimport { niceDomain } from \"#/core/ticks.ts\";\nimport type {\n\tChartChannel,\n\tChartPositionScale,\n\tChartPositionScaleKind,\n\tChartValue,\n} from \"#/core/types.ts\";\n\n/**\n * What a scale will be once the plot size is known: its kind and its domain,\n * read off every channel the marks put on it. Split from the range so the\n * axes can measure their tick labels before the plot rectangle exists.\n */\nexport interface ScalePlan {\n\treadonly kind: ChartPositionScaleKind;\n\treadonly domain: ReadonlyArray<ChartValue>;\n\treadonly label: string | undefined;\n\t/** The band padding a mark asked for, when the options name none. */\n\treadonly padding?: number;\n\treadonly paddingOuter?: number;\n}\n\nfunction inferKind(\n\tchannels: ReadonlyArray<ChartChannel>,\n\tvalues: ReadonlyArray<ChartValue>,\n): ChartPositionScaleKind {\n\tif (channels.some((channel) => channel.discrete === \"band\")) {\n\t\treturn \"band\";\n\t}\n\tconst askedPoint = channels.some((channel) => channel.discrete === \"point\");\n\tif (values.length > 0 && values.every((value) => value instanceof Date)) {\n\t\treturn askedPoint ? \"point\" : \"time\";\n\t}\n\tif (values.length > 0 && values.every((value) => typeof value === \"number\")) {\n\t\treturn askedPoint ? \"point\" : \"linear\";\n\t}\n\treturn \"point\";\n}\n\nfunction distinct(\n\tvalues: ReadonlyArray<ChartValue>,\n): ReadonlyArray<ChartValue> {\n\tconst seen = new Set<string>();\n\tconst kept: ChartValue[] = [];\n\tfor (const value of values) {\n\t\tconst key = categoryKey(value);\n\t\tif (!seen.has(key)) {\n\t\t\tseen.add(key);\n\t\t\tkept.push(value);\n\t\t}\n\t}\n\treturn kept;\n}\n\nfunction continuousDomain(\n\tvalues: ReadonlyArray<ChartValue>,\n\toptions: { readonly zero: boolean; readonly nice: boolean },\n\tkind: ChartPositionScaleKind,\n): readonly [number, number] {\n\tlet low = Number.POSITIVE_INFINITY;\n\tlet high = Number.NEGATIVE_INFINITY;\n\tfor (const value of values) {\n\t\tconst number = toNumber(value);\n\t\tif (Number.isFinite(number)) {\n\t\t\tlow = Math.min(low, number);\n\t\t\thigh = Math.max(high, number);\n\t\t}\n\t}\n\tif (!Number.isFinite(low)) {\n\t\treturn [0, 1];\n\t}\n\tif (options.zero && kind === \"linear\") {\n\t\tlow = Math.min(low, 0);\n\t\thigh = Math.max(high, 0);\n\t}\n\tif (options.nice && kind === \"linear\") {\n\t\treturn niceDomain(low, high);\n\t}\n\treturn [low, high];\n}\n\nexport function planScale(\n\tchannels: ReadonlyArray<ChartChannel>,\n\toptions: ChartPositionScaleOptions | undefined,\n\taxis: \"x\" | \"y\",\n): ScalePlan | undefined {\n\tif (channels.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst values = channels.flatMap((channel) => channel.values);\n\tconst kind = options?.type ?? inferKind(channels, values);\n\tconst label =\n\t\toptions?.label ?? channels.find((channel) => channel.label)?.label;\n\tif (kind === \"band\" || kind === \"point\") {\n\t\tconst padding = channels.find(\n\t\t\t(channel) => channel.padding !== undefined,\n\t\t)?.padding;\n\t\tconst paddingOuter = channels.find(\n\t\t\t(channel) => channel.paddingOuter !== undefined,\n\t\t)?.paddingOuter;\n\t\treturn {\n\t\t\tkind,\n\t\t\tdomain: options?.domain ?? distinct(values),\n\t\t\tlabel,\n\t\t\tpadding,\n\t\t\tpaddingOuter: paddingOuter ?? padding,\n\t\t};\n\t}\n\tif (options?.domain !== undefined) {\n\t\
|
|
5
|
-
"mappings": "AACA,SAAS,aAAa,UAAU,gBAAgB;AAChD,SAAS,WAAW,aAAa,kBAAkB;AACnD,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAsB3B,SAAS,UACR,UACA,QACyB;AACzB,MAAI,SAAS,KAAK,CAAC,YAAY,QAAQ,aAAa,MAAM,GAAG;AAC5D,WAAO;AAAA,EACR;AACA,QAAM,aAAa,SAAS,KAAK,CAAC,YAAY,QAAQ,aAAa,OAAO;AAC1E,MAAI,OAAO,SAAS,KAAK,OAAO,MAAM,CAAC,UAAU,iBAAiB,IAAI,GAAG;AACxE,WAAO,aAAa,UAAU;AAAA,EAC/B;AACA,MAAI,OAAO,SAAS,KAAK,OAAO,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ,GAAG;AAC5E,WAAO,aAAa,UAAU;AAAA,EAC/B;AACA,SAAO;AACR;AAEA,SAAS,SACR,QAC4B;AAC5B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAqB,CAAC;AAC5B,aAAW,SAAS,QAAQ;AAC3B,UAAM,MAAM,YAAY,KAAK;AAC7B,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AACnB,WAAK,IAAI,GAAG;AACZ,WAAK,KAAK,KAAK;AAAA,IAChB;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,iBACR,QACA,SACA,MAC4B;AAC5B,MAAI,MAAM,OAAO;AACjB,MAAI,OAAO,OAAO;AAClB,aAAW,SAAS,QAAQ;AAC3B,UAAM,SAAS,SAAS,KAAK;AAC7B,QAAI,OAAO,SAAS,MAAM,GAAG;AAC5B,YAAM,KAAK,IAAI,KAAK,MAAM;AAC1B,aAAO,KAAK,IAAI,MAAM,MAAM;AAAA,IAC7B;AAAA,EACD;AACA,MAAI,CAAC,OAAO,SAAS,GAAG,GAAG;AAC1B,WAAO,CAAC,GAAG,CAAC;AAAA,EACb;AACA,MAAI,QAAQ,QAAQ,SAAS,UAAU;AACtC,UAAM,KAAK,IAAI,KAAK,CAAC;AACrB,WAAO,KAAK,IAAI,MAAM,CAAC;AAAA,EACxB;AACA,MAAI,QAAQ,QAAQ,SAAS,UAAU;AACtC,WAAO,WAAW,KAAK,IAAI;AAAA,EAC5B;AACA,SAAO,CAAC,KAAK,IAAI;AAClB;AAEO,SAAS,UACf,UACA,SACA,MACwB;AACxB,MAAI,SAAS,WAAW,GAAG;AAC1B,WAAO;AAAA,EACR;AACA,QAAM,SAAS,SAAS,QAAQ,CAAC,YAAY,QAAQ,MAAM;AAC3D,QAAM,OAAO,SAAS,QAAQ,UAAU,UAAU,MAAM;AACxD,QAAM,QACL,SAAS,SAAS,SAAS,KAAK,CAAC,YAAY,QAAQ,KAAK,GAAG;AAC9D,MAAI,SAAS,UAAU,SAAS,SAAS;AACxC,UAAM,UAAU,SAAS;AAAA,MACxB,CAAC,YAAY,QAAQ,YAAY;AAAA,IAClC,GAAG;AACH,UAAM,eAAe,SAAS;AAAA,MAC7B,CAAC,YAAY,QAAQ,iBAAiB;AAAA,IACvC,GAAG;AACH,WAAO;AAAA,MACN;AAAA,MACA,QAAQ,SAAS,UAAU,SAAS,MAAM;AAAA,MAC1C;AAAA,MACA;AAAA,MACA,cAAc,gBAAgB;AAAA,IAC/B;AAAA,EACD;AACA,MAAI,SAAS,WAAW,QAAW;AAClC,
|
|
4
|
+
"sourcesContent": ["import type { ChartPositionScaleOptions } from \"#/core/define-chart.ts\";\nimport { linearScale, logScale, toNumber } from \"#/core/scales/continuous.ts\";\nimport { bandScale, categoryKey, pointScale } from \"#/core/scales/discrete.ts\";\nimport { timeScale } from \"#/core/scales/time.ts\";\nimport { niceDomain } from \"#/core/ticks.ts\";\nimport type {\n\tChartChannel,\n\tChartPositionScale,\n\tChartPositionScaleKind,\n\tChartValue,\n} from \"#/core/types.ts\";\n\n/**\n * What a scale will be once the plot size is known: its kind and its domain,\n * read off every channel the marks put on it. Split from the range so the\n * axes can measure their tick labels before the plot rectangle exists.\n */\nexport interface ScalePlan {\n\treadonly kind: ChartPositionScaleKind;\n\treadonly domain: ReadonlyArray<ChartValue>;\n\treadonly label: string | undefined;\n\t/** The band padding a mark asked for, when the options name none. */\n\treadonly padding?: number;\n\treadonly paddingOuter?: number;\n}\n\nfunction inferKind(\n\tchannels: ReadonlyArray<ChartChannel>,\n\tvalues: ReadonlyArray<ChartValue>,\n): ChartPositionScaleKind {\n\tif (channels.some((channel) => channel.discrete === \"band\")) {\n\t\treturn \"band\";\n\t}\n\tconst askedPoint = channels.some((channel) => channel.discrete === \"point\");\n\tif (values.length > 0 && values.every((value) => value instanceof Date)) {\n\t\treturn askedPoint ? \"point\" : \"time\";\n\t}\n\tif (values.length > 0 && values.every((value) => typeof value === \"number\")) {\n\t\treturn askedPoint ? \"point\" : \"linear\";\n\t}\n\treturn \"point\";\n}\n\nfunction distinct(\n\tvalues: ReadonlyArray<ChartValue>,\n): ReadonlyArray<ChartValue> {\n\tconst seen = new Set<string>();\n\tconst kept: ChartValue[] = [];\n\tfor (const value of values) {\n\t\tconst key = categoryKey(value);\n\t\tif (!seen.has(key)) {\n\t\t\tseen.add(key);\n\t\t\tkept.push(value);\n\t\t}\n\t}\n\treturn kept;\n}\n\nfunction continuousDomain(\n\tvalues: ReadonlyArray<ChartValue>,\n\toptions: { readonly zero: boolean; readonly nice: boolean },\n\tkind: ChartPositionScaleKind,\n): readonly [number, number] {\n\tlet low = Number.POSITIVE_INFINITY;\n\tlet high = Number.NEGATIVE_INFINITY;\n\tfor (const value of values) {\n\t\tconst number = toNumber(value);\n\t\tif (Number.isFinite(number)) {\n\t\t\tlow = Math.min(low, number);\n\t\t\thigh = Math.max(high, number);\n\t\t}\n\t}\n\tif (!Number.isFinite(low)) {\n\t\treturn [0, 1];\n\t}\n\tif (options.zero && kind === \"linear\") {\n\t\tlow = Math.min(low, 0);\n\t\thigh = Math.max(high, 0);\n\t}\n\tif (options.nice && kind === \"linear\") {\n\t\treturn niceDomain(low, high);\n\t}\n\treturn [low, high];\n}\n\nexport function planScale(\n\tchannels: ReadonlyArray<ChartChannel>,\n\toptions: ChartPositionScaleOptions | undefined,\n\taxis: \"x\" | \"y\",\n): ScalePlan | undefined {\n\tif (channels.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst values = channels.flatMap((channel) => channel.values);\n\tconst kind = options?.type ?? inferKind(channels, values);\n\tconst label =\n\t\toptions?.label ?? channels.find((channel) => channel.label)?.label;\n\tif (kind === \"band\" || kind === \"point\") {\n\t\tconst padding = channels.find(\n\t\t\t(channel) => channel.padding !== undefined,\n\t\t)?.padding;\n\t\tconst paddingOuter = channels.find(\n\t\t\t(channel) => channel.paddingOuter !== undefined,\n\t\t)?.paddingOuter;\n\t\treturn {\n\t\t\tkind,\n\t\t\tdomain: options?.domain ?? distinct(values),\n\t\t\tlabel,\n\t\t\tpadding,\n\t\t\tpaddingOuter: paddingOuter ?? padding,\n\t\t};\n\t}\n\tif (options?.domain !== undefined) {\n\t\tconst [low, high] = options.domain.map(toNumber);\n\t\t// An explicit domain still widens to round ticks when asked to: the\n\t\t// data's own maximum as the top of the axis would end it mid-air.\n\t\treturn {\n\t\t\tkind,\n\t\t\tdomain:\n\t\t\t\toptions.nice === true && kind === \"linear\"\n\t\t\t\t\t? niceDomain(low, high)\n\t\t\t\t\t: [low, high],\n\t\t\tlabel,\n\t\t};\n\t}\n\tconst zero =\n\t\toptions?.zero ?? channels.some((channel) => channel.includeZero === true);\n\t// A value axis (y, or the x of horizontal bars) reads best on round\n\t// numbers; a position axis (the x of a scatter) on the data's own extent.\n\tconst nice = options?.nice ?? (axis === \"y\" || zero);\n\treturn {\n\t\tkind,\n\t\tdomain: continuousDomain(values, { zero, nice }, kind),\n\t\tlabel,\n\t};\n}\n\nexport function buildScale(\n\tplan: ScalePlan,\n\toptions: ChartPositionScaleOptions | undefined,\n\trange: readonly [number, number],\n\tlocale: string,\n\ttickCount: number,\n): ChartPositionScale {\n\tconst ordered: readonly [number, number] = options?.reverse\n\t\t? [range[1], range[0]]\n\t\t: range;\n\tif (plan.kind === \"band\" || plan.kind === \"point\") {\n\t\tconst discrete = {\n\t\t\tdomain: plan.domain,\n\t\t\trange: ordered,\n\t\t\tlocale,\n\t\t\tpaddingInner: options?.paddingInner ?? plan.padding,\n\t\t\tpaddingOuter: options?.paddingOuter ?? plan.paddingOuter,\n\t\t};\n\t\treturn plan.kind === \"band\" ? bandScale(discrete) : pointScale(discrete);\n\t}\n\tconst continuous = {\n\t\tdomain: [toNumber(plan.domain[0]), toNumber(plan.domain[1])] as const,\n\t\trange: ordered,\n\t\tlocale,\n\t\ttickCount,\n\t};\n\tif (plan.kind === \"time\") {\n\t\treturn timeScale(continuous);\n\t}\n\treturn plan.kind === \"log\" ? logScale(continuous) : linearScale(continuous);\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": "
|
|
3
|
+
"version": "5.0.110",
|
|
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",
|
package/src/core/marks/rule.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { isPlaceable, readChannel } from "#/core/channel.ts";
|
|
2
2
|
import { markId, paint, valueOn } from "#/core/marks/shared.ts";
|
|
3
|
+
import { categoryKey } from "#/core/scales/discrete.ts";
|
|
3
4
|
import type {
|
|
4
5
|
ChartAccessor,
|
|
5
6
|
ChartMark,
|
|
7
|
+
ChartPositionScale,
|
|
6
8
|
ChartValue,
|
|
7
9
|
SceneNode,
|
|
8
10
|
} from "#/core/types.ts";
|
|
@@ -17,10 +19,30 @@ export interface RuleOptions<TDatum> {
|
|
|
17
19
|
readonly opacity?: number;
|
|
18
20
|
/** Text written at the end of the rule: "Objectif", "Moyenne". */
|
|
19
21
|
readonly label?: string;
|
|
22
|
+
/**
|
|
23
|
+
* On a band or point axis, `before` draws the rule between the value's
|
|
24
|
+
* column and the one before it: where a period starts, not its middle.
|
|
25
|
+
*/
|
|
26
|
+
readonly position?: "center" | "before";
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
const LABEL_GAP = 4;
|
|
23
30
|
|
|
31
|
+
/** Halfway between a category's slot and the previous one's, or its own start for the first. */
|
|
32
|
+
function before(scale: ChartPositionScale, value: ChartValue): number {
|
|
33
|
+
if (scale.kind !== "band" && scale.kind !== "point") {
|
|
34
|
+
return scale.center(value);
|
|
35
|
+
}
|
|
36
|
+
const index = scale.domain.findIndex(
|
|
37
|
+
(candidate) => categoryKey(candidate) === categoryKey(value),
|
|
38
|
+
);
|
|
39
|
+
const previous = scale.domain[index - 1];
|
|
40
|
+
if (index <= 0 || previous === undefined) {
|
|
41
|
+
return scale.map(value);
|
|
42
|
+
}
|
|
43
|
+
return (scale.map(previous) + scale.bandwidth + scale.map(value)) / 2;
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
function ruleMark<TDatum>(
|
|
25
47
|
kind: "ruleY" | "ruleX",
|
|
26
48
|
data: ReadonlyArray<TDatum>,
|
|
@@ -50,7 +72,10 @@ function ruleMark<TDatum>(
|
|
|
50
72
|
if (value === undefined) {
|
|
51
73
|
continue;
|
|
52
74
|
}
|
|
53
|
-
const at =
|
|
75
|
+
const at =
|
|
76
|
+
options.position === "before"
|
|
77
|
+
? before(scale, value)
|
|
78
|
+
: scale.center(value);
|
|
54
79
|
nodes.push({
|
|
55
80
|
kind: "line",
|
|
56
81
|
key: `${id}:${index}`,
|
|
@@ -111,7 +111,17 @@ export function planScale(
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
if (options?.domain !== undefined) {
|
|
114
|
-
|
|
114
|
+
const [low, high] = options.domain.map(toNumber);
|
|
115
|
+
// An explicit domain still widens to round ticks when asked to: the
|
|
116
|
+
// data's own maximum as the top of the axis would end it mid-air.
|
|
117
|
+
return {
|
|
118
|
+
kind,
|
|
119
|
+
domain:
|
|
120
|
+
options.nice === true && kind === "linear"
|
|
121
|
+
? niceDomain(low, high)
|
|
122
|
+
: [low, high],
|
|
123
|
+
label,
|
|
124
|
+
};
|
|
115
125
|
}
|
|
116
126
|
const zero =
|
|
117
127
|
options?.zero ?? channels.some((channel) => channel.includeZero === true);
|