@circutor/charts 0.3.0 → 0.3.1
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/bundler/itic-chart.js +1 -1
- package/dist/charts/charts.esm.js +1 -1
- package/dist/charts/{p-7d11d611.entry.js → p-70293967.entry.js} +2 -2
- package/dist/cjs/itic-chart.cjs.entry.js +5 -3
- package/dist/collection/components/itic-chart/index.js +4 -4
- package/dist/collection/components/itic-chart/itic-chart.options.js +3 -2
- package/dist/collection/playground/itic-chart.scenarios.js +129 -0
- package/dist/collection/playground/playground.js +170 -0
- package/dist/components/itic-chart.js +1 -1
- package/dist/esm/itic-chart.entry.js +5 -3
- package/dist/types/components/itic-chart/index.d.ts +1 -1
- package/dist/types/components.d.ts +1 -1
- package/package.json +2 -2
|
@@ -45408,7 +45408,9 @@ function parseGroups(groups) {
|
|
|
45408
45408
|
}
|
|
45409
45409
|
function isValidTimeZone(timeZone) {
|
|
45410
45410
|
try {
|
|
45411
|
-
|
|
45411
|
+
// The result must be used: Rollup and terser drop an unused `new Intl.DateTimeFormat()` as side-effect free,
|
|
45412
|
+
// which turned this check into `return true` in the built packages
|
|
45413
|
+
return Boolean(new Intl.DateTimeFormat('en', { timeZone }).resolvedOptions().timeZone);
|
|
45412
45414
|
}
|
|
45413
45415
|
catch (_a) {
|
|
45414
45416
|
return false;
|
|
@@ -45741,7 +45743,7 @@ const IticChart = class {
|
|
|
45741
45743
|
if (!this.timeZone) {
|
|
45742
45744
|
return;
|
|
45743
45745
|
}
|
|
45744
|
-
if (isValidTimeZone()) {
|
|
45746
|
+
if (isValidTimeZone(this.timeZone)) {
|
|
45745
45747
|
this.validTimeZone = this.timeZone;
|
|
45746
45748
|
}
|
|
45747
45749
|
else {
|
|
@@ -45783,7 +45785,7 @@ const IticChart = class {
|
|
|
45783
45785
|
this.chart.setOption(options, { notMerge: true });
|
|
45784
45786
|
}
|
|
45785
45787
|
render() {
|
|
45786
|
-
return index.h("div", { key: '
|
|
45788
|
+
return index.h("div", { key: 'e202bbf61ad19502fd76a42f37a8ea69f2d8c58b', class: "chart", ref: el => (this.container = el) });
|
|
45787
45789
|
}
|
|
45788
45790
|
get el() { return index.getElement(this); }
|
|
45789
45791
|
static get watchers() { return {
|
|
@@ -105,7 +105,7 @@ export class IticChart {
|
|
|
105
105
|
this.chart.setOption(options, { notMerge: true });
|
|
106
106
|
}
|
|
107
107
|
render() {
|
|
108
|
-
return h("div", { key: '
|
|
108
|
+
return h("div", { key: 'e202bbf61ad19502fd76a42f37a8ea69f2d8c58b', class: "chart", ref: el => (this.container = el) });
|
|
109
109
|
}
|
|
110
110
|
static get is() { return "itic-chart"; }
|
|
111
111
|
static get encapsulation() { return "shadow"; }
|
|
@@ -126,7 +126,7 @@ export class IticChart {
|
|
|
126
126
|
"mutable": false,
|
|
127
127
|
"complexType": {
|
|
128
128
|
"original": "ITICChartMarks[] | string",
|
|
129
|
-
"resolved": "ITICChartMarks[] | string",
|
|
129
|
+
"resolved": "ITICChartMarks[] | string | undefined",
|
|
130
130
|
"references": {
|
|
131
131
|
"ITICChartMarks": {
|
|
132
132
|
"location": "import",
|
|
@@ -137,7 +137,7 @@ export class IticChart {
|
|
|
137
137
|
}
|
|
138
138
|
},
|
|
139
139
|
"required": false,
|
|
140
|
-
"optional":
|
|
140
|
+
"optional": true,
|
|
141
141
|
"docs": {
|
|
142
142
|
"tags": [],
|
|
143
143
|
"text": "Point groups, as an array or a JSON string. Each group's `phase` code sets its name, color and order.\nExtra fields in groups and points are kept and passed back in `pointClick`."
|
|
@@ -172,7 +172,7 @@ export class IticChart {
|
|
|
172
172
|
"mutable": false,
|
|
173
173
|
"complexType": {
|
|
174
174
|
"original": "string",
|
|
175
|
-
"resolved": "string",
|
|
175
|
+
"resolved": "string | undefined",
|
|
176
176
|
"references": {}
|
|
177
177
|
},
|
|
178
178
|
"required": false,
|
|
@@ -17,8 +17,9 @@ export function parseGroups(groups) {
|
|
|
17
17
|
}
|
|
18
18
|
export function isValidTimeZone(timeZone) {
|
|
19
19
|
try {
|
|
20
|
-
new Intl.DateTimeFormat(
|
|
21
|
-
return true
|
|
20
|
+
// The result must be used: Rollup and terser drop an unused `new Intl.DateTimeFormat()` as side-effect free,
|
|
21
|
+
// which turned this check into `return true` in the built packages
|
|
22
|
+
return Boolean(new Intl.DateTimeFormat('en', { timeZone }).resolvedOptions().timeZone);
|
|
22
23
|
}
|
|
23
24
|
catch (_a) {
|
|
24
25
|
return false;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Sample data for the itic-chart playground page. Each scenario is a value for the `groups` prop.
|
|
2
|
+
|
|
3
|
+
const NOMINAL_VOLTS = 230;
|
|
4
|
+
|
|
5
|
+
const point = (duration, voltage, ts, extra = {}) => ({
|
|
6
|
+
duration,
|
|
7
|
+
voltage,
|
|
8
|
+
ts,
|
|
9
|
+
eventType: voltage < 10 ? 'POWER_INTERRUPT' : voltage < 90 ? 'VOLTAGE_DIP' : voltage > 110 ? 'VOLTAGE_SWELL' : 'VOLTAGE_VARIATION',
|
|
10
|
+
status: 'CLOSED',
|
|
11
|
+
min: Math.round(NOMINAL_VOLTS * Math.min(voltage, 100)) / 100,
|
|
12
|
+
max: Math.round(NOMINAL_VOLTS * Math.max(voltage, 100)) / 100,
|
|
13
|
+
...extra,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const TYPICAL = [
|
|
17
|
+
{
|
|
18
|
+
phase: 'AN',
|
|
19
|
+
assetName: 'Main switchboard',
|
|
20
|
+
points: [
|
|
21
|
+
point(230, 93.46, '2024-06-26T20:23:45.220+02:00'),
|
|
22
|
+
point(12, 62, '2024-07-02T08:11:03.051+02:00'),
|
|
23
|
+
point(4000, 85, '2024-09-14T17:45:12.900+02:00', { status: 'OPEN' }),
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
phase: 'BN',
|
|
28
|
+
assetName: 'Main switchboard',
|
|
29
|
+
points: [point(20, 0.4, '2025-01-26T23:04:34.840+01:00'), point(3, 150, '2025-02-03T10:20:00.004+01:00')],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
phase: 'CN',
|
|
33
|
+
assetName: 'Main switchboard',
|
|
34
|
+
points: [point(800, 125, '2025-03-11T13:02:47.123+01:00'), point(30000, 88, '2025-04-01T06:30:00.000+02:00')],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
phase: 'AN,BN,CN',
|
|
38
|
+
assetName: 'Main switchboard',
|
|
39
|
+
points: [point(60, 45, '2025-05-20T19:59:59.999+02:00', { status: 'OPEN' })],
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Every code of the phase catalog, plus one unknown code (shown as is, with an ECharts palette color)
|
|
44
|
+
const PHASE_CODES = ['AN', 'BN', 'CN', 'AB', 'BC', 'CA', 'A', 'B', 'C', 'AN,BN', 'AN,CN', 'BN,CN', 'AB,BC', 'AB,CA', 'BC,CA', 'AN,BN,CN', 'AB,BC,CA', 'ALL', 'NONE', 'XYZ'];
|
|
45
|
+
|
|
46
|
+
const PHASES = PHASE_CODES.map((phase, i) => ({
|
|
47
|
+
phase,
|
|
48
|
+
assetName: 'Main switchboard',
|
|
49
|
+
points: [point(2 * 1.8 ** i, 30 + i * 8, '2025-01-26T23:04:34.840+01:00')],
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
// Seeded random numbers, so the generated data is the same on every load
|
|
53
|
+
function random(seed) {
|
|
54
|
+
return () => {
|
|
55
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
56
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
57
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
58
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function manyPoints(count) {
|
|
63
|
+
const next = random(42);
|
|
64
|
+
const start = Date.parse('2025-01-01T00:00:00Z');
|
|
65
|
+
const year = 365 * 24 * 3600 * 1000;
|
|
66
|
+
|
|
67
|
+
return ['AN', 'BN', 'CN', 'AN,BN,CN'].map(phase => ({
|
|
68
|
+
phase,
|
|
69
|
+
assetName: 'Main switchboard',
|
|
70
|
+
points: Array.from({ length: count }, () => {
|
|
71
|
+
// Durations spread evenly on the log axis; most events are dips, some swells
|
|
72
|
+
const duration = Math.round(10 ** (next() * 5) * 100) / 100;
|
|
73
|
+
const voltage = Math.round((next() < 0.8 ? next() * 95 : 105 + next() * 90) * 100) / 100;
|
|
74
|
+
return point(duration, voltage, new Date(start + next() * year).toISOString());
|
|
75
|
+
}),
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Corners of the axes, plus two points outside them that ECharts doesn't draw
|
|
80
|
+
const AXIS_LIMITS = [
|
|
81
|
+
{
|
|
82
|
+
phase: 'ALL',
|
|
83
|
+
points: [
|
|
84
|
+
point(1, 0, '2025-01-26T23:04:34.840+01:00'),
|
|
85
|
+
point(1, 200, '2025-01-26T23:04:34.840+01:00'),
|
|
86
|
+
point(100000, 0, '2025-01-26T23:04:34.840+01:00'),
|
|
87
|
+
point(100000, 200, '2025-01-26T23:04:34.840+01:00'),
|
|
88
|
+
point(0.5, 100, '2025-01-26T23:04:34.840+01:00'),
|
|
89
|
+
point(1000, 250, '2025-01-26T23:04:34.840+01:00'),
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const NOT_CLICKABLE = [
|
|
95
|
+
{
|
|
96
|
+
phase: 'AN',
|
|
97
|
+
assetName: 'Main switchboard',
|
|
98
|
+
points: [
|
|
99
|
+
point(50, 40, '2025-01-26T23:04:34.840+01:00'),
|
|
100
|
+
point(500, 75, '2025-01-27T09:00:00.000+01:00', { clickable: false }),
|
|
101
|
+
point(5000, 130, '2025-01-28T15:30:00.000+01:00', { clickable: false }),
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Long names, unknown codes, HTML (must be shown as text) and missing optional fields
|
|
107
|
+
const EDGE_CASES = [
|
|
108
|
+
{
|
|
109
|
+
phase: 'AN',
|
|
110
|
+
assetName: 'Very long asset name of an industrial plant, building B, floor 3, switchboard 12 <b>not bold</b>',
|
|
111
|
+
points: [
|
|
112
|
+
point(100, 50, '2025-01-26T23:04:34.840+01:00', { eventType: 'UNKNOWN_EVENT_CODE', status: 'PENDING_REVIEW_BY_MAINTENANCE' }),
|
|
113
|
+
{ duration: 1000, voltage: 80, ts: '2025-01-26T23:04:34.840+01:00' },
|
|
114
|
+
point(10, 120, 'not a date', { min: 0, max: 0 }),
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/** @type {import('./playground.js').Scenario[]} */
|
|
120
|
+
export const scenarios = [
|
|
121
|
+
{ id: 'typical', label: 'Typical installation', data: TYPICAL },
|
|
122
|
+
{ id: 'phases', label: 'Phase catalog (colors and legend)', data: PHASES },
|
|
123
|
+
{ id: 'many', label: 'Many events (4 × 250)', data: manyPoints(250) },
|
|
124
|
+
{ id: 'empty', label: 'No events', data: [] },
|
|
125
|
+
{ id: 'axis-limits', label: 'Axis limits and points outside them', data: AXIS_LIMITS },
|
|
126
|
+
{ id: 'not-clickable', label: 'Points that are not clickable', data: NOT_CLICKABLE },
|
|
127
|
+
{ id: 'edge-cases', label: 'Long texts, unknown codes, missing fields', data: EDGE_CASES },
|
|
128
|
+
{ id: 'invalid', label: 'Invalid data (not an array)', data: { phase: 'AN', points: [] } },
|
|
129
|
+
];
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Shared UI of the playground pages: controls, data editor and event log for one component.
|
|
2
|
+
// Plain JS, copied as is to www/playground/ by the `www` output target, so it isn't compiled or bundled.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{ label: string, value: string }} Option
|
|
6
|
+
* @typedef {{ id: string, label: string, prop?: string, cssVar?: string, options: Option[], value?: string }} Control
|
|
7
|
+
* @typedef {{ id: string, label: string, data: unknown }} Scenario
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const params = new URLSearchParams(location.search);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {{
|
|
14
|
+
* element: HTMLElement,
|
|
15
|
+
* dataProp: string,
|
|
16
|
+
* scenarios: Scenario[],
|
|
17
|
+
* controls?: Control[],
|
|
18
|
+
* events?: string[],
|
|
19
|
+
* }} config
|
|
20
|
+
*/
|
|
21
|
+
export function mountPlayground({ element, dataProp, scenarios, controls = [], events = [] }) {
|
|
22
|
+
const log = createLog(document.querySelector('#log'));
|
|
23
|
+
const editor = createDataEditor(document.querySelector('#data'), value => (element[dataProp] = value));
|
|
24
|
+
|
|
25
|
+
captureConsole(log);
|
|
26
|
+
events.forEach(name => element.addEventListener(name, event => log.add(name, event.detail)));
|
|
27
|
+
|
|
28
|
+
const panel = document.querySelector('#controls');
|
|
29
|
+
|
|
30
|
+
const scenarioSelect = addSelect(panel, {
|
|
31
|
+
id: 'scenario',
|
|
32
|
+
label: 'Data',
|
|
33
|
+
options: scenarios.map(({ id, label }) => ({ value: id, label })),
|
|
34
|
+
});
|
|
35
|
+
const loadScenario = id => editor.load((scenarios.find(scenario => scenario.id === id) ?? scenarios[0]).data);
|
|
36
|
+
scenarioSelect.addEventListener('change', () => loadScenario(scenarioSelect.value));
|
|
37
|
+
loadScenario(scenarioSelect.value);
|
|
38
|
+
|
|
39
|
+
controls.forEach(control => {
|
|
40
|
+
const select = addSelect(panel, control);
|
|
41
|
+
const apply = () => {
|
|
42
|
+
const value = select.value === '' ? undefined : select.value;
|
|
43
|
+
if (control.cssVar) {
|
|
44
|
+
value === undefined ? element.style.removeProperty(control.cssVar) : element.style.setProperty(control.cssVar, value);
|
|
45
|
+
} else {
|
|
46
|
+
element[control.prop ?? control.id] = value;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
select.addEventListener('change', apply);
|
|
50
|
+
apply();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
mountSizeControls(document.querySelector('#frame'), panel);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Adds a labelled `<select>` whose value is kept in the URL, so a link reproduces the same state. */
|
|
57
|
+
function addSelect(panel, { id, label, options, value }) {
|
|
58
|
+
const field = document.createElement('label');
|
|
59
|
+
field.className = 'pg-field';
|
|
60
|
+
field.textContent = label;
|
|
61
|
+
|
|
62
|
+
const select = document.createElement('select');
|
|
63
|
+
select.name = id;
|
|
64
|
+
options.forEach(option => select.add(new Option(option.label, option.value)));
|
|
65
|
+
const initial = params.get(id) ?? value;
|
|
66
|
+
if (initial !== undefined && options.some(option => option.value === initial)) {
|
|
67
|
+
select.value = initial;
|
|
68
|
+
}
|
|
69
|
+
select.addEventListener('change', () => {
|
|
70
|
+
params.set(id, select.value);
|
|
71
|
+
history.replaceState(null, '', `?${params}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
field.append(select);
|
|
75
|
+
panel.append(field);
|
|
76
|
+
return select;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const WIDTHS = ['100%', '1024px', '800px', '480px', '320px'];
|
|
80
|
+
|
|
81
|
+
/** Width presets for the frame around the component, which can also be resized by dragging its corner. */
|
|
82
|
+
function mountSizeControls(frame, panel) {
|
|
83
|
+
const field = document.createElement('div');
|
|
84
|
+
field.className = 'pg-field';
|
|
85
|
+
field.textContent = 'Width';
|
|
86
|
+
|
|
87
|
+
const buttons = document.createElement('div');
|
|
88
|
+
buttons.className = 'pg-buttons';
|
|
89
|
+
WIDTHS.forEach(width => {
|
|
90
|
+
const button = document.createElement('button');
|
|
91
|
+
button.type = 'button';
|
|
92
|
+
button.textContent = width;
|
|
93
|
+
button.addEventListener('click', () => (frame.style.width = width));
|
|
94
|
+
buttons.append(button);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const size = document.createElement('output');
|
|
98
|
+
size.className = 'pg-size';
|
|
99
|
+
new ResizeObserver(([entry]) => {
|
|
100
|
+
const { width, height } = entry.contentRect;
|
|
101
|
+
size.textContent = `${Math.round(width)} × ${Math.round(height)} px · drag the corner to resize`;
|
|
102
|
+
}).observe(frame);
|
|
103
|
+
|
|
104
|
+
field.append(buttons, size);
|
|
105
|
+
panel.append(field);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** JSON editor for the component's data. Applied with the button or Ctrl/Cmd + Enter. */
|
|
109
|
+
function createDataEditor(root, onApply) {
|
|
110
|
+
const textarea = root.querySelector('textarea');
|
|
111
|
+
const error = root.querySelector('.pg-error');
|
|
112
|
+
|
|
113
|
+
const apply = () => {
|
|
114
|
+
try {
|
|
115
|
+
onApply(textarea.value.trim() === '' ? undefined : JSON.parse(textarea.value));
|
|
116
|
+
error.textContent = '';
|
|
117
|
+
} catch (e) {
|
|
118
|
+
error.textContent = `Not applied: ${e.message}`;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
root.querySelector('button').addEventListener('click', apply);
|
|
123
|
+
textarea.addEventListener('keydown', event => {
|
|
124
|
+
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
apply();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
load(data) {
|
|
132
|
+
textarea.value = JSON.stringify(data, null, 2);
|
|
133
|
+
apply();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Event log, newest first. Also shows the component's console errors, which designers wouldn't see otherwise. */
|
|
139
|
+
function createLog(root) {
|
|
140
|
+
const list = root.querySelector('ol');
|
|
141
|
+
root.querySelector('button').addEventListener('click', () => list.replaceChildren());
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
add(name, detail, type = 'event') {
|
|
145
|
+
const item = document.createElement('li');
|
|
146
|
+
item.className = `pg-log-${type}`;
|
|
147
|
+
|
|
148
|
+
const entry = document.createElement('details');
|
|
149
|
+
const summary = document.createElement('summary');
|
|
150
|
+
summary.textContent = `${new Date().toLocaleTimeString()} · ${name}`;
|
|
151
|
+
const body = document.createElement('pre');
|
|
152
|
+
body.textContent = typeof detail === 'string' ? detail : JSON.stringify(detail, null, 2);
|
|
153
|
+
|
|
154
|
+
entry.append(summary, body);
|
|
155
|
+
item.append(entry);
|
|
156
|
+
list.prepend(item);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function captureConsole(log) {
|
|
162
|
+
['error', 'warn'].forEach(level => {
|
|
163
|
+
const original = console[level].bind(console);
|
|
164
|
+
console[level] = (...args) => {
|
|
165
|
+
original(...args);
|
|
166
|
+
const text = args.map(arg => (arg instanceof Error ? arg.message : typeof arg === 'string' ? arg : JSON.stringify(arg))).join(' ');
|
|
167
|
+
log.add(`console.${level}`, text, level);
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|