@limetech/lime-elements 40.21.0 → 40.22.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/CHANGELOG.md +22 -0
- package/dist/cjs/language-s0bKUCvO.js +58 -0
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/limel-action-bar_3.cjs.entry.js +2 -2
- package/dist/cjs/limel-ai-avatar.cjs.entry.js +2 -2
- package/dist/cjs/limel-badge_7.cjs.entry.js +12 -2
- package/dist/cjs/limel-callout.cjs.entry.js +2 -2
- package/dist/cjs/limel-chart.cjs.entry.js +524 -76
- package/dist/cjs/limel-chip-set.cjs.entry.js +2 -2
- package/dist/cjs/limel-chip_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-code-diff.cjs.entry.js +2 -2
- package/dist/cjs/limel-code-editor.cjs.entry.js +2 -2
- package/dist/cjs/limel-collapsible-section.cjs.entry.js +2 -2
- package/dist/cjs/limel-date-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-dock.cjs.entry.js +2 -2
- package/dist/cjs/limel-drag-handle_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-email-viewer.cjs.entry.js +2 -2
- package/dist/cjs/limel-file-viewer.cjs.entry.js +164 -2
- package/dist/cjs/limel-file.cjs.entry.js +2 -2
- package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +2 -2
- package/dist/cjs/limel-list-item.cjs.entry.js +2 -2
- package/dist/cjs/limel-pagination-jump.cjs.entry.js +2 -2
- package/dist/cjs/limel-pagination.cjs.entry.js +14 -26
- package/dist/cjs/limel-picker.cjs.entry.js +70 -27
- package/dist/cjs/limel-profile-picture.cjs.entry.js +2 -2
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +2 -2
- package/dist/cjs/limel-resize-handle.cjs.entry.js +2 -2
- package/dist/cjs/limel-side-panel.cjs.entry.js +2 -2
- package/dist/cjs/limel-slider.cjs.entry.js +2 -2
- package/dist/cjs/limel-snackbar.cjs.entry.js +2 -2
- package/dist/cjs/limel-table.cjs.entry.js +2 -2
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +2 -2
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{translations-CqJEWcfw.js → translations-JLP5icQ9.js} +1 -1
- package/dist/collection/components/chart/chart.js +116 -45
- package/dist/collection/components/chart/format.js +302 -0
- package/dist/collection/components/chart/options.js +26 -14
- package/dist/collection/components/chart/scale.js +132 -19
- package/dist/collection/components/input-field/input-field.js +10 -0
- package/dist/collection/components/pagination/pagination.js +13 -25
- package/dist/collection/components/picker/picker.js +67 -24
- package/dist/collection/util/language.js +29 -0
- package/dist/esm/language-VamkUr90.js +55 -0
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/limel-action-bar_3.entry.js +2 -2
- package/dist/esm/limel-ai-avatar.entry.js +2 -2
- package/dist/esm/limel-badge_7.entry.js +12 -2
- package/dist/esm/limel-callout.entry.js +2 -2
- package/dist/esm/limel-chart.entry.js +524 -76
- package/dist/esm/limel-chip-set.entry.js +2 -2
- package/dist/esm/limel-chip_2.entry.js +2 -2
- package/dist/esm/limel-code-diff.entry.js +2 -2
- package/dist/esm/limel-code-editor.entry.js +2 -2
- package/dist/esm/limel-collapsible-section.entry.js +2 -2
- package/dist/esm/limel-date-picker.entry.js +1 -1
- package/dist/esm/limel-dock.entry.js +2 -2
- package/dist/esm/limel-drag-handle_2.entry.js +2 -2
- package/dist/esm/limel-email-viewer.entry.js +2 -2
- package/dist/esm/limel-file-viewer.entry.js +164 -2
- package/dist/esm/limel-file.entry.js +2 -2
- package/dist/esm/limel-flatpickr-adapter.entry.js +2 -2
- package/dist/esm/limel-list-item.entry.js +2 -2
- package/dist/esm/limel-pagination-jump.entry.js +2 -2
- package/dist/esm/limel-pagination.entry.js +14 -26
- package/dist/esm/limel-picker.entry.js +71 -28
- package/dist/esm/limel-profile-picture.entry.js +2 -2
- package/dist/esm/limel-prosemirror-adapter.entry.js +2 -2
- package/dist/esm/limel-resize-handle.entry.js +2 -2
- package/dist/esm/limel-side-panel.entry.js +2 -2
- package/dist/esm/limel-slider.entry.js +2 -2
- package/dist/esm/limel-snackbar.entry.js +2 -2
- package/dist/esm/limel-table.entry.js +2 -2
- package/dist/esm/limel-text-editor-link-menu.entry.js +2 -2
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{translations-kct5Kybu.js → translations-D9ZGrMhe.js} +1 -1
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-6e9eeb0a.entry.js → p-0a7cbdc3.entry.js} +1 -1
- package/dist/lime-elements/{p-d35d2859.entry.js → p-0f96e94d.entry.js} +1 -1
- package/dist/lime-elements/{p-a7be03e1.entry.js → p-1a0c5e45.entry.js} +1 -1
- package/dist/lime-elements/{p-5e408c27.entry.js → p-401d5c92.entry.js} +1 -1
- package/dist/lime-elements/{p-6d2d25b9.entry.js → p-430db3b9.entry.js} +1 -1
- package/dist/lime-elements/{p-a6c6a31b.entry.js → p-43b62587.entry.js} +1 -1
- package/dist/lime-elements/{p-958396b5.entry.js → p-48dccb83.entry.js} +1 -1
- package/dist/lime-elements/{p-814b4934.entry.js → p-5c918155.entry.js} +1 -1
- package/dist/lime-elements/{p-22e9e327.entry.js → p-66101ed6.entry.js} +1 -1
- package/dist/lime-elements/{p-d8559847.entry.js → p-7ab72b52.entry.js} +1 -1
- package/dist/lime-elements/{p-7ab726c2.entry.js → p-8d9a2a22.entry.js} +1 -1
- package/dist/lime-elements/{p-55fa19ae.entry.js → p-8de4f0f3.entry.js} +1 -1
- package/dist/lime-elements/p-8e80a884.entry.js +56 -0
- package/dist/lime-elements/{p-bffb5ed2.entry.js → p-99984c66.entry.js} +1 -1
- package/dist/lime-elements/{p-56e2fa48.entry.js → p-9d46f720.entry.js} +1 -1
- package/dist/lime-elements/p-9e2b426c.entry.js +1 -0
- package/dist/lime-elements/{p-Dzp8dXTo.js → p-C9dBa_Im.js} +1 -1
- package/dist/lime-elements/p-VamkUr90.js +1 -0
- package/dist/lime-elements/{p-3ac39bd5.entry.js → p-b1c7033b.entry.js} +2 -2
- package/dist/lime-elements/{p-47fe76a8.entry.js → p-b5451350.entry.js} +1 -1
- package/dist/lime-elements/{p-9edd79a8.entry.js → p-c75f3b0d.entry.js} +1 -1
- package/dist/lime-elements/{p-4c080cd9.entry.js → p-ca4247c4.entry.js} +1 -1
- package/dist/lime-elements/{p-1a7d1d2b.entry.js → p-d53421af.entry.js} +1 -1
- package/dist/lime-elements/{p-6d9b89db.entry.js → p-d8901e39.entry.js} +1 -1
- package/dist/lime-elements/{p-933ffaa8.entry.js → p-d89553b3.entry.js} +1 -1
- package/dist/lime-elements/{p-496b2437.entry.js → p-d93b8ce0.entry.js} +1 -1
- package/dist/lime-elements/{p-23067836.entry.js → p-e97340c4.entry.js} +1 -1
- package/dist/lime-elements/{p-7b87e547.entry.js → p-ec0c113d.entry.js} +1 -1
- package/dist/lime-elements/p-f197000b.entry.js +159 -0
- package/dist/lime-elements/{p-07f3565b.entry.js → p-f6bc6658.entry.js} +1 -1
- package/dist/lime-elements/{p-7fcd48c6.entry.js → p-fa1aa440.entry.js} +1 -1
- package/dist/types/components/chart/chart.d.ts +32 -7
- package/dist/types/components/chart/chart.types.d.ts +15 -1
- package/dist/types/components/chart/format.d.ts +54 -0
- package/dist/types/components/chart/options.d.ts +3 -4
- package/dist/types/components/chart/scale.d.ts +39 -7
- package/dist/types/components/input-field/input-field.d.ts +1 -0
- package/dist/types/components/pagination/pagination.d.ts +6 -12
- package/dist/types/components/picker/picker.d.ts +10 -2
- package/dist/types/components.d.ts +36 -8
- package/dist/types/util/language.d.ts +19 -0
- package/package.json +2 -2
- package/dist/cjs/language-v-MMS6ym.js +0 -28
- package/dist/esm/language-ChagAaNf.js +0 -26
- package/dist/lime-elements/p-7d6a12fe.entry.js +0 -56
- package/dist/lime-elements/p-ChagAaNf.js +0 -1
- package/dist/lime-elements/p-a807a45f.entry.js +0 -159
- package/dist/lime-elements/p-cd479c23.entry.js +0 -1
|
@@ -6,7 +6,8 @@ import { SVGRenderer } from "echarts/renderers";
|
|
|
6
6
|
import translate from "../../global/translations";
|
|
7
7
|
import { findNavigationTarget, findTabStop, isNavigationKey, NO_NAVIGATION_TARGET, } from "../../util/keyboard-navigation";
|
|
8
8
|
import { drawChart, fitItemTexts, getItemIndex, getItemLabel, locateItem, } from "./options";
|
|
9
|
-
import {
|
|
9
|
+
import { getAxisRange, isDate, MAX_AXIS_STEPS, usesAxisIncrement, } from "./scale";
|
|
10
|
+
import { canWriteIn, formatValue, isCurrencyCode } from "./format";
|
|
10
11
|
import { createThemeReader, watchTheme, } from "./theme";
|
|
11
12
|
const orientations = ['horizontal', 'vertical'];
|
|
12
13
|
const extensions = [
|
|
@@ -29,6 +30,8 @@ const extensions = [
|
|
|
29
30
|
* @exampleComponent limel-example-chart-orientation
|
|
30
31
|
* @exampleComponent limel-example-chart-max-value
|
|
31
32
|
* @exampleComponent limel-example-chart-display-item-percentage
|
|
33
|
+
* @exampleComponent limel-example-chart-value-format
|
|
34
|
+
* @exampleComponent limel-example-chart-value-format-date
|
|
32
35
|
* @exampleComponent limel-example-chart-type-bar
|
|
33
36
|
* @exampleComponent limel-example-chart-type-dot
|
|
34
37
|
* @exampleComponent limel-example-chart-type-area
|
|
@@ -53,8 +56,8 @@ const extensions = [
|
|
|
53
56
|
export class Chart {
|
|
54
57
|
constructor() {
|
|
55
58
|
/**
|
|
56
|
-
* Defines the language for translations
|
|
57
|
-
*
|
|
59
|
+
* Defines the language for translations, and for the way values are
|
|
60
|
+
* written, such as `1 234,5` in Swedish.
|
|
58
61
|
*/
|
|
59
62
|
this.language = 'en';
|
|
60
63
|
/**
|
|
@@ -94,6 +97,16 @@ export class Chart {
|
|
|
94
97
|
* Does not have any effect on chart types which generate circular forms.
|
|
95
98
|
*/
|
|
96
99
|
this.orientation = 'landscape';
|
|
100
|
+
/**
|
|
101
|
+
* Defines how values are written: as numbers, as percentages where
|
|
102
|
+
* `0.25` is `25 %`, as amounts of `currency`, or as dates. With `date`,
|
|
103
|
+
* each value is a date in milliseconds since 1970, and the value axis
|
|
104
|
+
* steps through days, weeks, months or years. The axes, the values
|
|
105
|
+
* shown with `displayItemValue`, the tooltips, and the screen reader
|
|
106
|
+
* table all follow it, in the chart's `language`. An item's
|
|
107
|
+
* `formattedValue` is shown as it is.
|
|
108
|
+
*/
|
|
109
|
+
this.valueFormat = 'number';
|
|
97
110
|
/**
|
|
98
111
|
* Indicates whether the chart is in a loading state.
|
|
99
112
|
*/
|
|
@@ -103,6 +116,7 @@ export class Chart {
|
|
|
103
116
|
*/
|
|
104
117
|
this.focusedIndex = 0;
|
|
105
118
|
this.themeMayHaveChanged = false;
|
|
119
|
+
this.warnings = new Map();
|
|
106
120
|
this.invalidColors = new Set();
|
|
107
121
|
this.setPlot = (element) => {
|
|
108
122
|
this.plot = element;
|
|
@@ -167,9 +181,14 @@ export class Chart {
|
|
|
167
181
|
};
|
|
168
182
|
}
|
|
169
183
|
componentWillLoad() {
|
|
170
|
-
this.warnAboutAxisIncrement();
|
|
171
184
|
this.warnAboutOldTypeName();
|
|
172
185
|
}
|
|
186
|
+
componentWillRender() {
|
|
187
|
+
this.warnOnChange('axisIncrement', this.getAxisIncrementWarning());
|
|
188
|
+
this.warnOnChange('language', this.getLanguageWarning());
|
|
189
|
+
this.warnOnChange('currency', this.getCurrencyWarning());
|
|
190
|
+
this.warnOnChange('items', this.getDateWarning());
|
|
191
|
+
}
|
|
173
192
|
connectedCallback() {
|
|
174
193
|
this.draw();
|
|
175
194
|
}
|
|
@@ -186,25 +205,60 @@ export class Chart {
|
|
|
186
205
|
(_c = this.themeReader) === null || _c === void 0 ? void 0 : _c.dispose();
|
|
187
206
|
this.themeReader = undefined;
|
|
188
207
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (warning && warning !== this.axisIncrementWarning) {
|
|
208
|
+
warnOnChange(prop, warning) {
|
|
209
|
+
if (warning && warning !== this.warnings.get(prop)) {
|
|
192
210
|
console.warn(warning);
|
|
193
211
|
}
|
|
194
|
-
this.
|
|
212
|
+
this.warnings.set(prop, warning);
|
|
195
213
|
}
|
|
196
214
|
warnAboutOldTypeName() {
|
|
197
215
|
if (this.type === 'stacked-bar') {
|
|
198
216
|
console.warn('limel-chart: the type `stacked-bar` is now called `proportion-bar`. It draws the same chart, but please use the new name.');
|
|
199
217
|
}
|
|
200
218
|
}
|
|
219
|
+
getLanguageWarning() {
|
|
220
|
+
if (canWriteIn(this.language)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
return `limel-chart: \`language\` must be one that this browser can write values in, such as \`sv\`, but was ${JSON.stringify(this.language)}. Writing the values in English instead.`;
|
|
224
|
+
}
|
|
225
|
+
getCurrencyWarning() {
|
|
226
|
+
if (this.valueFormat !== 'currency' || isCurrencyCode(this.currency)) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
return `limel-chart: \`currency\` must be a three-letter code such as \`SEK\` when \`valueFormat\` is \`currency\`, but was ${JSON.stringify(this.currency)}. Writing the values as numbers instead.`;
|
|
230
|
+
}
|
|
231
|
+
getDateWarning() {
|
|
232
|
+
var _a;
|
|
233
|
+
if (this.valueFormat !== 'date') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const wrong = ((_a = this.items) !== null && _a !== void 0 ? _a : [])
|
|
237
|
+
.flatMap((item) => [item.value].flat())
|
|
238
|
+
.find((value) => !isDate(value));
|
|
239
|
+
if (wrong === undefined) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const shown = typeof wrong === 'number' ? wrong : JSON.stringify(wrong);
|
|
243
|
+
return `limel-chart: with \`valueFormat\` \`date\`, each value must be a date in milliseconds since 1970, as \`Date.getTime()\` gives, but was ${shown}. Showing it as it is.`;
|
|
244
|
+
}
|
|
245
|
+
get valueStyle() {
|
|
246
|
+
return {
|
|
247
|
+
type: this.chartType,
|
|
248
|
+
language: this.language,
|
|
249
|
+
valueFormat: this.valueFormat,
|
|
250
|
+
currency: this.currency,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
201
253
|
get chartType() {
|
|
202
254
|
return this.type === 'stacked-bar' ? 'proportion-bar' : this.type;
|
|
203
255
|
}
|
|
204
256
|
getAxisIncrementWarning() {
|
|
205
257
|
var _a;
|
|
206
258
|
const increment = this.axisIncrement;
|
|
207
|
-
if (increment === undefined ||
|
|
259
|
+
if (increment === undefined ||
|
|
260
|
+
increment === null ||
|
|
261
|
+
this.valueFormat === 'date') {
|
|
208
262
|
return;
|
|
209
263
|
}
|
|
210
264
|
if (increment <= 0 || Number.isNaN(increment)) {
|
|
@@ -244,15 +298,10 @@ export class Chart {
|
|
|
244
298
|
renderItems() {
|
|
245
299
|
var _a, _b;
|
|
246
300
|
const tabStop = findTabStop((_a = this.items) !== null && _a !== void 0 ? _a : [], this.focusedIndex);
|
|
247
|
-
return (_b = this.items) === null || _b === void 0 ? void 0 : _b.map((item, index) => (h("tr", { "data-index": index, "aria-description": this.getRowDescription(index), tabIndex: index === tabStop ? 0 : -1, role: item.clickable ? 'button' : null, onClick: this.handleClick, onKeyDown: this.handleKeyDown, onFocus: this.handleFocus, onBlur: this.handleBlur }, h("td", null, item.text), h("td", null, formatValue(item, this.
|
|
301
|
+
return (_b = this.items) === null || _b === void 0 ? void 0 : _b.map((item, index) => (h("tr", { "data-index": index, "aria-description": this.getRowDescription(index), tabIndex: index === tabStop ? 0 : -1, role: item.clickable ? 'button' : null, onClick: this.handleClick, onKeyDown: this.handleKeyDown, onFocus: this.handleFocus, onBlur: this.handleBlur }, h("td", null, item.text), h("td", null, formatValue(item, this.valueStyle)))));
|
|
248
302
|
}
|
|
249
303
|
getRowDescription(index) {
|
|
250
|
-
const label = getItemLabel({
|
|
251
|
-
items: this.items,
|
|
252
|
-
type: this.chartType,
|
|
253
|
-
maxValue: this.maxValue,
|
|
254
|
-
displayItemPercentage: this.displayItemPercentage,
|
|
255
|
-
}, index);
|
|
304
|
+
const label = getItemLabel(Object.assign(Object.assign({}, this.valueStyle), { items: this.items, maxValue: this.maxValue, displayItemPercentage: this.displayItemPercentage }), index);
|
|
256
305
|
return label === this.items[index].text ? undefined : label;
|
|
257
306
|
}
|
|
258
307
|
draw() {
|
|
@@ -375,25 +424,13 @@ export class Chart {
|
|
|
375
424
|
}
|
|
376
425
|
getSettings() {
|
|
377
426
|
var _a;
|
|
378
|
-
return {
|
|
379
|
-
items: ((_a = this.items) !== null && _a !== void 0 ? _a : []).map((item) => ({
|
|
427
|
+
return Object.assign(Object.assign({ items: ((_a = this.items) !== null && _a !== void 0 ? _a : []).map((item) => ({
|
|
380
428
|
text: item.text,
|
|
381
429
|
value: item.value,
|
|
382
430
|
formattedValue: item.formattedValue,
|
|
383
431
|
color: item.color,
|
|
384
432
|
clickable: item.clickable,
|
|
385
|
-
})),
|
|
386
|
-
type: this.chartType,
|
|
387
|
-
orientation: this.orientation,
|
|
388
|
-
maxValue: this.maxValue,
|
|
389
|
-
axisIncrement: this.axisIncrement,
|
|
390
|
-
displayAxisLabels: this.displayAxisLabels,
|
|
391
|
-
itemsLabel: this.itemsLabel,
|
|
392
|
-
valuesLabel: this.valuesLabel,
|
|
393
|
-
displayItemText: this.displayItemText,
|
|
394
|
-
displayItemValue: this.displayItemValue,
|
|
395
|
-
displayItemPercentage: this.displayItemPercentage,
|
|
396
|
-
};
|
|
433
|
+
})) }, this.valueStyle), { orientation: this.orientation, maxValue: this.maxValue, axisIncrement: this.axisIncrement, displayAxisLabels: this.displayAxisLabels, itemsLabel: this.itemsLabel, valuesLabel: this.valuesLabel, displayItemText: this.displayItemText, displayItemValue: this.displayItemValue, displayItemPercentage: this.displayItemPercentage });
|
|
397
434
|
}
|
|
398
435
|
withItemColors(settings, theme) {
|
|
399
436
|
return Object.assign(Object.assign({}, settings), { items: settings.items.map((item) => (Object.assign(Object.assign({}, item), { color: this.getItemColor(item, theme) }))) });
|
|
@@ -499,7 +536,7 @@ export class Chart {
|
|
|
499
536
|
"optional": false,
|
|
500
537
|
"docs": {
|
|
501
538
|
"tags": [],
|
|
502
|
-
"text": "Defines the language for translations
|
|
539
|
+
"text": "Defines the language for translations, and for the way values are\nwritten, such as `1 234,5` in Swedish."
|
|
503
540
|
},
|
|
504
541
|
"getter": false,
|
|
505
542
|
"setter": false,
|
|
@@ -727,7 +764,7 @@ export class Chart {
|
|
|
727
764
|
"optional": true,
|
|
728
765
|
"docs": {
|
|
729
766
|
"tags": [],
|
|
730
|
-
"text": "Sets the value used to scale item values.\nFor `proportion-bar`, `pie`, `doughnut`, and `ring`, it is the denominator\nused as the whole. It defaults to the combined item values or range\nextents. The combined size can exceed `maxValue` and produce a total\nabove 100%.\nFor `bar`, `dot`, `area`, and `line`, it is the requested upper bound.\nThe chart can round that bound up to the next `axisIncrement`.\nHas no effect on `nps` or `scatter`."
|
|
767
|
+
"text": "Sets the value used to scale item values.\nFor `proportion-bar`, `pie`, `doughnut`, and `ring`, it is the denominator\nused as the whole. It defaults to the combined item values or range\nextents. The combined size can exceed `maxValue` and produce a total\nabove 100%.\nFor `bar`, `dot`, `area`, and `line`, it is the requested upper bound.\nThe chart can round that bound up to the next `axisIncrement`, or to\nthe end of its day when `valueFormat` is `date`.\nHas no effect on `nps` or `scatter`."
|
|
731
768
|
},
|
|
732
769
|
"getter": false,
|
|
733
770
|
"setter": false,
|
|
@@ -746,13 +783,59 @@ export class Chart {
|
|
|
746
783
|
"optional": true,
|
|
747
784
|
"docs": {
|
|
748
785
|
"tags": [],
|
|
749
|
-
"text": "Sets the axis increment for `bar`, `dot`, `area`, and `line` charts.\nWhen it is not set, or not above zero, the chart works out a step\nfrom the values. It also does that when the step would divide the\naxis into more than 100 parts, so that it does not draw too many lines.\nHas no effect on other chart types."
|
|
786
|
+
"text": "Sets the axis increment for `bar`, `dot`, `area`, and `line` charts.\nWhen it is not set, or not above zero, the chart works out a step\nfrom the values. It also does that when the step would divide the\naxis into more than 100 parts, so that it does not draw too many lines.\nHas no effect on other chart types, or when `valueFormat` is `date`,\nwhere the axis steps through days, weeks, months or years by itself."
|
|
750
787
|
},
|
|
751
788
|
"getter": false,
|
|
752
789
|
"setter": false,
|
|
753
790
|
"reflect": true,
|
|
754
791
|
"attribute": "axis-increment"
|
|
755
792
|
},
|
|
793
|
+
"valueFormat": {
|
|
794
|
+
"type": "string",
|
|
795
|
+
"mutable": false,
|
|
796
|
+
"complexType": {
|
|
797
|
+
"original": "ChartValueFormat",
|
|
798
|
+
"resolved": "\"currency\" | \"date\" | \"number\" | \"percent\"",
|
|
799
|
+
"references": {
|
|
800
|
+
"ChartValueFormat": {
|
|
801
|
+
"location": "import",
|
|
802
|
+
"path": "./chart.types",
|
|
803
|
+
"id": "src/components/chart/chart.types.ts::ChartValueFormat",
|
|
804
|
+
"referenceLocation": "ChartValueFormat"
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
"required": false,
|
|
809
|
+
"optional": false,
|
|
810
|
+
"docs": {
|
|
811
|
+
"tags": [],
|
|
812
|
+
"text": "Defines how values are written: as numbers, as percentages where\n`0.25` is `25 %`, as amounts of `currency`, or as dates. With `date`,\neach value is a date in milliseconds since 1970, and the value axis\nsteps through days, weeks, months or years. The axes, the values\nshown with `displayItemValue`, the tooltips, and the screen reader\ntable all follow it, in the chart's `language`. An item's\n`formattedValue` is shown as it is."
|
|
813
|
+
},
|
|
814
|
+
"getter": false,
|
|
815
|
+
"setter": false,
|
|
816
|
+
"reflect": true,
|
|
817
|
+
"attribute": "value-format",
|
|
818
|
+
"defaultValue": "'number'"
|
|
819
|
+
},
|
|
820
|
+
"currency": {
|
|
821
|
+
"type": "string",
|
|
822
|
+
"mutable": false,
|
|
823
|
+
"complexType": {
|
|
824
|
+
"original": "string",
|
|
825
|
+
"resolved": "string",
|
|
826
|
+
"references": {}
|
|
827
|
+
},
|
|
828
|
+
"required": false,
|
|
829
|
+
"optional": true,
|
|
830
|
+
"docs": {
|
|
831
|
+
"tags": [],
|
|
832
|
+
"text": "The currency of the values when `valueFormat` is `currency`, as a\nthree-letter code such as `SEK`. Without one, the values are written\nas numbers."
|
|
833
|
+
},
|
|
834
|
+
"getter": false,
|
|
835
|
+
"setter": false,
|
|
836
|
+
"reflect": true,
|
|
837
|
+
"attribute": "currency"
|
|
838
|
+
},
|
|
756
839
|
"loading": {
|
|
757
840
|
"type": "boolean",
|
|
758
841
|
"mutable": false,
|
|
@@ -808,18 +891,6 @@ export class Chart {
|
|
|
808
891
|
static get elementRef() { return "host"; }
|
|
809
892
|
static get watchers() {
|
|
810
893
|
return [{
|
|
811
|
-
"propName": "axisIncrement",
|
|
812
|
-
"methodName": "warnAboutAxisIncrement"
|
|
813
|
-
}, {
|
|
814
|
-
"propName": "items",
|
|
815
|
-
"methodName": "warnAboutAxisIncrement"
|
|
816
|
-
}, {
|
|
817
|
-
"propName": "maxValue",
|
|
818
|
-
"methodName": "warnAboutAxisIncrement"
|
|
819
|
-
}, {
|
|
820
|
-
"propName": "type",
|
|
821
|
-
"methodName": "warnAboutAxisIncrement"
|
|
822
|
-
}, {
|
|
823
894
|
"propName": "type",
|
|
824
895
|
"methodName": "warnAboutOldTypeName"
|
|
825
896
|
}];
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { getSupportedLocale } from "../../util/language";
|
|
2
|
+
import { getMaximumValue, getMinimumValue, isDate, } from "./scale";
|
|
3
|
+
const FALLBACK_LOCALE = 'en';
|
|
4
|
+
const PERCENT = 100;
|
|
5
|
+
/**
|
|
6
|
+
* The largest value on an axis from which its values are shortened,
|
|
7
|
+
* such as `1.2M` for 1 200 000.
|
|
8
|
+
*/
|
|
9
|
+
const SHORTEN_FROM = 10000;
|
|
10
|
+
/**
|
|
11
|
+
* The most decimals `Intl` writes in every browser.
|
|
12
|
+
*/
|
|
13
|
+
const MAX_DECIMALS = 20;
|
|
14
|
+
/**
|
|
15
|
+
* The most significant digits `Intl` writes.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_SIGNIFICANT_DIGITS = 21;
|
|
18
|
+
/**
|
|
19
|
+
* Up to three decimals, and six significant digits below one, so `0.0004`
|
|
20
|
+
* is not written as `0`.
|
|
21
|
+
*/
|
|
22
|
+
const NUMBERS = {
|
|
23
|
+
options: { maximumFractionDigits: 3 },
|
|
24
|
+
small: { maximumSignificantDigits: 6 },
|
|
25
|
+
smallBelow: 1,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Up to one decimal, and two significant digits below 1 %, so `0.0004` is
|
|
29
|
+
* written as `0.04%` rather than `0%`.
|
|
30
|
+
*/
|
|
31
|
+
const PERCENTAGES = {
|
|
32
|
+
options: { style: 'percent', maximumFractionDigits: 1 },
|
|
33
|
+
small: { style: 'percent', maximumSignificantDigits: 2 },
|
|
34
|
+
smallBelow: 0.01,
|
|
35
|
+
};
|
|
36
|
+
const SHARE_OPTIONS = {
|
|
37
|
+
style: 'percent',
|
|
38
|
+
minimumFractionDigits: 2,
|
|
39
|
+
maximumFractionDigits: 2,
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The ticks of an axis of dates that are not known, whose dates are all
|
|
43
|
+
* written with their year.
|
|
44
|
+
*/
|
|
45
|
+
const UNKNOWN_DATE_TICKS = { values: [], unit: 'day' };
|
|
46
|
+
const locales = new Map();
|
|
47
|
+
const numberFormats = new Map();
|
|
48
|
+
const currencyCodes = new Map();
|
|
49
|
+
const dateFormats = new Map();
|
|
50
|
+
/**
|
|
51
|
+
* The item's value as text: its `formattedValue` when set, otherwise its
|
|
52
|
+
* value, range, or scatter point, written in the chart's language and
|
|
53
|
+
* value format.
|
|
54
|
+
* @param item - the item
|
|
55
|
+
* @param style - how the chart writes values
|
|
56
|
+
*/
|
|
57
|
+
export function formatValue(item, style) {
|
|
58
|
+
const { value, formattedValue } = item;
|
|
59
|
+
if (formattedValue) {
|
|
60
|
+
return formattedValue;
|
|
61
|
+
}
|
|
62
|
+
const write = getValueWriter(style);
|
|
63
|
+
if (!Array.isArray(value)) {
|
|
64
|
+
return write(value);
|
|
65
|
+
}
|
|
66
|
+
const [first, second] = value.map((part) => write(part));
|
|
67
|
+
if (style.type === 'scatter') {
|
|
68
|
+
return writePoint(first, second);
|
|
69
|
+
}
|
|
70
|
+
const [start, end] = [getMinimumValue(item), getMaximumValue(item)];
|
|
71
|
+
if (style.valueFormat === 'date' && isDate(start) && isDate(end)) {
|
|
72
|
+
return getDateFormat(style.language).formatRange(start, end);
|
|
73
|
+
}
|
|
74
|
+
return `${first} — ${second}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Writes the values of an axis in the chart's language and value format,
|
|
78
|
+
* with as many decimals as the step between them has. Amounts leave out
|
|
79
|
+
* their currency, which the item values and tooltips show. On an axis
|
|
80
|
+
* that reaches 10 000 or more, every number is shortened, such as `1.2M`,
|
|
81
|
+
* so the values stay short and alike. Dates are written by what the axis
|
|
82
|
+
* steps through: days, months or years, with the year on the first date
|
|
83
|
+
* and on the first date of each new year.
|
|
84
|
+
* @param range - the range of the axis
|
|
85
|
+
* @param style - how the chart writes values
|
|
86
|
+
* @param dates - the dates an axis of dates marks
|
|
87
|
+
*/
|
|
88
|
+
export function getAxisValueFormatter(range, style, dates) {
|
|
89
|
+
const locale = getLocale(style.language);
|
|
90
|
+
if (style.valueFormat === 'date') {
|
|
91
|
+
return getDateAxisFormatter(locale, dates);
|
|
92
|
+
}
|
|
93
|
+
if (style.valueFormat === 'percent') {
|
|
94
|
+
const percentages = getNumberFormat(locale, {
|
|
95
|
+
style: 'percent',
|
|
96
|
+
maximumFractionDigits: countDecimals(range.increment * PERCENT),
|
|
97
|
+
});
|
|
98
|
+
return (value) => percentages.format(value);
|
|
99
|
+
}
|
|
100
|
+
const largest = Math.max(Math.abs(range.minValue), Math.abs(range.maxValue));
|
|
101
|
+
const numbers = getNumberFormat(locale, largest >= SHORTEN_FROM
|
|
102
|
+
? {
|
|
103
|
+
notation: 'compact',
|
|
104
|
+
maximumSignificantDigits: countSignificantDigits(largest, range.increment),
|
|
105
|
+
}
|
|
106
|
+
: { maximumFractionDigits: countDecimals(range.increment) });
|
|
107
|
+
return (value) => numbers.format(value);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* An item's share of the whole as a percentage with two decimals, such as
|
|
111
|
+
* `25.00%`, written in the chart's language.
|
|
112
|
+
* @param share - the share, in percent
|
|
113
|
+
* @param language - the language of the chart
|
|
114
|
+
*/
|
|
115
|
+
export function formatShare(share, language) {
|
|
116
|
+
return getNumberFormat(getLocale(language), SHARE_OPTIONS).format(share / PERCENT);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether `currency` can be written as one: a three-letter code such as
|
|
120
|
+
* `SEK`.
|
|
121
|
+
* @param currency - the currency of the chart
|
|
122
|
+
*/
|
|
123
|
+
export function isCurrencyCode(currency) {
|
|
124
|
+
let valid = currencyCodes.get(currency);
|
|
125
|
+
if (valid === undefined) {
|
|
126
|
+
valid = canWriteAmountsIn(currency);
|
|
127
|
+
currencyCodes.set(currency, valid);
|
|
128
|
+
}
|
|
129
|
+
return valid;
|
|
130
|
+
}
|
|
131
|
+
function canWriteAmountsIn(currency) {
|
|
132
|
+
try {
|
|
133
|
+
getNumberFormat(FALLBACK_LOCALE, { style: 'currency', currency });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch (_a) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether values can be written in `language`. When they cannot, they are
|
|
142
|
+
* written in English.
|
|
143
|
+
* @param language - the language of the chart
|
|
144
|
+
*/
|
|
145
|
+
export function canWriteIn(language) {
|
|
146
|
+
return findLocale(language) !== undefined;
|
|
147
|
+
}
|
|
148
|
+
function getValueWriter(style) {
|
|
149
|
+
if (style.valueFormat === 'date') {
|
|
150
|
+
const dates = getDateFormat(style.language);
|
|
151
|
+
return (value) => (isDate(value) ? dates.format(value) : `${value}`);
|
|
152
|
+
}
|
|
153
|
+
const numbers = getValueFormat(style);
|
|
154
|
+
return (value) => numbers.format(value);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Writes the dates on an axis by what it steps through: day and month for
|
|
158
|
+
* days and weeks, the month for months, and the year for years. The first
|
|
159
|
+
* date, and the first date of each new year, also get their year, except
|
|
160
|
+
* January among months, which is written as its year alone.
|
|
161
|
+
* @param locale - the locale of the chart
|
|
162
|
+
* @param ticks - the dates the axis marks
|
|
163
|
+
*/
|
|
164
|
+
function getDateAxisFormatter(locale, ticks = UNKNOWN_DATE_TICKS) {
|
|
165
|
+
const years = getDateTimeFormat(locale, { year: 'numeric' });
|
|
166
|
+
if (ticks.unit === 'year') {
|
|
167
|
+
return (value) => years.format(value);
|
|
168
|
+
}
|
|
169
|
+
const parts = ticks.unit === 'month'
|
|
170
|
+
? { month: 'short' }
|
|
171
|
+
: { day: 'numeric', month: 'short' };
|
|
172
|
+
const dates = getDateTimeFormat(locale, parts);
|
|
173
|
+
const datesWithYear = getDateTimeFormat(locale, Object.assign(Object.assign({}, parts), { year: 'numeric' }));
|
|
174
|
+
return (value) => {
|
|
175
|
+
if (!isFirstOfItsYear(value, ticks.values)) {
|
|
176
|
+
return dates.format(value);
|
|
177
|
+
}
|
|
178
|
+
return ticks.unit === 'month' && new Date(value).getMonth() === 0
|
|
179
|
+
? years.format(value)
|
|
180
|
+
: datesWithYear.format(value);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Whether `value` is the first of `ticks` in its year. A date that is not
|
|
185
|
+
* one of them counts as the first, so that it gets its year.
|
|
186
|
+
* @param value - the date
|
|
187
|
+
* @param ticks - the dates an axis marks
|
|
188
|
+
*/
|
|
189
|
+
function isFirstOfItsYear(value, ticks) {
|
|
190
|
+
const index = ticks.indexOf(value);
|
|
191
|
+
return (index <= 0 ||
|
|
192
|
+
new Date(ticks[index - 1]).getFullYear() !==
|
|
193
|
+
new Date(value).getFullYear());
|
|
194
|
+
}
|
|
195
|
+
function getDateFormat(language) {
|
|
196
|
+
return getDateTimeFormat(getLocale(language), { dateStyle: 'medium' });
|
|
197
|
+
}
|
|
198
|
+
function getValueFormat(style) {
|
|
199
|
+
const locale = getLocale(style.language);
|
|
200
|
+
if (style.valueFormat === 'percent') {
|
|
201
|
+
return getNumberWriter(locale, PERCENTAGES);
|
|
202
|
+
}
|
|
203
|
+
if (style.valueFormat === 'currency' && isCurrencyCode(style.currency)) {
|
|
204
|
+
return getNumberFormat(locale, {
|
|
205
|
+
style: 'currency',
|
|
206
|
+
currency: style.currency,
|
|
207
|
+
trailingZeroDisplay: 'stripIfInteger',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return getNumberWriter(locale, NUMBERS);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The two values of a scatter point, separated by a semicolon when either
|
|
214
|
+
* of them has a comma in it, such as `(1,5; 2,5)` in Swedish.
|
|
215
|
+
* @param first - the first value, as text
|
|
216
|
+
* @param second - the second value, as text
|
|
217
|
+
*/
|
|
218
|
+
function writePoint(first, second) {
|
|
219
|
+
const separator = `${first}${second}`.includes(',') ? '; ' : ', ';
|
|
220
|
+
return `(${first}${separator}${second})`;
|
|
221
|
+
}
|
|
222
|
+
function countDecimals(value) {
|
|
223
|
+
if (!Number.isFinite(value)) {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
return Math.min(MAX_DECIMALS, Math.max(0, -getLowestDigit(value)));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* How many significant digits the values of an axis need for every step
|
|
230
|
+
* between them to show, such as four for `1.025M` on an axis that steps
|
|
231
|
+
* by 25 000 up to 2 000 000.
|
|
232
|
+
* @param largest - the largest value on the axis, ignoring its sign
|
|
233
|
+
* @param increment - the step between the values
|
|
234
|
+
*/
|
|
235
|
+
function countSignificantDigits(largest, increment) {
|
|
236
|
+
if (!Number.isFinite(increment)) {
|
|
237
|
+
return MAX_SIGNIFICANT_DIGITS;
|
|
238
|
+
}
|
|
239
|
+
const digits = Math.floor(Math.log10(largest)) - getLowestDigit(increment);
|
|
240
|
+
return Math.min(MAX_SIGNIFICANT_DIGITS, Math.max(1, digits + 1));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The power of ten of the last digit of `value` that is not zero, such as
|
|
244
|
+
* 3 for 25 000 and -2 for 0.25.
|
|
245
|
+
* @param value - a finite number
|
|
246
|
+
*/
|
|
247
|
+
function getLowestDigit(value) {
|
|
248
|
+
var _a, _b;
|
|
249
|
+
const [digits, exponent] = Number(value.toPrecision(12))
|
|
250
|
+
.toExponential()
|
|
251
|
+
.split('e', 2);
|
|
252
|
+
const decimals = (_b = (_a = digits.split('.', 2)[1]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
|
|
253
|
+
return Number(exponent) - decimals;
|
|
254
|
+
}
|
|
255
|
+
function getNumberWriter(locale, style) {
|
|
256
|
+
const numbers = getNumberFormat(locale, style.options);
|
|
257
|
+
const small = getNumberFormat(locale, style.small);
|
|
258
|
+
return {
|
|
259
|
+
format: (value) => value !== 0 && Math.abs(value) < style.smallBelow
|
|
260
|
+
? small.format(value)
|
|
261
|
+
: numbers.format(value),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Making an `Intl.NumberFormat` costs far more than using one, and values
|
|
266
|
+
* are written on every render, so each one is made once.
|
|
267
|
+
* @param locale - the locale to write in
|
|
268
|
+
* @param options - how to write the numbers
|
|
269
|
+
*/
|
|
270
|
+
function getNumberFormat(locale, options) {
|
|
271
|
+
const key = `${locale} ${JSON.stringify(options)}`;
|
|
272
|
+
let format = numberFormats.get(key);
|
|
273
|
+
if (!format) {
|
|
274
|
+
format = new Intl.NumberFormat(locale, options);
|
|
275
|
+
numberFormats.set(key, format);
|
|
276
|
+
}
|
|
277
|
+
return format;
|
|
278
|
+
}
|
|
279
|
+
function getDateTimeFormat(locale, options) {
|
|
280
|
+
const key = `${locale} ${JSON.stringify(options)}`;
|
|
281
|
+
let format = dateFormats.get(key);
|
|
282
|
+
if (!format) {
|
|
283
|
+
format = new Intl.DateTimeFormat(locale, options);
|
|
284
|
+
dateFormats.set(key, format);
|
|
285
|
+
}
|
|
286
|
+
return format;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The locale to write values in: the chart's language when `Intl` can
|
|
290
|
+
* write in it, otherwise English.
|
|
291
|
+
* @param language - the language of the chart
|
|
292
|
+
*/
|
|
293
|
+
function getLocale(language) {
|
|
294
|
+
var _a;
|
|
295
|
+
return (_a = findLocale(language)) !== null && _a !== void 0 ? _a : FALLBACK_LOCALE;
|
|
296
|
+
}
|
|
297
|
+
function findLocale(language) {
|
|
298
|
+
if (!locales.has(language)) {
|
|
299
|
+
locales.set(language, getSupportedLocale(language));
|
|
300
|
+
}
|
|
301
|
+
return locales.get(language);
|
|
302
|
+
}
|