@santi020k/lumen-core 3.0.0 → 4.0.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/PHONE_FLAG_LICENSE.txt +21 -0
- package/README.md +379 -4
- package/THIRD_PARTY_NOTICES.md +16 -2
- package/dist/ai-surfaces.d.ts +25 -0
- package/dist/ai-surfaces.d.ts.map +1 -0
- package/dist/ai-surfaces.js +162 -0
- package/dist/amount-field.d.ts +22 -0
- package/dist/amount-field.d.ts.map +1 -0
- package/dist/amount-field.js +207 -0
- package/dist/attachments.d.ts +15 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +123 -0
- package/dist/bullet-chart.d.ts +33 -0
- package/dist/bullet-chart.d.ts.map +1 -0
- package/dist/bullet-chart.js +80 -0
- package/dist/chart-activation.d.ts +32 -0
- package/dist/chart-activation.d.ts.map +1 -0
- package/dist/chart-activation.js +97 -0
- package/dist/chart-interaction.d.ts +12 -0
- package/dist/chart-interaction.d.ts.map +1 -0
- package/dist/chart-interaction.js +274 -0
- package/dist/chart-models.d.ts +156 -0
- package/dist/chart-models.d.ts.map +1 -0
- package/dist/chart-models.js +348 -0
- package/dist/chart-motion.d.ts +6 -0
- package/dist/chart-motion.d.ts.map +1 -0
- package/dist/chart-motion.js +107 -0
- package/dist/charts.d.ts +68 -3
- package/dist/charts.d.ts.map +1 -1
- package/dist/charts.js +328 -29
- package/dist/code.js +1 -1
- package/dist/combobox.d.ts +7 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +253 -0
- package/dist/comparison-chart.d.ts +32 -0
- package/dist/comparison-chart.d.ts.map +1 -0
- package/dist/comparison-chart.js +64 -0
- package/dist/components.d.ts +210 -25
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +102 -26
- package/dist/dashboard.d.ts +16 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +21 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +21 -5
- package/dist/dates.d.ts +13 -0
- package/dist/dates.d.ts.map +1 -0
- package/dist/dates.js +37 -0
- package/dist/device-frame.d.ts +25 -0
- package/dist/device-frame.d.ts.map +1 -0
- package/dist/device-frame.js +50 -0
- package/dist/direction.d.ts +3 -0
- package/dist/direction.d.ts.map +1 -0
- package/dist/direction.js +10 -0
- package/dist/extended-charts.d.ts +95 -0
- package/dist/extended-charts.d.ts.map +1 -0
- package/dist/extended-charts.js +175 -0
- package/dist/figma.d.ts.map +1 -1
- package/dist/figma.js +10 -1
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +18 -4
- package/dist/icon-data.generated.d.ts +1 -1
- package/dist/icon-data.generated.d.ts.map +1 -1
- package/dist/icon-data.generated.js +1 -1
- package/dist/icons.js +3 -4
- package/dist/illustrations.generated.d.ts +1 -1
- package/dist/image-comparison.d.ts +14 -0
- package/dist/image-comparison.d.ts.map +1 -0
- package/dist/image-comparison.js +48 -0
- package/dist/index.d.ts +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -3
- package/dist/media-viewport.d.ts +24 -0
- package/dist/media-viewport.d.ts.map +1 -0
- package/dist/media-viewport.js +119 -0
- package/dist/media-workspace.d.ts +27 -0
- package/dist/media-workspace.d.ts.map +1 -0
- package/dist/media-workspace.js +83 -0
- package/dist/message-scroller.d.ts +14 -0
- package/dist/message-scroller.d.ts.map +1 -0
- package/dist/message-scroller.js +113 -0
- package/dist/motion-workflows.d.ts +24 -0
- package/dist/motion-workflows.d.ts.map +1 -0
- package/dist/motion-workflows.js +220 -0
- package/dist/motion.d.ts +12 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +96 -0
- package/dist/native-input.d.ts +25 -0
- package/dist/native-input.d.ts.map +1 -0
- package/dist/native-input.js +188 -0
- package/dist/phone-flags.generated.d.ts +2 -0
- package/dist/phone-flags.generated.d.ts.map +1 -0
- package/dist/phone-flags.generated.js +263 -0
- package/dist/phone.d.ts +4 -0
- package/dist/phone.d.ts.map +1 -1
- package/dist/phone.js +6 -1
- package/dist/rich-text.d.ts +7 -0
- package/dist/rich-text.d.ts.map +1 -1
- package/dist/rich-text.js +13 -0
- package/dist/rive.d.ts +37 -0
- package/dist/rive.d.ts.map +1 -0
- package/dist/rive.js +129 -0
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +63 -23
- package/dist/tab-indicator.d.ts +3 -0
- package/dist/tab-indicator.d.ts.map +1 -0
- package/dist/tab-indicator.js +57 -0
- package/dist/theme-audit.d.ts +16 -0
- package/dist/theme-audit.d.ts.map +1 -0
- package/dist/theme-audit.js +57 -0
- package/dist/theme-builder.d.ts +7 -1
- package/dist/theme-builder.d.ts.map +1 -1
- package/dist/theme-builder.js +45 -23
- package/dist/theme-presets.generated.d.ts +217 -0
- package/dist/theme-presets.generated.d.ts.map +1 -0
- package/dist/theme-presets.generated.js +231 -0
- package/dist/theme.d.ts +11 -3
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +56 -6
- package/dist/virtual-collection.d.ts +14 -0
- package/dist/virtual-collection.d.ts.map +1 -0
- package/dist/virtual-collection.js +132 -0
- package/dist/virtual-list.d.ts +7 -0
- package/dist/virtual-list.d.ts.map +1 -0
- package/dist/virtual-list.js +135 -0
- package/dist/virtual-window.d.ts +19 -0
- package/dist/virtual-window.d.ts.map +1 -0
- package/dist/virtual-window.js +84 -0
- package/dist/visual-effects.d.ts +6 -0
- package/dist/visual-effects.d.ts.map +1 -0
- package/dist/visual-effects.js +44 -0
- package/dist/world-map-data.generated.d.ts +9 -0
- package/dist/world-map-data.generated.d.ts.map +1 -0
- package/dist/world-map-data.generated.js +240 -0
- package/dist/world-map-navigation.d.ts +16 -0
- package/dist/world-map-navigation.d.ts.map +1 -0
- package/dist/world-map-navigation.js +53 -0
- package/dist/world-map-pan.d.ts +3 -0
- package/dist/world-map-pan.d.ts.map +1 -0
- package/dist/world-map-pan.js +66 -0
- package/dist/world-map-zoom.d.ts +13 -0
- package/dist/world-map-zoom.d.ts.map +1 -0
- package/dist/world-map-zoom.js +61 -0
- package/dist/world-map.d.ts +71 -0
- package/dist/world-map.d.ts.map +1 -0
- package/dist/world-map.js +143 -0
- package/package.json +51 -4
package/dist/charts.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { createLumenChartActivationController, createLumenChartDatumActivation, createLumenHeatmapDatumActivation, createLumenRangeDatumActivation, parseLumenChartDatumActivation } from './chart-activation.js';
|
|
1
2
|
export const lumenChartTones = [
|
|
2
3
|
'series-1',
|
|
3
4
|
'series-2',
|
|
@@ -25,11 +26,18 @@ const formatDefaultLumenChartSummary = (summary, formatValue) => {
|
|
|
25
26
|
`Values range from ${formatValue(summary.minimum ?? 0)} to ${formatValue(summary.maximum ?? 0)}.${missing}`;
|
|
26
27
|
};
|
|
27
28
|
export const lumenChartLabels = Object.freeze({
|
|
29
|
+
count: 'Count',
|
|
30
|
+
density: 'Frequency density',
|
|
31
|
+
end: 'End',
|
|
32
|
+
invalidData: 'Chart data is invalid. Check the supplied values.',
|
|
33
|
+
start: 'Start',
|
|
28
34
|
category: 'Category',
|
|
29
35
|
chartData: 'Chart data',
|
|
30
36
|
chartLegend: 'Chart legend',
|
|
31
37
|
column: 'Column',
|
|
32
38
|
empty: 'No chart data available.',
|
|
39
|
+
exploreData: 'Explore chart data',
|
|
40
|
+
formatDatumAction: (context) => `Open details: ${context}`,
|
|
33
41
|
formatHeatmapSummary: (count) => count === 0 ?
|
|
34
42
|
'No chart data available.' :
|
|
35
43
|
`${count} available heatmap ${count === 1 ? 'cell' : 'cells'}.`,
|
|
@@ -88,9 +96,9 @@ export const getLumenChartDomain = (values, includeZero = true) => {
|
|
|
88
96
|
max = Math.max(0, max);
|
|
89
97
|
}
|
|
90
98
|
if (min === max) {
|
|
91
|
-
const offset = Math.abs(min || 1) * 0.1;
|
|
92
|
-
min
|
|
93
|
-
max
|
|
99
|
+
const offset = Math.max(Number.MIN_VALUE, Math.abs(min || 1) * 0.1);
|
|
100
|
+
min = Math.max(-Number.MAX_VALUE, min - offset);
|
|
101
|
+
max = Math.min(Number.MAX_VALUE, max + offset);
|
|
94
102
|
}
|
|
95
103
|
return { max, min };
|
|
96
104
|
};
|
|
@@ -103,9 +111,12 @@ const resolveLumenChartDomain = (values, requested, includeZero) => {
|
|
|
103
111
|
};
|
|
104
112
|
export const scaleLumenChartValue = (value, domain, rangeStart, rangeEnd) => {
|
|
105
113
|
const domainSize = domain.max - domain.min;
|
|
106
|
-
if (!Number.isFinite(value) || domainSize === 0)
|
|
114
|
+
if (!Number.isFinite(value) || !Number.isFinite(domain.min) || !Number.isFinite(domain.max) || domainSize === 0)
|
|
107
115
|
return rangeStart;
|
|
108
|
-
|
|
116
|
+
// A valid domain can span opposite finite extremes whose difference overflows.
|
|
117
|
+
const ratio = Number.isFinite(domainSize) ?
|
|
118
|
+
(value - domain.min) / domainSize :
|
|
119
|
+
(value / 2 - domain.min / 2) / (domain.max / 2 - domain.min / 2);
|
|
109
120
|
return rangeStart + ratio * (rangeEnd - rangeStart);
|
|
110
121
|
};
|
|
111
122
|
export const getLumenChartTicks = (domain, count = 5) => {
|
|
@@ -134,6 +145,148 @@ export const getLumenChartTicks = (domain, count = 5) => {
|
|
|
134
145
|
return ticks;
|
|
135
146
|
return [domain.min, domain.max];
|
|
136
147
|
};
|
|
148
|
+
const chartAxisLabelCharacterWidth = 7;
|
|
149
|
+
const chartAxisLabelNarrowCharacterWidth = 4;
|
|
150
|
+
const chartAxisLabelWideCharacterWidth = 11;
|
|
151
|
+
const chartAxisLabelGap = 16;
|
|
152
|
+
const maximumChartAxisPadding = 240;
|
|
153
|
+
const chartAxisLabelNarrowCharacters = new Set([
|
|
154
|
+
' ', '.', ',', ':', ';', '!', '|', '\'', 'i', 'j', 'l', 'I'
|
|
155
|
+
]);
|
|
156
|
+
const chartAxisLabelWideCharacters = new Set(['M', 'W', '@', '%', '&', '#']);
|
|
157
|
+
const chartAxisCombiningCodePointRanges = [
|
|
158
|
+
[0x0300, 0x036F],
|
|
159
|
+
[0x1AB0, 0x1AFF],
|
|
160
|
+
[0x1DC0, 0x1DFF],
|
|
161
|
+
[0x20D0, 0x20FF],
|
|
162
|
+
[0xFE00, 0xFE0F],
|
|
163
|
+
[0xFE20, 0xFE2F],
|
|
164
|
+
[0x1F3FB, 0x1F3FF]
|
|
165
|
+
];
|
|
166
|
+
const chartAxisWideCodePointRanges = [
|
|
167
|
+
[0x1100, 0x115F],
|
|
168
|
+
[0x2329, 0x232A],
|
|
169
|
+
[0x2E80, 0xA4CF],
|
|
170
|
+
[0xAC00, 0xD7A3],
|
|
171
|
+
[0xF900, 0xFAFF],
|
|
172
|
+
[0xFE10, 0xFE19],
|
|
173
|
+
[0xFE30, 0xFE6F],
|
|
174
|
+
[0xFF00, 0xFF60],
|
|
175
|
+
[0xFFE0, 0xFFE6],
|
|
176
|
+
[0x1F300, 0x1FAFF],
|
|
177
|
+
[0x20000, 0x3FFFD]
|
|
178
|
+
];
|
|
179
|
+
const isLumenChartCodePointInRanges = (codePoint, ranges) => ranges.some(([start, end]) => codePoint >= start && codePoint <= end);
|
|
180
|
+
const isLumenChartCombiningCodePoint = (codePoint) => (codePoint === 0x200D ||
|
|
181
|
+
isLumenChartCodePointInRanges(codePoint, chartAxisCombiningCodePointRanges));
|
|
182
|
+
const isLumenChartWideCodePoint = (codePoint) => (isLumenChartCodePointInRanges(codePoint, chartAxisWideCodePointRanges));
|
|
183
|
+
const getLumenChartAxisCharacterWidth = (character) => {
|
|
184
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
185
|
+
if (isLumenChartCombiningCodePoint(codePoint))
|
|
186
|
+
return 0;
|
|
187
|
+
if (isLumenChartWideCodePoint(codePoint))
|
|
188
|
+
return chartAxisLabelWideCharacterWidth;
|
|
189
|
+
if (chartAxisLabelWideCharacters.has(character))
|
|
190
|
+
return 10;
|
|
191
|
+
if (chartAxisLabelNarrowCharacters.has(character))
|
|
192
|
+
return chartAxisLabelNarrowCharacterWidth;
|
|
193
|
+
return chartAxisLabelCharacterWidth;
|
|
194
|
+
};
|
|
195
|
+
const estimateLumenChartAxisLabelWidth = (label) => {
|
|
196
|
+
let width = 0;
|
|
197
|
+
for (const character of label) {
|
|
198
|
+
width += getLumenChartAxisCharacterWidth(character);
|
|
199
|
+
}
|
|
200
|
+
return Math.max(label.length * chartAxisLabelCharacterWidth, width);
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Reserves enough SVG space for formatted value-axis labels when text measurement is unavailable
|
|
204
|
+
* during server rendering. The estimate targets Lumen's 0.6875rem chart label style and stays
|
|
205
|
+
* bounded so unusually verbose formatters cannot consume the complete plot.
|
|
206
|
+
*/
|
|
207
|
+
export const getLumenChartAxisPadding = (labels, minimum = 44) => {
|
|
208
|
+
const safeMinimum = Math.max(0, minimum);
|
|
209
|
+
const widestLabelWidth = labels.reduce((widest, label) => Math.max(widest, estimateLumenChartAxisLabelWidth(label)), 0);
|
|
210
|
+
const estimated = widestLabelWidth + chartAxisLabelGap;
|
|
211
|
+
return Math.min(maximumChartAxisPadding, Math.max(safeMinimum, estimated));
|
|
212
|
+
};
|
|
213
|
+
const fitLumenChartLabel = (label, maximumWidth) => {
|
|
214
|
+
if (estimateLumenChartAxisLabelWidth(label) <= maximumWidth)
|
|
215
|
+
return label;
|
|
216
|
+
const suffix = '…';
|
|
217
|
+
let result = '';
|
|
218
|
+
let width = estimateLumenChartAxisLabelWidth(suffix);
|
|
219
|
+
for (const character of label) {
|
|
220
|
+
width += Math.max(chartAxisLabelCharacterWidth, getLumenChartAxisCharacterWidth(character));
|
|
221
|
+
if (width > maximumWidth)
|
|
222
|
+
break;
|
|
223
|
+
result += character;
|
|
224
|
+
}
|
|
225
|
+
return result + suffix;
|
|
226
|
+
};
|
|
227
|
+
const getLumenChartTickAnchor = (index, count) => {
|
|
228
|
+
if (count === 1)
|
|
229
|
+
return 'middle';
|
|
230
|
+
if (index === 0)
|
|
231
|
+
return 'start';
|
|
232
|
+
return index === count - 1 ? 'end' : 'middle';
|
|
233
|
+
};
|
|
234
|
+
const selectLumenChartCategoryTicks = (candidates, gap) => {
|
|
235
|
+
const first = candidates[0];
|
|
236
|
+
const last = candidates.at(-1);
|
|
237
|
+
if (!first || !last)
|
|
238
|
+
return [];
|
|
239
|
+
if (candidates.length === 1)
|
|
240
|
+
return [first];
|
|
241
|
+
const lastStart = last.position - estimateLumenChartAxisLabelWidth(last.label);
|
|
242
|
+
const ticks = [first];
|
|
243
|
+
let previousEnd = first.position + estimateLumenChartAxisLabelWidth(first.label);
|
|
244
|
+
for (const candidate of candidates.slice(1, -1)) {
|
|
245
|
+
const halfWidth = estimateLumenChartAxisLabelWidth(candidate.label) / 2;
|
|
246
|
+
const labelStart = candidate.position - halfWidth;
|
|
247
|
+
const labelEnd = candidate.position + halfWidth;
|
|
248
|
+
if (labelStart < previousEnd + gap || labelEnd + gap > lastStart)
|
|
249
|
+
continue;
|
|
250
|
+
ticks.push(candidate);
|
|
251
|
+
previousEnd = labelEnd;
|
|
252
|
+
}
|
|
253
|
+
if (lastStart >= previousEnd + gap)
|
|
254
|
+
ticks.push(last);
|
|
255
|
+
return ticks;
|
|
256
|
+
};
|
|
257
|
+
const resolveLumenChartTickPositions = (count, end, positions, start) => {
|
|
258
|
+
const validPositions = positions?.length === count && positions.every((position, index) => (Number.isFinite(position) && position >= start && position <= end && position >= (positions[index - 1] ?? start)));
|
|
259
|
+
if (validPositions)
|
|
260
|
+
return positions;
|
|
261
|
+
return Array.from({ length: count }, (_, index) => (start + (count === 1 ? 0.5 : index / (count - 1)) * (end - start)));
|
|
262
|
+
};
|
|
263
|
+
/** Selects readable axis labels without changing or downsampling the underlying chart data. */
|
|
264
|
+
export const getLumenChartCategoryTicks = (labels, { end, minimumGap = 16, positions, start }) => {
|
|
265
|
+
if (labels.length === 0 || !Number.isFinite(start) || !Number.isFinite(end) || end < start)
|
|
266
|
+
return [];
|
|
267
|
+
const gap = Number.isFinite(minimumGap) ? Math.max(0, minimumGap) : 16;
|
|
268
|
+
const resolvedPositions = resolveLumenChartTickPositions(labels.length, end, positions, start);
|
|
269
|
+
const span = (resolvedPositions.at(-1) ?? end) - (resolvedPositions[0] ?? start);
|
|
270
|
+
const maximumWidth = Math.max(0, labels.length === 1 ? end - start : (span - gap) / 2);
|
|
271
|
+
const candidates = labels.map((label, index) => ({
|
|
272
|
+
index,
|
|
273
|
+
label: fitLumenChartLabel(label, maximumWidth),
|
|
274
|
+
position: resolvedPositions[index] ?? start,
|
|
275
|
+
textAnchor: getLumenChartTickAnchor(index, labels.length)
|
|
276
|
+
}));
|
|
277
|
+
return selectLumenChartCategoryTicks(candidates, gap);
|
|
278
|
+
};
|
|
279
|
+
/** Axis labels prefer xLabel; detail labels prefer an explicitly supplied full formatter. */
|
|
280
|
+
export const getLumenChartCategoryLabel = (series, category, formatCategory, context = 'axis') => {
|
|
281
|
+
if (context === 'detail' && formatCategory)
|
|
282
|
+
return formatCategory(category);
|
|
283
|
+
for (const item of series) {
|
|
284
|
+
const label = item.data.find(datum => datum.x === category)?.xLabel;
|
|
285
|
+
if (label !== undefined)
|
|
286
|
+
return label;
|
|
287
|
+
}
|
|
288
|
+
return formatCategory?.(category) ?? String(category);
|
|
289
|
+
};
|
|
137
290
|
export const resolveLumenChartTone = (tone, index = 0) => tone ?? lumenChartTones[index % 8] ?? 'series-1';
|
|
138
291
|
export const getLumenChartToneClassName = (tone, index = 0) => lumenChartToneClassNames[resolveLumenChartTone(tone, index)];
|
|
139
292
|
export const getLumenPieChartVariantClassName = (variant) => lumenPieChartVariantClassNames[variant];
|
|
@@ -237,6 +390,8 @@ export const getLumenChartNumericX = (value, scale) => {
|
|
|
237
390
|
if (typeof value === 'number')
|
|
238
391
|
return Number.isFinite(value) ? value : null;
|
|
239
392
|
if (scale === 'linear') {
|
|
393
|
+
if (!value.trim())
|
|
394
|
+
return null;
|
|
240
395
|
const numeric = Number(value);
|
|
241
396
|
return Number.isFinite(numeric) ? numeric : null;
|
|
242
397
|
}
|
|
@@ -250,24 +405,24 @@ const getLumenChartXDomain = (data, scale, requested) => {
|
|
|
250
405
|
min: requested?.min ?? calculated.min
|
|
251
406
|
};
|
|
252
407
|
};
|
|
253
|
-
const getLumenLineXCoordinate = (datum, index, dataLength,
|
|
408
|
+
const getLumenLineXCoordinate = (datum, index, dataLength, paddingLeft, drawableWidth, xScale, xDomain) => {
|
|
254
409
|
if (xScale === 'categorical') {
|
|
255
410
|
const denominator = Math.max(1, dataLength - 1);
|
|
256
|
-
return
|
|
411
|
+
return paddingLeft + (dataLength === 1 ? 0.5 : index / denominator) * drawableWidth;
|
|
257
412
|
}
|
|
258
413
|
const numericX = getLumenChartNumericX(datum.x, xScale);
|
|
259
414
|
if (numericX === null || xDomain === undefined)
|
|
260
415
|
return null;
|
|
261
|
-
return scaleLumenChartValue(numericX, xDomain,
|
|
416
|
+
return scaleLumenChartValue(numericX, xDomain, paddingLeft, paddingLeft + drawableWidth);
|
|
262
417
|
};
|
|
263
418
|
const isAvailableLumenChartY = (value) => value !== null && Number.isFinite(value);
|
|
264
|
-
const splitGeometrySegments = (data, domain, width, height,
|
|
265
|
-
const drawableWidth = Math.max(0, width -
|
|
266
|
-
const drawableHeight = Math.max(0, height -
|
|
419
|
+
const splitGeometrySegments = (data, domain, width, height, paddingBottom, paddingLeft, paddingRight, paddingTop, xScale, xDomain) => {
|
|
420
|
+
const drawableWidth = Math.max(0, width - paddingLeft - paddingRight);
|
|
421
|
+
const drawableHeight = Math.max(0, height - paddingTop - paddingBottom);
|
|
267
422
|
const segments = [];
|
|
268
423
|
let current = [];
|
|
269
424
|
for (const [index, datum] of data.entries()) {
|
|
270
|
-
const xCoordinate = getLumenLineXCoordinate(datum, index, data.length,
|
|
425
|
+
const xCoordinate = getLumenLineXCoordinate(datum, index, data.length, paddingLeft, drawableWidth, xScale, xDomain);
|
|
271
426
|
if (!isAvailableLumenChartY(datum.y) || xCoordinate === null) {
|
|
272
427
|
if (current.length > 0)
|
|
273
428
|
segments.push(current);
|
|
@@ -277,7 +432,7 @@ const splitGeometrySegments = (data, domain, width, height, padding, xScale, xDo
|
|
|
277
432
|
current.push({
|
|
278
433
|
...datum,
|
|
279
434
|
xCoordinate,
|
|
280
|
-
yCoordinate: scaleLumenChartValue(datum.y, domain,
|
|
435
|
+
yCoordinate: scaleLumenChartValue(datum.y, domain, paddingTop + drawableHeight, paddingTop)
|
|
281
436
|
});
|
|
282
437
|
}
|
|
283
438
|
if (current.length > 0)
|
|
@@ -303,8 +458,13 @@ const lumenLineAreaPath = (points, baseline) => {
|
|
|
303
458
|
'Z'
|
|
304
459
|
].join(' ');
|
|
305
460
|
};
|
|
461
|
+
const resolveLumenLinePadding = (value, fallback) => value ?? fallback;
|
|
306
462
|
export const createLumenLineGeometry = (data, options = {}) => {
|
|
307
|
-
const { domain: requestedDomain, height = defaultChartSize, includeZero = false, padding = defaultChartPadding, width = defaultChartSize, xDomain: requestedXDomain, xScale = 'categorical' } = options;
|
|
463
|
+
const { domain: requestedDomain, height = defaultChartSize, includeZero = false, padding = defaultChartPadding, paddingBottom: requestedPaddingBottom, paddingLeft: requestedPaddingLeft, paddingRight: requestedPaddingRight, paddingTop: requestedPaddingTop, width = defaultChartSize, xDomain: requestedXDomain, xScale = 'categorical' } = options;
|
|
464
|
+
const paddingBottom = resolveLumenLinePadding(requestedPaddingBottom, padding);
|
|
465
|
+
const paddingLeft = resolveLumenLinePadding(requestedPaddingLeft, padding);
|
|
466
|
+
const paddingRight = resolveLumenLinePadding(requestedPaddingRight, padding);
|
|
467
|
+
const paddingTop = resolveLumenLinePadding(requestedPaddingTop, padding);
|
|
308
468
|
const domainValues = xScale === 'categorical' ?
|
|
309
469
|
data.map(datum => datum.y) :
|
|
310
470
|
data
|
|
@@ -312,8 +472,8 @@ export const createLumenLineGeometry = (data, options = {}) => {
|
|
|
312
472
|
.map(datum => datum.y);
|
|
313
473
|
const domain = resolveLumenChartDomain(domainValues, requestedDomain, includeZero);
|
|
314
474
|
const xDomain = xScale === 'categorical' ? undefined : getLumenChartXDomain(data, xScale, requestedXDomain);
|
|
315
|
-
const segments = splitGeometrySegments(data, domain, width, height,
|
|
316
|
-
const baseline = scaleLumenChartValue(Math.max(domain.min, Math.min(domain.max, 0)), domain, height -
|
|
475
|
+
const segments = splitGeometrySegments(data, domain, width, height, paddingBottom, paddingLeft, paddingRight, paddingTop, xScale, xDomain);
|
|
476
|
+
const baseline = scaleLumenChartValue(Math.max(domain.min, Math.min(domain.max, 0)), domain, height - paddingBottom, paddingTop);
|
|
317
477
|
return {
|
|
318
478
|
areaPaths: segments
|
|
319
479
|
.map(points => lumenLineAreaPath(points, baseline))
|
|
@@ -384,7 +544,7 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
384
544
|
const layout = options.layout ?? 'grouped';
|
|
385
545
|
const categories = getLumenChartCategories(series);
|
|
386
546
|
const indexedSeries = series.map(indexLumenChartSeries);
|
|
387
|
-
const values =
|
|
547
|
+
const values = indexedSeries.flatMap(item => [...item.values()].map(datum => datum.y));
|
|
388
548
|
const calculatedDomain = layout === 'stacked' ?
|
|
389
549
|
getStackedDomain(series, categories) :
|
|
390
550
|
getLumenChartDomain(values);
|
|
@@ -395,11 +555,16 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
395
555
|
const margin = orientation === 'horizontal' ?
|
|
396
556
|
{
|
|
397
557
|
bottom: 24,
|
|
398
|
-
left: Math.max(64, Math.min(240, options.categoryWidth ?? 112)),
|
|
558
|
+
left: Math.max(64, Math.min(240, options.categoryWidth ?? getLumenChartAxisPadding(categories.map(category => getLumenChartCategoryLabel(series, category, options.formatCategory)), 112))),
|
|
399
559
|
right: 20,
|
|
400
560
|
top: 16
|
|
401
561
|
} :
|
|
402
|
-
{
|
|
562
|
+
{
|
|
563
|
+
bottom: 52,
|
|
564
|
+
left: getLumenChartAxisPadding(getLumenChartTicks(domain).map(tick => (options.formatValue ?? String)(tick)), 52),
|
|
565
|
+
right: 16,
|
|
566
|
+
top: 16
|
|
567
|
+
};
|
|
403
568
|
const plotWidth = Math.max(1, width - margin.left - margin.right);
|
|
404
569
|
const plotHeight = Math.max(1, height - margin.top - margin.bottom);
|
|
405
570
|
const categorySize = (orientation === 'horizontal' ? plotHeight : plotWidth) /
|
|
@@ -417,9 +582,7 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
417
582
|
let negativeOffset = 0;
|
|
418
583
|
categoryPositions.push({
|
|
419
584
|
category,
|
|
420
|
-
label: series
|
|
421
|
-
.flatMap(item => item.data)
|
|
422
|
-
.find(datum => datum.x === category)?.xLabel ?? category,
|
|
585
|
+
label: getLumenChartCategoryLabel(series, category, options.formatCategory),
|
|
423
586
|
x: orientation === 'horizontal' ?
|
|
424
587
|
margin.left - 8 :
|
|
425
588
|
categoryStart + usableCategorySize / 2,
|
|
@@ -487,6 +650,7 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
487
650
|
categories: categoryPositions,
|
|
488
651
|
domain,
|
|
489
652
|
height,
|
|
653
|
+
margin,
|
|
490
654
|
marks,
|
|
491
655
|
width
|
|
492
656
|
};
|
|
@@ -552,8 +716,18 @@ const validateLumenChartDatumNumbers = (datum, context) => {
|
|
|
552
716
|
}
|
|
553
717
|
};
|
|
554
718
|
const validateLumenChartDatumX = (datum, context) => {
|
|
555
|
-
if (context.xScale === 'categorical')
|
|
719
|
+
if (context.xScale === 'categorical') {
|
|
720
|
+
const key = lumenChartCategoryKey(datum.x);
|
|
721
|
+
if (context.categories.has(key)) {
|
|
722
|
+
context.issues.push({
|
|
723
|
+
code: 'duplicate-category',
|
|
724
|
+
message: 'Category x values must be unique within a series. Use xLabel for repeated display labels.',
|
|
725
|
+
path: `${context.path}.x`
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
context.categories.add(key);
|
|
556
729
|
return context.previousX;
|
|
730
|
+
}
|
|
557
731
|
const numericX = getLumenChartNumericX(datum.x, context.xScale);
|
|
558
732
|
if (numericX === null) {
|
|
559
733
|
context.issues.push({
|
|
@@ -591,9 +765,11 @@ export const validateLumenChartSeries = (series, xScale = 'categorical') => {
|
|
|
591
765
|
}
|
|
592
766
|
seriesIds.add(item.id);
|
|
593
767
|
const datumIds = new Set();
|
|
768
|
+
const categories = new Set();
|
|
594
769
|
let previousX = Number.NEGATIVE_INFINITY;
|
|
595
770
|
for (const [datumIndex, datum] of item.data.entries()) {
|
|
596
771
|
previousX = validateLumenChartDatum(datum, {
|
|
772
|
+
categories,
|
|
597
773
|
datumIds,
|
|
598
774
|
issues,
|
|
599
775
|
path: `${seriesPath}.data[${datumIndex}]`,
|
|
@@ -653,6 +829,68 @@ export const appendLumenChartDatum = (series, datum, maximumPoints = 500) => {
|
|
|
653
829
|
data: data.slice(Math.max(0, data.length - safeMaximum))
|
|
654
830
|
};
|
|
655
831
|
};
|
|
832
|
+
const getLumenScatterNumericX = (value, scale) => {
|
|
833
|
+
const numeric = getLumenChartNumericX(value, scale === 'log' ? 'linear' : scale);
|
|
834
|
+
return scale === 'log' && numeric !== null && numeric <= 0 ? null : numeric;
|
|
835
|
+
};
|
|
836
|
+
const validScatterDomain = (domain, scale) => {
|
|
837
|
+
const finite = Number.isFinite(domain.min) && Number.isFinite(domain.max);
|
|
838
|
+
return finite && domain.max > domain.min && (scale !== 'log' || domain.min > 0);
|
|
839
|
+
};
|
|
840
|
+
const getLumenScatterXDomain = (data, scale, requested) => {
|
|
841
|
+
const values = data.map(datum => getLumenScatterNumericX(datum.x, scale));
|
|
842
|
+
const calculated = scale === 'log' && !values.some(value => value !== null) ?
|
|
843
|
+
{ min: 1, max: 10 } :
|
|
844
|
+
getLumenChartDomain(values, false);
|
|
845
|
+
const domain = { min: requested?.min ?? calculated.min, max: requested?.max ?? calculated.max };
|
|
846
|
+
if (!validScatterDomain(domain, scale))
|
|
847
|
+
throw new RangeError('Scatter x domain must be finite, ordered, and positive for log scales');
|
|
848
|
+
return domain;
|
|
849
|
+
};
|
|
850
|
+
export const scaleLumenScatterX = (value, domain, rangeStart, rangeEnd, scale = 'linear') => {
|
|
851
|
+
if (scale !== 'log')
|
|
852
|
+
return scaleLumenChartValue(value, domain, rangeStart, rangeEnd);
|
|
853
|
+
if (value <= 0 || !Number.isFinite(value))
|
|
854
|
+
return Number.NaN;
|
|
855
|
+
return scaleLumenChartValue(Math.log10(value), { min: Math.log10(domain.min), max: Math.log10(domain.max) }, rangeStart, rangeEnd);
|
|
856
|
+
};
|
|
857
|
+
export const getLumenScatterXTicks = (domain, scale) => scale === 'log' ?
|
|
858
|
+
getLumenChartTicks({ min: Math.log10(domain.min), max: Math.log10(domain.max) }).map(value => 10 ** value) :
|
|
859
|
+
getLumenChartTicks(domain);
|
|
860
|
+
const scatterReferenceCoordinate = (value, fallback, project) => {
|
|
861
|
+
if (value === undefined)
|
|
862
|
+
return fallback;
|
|
863
|
+
return project(value);
|
|
864
|
+
};
|
|
865
|
+
const isLumenScatterReference = (value) => {
|
|
866
|
+
if (typeof value !== 'object' || value === null || !('id' in value) || !('label' in value))
|
|
867
|
+
return false;
|
|
868
|
+
if (typeof value.id !== 'string' || typeof value.label !== 'string')
|
|
869
|
+
return false;
|
|
870
|
+
return ['x', 'y', 'xEnd', 'yEnd'].every(key => {
|
|
871
|
+
const coordinate = Reflect.get(value, key);
|
|
872
|
+
return coordinate === undefined || (typeof coordinate === 'number' && Number.isFinite(coordinate));
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
export const createLumenScatterReferences = (references, geometry, scale = 'linear', padding = 44) => {
|
|
876
|
+
const result = [];
|
|
877
|
+
if (!Array.isArray(references))
|
|
878
|
+
return result;
|
|
879
|
+
const x = (value) => scaleLumenScatterX(value, geometry.xDomain, padding, geometry.width - padding, scale);
|
|
880
|
+
const y = (value) => scaleLumenChartValue(value, geometry.domain, geometry.height - padding, padding);
|
|
881
|
+
for (const reference of references) {
|
|
882
|
+
if (!isLumenScatterReference(reference))
|
|
883
|
+
continue;
|
|
884
|
+
const region = reference.xEnd !== undefined && reference.yEnd !== undefined;
|
|
885
|
+
const x1 = scatterReferenceCoordinate(reference.x, padding, x);
|
|
886
|
+
const y1 = scatterReferenceCoordinate(reference.y, padding, y);
|
|
887
|
+
const x2 = scatterReferenceCoordinate(reference.xEnd, reference.x === undefined ? geometry.width - padding : x1, x);
|
|
888
|
+
const y2 = scatterReferenceCoordinate(reference.yEnd, reference.y === undefined ? geometry.height - padding : y1, y);
|
|
889
|
+
if ([x1, x2, y1, y2].every(Number.isFinite))
|
|
890
|
+
result.push({ ...reference, x1, x2, y1, y2, region });
|
|
891
|
+
}
|
|
892
|
+
return result;
|
|
893
|
+
};
|
|
656
894
|
const resolveLumenScatterGeometryOptions = (options) => ({
|
|
657
895
|
height: options.height ?? 320,
|
|
658
896
|
maximumRadius: options.maximumRadius ?? 18,
|
|
@@ -664,7 +902,7 @@ const resolveLumenScatterGeometryOptions = (options) => ({
|
|
|
664
902
|
xScale: options.xScale ?? 'linear'
|
|
665
903
|
});
|
|
666
904
|
const createLumenScatterPoint = (datum, context) => {
|
|
667
|
-
const numericX =
|
|
905
|
+
const numericX = getLumenScatterNumericX(datum.x, context.xScale);
|
|
668
906
|
if (numericX === null || !isAvailableLumenChartY(datum.y))
|
|
669
907
|
return null;
|
|
670
908
|
const radius = datum.size === null || datum.size === undefined || !Number.isFinite(datum.size) || datum.size < 0 ?
|
|
@@ -676,20 +914,52 @@ const createLumenScatterPoint = (datum, context) => {
|
|
|
676
914
|
seriesId: context.series.id,
|
|
677
915
|
seriesLabel: context.series.label,
|
|
678
916
|
tone: resolveLumenChartTone(datum.tone ?? context.series.tone, context.seriesIndex),
|
|
679
|
-
xCoordinate:
|
|
917
|
+
xCoordinate: scaleLumenScatterX(numericX, context.xDomain, context.padding, context.width - context.padding, context.xScale),
|
|
680
918
|
yCoordinate: scaleLumenChartValue(datum.y, context.domain, context.height - context.padding, context.padding)
|
|
681
919
|
};
|
|
682
920
|
};
|
|
921
|
+
const finiteLumenScatterBound = (value, fallback, scale) => (Number.isFinite(value) && (scale !== 'log' || value > 0) ? value : fallback);
|
|
922
|
+
// Automatic extents leave room for the complete bubble, including its stroke.
|
|
923
|
+
// Explicit bounds remain exact so consumers can intentionally crop a viewport.
|
|
924
|
+
const padLumenScatterDomain = (domain, values, requested, extent, radius, scale = 'linear') => {
|
|
925
|
+
if (values.length === 0 || extent <= 0)
|
|
926
|
+
return domain;
|
|
927
|
+
const project = (value) => scale === 'log' ? Math.log10(value) : value;
|
|
928
|
+
const fromScale = (value) => scale === 'log' ? 10 ** value : value;
|
|
929
|
+
const min = project(domain.min);
|
|
930
|
+
const max = project(domain.max);
|
|
931
|
+
const inset = Math.min(radius + 2, extent / 4);
|
|
932
|
+
const extra = (max - min) * inset / (extent - 2 * inset);
|
|
933
|
+
const projected = values.map(project);
|
|
934
|
+
const bounds = projected.reduce((result, value) => ({
|
|
935
|
+
min: Math.min(result.min, value), max: Math.max(result.max, value)
|
|
936
|
+
}), { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY });
|
|
937
|
+
const paddedMin = fromScale(Math.min(min, bounds.min - extra));
|
|
938
|
+
const paddedMax = fromScale(Math.max(max, bounds.max + extra));
|
|
939
|
+
return {
|
|
940
|
+
min: requested?.min ?? finiteLumenScatterBound(paddedMin, domain.min, scale),
|
|
941
|
+
max: requested?.max ?? finiteLumenScatterBound(paddedMax, domain.max, scale)
|
|
942
|
+
};
|
|
943
|
+
};
|
|
683
944
|
export const createLumenScatterGeometry = (series, options = {}) => {
|
|
684
945
|
const { height, maximumRadius: requestedMaximumRadius, minimumRadius: requestedMinimumRadius, padding, requestedDomain, requestedXDomain, width, xScale } = resolveLumenScatterGeometryOptions(options);
|
|
685
946
|
const data = series.flatMap(item => item.data);
|
|
686
|
-
const projectedData = data.filter(datum => (
|
|
687
|
-
const
|
|
688
|
-
|
|
947
|
+
const projectedData = data.filter(datum => (getLumenScatterNumericX(datum.x, xScale) !== null && isAvailableLumenChartY(datum.y)));
|
|
948
|
+
const calculatedDomain = resolveLumenChartDomain(projectedData.map(datum => datum.y), requestedDomain, false);
|
|
949
|
+
if (!validScatterDomain(calculatedDomain, 'linear')) {
|
|
950
|
+
throw new RangeError('Scatter y domain must be finite and increasing');
|
|
951
|
+
}
|
|
952
|
+
const calculatedXDomain = getLumenScatterXDomain(projectedData, xScale, requestedXDomain);
|
|
689
953
|
const sizes = projectedData.map(datum => (datum.size !== undefined && datum.size !== null && datum.size >= 0 ? datum.size : null));
|
|
690
954
|
const sizeDomain = getLumenChartDomain(sizes, false);
|
|
691
955
|
const minimumRadius = Math.max(1, requestedMinimumRadius);
|
|
692
956
|
const maximumRadius = Math.max(minimumRadius, requestedMaximumRadius);
|
|
957
|
+
const yValues = projectedData.flatMap(datum => datum.y === null ? [] : [datum.y]);
|
|
958
|
+
const domain = padLumenScatterDomain(calculatedDomain, yValues, requestedDomain, height - padding * 2, maximumRadius);
|
|
959
|
+
const xDomain = padLumenScatterDomain(calculatedXDomain, projectedData.flatMap(datum => {
|
|
960
|
+
const value = getLumenScatterNumericX(datum.x, xScale);
|
|
961
|
+
return value === null ? [] : [value];
|
|
962
|
+
}), requestedXDomain, width - padding * 2, maximumRadius, xScale);
|
|
693
963
|
const points = [];
|
|
694
964
|
for (const [seriesIndex, item] of series.entries()) {
|
|
695
965
|
for (const datum of item.data) {
|
|
@@ -718,6 +988,33 @@ const uniqueLumenChartCategories = (values) => {
|
|
|
718
988
|
categories.set(lumenChartCategoryKey(value), value);
|
|
719
989
|
return [...categories.values()];
|
|
720
990
|
};
|
|
991
|
+
const isHeatmapRecord = (value) => {
|
|
992
|
+
if (Array.isArray(value))
|
|
993
|
+
return false;
|
|
994
|
+
return typeof value === 'object' && value !== null;
|
|
995
|
+
};
|
|
996
|
+
const isHeatmapCoordinate = (value) => typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
|
|
997
|
+
const isOptionalHeatmapLabel = (value) => value === undefined || typeof value === 'string';
|
|
998
|
+
const isHeatmapTone = (value) => {
|
|
999
|
+
if (value === undefined)
|
|
1000
|
+
return true;
|
|
1001
|
+
return lumenChartTones.some(tone => tone === value);
|
|
1002
|
+
};
|
|
1003
|
+
const isHeatmapDatum = (datum) => {
|
|
1004
|
+
if (!isHeatmapRecord(datum))
|
|
1005
|
+
return false;
|
|
1006
|
+
return isHeatmapCoordinate(datum.x) && isHeatmapCoordinate(datum.y) &&
|
|
1007
|
+
// Non-finite measurements are missing cells; coordinates still require finite numbers.
|
|
1008
|
+
(datum.value === null || typeof datum.value === 'number') &&
|
|
1009
|
+
[datum.id, datum.label, datum.xLabel, datum.yLabel].every(isOptionalHeatmapLabel) && isHeatmapTone(datum.tone);
|
|
1010
|
+
};
|
|
1011
|
+
/** Reject the whole decoded collection before adapters read or format cell fields. */
|
|
1012
|
+
export const normalizeLumenHeatmapData = (data) => {
|
|
1013
|
+
if (!Array.isArray(data))
|
|
1014
|
+
return [];
|
|
1015
|
+
const rows = Array.from(data);
|
|
1016
|
+
return rows.every(isHeatmapDatum) ? rows : [];
|
|
1017
|
+
};
|
|
721
1018
|
const createLumenHeatmapCell = (datum, context) => {
|
|
722
1019
|
const xIndex = context.xIndexes.get(lumenChartCategoryKey(datum.x));
|
|
723
1020
|
const yIndex = context.yIndexes.get(lumenChartCategoryKey(datum.y));
|
|
@@ -734,7 +1031,8 @@ const createLumenHeatmapCell = (datum, context) => {
|
|
|
734
1031
|
yCoordinate: yIndex * context.cellHeight
|
|
735
1032
|
};
|
|
736
1033
|
};
|
|
737
|
-
export const createLumenHeatmapGeometry = (
|
|
1034
|
+
export const createLumenHeatmapGeometry = (rawData, width = 640, height = 320) => {
|
|
1035
|
+
const data = normalizeLumenHeatmapData(rawData);
|
|
738
1036
|
const xCategories = uniqueLumenChartCategories(data.map(datum => datum.x));
|
|
739
1037
|
const yCategories = uniqueLumenChartCategories(data.map(datum => datum.y));
|
|
740
1038
|
const domain = getLumenChartDomain(data.map(datum => datum.value), false);
|
|
@@ -803,3 +1101,4 @@ export const createLumenRangeGeometry = (data, options = {}) => {
|
|
|
803
1101
|
points: segments.flat()
|
|
804
1102
|
};
|
|
805
1103
|
};
|
|
1104
|
+
export * from './extended-charts.js';
|
package/dist/code.js
CHANGED
|
@@ -320,7 +320,7 @@ const getMarkupTokenKind = (value, source, start) => {
|
|
|
320
320
|
return 'symbol';
|
|
321
321
|
const openingTagIndex = source.lastIndexOf('<', start);
|
|
322
322
|
const closingTagIndex = source.lastIndexOf('>', start);
|
|
323
|
-
if (openingTagIndex
|
|
323
|
+
if (openingTagIndex <= closingTagIndex)
|
|
324
324
|
return undefined;
|
|
325
325
|
const beforeValue = source
|
|
326
326
|
.slice(openingTagIndex + 1, start)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../src/combobox.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAyBD,yBAAyB;AACzB,eAAO,MAAM,6BAA6B,GAAI,MAAM,WAAW,KAAG,uBAwTjE,CAAA"}
|