@circutor/charts-react 0.1.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.
package/README.md
CHANGED
|
@@ -1,122 +1,209 @@
|
|
|
1
|
-
###
|
|
1
|
+
### @circutor/charts-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React components for the charts used by the Circutor Cloud team. They wrap the Web Components from [`@circutor/charts`](https://www.npmjs.com/package/@circutor/charts), so you get typed props and React-friendly usage.
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### Install
|
|
6
6
|
|
|
7
|
-
**Base Scenario**: We want to use the ITIC curve chart.
|
|
8
|
-
|
|
9
|
-
#### As a Web Component
|
|
10
|
-
|
|
11
|
-
To use the chart as a Web Component, simply import it from the CDN in your HTML code.
|
|
12
|
-
|
|
13
|
-
```html
|
|
14
|
-
<script
|
|
15
|
-
type="module"
|
|
16
|
-
src="https://unpkg.com/@circutor/charts/dist/charts/charts.esm.js"
|
|
17
|
-
></script>
|
|
18
|
-
<script
|
|
19
|
-
nomodule
|
|
20
|
-
src="https://unpkg.com/@circutor/charts/dist/charts/charts.js"
|
|
21
|
-
></script>
|
|
22
7
|
```
|
|
23
|
-
|
|
24
|
-
Then, you can directly insert the chart into your HTML:
|
|
25
|
-
|
|
26
|
-
```html
|
|
27
|
-
<itic-chart
|
|
28
|
-
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"}]'
|
|
29
|
-
></itic-chart>
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
<details>
|
|
33
|
-
<summary>Complete example</summary>
|
|
34
|
-
|
|
35
|
-
```html
|
|
36
|
-
<!DOCTYPE html>
|
|
37
|
-
<html lang="es">
|
|
38
|
-
<head>
|
|
39
|
-
<meta charset="UTF-8" />
|
|
40
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
41
|
-
<title>Charts</title>
|
|
42
|
-
|
|
43
|
-
<script
|
|
44
|
-
type="module"
|
|
45
|
-
src="https://unpkg.com/@circutor/charts/dist/charts/charts.esm.js"
|
|
46
|
-
></script>
|
|
47
|
-
<script
|
|
48
|
-
nomodule
|
|
49
|
-
src="https://unpkg.com/@circutor/charts/dist/charts/charts.js"
|
|
50
|
-
></script>
|
|
51
|
-
|
|
52
|
-
<style>
|
|
53
|
-
body {
|
|
54
|
-
font-family: Arial, sans-serif;
|
|
55
|
-
text-align: center;
|
|
56
|
-
padding: 20px;
|
|
57
|
-
}
|
|
58
|
-
mi-componente {
|
|
59
|
-
display: block;
|
|
60
|
-
margin-top: 20px;
|
|
61
|
-
}
|
|
62
|
-
</style>
|
|
63
|
-
</head>
|
|
64
|
-
<body>
|
|
65
|
-
<itic-chart
|
|
66
|
-
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"}]'
|
|
67
|
-
></itic-chart>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
|
8
|
+
npm install @circutor/charts-react echarts
|
|
70
9
|
```
|
|
71
10
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
11
|
+
- `echarts` 6 is a peer dependency of `@circutor/charts`: the charts use your application's copy, so it isn't bundled twice when your app also uses ECharts.
|
|
12
|
+
- `react` and `react-dom` 18 or 19 must be installed in your application. `@circutor/charts` is installed automatically.
|
|
13
|
+
|
|
14
|
+
Each component imports only its own Web Component, so your bundler only includes the charts you import.
|
|
15
|
+
|
|
16
|
+
### ITIC chart
|
|
17
|
+
|
|
18
|
+
The chart only takes data, plus the language and time zone. It decides what is shown and how: the tooltip, the translations, the axis names, the accessible name, and each phase's name, color and order.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { IticChart } from '@circutor/charts-react';
|
|
22
|
+
|
|
23
|
+
const groups = [
|
|
24
|
+
{
|
|
25
|
+
phase: 'AN,BN,CN',
|
|
26
|
+
assetName: 'Cuadro general',
|
|
27
|
+
points: [{ duration: 230, voltage: 93.46, ts: '2024-06-26T20:23:45.220000+02:00', eventType: 'VOLTAGE_DIP' }],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
phase: 'BN',
|
|
31
|
+
assetName: 'Cuadro general',
|
|
32
|
+
points: [{ duration: 20, voltage: 0.4, ts: '2025-01-26T23:04:34.840000+01:00', eventType: 'POWER_INTERRUPT', clickable: false }],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export function PowerQuality() {
|
|
37
|
+
return (
|
|
38
|
+
<IticChart
|
|
39
|
+
groups={groups}
|
|
40
|
+
locale="es"
|
|
41
|
+
timeZone="Europe/Madrid"
|
|
42
|
+
onPointClick={({ detail }) => console.log(detail.point, detail.group)}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
81
46
|
```
|
|
82
47
|
|
|
83
|
-
|
|
84
|
-
|
|
48
|
+
| Prop | Type | Default | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `groups` | `ITICChartMarks[]` | – | Point groups (see [Data](#data)). |
|
|
51
|
+
| `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`. |
|
|
52
|
+
| `timeZone` | `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`. |
|
|
53
|
+
| `onPointClick` | `(event: CustomEvent<ITICPointClickDetail>) => void` | – | A clickable point was clicked. `event.detail` holds the `point` and `group` objects you passed in `groups`. |
|
|
54
|
+
| `aria-label` | `string` | Translated "ITIC chart" | Accessible name. The element has `role="img"`, since the chart is a canvas. The default follows `locale`; a value you set is left alone. |
|
|
55
|
+
|
|
56
|
+
The `ITICChartMarks`, `ITICPoint` and `ITICPointClickDetail` types are exported by this package.
|
|
57
|
+
|
|
58
|
+
The background is transparent; set it with the `--itic-chart-background` CSS custom property, e.g. `itic-chart { --itic-chart-background: #fafafb; }`.
|
|
59
|
+
|
|
60
|
+
#### Data
|
|
61
|
+
|
|
62
|
+
| Group field | Type | Description |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `phase` | `string` | Phase code (`AN`, `AB,BC,CA`, `ALL`, `NONE`…). Sets the group's name, color and order (see [Phases](#phases)). |
|
|
65
|
+
| `assetName` | `string`, optional | Asset name, shown in the tooltip. |
|
|
66
|
+
| `points` | `ITICPoint[]` | The voltage events. |
|
|
67
|
+
|
|
68
|
+
| Point field | Type | Description |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `duration` | `number` | Duration, in ms. |
|
|
71
|
+
| `voltage` | `number` | Voltage, in % of the nominal voltage. |
|
|
72
|
+
| `ts` | `string` | Date of the event, ISO 8601. |
|
|
73
|
+
| `clickable` | `boolean`, optional | `false` hides the pointer cursor and emits no `onPointClick`. Defaults to `true`. |
|
|
74
|
+
| `eventType` | `string`, optional | Event type code from the backend (see [Event types](#event-types)), translated in the tooltip. |
|
|
75
|
+
| `status` | `string`, optional | Status code from the backend, shown as is. |
|
|
76
|
+
| `min` / `max` | `number`, optional | Minimum and maximum voltage, in V. |
|
|
77
|
+
|
|
78
|
+
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%).
|
|
79
|
+
|
|
80
|
+
#### Tooltip
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
84
|
+
| Row (`en`) | Value | Left out when |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Asset | `assetName` | missing or empty |
|
|
87
|
+
| 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 |
|
|
88
|
+
| Event | translated `eventType`; unknown codes as is | missing or empty |
|
|
89
|
+
| Duration | e.g. `1,500 ms` (`1500 ms` in `es`) | never |
|
|
90
|
+
| Voltage | rounded to 1 decimal, e.g. `93.5%` (`93,5 %` in `es`) | never |
|
|
91
|
+
| Status | `status` as is | missing or empty |
|
|
92
|
+
| Phase | phase name; `-` for `NONE` | never |
|
|
93
|
+
| Min / Max | 2 decimals, e.g. `207.12 V` | missing or `0` |
|
|
94
|
+
|
|
95
|
+
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).
|
|
96
|
+
|
|
97
|
+
The tooltip is added to `document.body`, not inside the chart, so containers with `overflow: hidden` don't clip it.
|
|
98
|
+
|
|
99
|
+
#### Phases
|
|
100
|
+
|
|
101
|
+
| Code | Name | Color |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `AN`, `AB` | L1 | `#a67c52` |
|
|
104
|
+
| `BN`, `BC` | L2 | `#20262C` |
|
|
105
|
+
| `CN`, `CA` | L3 | `#B9BDC0` |
|
|
106
|
+
| `AN,BN,CN`, `AB,BC,CA` | L1,L2,L3 | `#00B388` |
|
|
107
|
+
| `ALL` | Translated: All, Todas, Totes, Alle, Toutes | `#4C5CC5` |
|
|
108
|
+
| `A`, `B`, `C` | L1, L2, L3 | ECharts palette |
|
|
109
|
+
| `AN,BN`, `AN,CN`, `BN,CN`, `AB,BC`, `AB,CA`, `BC,CA` | L1,L2 · L1,L3 · L2,L3 | ECharts palette |
|
|
110
|
+
| `NONE` | No name | ECharts palette |
|
|
111
|
+
| Any other | The code as is | ECharts palette |
|
|
112
|
+
|
|
113
|
+
- 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.
|
|
114
|
+
- 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.
|
|
115
|
+
- `NONE` groups have no name, so they don't appear in the legend (their points are drawn) and their tooltip shows `-` as phase.
|
|
116
|
+
- Phases without a fixed color take the next one of the ECharts palette, so the same code may get different colors in different charts.
|
|
117
|
+
|
|
118
|
+
#### Event types
|
|
119
|
+
|
|
120
|
+
`eventType` codes with a translation (in `ca`, `de`, `en`, `es`, `fr` and `pt`). Any other code is shown as is.
|
|
121
|
+
|
|
122
|
+
| Code | `en` | `es` |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `VOLTAGE_DIP` | Voltage dip | Hueco de tensión |
|
|
125
|
+
| `VOLTAGE_SWELL` | Voltage swell | Aumento de tensión |
|
|
126
|
+
| `POWER_INTERRUPT` | Power interruption | Interrupción |
|
|
127
|
+
| `VOLTAGE_TRANSIENT` | Voltage transient | Transitorio de voltaje |
|
|
128
|
+
| `VOLTAGE_VARIATION` | Voltage variation | Variación de voltaje |
|
|
129
|
+
| `OVER_VOLTAGE` | Overvoltage | Sobretensión |
|
|
130
|
+
| `SUB_VOLTAGE` | Subvoltage | Subtensión |
|
|
131
|
+
|
|
132
|
+
#### Clickable points
|
|
133
|
+
|
|
134
|
+
Every point is clickable by default: it shows a pointer cursor and emits `onPointClick`. Set `clickable: false` on a point to turn both off, e.g. for events without waveforms.
|
|
135
|
+
|
|
136
|
+
#### Your own data in points and groups
|
|
137
|
+
|
|
138
|
+
Points and groups accept any extra field (e.g. `waveforms`). The chart doesn't read or validate them; it passes them back in `onPointClick`. Cast them to your own types there:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { IticChart, type ITICChartMarks, type ITICPoint } from '@circutor/charts-react';
|
|
142
|
+
|
|
143
|
+
// Use `extends` (or `&`) so your types are assignable to the chart's
|
|
144
|
+
interface EventPoint extends ITICPoint {
|
|
145
|
+
waveforms: Waveform[];
|
|
146
|
+
}
|
|
147
|
+
interface AssetGroup extends ITICChartMarks {
|
|
148
|
+
points: EventPoint[];
|
|
149
|
+
}
|
|
85
150
|
|
|
86
|
-
|
|
87
|
-
|
|
151
|
+
<IticChart
|
|
152
|
+
groups={groups}
|
|
153
|
+
locale={locale}
|
|
154
|
+
timeZone={timeZone}
|
|
155
|
+
onPointClick={({ detail }) => openWaveforms(detail.point as EventPoint, detail.group as AssetGroup)}
|
|
156
|
+
/>;
|
|
88
157
|
```
|
|
89
158
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
159
|
+
### Size and data updates
|
|
160
|
+
|
|
161
|
+
- The chart takes the full width of its container and is `400px` high by default. Any CSS rule on `itic-chart` wins over that default, e.g. `itic-chart { height: 300px; }`, or, to grow with a flex container:
|
|
162
|
+
```css
|
|
163
|
+
.card { display: flex; flex-direction: column; }
|
|
164
|
+
.card itic-chart { flex: 1; min-height: 400px; }
|
|
165
|
+
```
|
|
166
|
+
The chart redraws automatically whenever its size changes.
|
|
167
|
+
- Passing a new `groups` array, `locale` or `timeZone` redraws the chart; groups that are no longer present are removed.
|
|
168
|
+
|
|
169
|
+
### Next.js (App Router)
|
|
170
|
+
|
|
171
|
+
- The 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.
|
|
172
|
+
- 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.
|
|
173
|
+
|
|
174
|
+
### Testing with Vitest and jsdom
|
|
175
|
+
|
|
176
|
+
jsdom has no canvas, so ECharts can't draw there and logs `getContext()` errors. Mock the wrappers in a setup file (`setupFiles` in your Vitest config), so every test gets a plain element instead:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// vitest.setup.tsx
|
|
180
|
+
import { vi } from 'vitest';
|
|
181
|
+
import type { ITICChartMarks, ITICPointClickDetail } from '@circutor/charts-react';
|
|
182
|
+
|
|
183
|
+
vi.mock('@circutor/charts-react', () => ({
|
|
184
|
+
// One button per point, so tests can find points and click them. `locale` and `timeZone` are ignored.
|
|
185
|
+
IticChart: ({ groups = [], onPointClick, 'aria-label': ariaLabel = 'ITIC chart' }: {
|
|
186
|
+
groups?: ITICChartMarks[];
|
|
187
|
+
locale?: string;
|
|
188
|
+
timeZone?: string;
|
|
189
|
+
onPointClick?: (event: CustomEvent<ITICPointClickDetail>) => void;
|
|
190
|
+
'aria-label'?: string;
|
|
191
|
+
}) => (
|
|
192
|
+
<div role="img" aria-label={ariaLabel} data-testid="itic-chart">
|
|
193
|
+
{groups.flatMap(group =>
|
|
194
|
+
group.points.map((point, index) => (
|
|
195
|
+
<button
|
|
196
|
+
key={`${group.phase}-${index}`}
|
|
197
|
+
disabled={point.clickable === false}
|
|
198
|
+
onClick={() => onPointClick?.(new CustomEvent('pointClick', { detail: { point, group } }))}
|
|
199
|
+
>
|
|
200
|
+
{group.phase} {point.ts}
|
|
201
|
+
</button>
|
|
202
|
+
)),
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
),
|
|
206
|
+
}));
|
|
120
207
|
```
|
|
121
208
|
|
|
122
|
-
|
|
209
|
+
Then `fireEvent.click(screen.getByText('AN 2024-06-26T20:23:45.220000+02:00'))` calls your `onPointClick` with the original point.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { createComponent } from '@stencil/react-output-target/runtime';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { IticChart as IticChartElement, defineCustomElement as defineIticChart } from "@circutor/charts/dist/
|
|
4
|
+
import { IticChart as IticChartElement, defineCustomElement as defineIticChart } from "@circutor/charts/dist/bundler/itic-chart.js";
|
|
5
5
|
export const IticChart = createComponent({
|
|
6
6
|
tagName: 'itic-chart',
|
|
7
7
|
elementClass: IticChartElement,
|
|
8
8
|
react: React,
|
|
9
|
-
events: {},
|
|
9
|
+
events: { onPointClick: 'pointClick' },
|
|
10
10
|
defineCustomElement: defineIticChart
|
|
11
11
|
});
|
|
12
12
|
//# sourceMappingURL=components.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../../lib/components/stencil-generated/components.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAUb,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../../lib/components/stencil-generated/components.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAUb,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,mBAAmB,IAAI,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAIpI,MAAM,CAAC,MAAM,SAAS,GAAiG,eAAe,CAA0D;IAC5L,OAAO,EAAE,YAAY;IACrB,YAAY,EAAE,gBAAgB;IAE9B,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,EAAE,YAAY,EAAE,YAAY,EAAqB;IACzD,mBAAmB,EAAE,eAAe;CACvC,CAAC,CAAC"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type { StencilReactComponent } from '@stencil/react-output-target/runtime';
|
|
2
|
-
import type
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import type { EventName, StencilReactComponent } from '@stencil/react-output-target/runtime';
|
|
2
|
+
import { type ITICPointClickDetail, type IticChartCustomEvent } from "@circutor/charts";
|
|
3
|
+
import type { Components } from "@circutor/charts/dist/bundler";
|
|
4
|
+
import { IticChart as IticChartElement } from "@circutor/charts/dist/bundler/itic-chart.js";
|
|
5
|
+
export type IticChartEvents = {
|
|
6
|
+
onPointClick: EventName<IticChartCustomEvent<ITICPointClickDetail>>;
|
|
7
|
+
};
|
|
5
8
|
export declare const IticChart: StencilReactComponent<IticChartElement, IticChartEvents, Components.IticChart>;
|
package/dist/types/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@circutor/charts-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React wrappers for the @circutor/charts Web Components",
|
|
5
5
|
"author": "apinto <apinto@circutor.com>",
|
|
6
6
|
"license": "ISC",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"typescript": "^5.7.3"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@circutor/charts": "^0.
|
|
27
|
+
"@circutor/charts": "^0.3.0",
|
|
28
28
|
"@stencil/react-output-target": "^1.6.5"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "00048fd4dea626e18224e8d3da1205d32f14e441"
|
|
38
38
|
}
|