@circutor/charts 0.2.0 → 0.3.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.
@@ -5,7 +5,7 @@ import { g as globalScripts } from './app-globals-DQuL1Twl.js';
5
5
  const defineCustomElements = async (win, options) => {
6
6
  if (typeof window === 'undefined') return undefined;
7
7
  await globalScripts();
8
- return bootstrapLazy([["itic-chart",[[1,"itic-chart",{"groups":[1],"xAxisName":[1,"x-axis-name"],"yAxisName":[1,"y-axis-name"],"tooltipFormatter":[16]},null,{"groups":[{"onOptionsChange":0}],"xAxisName":[{"onOptionsChange":0}],"yAxisName":[{"onOptionsChange":0}],"tooltipFormatter":[{"onOptionsChange":0}]}]]]], options);
8
+ return bootstrapLazy([["itic-chart",[[1,"itic-chart",{"groups":[1],"locale":[1],"timeZone":[1,"time-zone"]},null,{"groups":[{"onGroupsChange":0}],"locale":[{"onLocaleChange":0}],"timeZone":[{"onTimeZoneChange":0}]}]]]], options);
9
9
  };
10
10
 
11
11
  export { defineCustomElements };
@@ -1,26 +1,22 @@
1
1
  import { EventEmitter } from '../../stencil-public-runtime';
2
- import { type ITICChartMarks, type ITICPointClickDetail, type ITICTooltipFormatter } from './itic-chart.options';
2
+ import { type ITICChartMarks, type ITICPointClickDetail } from './itic-chart.options';
3
3
  export declare class IticChart {
4
4
  el: HTMLElement;
5
5
  /**
6
- * Point groups, as an array or a JSON string. Extra fields in groups and points are kept and passed
7
- * back in `pointClick` and `tooltipFormatter`.
6
+ * Point groups, as an array or a JSON string. Each group's `phase` code sets its name, color and order.
7
+ * Extra fields in groups and points are kept and passed back in `pointClick`.
8
8
  */
9
9
  groups: ITICChartMarks[] | string;
10
10
  /**
11
- * Name of the duration (X) axis.
11
+ * Language of the texts, dates and numbers: `ca`, `de`, `en`, `es`, `fr` or `pt`. Only the language
12
+ * is used (`es-ES` → `es`); any other value falls back to `en`.
12
13
  */
13
- xAxisName: string;
14
+ locale: string;
14
15
  /**
15
- * Name of the voltage (Y) axis.
16
+ * IANA time zone of the dates in the tooltip (e.g. `Europe/Madrid`). Defaults to the browser's,
17
+ * which is also used, with a console error, when the value isn't valid.
16
18
  */
17
- yAxisName: string;
18
- /**
19
- * Returns the HTML of a point's tooltip. JS/React property only (not an HTML attribute).
20
- * The HTML is inserted as is: escaping the values is up to the formatter.
21
- * Defaults to an English tooltip with phase, duration, voltage and timestamp.
22
- */
23
- tooltipFormatter?: ITICTooltipFormatter;
19
+ timeZone?: string;
24
20
  /**
25
21
  * Emitted when a clickable point is clicked (`clickable` isn't `false`). `detail` holds the original
26
22
  * point and group objects.
@@ -30,10 +26,18 @@ export declare class IticChart {
30
26
  private chart?;
31
27
  private resizeObserver?;
32
28
  private parsedGroups;
33
- onOptionsChange(): void;
29
+ private validTimeZone?;
30
+ /** Last `aria-label` set by the chart: any other value was set by the consumer and is kept. */
31
+ private defaultAriaLabel?;
32
+ onGroupsChange(): void;
33
+ onLocaleChange(): void;
34
+ onTimeZoneChange(): void;
34
35
  connectedCallback(): void;
36
+ componentWillLoad(): void;
35
37
  componentDidLoad(): void;
36
38
  disconnectedCallback(): void;
39
+ private updateAriaLabel;
40
+ private validateTimeZone;
37
41
  private initChart;
38
42
  private updateChart;
39
43
  render(): any;
@@ -0,0 +1,13 @@
1
+ import en from './i18n/en.json';
2
+ /** Texts of the chart. `en.json` is the reference: every other language must have the same keys. */
3
+ export type ITICMessages = typeof en;
4
+ export declare const LOCALES: readonly ["ca", "de", "en", "es", "fr", "pt"];
5
+ export type ITICLocale = (typeof LOCALES)[number];
6
+ export declare const DEFAULT_LOCALE: ITICLocale;
7
+ /** Keeps the language of a locale (`es-ES` → `es`), falling back to `en` when it isn't translated. */
8
+ export declare function normalizeLocale(locale: string | null | undefined): ITICLocale;
9
+ export declare const getMessages: (locale: ITICLocale) => ITICMessages;
10
+ /** Translated event type, or the code as is when it isn't in the catalog. */
11
+ export declare function translateEventType(messages: ITICMessages, code: string): string;
12
+ /** Translated name of a phase code that has one (`ALL`, `NONE`), or `undefined`. */
13
+ export declare function translatePhase(messages: ITICMessages, code: string): string | undefined;
@@ -1,18 +1,32 @@
1
1
  import type { EChartsOption } from 'echarts';
2
+ import { type ITICLocale } from './itic-chart.i18n';
2
3
  export type ITICPoint = {
4
+ /** Duration of the event, in ms. */
3
5
  duration: number;
6
+ /** Voltage, in % of the nominal voltage. */
4
7
  voltage: number;
8
+ /** Date of the event, ISO 8601. */
5
9
  ts: string;
6
10
  /** Whether clicking the point emits `pointClick` and shows a pointer cursor. Defaults to `true`. */
7
11
  clickable?: boolean;
8
- /** Any other field is kept as is and passed back in `pointClick` and `tooltipFormatter`. */
12
+ /** Event type code from the backend (e.g. `VOLTAGE_DIP`), translated in the tooltip. Unknown codes are shown as is. */
13
+ eventType?: string;
14
+ /** Status code from the backend, shown as is in the tooltip. */
15
+ status?: string;
16
+ /** Minimum voltage, in V. Hidden in the tooltip when missing or `0`. */
17
+ min?: number;
18
+ /** Maximum voltage, in V. Hidden in the tooltip when missing or `0`. */
19
+ max?: number;
20
+ /** Any other field is kept as is and passed back in `pointClick`. */
9
21
  [key: string]: unknown;
10
22
  };
11
23
  export type ITICChartMarks = {
24
+ /** Phase code (`AN`, `AB,BC,CA`, `ALL`, `NONE`…): sets the group's name, color and order. */
25
+ phase: string;
26
+ /** Name of the asset, shown in the tooltip. */
27
+ assetName?: string;
12
28
  points: ITICPoint[];
13
- color: string;
14
- groupName: string;
15
- /** Any other field is kept as is and passed back in `pointClick` and `tooltipFormatter`. */
29
+ /** Any other field is kept as is and passed back in `pointClick`. */
16
30
  [key: string]: unknown;
17
31
  };
18
32
  /** `detail` of the `pointClick` event: the objects passed in `groups`, not copies. */
@@ -20,30 +34,31 @@ export type ITICPointClickDetail = {
20
34
  point: ITICPoint;
21
35
  group: ITICChartMarks;
22
36
  };
23
- /**
24
- * Returns the tooltip content of a point as HTML. The result is inserted as is: escape any value
25
- * that may contain HTML.
26
- */
27
- export type ITICTooltipFormatter = (point: ITICPoint, group: ITICChartMarks) => string;
28
37
  export type ITICChartSettings = {
29
- xAxisName: string;
30
- yAxisName: string;
31
- tooltipFormatter: ITICTooltipFormatter;
38
+ locale: ITICLocale;
39
+ /** IANA time zone of the dates. `undefined` uses the browser's. Must be valid (see `isValidTimeZone`). */
40
+ timeZone?: string;
32
41
  };
42
+ export declare const DEFAULT_SETTINGS: ITICChartSettings;
33
43
  /**
34
44
  * Normalizes the `groups` prop, which arrives as an array from JS/React
35
45
  * or as a JSON string when set as an HTML attribute.
36
46
  */
37
47
  export declare function parseGroups(groups: ITICChartMarks[] | string | null | undefined): ITICChartMarks[];
48
+ export declare function isValidTimeZone(timeZone: string): boolean;
38
49
  export declare function escapeHtml(value: string): string;
39
- /** Default tooltip, in English. Escapes every value, since they come from the consumer. */
40
- export declare const defaultTooltipFormatter: ITICTooltipFormatter;
41
- export declare const DEFAULT_SETTINGS: ITICChartSettings;
50
+ /**
51
+ * Returns the tooltip HTML of a point: one row per field, in the chart's language. Rows without a value
52
+ * are left out, and every value is escaped, since it comes from the consumer's data.
53
+ */
54
+ export declare function createTooltipFormatter({ locale, timeZone }: ITICChartSettings): (point: ITICPoint, group: ITICChartMarks) => string;
55
+ /** Formats the axis labels in the chart's language; ECharts always uses English separators. */
56
+ export declare function createAxisLabelFormatter(locale: ITICLocale): (value: number) => string;
42
57
  export declare const isClickable: (point: ITICPoint) => boolean;
43
58
  export declare function buildChartOptions(groups: ITICChartMarks[], settings?: ITICChartSettings): EChartsOption;
44
59
  /**
45
- * Maps an ECharts click back to the original point and group, which `buildChartOptions` draws as
46
- * scatter series after the ITIC curve series, in the same order as `groups`.
60
+ * Maps an ECharts click back to the original point and group. `buildChartOptions` draws the groups as
61
+ * scatter series after the ITIC curve series, sorted with `sortGroups`.
47
62
  * Returns `undefined` for clicks outside the points and on points that aren't clickable.
48
63
  */
49
64
  export declare function findClickedPoint(groups: ITICChartMarks[], { componentType, seriesIndex, dataIndex }: {
@@ -0,0 +1,17 @@
1
+ import { type ITICMessages } from './itic-chart.i18n';
2
+ export type ITICPhase = {
3
+ /** Name in the legend and the tooltip. Empty for `NONE`. */
4
+ name: string;
5
+ /** Fixed color, or `undefined` to use the ECharts palette. */
6
+ color?: string;
7
+ };
8
+ /** Name, in the given language, and color of a phase code. */
9
+ export declare function getPhase(phase: unknown, messages: ITICMessages): ITICPhase;
10
+ /**
11
+ * Sorts groups by phase name, then by code (e.g. `AN` and `AB` are both "L1"), with `NONE` and `ALL` last.
12
+ * Compares characters, not with a locale's rules, so the legend is the same in every language.
13
+ * Returns a new array with the same group objects; groups with the same code keep their order.
14
+ */
15
+ export declare function sortGroups<T extends {
16
+ phase?: unknown;
17
+ }>(groups: T[]): T[];
@@ -5,28 +5,23 @@
5
5
  * It contains typing information for all components that exist in this project.
6
6
  */
7
7
  import { HTMLStencilElement, JSXBase } from "./stencil-public-runtime";
8
- import { ITICChartMarks, ITICPointClickDetail, ITICTooltipFormatter } from "./components/itic-chart/itic-chart.options";
9
- export { ITICChartMarks, ITICPointClickDetail, ITICTooltipFormatter } from "./components/itic-chart/itic-chart.options";
8
+ import { ITICChartMarks, ITICPointClickDetail } from "./components/itic-chart/itic-chart.options";
9
+ export { ITICChartMarks, ITICPointClickDetail } from "./components/itic-chart/itic-chart.options";
10
10
  export namespace Components {
11
11
  interface IticChart {
12
12
  /**
13
- * Point groups, as an array or a JSON string. Extra fields in groups and points are kept and passed back in `pointClick` and `tooltipFormatter`.
13
+ * Point groups, as an array or a JSON string. Each group's `phase` code sets its name, color and order. Extra fields in groups and points are kept and passed back in `pointClick`.
14
14
  */
15
15
  "groups": ITICChartMarks[] | string;
16
16
  /**
17
- * Returns the HTML of a point's tooltip. JS/React property only (not an HTML attribute). The HTML is inserted as is: escaping the values is up to the formatter. Defaults to an English tooltip with phase, duration, voltage and timestamp.
17
+ * Language of the texts, dates and numbers: `ca`, `de`, `en`, `es`, `fr` or `pt`. Only the language is used (`es-ES` → `es`); any other value falls back to `en`.
18
+ * @default 'en'
18
19
  */
19
- "tooltipFormatter"?: ITICTooltipFormatter;
20
+ "locale": string;
20
21
  /**
21
- * Name of the duration (X) axis.
22
- * @default 'Duration (ms)'
22
+ * IANA time zone of the dates in the tooltip (e.g. `Europe/Madrid`). Defaults to the browser's, which is also used, with a console error, when the value isn't valid.
23
23
  */
24
- "xAxisName": string;
25
- /**
26
- * Name of the voltage (Y) axis.
27
- * @default 'Voltage (% of nominal voltage)'
28
- */
29
- "yAxisName": string;
24
+ "timeZone"?: string;
30
25
  }
31
26
  }
32
27
  export interface IticChartCustomEvent<T> extends CustomEvent<T> {
@@ -58,33 +53,28 @@ declare global {
58
53
  declare namespace LocalJSX {
59
54
  interface IticChart {
60
55
  /**
61
- * Point groups, as an array or a JSON string. Extra fields in groups and points are kept and passed back in `pointClick` and `tooltipFormatter`.
56
+ * Point groups, as an array or a JSON string. Each group's `phase` code sets its name, color and order. Extra fields in groups and points are kept and passed back in `pointClick`.
62
57
  */
63
58
  "groups"?: ITICChartMarks[] | string;
64
59
  /**
65
- * Emitted when a clickable point is clicked (`clickable` isn't `false`). `detail` holds the original point and group objects.
66
- */
67
- "onPointClick"?: (event: IticChartCustomEvent<ITICPointClickDetail>) => void;
68
- /**
69
- * Returns the HTML of a point's tooltip. JS/React property only (not an HTML attribute). The HTML is inserted as is: escaping the values is up to the formatter. Defaults to an English tooltip with phase, duration, voltage and timestamp.
60
+ * Language of the texts, dates and numbers: `ca`, `de`, `en`, `es`, `fr` or `pt`. Only the language is used (`es-ES` → `es`); any other value falls back to `en`.
61
+ * @default 'en'
70
62
  */
71
- "tooltipFormatter"?: ITICTooltipFormatter;
63
+ "locale"?: string;
72
64
  /**
73
- * Name of the duration (X) axis.
74
- * @default 'Duration (ms)'
65
+ * Emitted when a clickable point is clicked (`clickable` isn't `false`). `detail` holds the original point and group objects.
75
66
  */
76
- "xAxisName"?: string;
67
+ "onPointClick"?: (event: IticChartCustomEvent<ITICPointClickDetail>) => void;
77
68
  /**
78
- * Name of the voltage (Y) axis.
79
- * @default 'Voltage (% of nominal voltage)'
69
+ * IANA time zone of the dates in the tooltip (e.g. `Europe/Madrid`). Defaults to the browser's, which is also used, with a console error, when the value isn't valid.
80
70
  */
81
- "yAxisName"?: string;
71
+ "timeZone"?: string;
82
72
  }
83
73
 
84
74
  interface IticChartAttributes {
85
75
  "groups": ITICChartMarks[] | string;
86
- "xAxisName": string;
87
- "yAxisName": string;
76
+ "locale": string;
77
+ "timeZone": string;
88
78
  }
89
79
 
90
80
  interface IntrinsicElements {
@@ -8,4 +8,4 @@
8
8
  * to consume components of this package as outlined in the `README.md`.
9
9
  */
10
10
  export type * from './components';
11
- export type { ITICChartMarks, ITICPoint, ITICPointClickDetail, ITICTooltipFormatter, } from './components/itic-chart/itic-chart.options';
11
+ export type { ITICChartMarks, ITICPoint, ITICPointClickDetail, } from './components/itic-chart/itic-chart.options';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circutor/charts",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Charts used by the Circutor Cloud team, as framework-agnostic Web Components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
@@ -52,9 +52,6 @@
52
52
  "test:visual": "vitest run --project visual",
53
53
  "generate": "stencil generate"
54
54
  },
55
- "dependencies": {
56
- "dayjs": "^1.11.13"
57
- },
58
55
  "peerDependencies": {
59
56
  "echarts": "^6.1.0"
60
57
  },
@@ -70,5 +67,5 @@
70
67
  "vitest": "^5.0.5"
71
68
  },
72
69
  "license": "MIT",
73
- "gitHead": "8a3014c8d5a5691aa784640a5ad5008c661e7a57"
70
+ "gitHead": "00048fd4dea626e18224e8d3da1205d32f14e441"
74
71
  }
package/readme.md CHANGED
@@ -15,7 +15,7 @@ To use the chart as a Web Component, simply import it from the CDN in your HTML
15
15
  ```html
16
16
  <script
17
17
  type="module"
18
- src="https://unpkg.com/@circutor/charts@0.2.0/dist/charts/charts.esm.js"
18
+ src="https://unpkg.com/@circutor/charts@0.3.0/dist/charts/charts.esm.js"
19
19
  ></script>
20
20
  ```
21
21
 
@@ -23,7 +23,9 @@ Then, you can directly insert the chart into your HTML:
23
23
 
24
24
  ```html
25
25
  <itic-chart
26
- groups='[{"groupName":"L1,L2,L3","points":[{"duration":230,"voltage":93.46,"ts":"2024-06-26T20:23:45.220000+02:00"}],"color":"#00B388"},{"groupName":"L2","points":[{"duration":20,"voltage":0.4000000000000057,"ts":"2025-01-26T23:04:34.840000+01:00"}],"color":"#20262C"}]'
26
+ locale="es"
27
+ time-zone="Europe/Madrid"
28
+ groups='[{"phase":"AN,BN,CN","assetName":"Cuadro general","points":[{"duration":230,"voltage":93.46,"ts":"2024-06-26T20:23:45.220000+02:00","eventType":"VOLTAGE_DIP"}]},{"phase":"BN","assetName":"Cuadro general","points":[{"duration":20,"voltage":0.4000000000000057,"ts":"2025-01-26T23:04:34.840000+01:00","eventType":"POWER_INTERRUPT"}]}]'
27
29
  ></itic-chart>
28
30
  ```
29
31
 
@@ -42,7 +44,7 @@ The CDN files include ECharts, so there is nothing else to load. Charts on the s
42
44
 
43
45
  <script
44
46
  type="module"
45
- src="https://unpkg.com/@circutor/charts@0.2.0/dist/charts/charts.esm.js"
47
+ src="https://unpkg.com/@circutor/charts@0.3.0/dist/charts/charts.esm.js"
46
48
  ></script>
47
49
 
48
50
  <style>
@@ -59,7 +61,9 @@ The CDN files include ECharts, so there is nothing else to load. Charts on the s
59
61
  </head>
60
62
  <body>
61
63
  <itic-chart
62
- groups='[{"groupName":"L1,L2,L3","points":[{"duration":230,"voltage":93.46,"ts":"2024-06-26T20:23:45.220000+02:00"}],"color":"#00B388"},{"groupName":"L2","points":[{"duration":20,"voltage":0.4000000000000057,"ts":"2025-01-26T23:04:34.840000+01:00"}],"color":"#20262C"}]'
64
+ locale="es"
65
+ time-zone="Europe/Madrid"
66
+ groups='[{"phase":"AN,BN,CN","assetName":"Cuadro general","points":[{"duration":230,"voltage":93.46,"ts":"2024-06-26T20:23:45.220000+02:00","eventType":"VOLTAGE_DIP"}]},{"phase":"BN","assetName":"Cuadro general","points":[{"duration":20,"voltage":0.4000000000000057,"ts":"2025-01-26T23:04:34.840000+01:00","eventType":"POWER_INTERRUPT"}]}]'
63
67
  ></itic-chart>
64
68
  </body>
65
69
  </html>
@@ -81,11 +85,11 @@ For example, to load only the ITIC chart:
81
85
  ```html
82
86
  <script
83
87
  type="module"
84
- src="https://unpkg.com/@circutor/charts@0.2.0/dist/components/itic-chart.js"
88
+ src="https://unpkg.com/@circutor/charts@0.3.0/dist/components/itic-chart.js"
85
89
  ></script>
86
90
  ```
87
91
 
88
- - Pin the version in the URL (`@circutor/charts@0.2.0`). Without it, the CDN serves the latest release, which may include breaking changes.
92
+ - Pin the version in the URL (`@circutor/charts@0.3.0`). Without it, the CDN serves the latest release, which may include breaking changes.
89
93
  - Use one method per page. If a tag is loaded both ways, the first script to register it wins and the other is downloaded for nothing.
90
94
 
91
95
  #### With a bundler (without React)
@@ -102,11 +106,10 @@ Import each chart from `@circutor/charts/components/<tag>`; importing it registe
102
106
  import '@circutor/charts/components/itic-chart';
103
107
  ```
104
108
 
105
- Function props (`tooltipFormatter`) and events (`pointClick`) are set from JavaScript:
109
+ Events (`pointClick`) are listened to from JavaScript:
106
110
 
107
111
  ```js
108
112
  const chart = document.querySelector('itic-chart');
109
- chart.tooltipFormatter = (point, group) => `${escapeHtml(group.groupName)}: ${point.duration} ms`;
110
113
  chart.addEventListener('pointClick', event => openWaveforms(event.detail.point));
111
114
  ```
112
115
 
@@ -129,47 +132,53 @@ import { IticChart } from '@circutor/charts-react';
129
132
  Each wrapper imports only its own Web Component, so your bundler only includes the charts you import. ECharts comes from your application, shared with any other ECharts chart.
130
133
 
131
134
  3. **Use the component**
132
- Now, you can use the chart in your application like any other React component. Just pass the necessary data and configurations:
135
+ Now, you can use the chart in your application like any other React component. Pass the data, the user's language and time zone:
133
136
 
134
- ```html
137
+ ```tsx
135
138
  <IticChart
136
- groups={[
137
- {
138
- groupName: "L1,L2,L3",
139
- points: [
140
- {
141
- duration: 230,
142
- voltage: 93.46,
143
- ts: "2024-06-26T20:23:45.220000+02:00",
144
- },
145
- ],
146
- color: "#00B388",
147
- },
148
- {
149
- groupName: "L2",
150
- points: [
151
- {
152
- duration: 20,
153
- voltage: 0.4000000000000057,
154
- ts: "2025-01-26T23:04:34.840000+01:00",
155
- },
156
- ],
157
- color: "#20262C",
158
- },
159
- ]}
160
- ></IticChart>
139
+ locale="es"
140
+ timeZone="Europe/Madrid"
141
+ groups={[
142
+ {
143
+ phase: "AN,BN,CN",
144
+ assetName: "Cuadro general",
145
+ points: [
146
+ {
147
+ duration: 230,
148
+ voltage: 93.46,
149
+ ts: "2024-06-26T20:23:45.220000+02:00",
150
+ eventType: "VOLTAGE_DIP",
151
+ },
152
+ ],
153
+ },
154
+ {
155
+ phase: "BN",
156
+ assetName: "Cuadro general",
157
+ points: [
158
+ {
159
+ duration: 20,
160
+ voltage: 0.4000000000000057,
161
+ ts: "2025-01-26T23:04:34.840000+01:00",
162
+ eventType: "POWER_INTERRUPT",
163
+ },
164
+ ],
165
+ },
166
+ ]}
167
+ onPointClick={({ detail }) => openWaveforms(detail.point)}
168
+ />
161
169
  ```
162
170
 
163
171
  `@circutor/charts-react` expects `react` and `react-dom` 18 or 19 to be installed in your application.
164
172
 
165
173
  ### ITIC chart options
166
174
 
175
+ The ITIC chart only takes data, plus the language and time zone. The chart decides what is shown and how: the tooltip, the translations, the axis names, the accessible name, and each phase's name, color and order. Every prop is data or a string, so the chart is the same in plain HTML and React.
176
+
167
177
  | Property (React prop) | Attribute | Type | Default | Description |
168
178
  |---|---|---|---|---|
169
- | `groups` | `groups` | `ITICChartMarks[] \| string` | – | Point groups: `groupName` (legend label), `color` and `points` (`duration` in ms, `voltage` in % of nominal, `ts` timestamp, optional `clickable`). As an attribute, a JSON string. |
170
- | `xAxisName` | `x-axis-name` | `string` | `Duration (ms)` | Name of the duration axis. |
171
- | `yAxisName` | `y-axis-name` | `string` | `Voltage (% of nominal voltage)` | Name of the voltage axis. |
172
- | `tooltipFormatter` | – | `(point, group) => string` | English tooltip | HTML of a point's tooltip. JS/React property only. |
179
+ | `groups` | `groups` | `ITICChartMarks[] \| string` | – | Point groups (see [Data](#data)). As an attribute, a JSON string. |
180
+ | `locale` | `locale` | `string` | `'en'` | Language of the texts, event types, axes, `aria-label`, translated phases, dates and numbers. Only the language is used (`es-ES` → `es`); anything other than `ca`, `de`, `en`, `es`, `fr` or `pt` falls back to `en`. |
181
+ | `timeZone` | `time-zone` | `string` (IANA) | The browser's | Time zone of the dates in the tooltip, e.g. `Europe/Madrid`. An invalid value uses the browser's and logs a `console.error`. |
173
182
 
174
183
  | Event (React prop) | `detail` | Description |
175
184
  |---|---|---|
@@ -179,55 +188,109 @@ Each wrapper imports only its own Web Component, so your bundler only includes t
179
188
  |---|---|---|
180
189
  | `--itic-chart-background` | `transparent` | Background behind the whole chart. |
181
190
 
191
+ #### Data
192
+
193
+ Each group holds the events of one phase of an asset:
194
+
195
+ | Group field | Type | Description |
196
+ |---|---|---|
197
+ | `phase` | `string` | Phase code (`AN`, `AB,BC,CA`, `ALL`, `NONE`…). Sets the group's name, color and order (see [Phases](#phases)). |
198
+ | `assetName` | `string`, optional | Asset name, shown in the tooltip. |
199
+ | `points` | `ITICPoint[]` | The voltage events. |
200
+
201
+ | Point field | Type | Description |
202
+ |---|---|---|
203
+ | `duration` | `number` | Duration, in ms. |
204
+ | `voltage` | `number` | Voltage, in % of the nominal voltage. |
205
+ | `ts` | `string` | Date of the event, ISO 8601. |
206
+ | `clickable` | `boolean`, optional | `false` hides the pointer cursor and emits no `pointClick`. Defaults to `true`. |
207
+ | `eventType` | `string`, optional | Event type code from the backend (see [Event types](#event-types)), translated in the tooltip. |
208
+ | `status` | `string`, optional | Status code from the backend, shown as is. |
209
+ | `min` / `max` | `number`, optional | Minimum and maximum voltage, in V. |
210
+
211
+ The axes have fixed ranges: duration from 1 to 100,000 ms (logarithmic) and voltage from 0 to 200%. **Points outside them aren't drawn** (e.g. a duration below 1 ms or a voltage above 200%).
212
+
213
+ #### Tooltip
214
+
215
+ The tooltip shows these rows, in this order, in the chart's `locale`. Rows without a value are left out, and every value is escaped, so data can't inject HTML.
216
+
217
+ | Row (`en`) | Value | Left out when |
218
+ |---|---|---|
219
+ | Asset | `assetName` | missing or empty |
220
+ | Date | `ts` with milliseconds, in `timeZone` (e.g. `Jun 26, 20:23:45.220`, `26 jun, 20:23:45,220`) | never; an invalid date is shown as is |
221
+ | Event | translated `eventType`; unknown codes as is | missing or empty |
222
+ | Duration | e.g. `1,500 ms` (`1500 ms` in `es`) | never |
223
+ | Voltage | rounded to 1 decimal, e.g. `93.5%` (`93,5 %` in `es`) | never |
224
+ | Status | `status` as is | missing or empty |
225
+ | Phase | phase name; `-` for `NONE` | never |
226
+ | Min / Max | 2 decimals, e.g. `207.12 V` | missing or `0` |
227
+
228
+ Numbers use the separators of each language, also in the axis labels: in `es` the duration axis reads `1000`, `10.000`, `100.000` (Spanish doesn't group 4-digit numbers).
229
+
230
+ The tooltip is added to `document.body`, not inside the chart, so containers with `overflow: hidden` don't clip it. Its styles are inline, so it looks the same.
231
+
232
+ #### Phases
233
+
234
+ The chart names, colors and sorts the groups from their `phase` code, so every app draws the same phases the same way:
235
+
236
+ | Code | Name | Color |
237
+ |---|---|---|
238
+ | `AN`, `AB` | L1 | `#a67c52` |
239
+ | `BN`, `BC` | L2 | `#20262C` |
240
+ | `CN`, `CA` | L3 | `#B9BDC0` |
241
+ | `AN,BN,CN`, `AB,BC,CA` | L1,L2,L3 | `#00B388` |
242
+ | `ALL` | Translated: All, Todas, Totes, Alle, Toutes | `#4C5CC5` |
243
+ | `A`, `B`, `C` | L1, L2, L3 | ECharts palette |
244
+ | `AN,BN`, `AN,CN`, `BN,CN`, `AB,BC`, `AB,CA`, `BC,CA` | L1,L2 · L1,L3 · L2,L3 | ECharts palette |
245
+ | `NONE` | No name | ECharts palette |
246
+ | Any other | The code as is | ECharts palette |
247
+
248
+ - Groups are sorted by name (`L1`, `L1,L2,L3`, `L2`, `L3`), then by code, whatever order they come in, with `NONE` and then `ALL` last. Groups with the same code keep their order.
249
+ - Phase-neutral and phase-phase codes share names and colors: `AN` and `AB` in the same chart are two "L1" groups you can't tell apart, and one legend item toggles both.
250
+ - `NONE` groups have no name, so they don't appear in the legend (their points are drawn) and their tooltip shows `-` as phase.
251
+ - Phases without a fixed color take the next one of the ECharts palette, so the same code may get different colors in different charts.
252
+
253
+ #### Event types
254
+
255
+ `eventType` codes with a translation (in `ca`, `de`, `en`, `es`, `fr` and `pt`). Any other code is shown as is.
256
+
257
+ | Code | `en` | `es` |
258
+ |---|---|---|
259
+ | `VOLTAGE_DIP` | Voltage dip | Hueco de tensión |
260
+ | `VOLTAGE_SWELL` | Voltage swell | Aumento de tensión |
261
+ | `POWER_INTERRUPT` | Power interruption | Interrupción |
262
+ | `VOLTAGE_TRANSIENT` | Voltage transient | Transitorio de voltaje |
263
+ | `VOLTAGE_VARIATION` | Voltage variation | Variación de voltaje |
264
+ | `OVER_VOLTAGE` | Overvoltage | Sobretensión |
265
+ | `SUB_VOLTAGE` | Subvoltage | Subtensión |
266
+
182
267
  #### Clickable points
183
268
 
184
269
  Every point is clickable by default: it shows a pointer cursor and emits `pointClick`. Set `clickable: false` on a point to turn both off, e.g. for events without waveforms. Clicks on the ITIC curve or on non-clickable points emit nothing.
185
270
 
186
271
  #### Your own data in points and groups
187
272
 
188
- Points and groups accept any extra field (e.g. `eventType`, `waveforms`, `assetName`). The chart doesn't read or validate them; it passes them back in `pointClick` and `tooltipFormatter`. Cast them to your own types there:
273
+ Points and groups accept any extra field (e.g. `waveforms`). The chart doesn't read or validate them; it passes them back in `pointClick`. Cast them to your own types there:
189
274
 
190
275
  ```tsx
191
276
  import { IticChart, type ITICChartMarks, type ITICPoint } from '@circutor/charts-react';
192
277
 
193
278
  // Use `extends` (or `&`) so your types are assignable to the chart's
194
279
  interface EventPoint extends ITICPoint {
195
- eventType: string;
196
280
  waveforms: Waveform[];
197
281
  }
198
282
  interface AssetGroup extends ITICChartMarks {
199
- assetName: string;
200
283
  points: EventPoint[];
201
284
  }
202
285
 
203
286
  <IticChart
204
287
  groups={groups}
288
+ locale={locale}
289
+ timeZone={timeZone}
205
290
  onPointClick={({ detail }) => openWaveforms(detail.point as EventPoint, detail.group as AssetGroup)}
206
291
  />;
207
292
  ```
208
293
 
209
- #### Translations and custom tooltips
210
-
211
- The axis names and the default tooltip are in English. Pass `xAxisName`, `yAxisName` and a `tooltipFormatter` to translate them or to show other fields, formatting dates with your user's locale and time zone:
212
-
213
- ```tsx
214
- const formatDate = new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'medium', timeZone }).format;
215
-
216
- // Define it outside the component (or with useCallback): a new function redraws the chart
217
- const tooltipFormatter: ITICTooltipFormatter = (point, group) =>
218
- [
219
- `<strong>${t('asset')}</strong>: ${escapeHtml((group as AssetGroup).assetName)}`,
220
- `<strong>${t('timestamp')}</strong>: ${formatDate(new Date(point.ts))}`,
221
- `<strong>${t('duration')}</strong>: ${point.duration} ms`,
222
- ].join('<br/>');
223
-
224
- <IticChart groups={groups} xAxisName={t('duration')} yAxisName={t('voltage')} tooltipFormatter={tooltipFormatter} />;
225
- ```
226
-
227
- > **The HTML returned by `tooltipFormatter` is inserted as is.** Escape any value that may contain HTML (names, free text) yourself. The default tooltip escapes every value.
228
-
229
- The tooltip is added to `document.body`, not inside the chart, so containers with `overflow: hidden` don't clip it. Its styles are inline, so it looks the same.
230
-
231
294
  ### Size and data updates
232
295
 
233
296
  - The chart takes the full width of its container and is `400px` high by default. Any CSS rule on `itic-chart` in your page wins over that default, e.g. `itic-chart { height: 300px; }`, or, to grow with a flex container:
@@ -236,19 +299,19 @@ The tooltip is added to `document.body`, not inside the chart, so containers wit
236
299
  .card itic-chart { flex: 1; min-height: 400px; }
237
300
  ```
238
301
  The chart redraws automatically whenever its size changes.
239
- - Updating `groups` (a new array from JS/React or a new JSON string in the attribute) redraws the chart; groups that are no longer present are removed.
302
+ - Updating `groups` (a new array from JS/React or a new JSON string in the attribute), `locale` or `timeZone` redraws the chart; groups that are no longer present are removed.
240
303
 
241
304
  ### Accessibility
242
305
 
243
- The chart is drawn on a canvas, so the element gets `role="img"` (unless you set another `role`). Describe it with `aria-label`, which the React wrapper passes to the element:
306
+ The chart is drawn on a canvas, so the element gets `role="img"` (unless you set another `role`) and a translated `aria-label` ("ITIC chart", "Curva ITIC"…) that follows `locale`. Set your own `aria-label` to describe it better; the chart then leaves it alone:
244
307
 
245
308
  ```tsx
246
- <IticChart groups={groups} aria-label="ITIC curve of the voltage events of Main panel" />
309
+ <IticChart groups={groups} locale="es" aria-label="Curva ITIC de los eventos de tensión del cuadro general" />
247
310
  ```
248
311
 
249
312
  ### Next.js (App Router)
250
313
 
251
- - The React wrappers are client components (`'use client'`). A Server Component can render `<IticChart>` with serializable props (`groups`, axis names, `aria-label`), but `onPointClick` and `tooltipFormatter` are functions: pass them from your own `'use client'` component.
314
+ - The React wrappers are client components (`'use client'`). A Server Component can render `<IticChart>` with serializable props (`groups`, `locale`, `timeZone`, `aria-label`), but `onPointClick` is a function: pass it from your own `'use client'` component.
252
315
  - Server-side rendering works without `next/dynamic` or `ssr: false`: importing the wrappers doesn't touch the DOM, the server renders an empty `<itic-chart>` and the chart draws in the browser after hydration, without hydration warnings.
253
316
 
254
317
  ### Testing with Vitest and jsdom
@@ -261,9 +324,11 @@ import { vi } from 'vitest';
261
324
  import type { ITICChartMarks, ITICPointClickDetail } from '@circutor/charts-react';
262
325
 
263
326
  vi.mock('@circutor/charts-react', () => ({
264
- // One button per point, so tests can find points and click them
265
- IticChart: ({ groups = [], onPointClick, 'aria-label': ariaLabel }: {
327
+ // One button per point, so tests can find points and click them. `locale` and `timeZone` are ignored.
328
+ IticChart: ({ groups = [], onPointClick, 'aria-label': ariaLabel = 'ITIC chart' }: {
266
329
  groups?: ITICChartMarks[];
330
+ locale?: string;
331
+ timeZone?: string;
267
332
  onPointClick?: (event: CustomEvent<ITICPointClickDetail>) => void;
268
333
  'aria-label'?: string;
269
334
  }) => (
@@ -271,11 +336,11 @@ vi.mock('@circutor/charts-react', () => ({
271
336
  {groups.flatMap(group =>
272
337
  group.points.map((point, index) => (
273
338
  <button
274
- key={`${group.groupName}-${index}`}
339
+ key={`${group.phase}-${index}`}
275
340
  disabled={point.clickable === false}
276
341
  onClick={() => onPointClick?.(new CustomEvent('pointClick', { detail: { point, group } }))}
277
342
  >
278
- {group.groupName} {point.ts}
343
+ {group.phase} {point.ts}
279
344
  </button>
280
345
  )),
281
346
  )}
@@ -284,4 +349,4 @@ vi.mock('@circutor/charts-react', () => ({
284
349
  }));
285
350
  ```
286
351
 
287
- Then `fireEvent.click(screen.getByText('L1 2024-06-26T20:23:45.220000+02:00'))` calls your `onPointClick` with the original point.
352
+ Then `fireEvent.click(screen.getByText('AN 2024-06-26T20:23:45.220000+02:00'))` calls your `onPointClick` with the original point.