@circutor/charts-react 0.1.0 → 0.2.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,155 @@
1
- ### MYC Charts
1
+ ### @circutor/charts-react
2
2
 
3
- This repository aims to make the charts used by the Cloud team in their web applications accessible to the rest of the organization. This will facilitate their reuse, standardization, and maintenance across different projects.
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
- ### How to Use
5
+ ### Install
6
6
 
7
- **Base Scenario**: We want to use the ITIC curve chart.
7
+ ```
8
+ npm install @circutor/charts-react echarts
9
+ ```
8
10
 
9
- #### As a Web Component
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
+ ```tsx
19
+ import { IticChart } from '@circutor/charts-react';
20
+
21
+ const groups = [
22
+ {
23
+ groupName: 'L1,L2,L3',
24
+ color: '#00B388',
25
+ points: [{ duration: 230, voltage: 93.46, ts: '2024-06-26T20:23:45.220000+02:00' }],
26
+ },
27
+ {
28
+ groupName: 'L2',
29
+ color: '#20262C',
30
+ points: [{ duration: 20, voltage: 0.4, ts: '2025-01-26T23:04:34.840000+01:00', clickable: false }],
31
+ },
32
+ ];
33
+
34
+ export function PowerQuality() {
35
+ return (
36
+ <IticChart
37
+ groups={groups}
38
+ aria-label="ITIC curve of the voltage events"
39
+ onPointClick={({ detail }) => console.log(detail.point, detail.group)}
40
+ />
41
+ );
42
+ }
43
+ ```
10
44
 
11
- To use the chart as a Web Component, simply import it from the CDN in your HTML code.
45
+ | Prop | Type | Default | Description |
46
+ |---|---|---|---|
47
+ | `groups` | `ITICChartMarks[]` | – | Point groups: `groupName` (legend label), `color` and `points` (`duration` in ms, `voltage` in % of nominal, `ts` timestamp, optional `clickable`). |
48
+ | `xAxisName` | `string` | `Duration (ms)` | Name of the duration axis. |
49
+ | `yAxisName` | `string` | `Voltage (% of nominal voltage)` | Name of the voltage axis. |
50
+ | `tooltipFormatter` | `(point, group) => string` | English tooltip | HTML of a point's tooltip. |
51
+ | `onPointClick` | `(event: CustomEvent<ITICPointClickDetail>) => void` | – | A clickable point was clicked. `event.detail` holds the `point` and `group` objects you passed in `groups`. |
52
+ | `aria-label` | `string` | – | Accessible name. The element has `role="img"`, since the chart is a canvas. |
12
53
 
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
- ```
54
+ The `ITICChartMarks`, `ITICPoint`, `ITICPointClickDetail` and `ITICTooltipFormatter` types are exported by this package.
23
55
 
24
- Then, you can directly insert the chart into your HTML:
56
+ The background is transparent; set it with the `--itic-chart-background` CSS custom property, e.g. `itic-chart { --itic-chart-background: #fafafb; }`.
25
57
 
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
- ```
58
+ #### Clickable points
31
59
 
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>
70
- ```
60
+ 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.
71
61
 
72
- </details>
62
+ #### Your own data in points and groups
73
63
 
74
- #### From a React Application
64
+ 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 `onPointClick` and `tooltipFormatter`. Cast them to your own types there:
75
65
 
76
- 1. **Install the package**
77
- First, you need to install the `@circutor/charts-react` package in your project:
66
+ ```tsx
67
+ import { IticChart, type ITICChartMarks, type ITICPoint } from '@circutor/charts-react';
78
68
 
79
- ```
80
- npm install @circutor/charts-react
69
+ // Use `extends` (or `&`) so your types are assignable to the chart's
70
+ interface EventPoint extends ITICPoint {
71
+ eventType: string;
72
+ waveforms: Waveform[];
73
+ }
74
+ interface AssetGroup extends ITICChartMarks {
75
+ assetName: string;
76
+ points: EventPoint[];
77
+ }
78
+
79
+ <IticChart
80
+ groups={groups}
81
+ onPointClick={({ detail }) => openWaveforms(detail.point as EventPoint, detail.group as AssetGroup)}
82
+ />;
81
83
  ```
82
84
 
83
- 2. **Import the component**
84
- Then, import the component into your React file:
85
+ #### Translations and custom tooltips
85
86
 
86
- ```javascript
87
- import { IticChart } from '@circutor/charts-react';`>
88
- ```
87
+ 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:
89
88
 
90
- 3. **Use the component**
91
- Now, you can use the chart in your application like any other React component. Just pass the necessary data and configurations:
89
+ ```tsx
90
+ const formatDate = new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'medium', timeZone }).format;
92
91
 
93
- ```html
94
- <IticChart
95
- groups={[
96
- {
97
- groupName: "L1,L2,L3",
98
- points: [
99
- {
100
- duration: 230,
101
- voltage: 93.46,
102
- ts: "2024-06-26T20:23:45.220000+02:00",
103
- },
104
- ],
105
- color: "#00B388",
106
- },
107
- {
108
- groupName: "L2",
109
- points: [
110
- {
111
- duration: 20,
112
- voltage: 0.4000000000000057,
113
- ts: "2025-01-26T23:04:34.840000+01:00",
114
- },
115
- ],
116
- color: "#20262C",
117
- },
118
- ]}
119
- ></IticChart>
92
+ // Define it outside the component (or with useCallback): a new function redraws the chart
93
+ const tooltipFormatter: ITICTooltipFormatter = (point, group) =>
94
+ [
95
+ `<strong>${t('asset')}</strong>: ${escapeHtml((group as AssetGroup).assetName)}`,
96
+ `<strong>${t('timestamp')}</strong>: ${formatDate(new Date(point.ts))}`,
97
+ `<strong>${t('duration')}</strong>: ${point.duration} ms`,
98
+ ].join('<br/>');
99
+
100
+ <IticChart groups={groups} xAxisName={t('duration')} yAxisName={t('voltage')} tooltipFormatter={tooltipFormatter} />;
101
+ ```
102
+
103
+ > **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.
104
+
105
+ The tooltip is added to `document.body`, not inside the chart, so containers with `overflow: hidden` don't clip it.
106
+
107
+ ### Size and data updates
108
+
109
+ - 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:
110
+ ```css
111
+ .card { display: flex; flex-direction: column; }
112
+ .card itic-chart { flex: 1; min-height: 400px; }
113
+ ```
114
+ The chart redraws automatically whenever its size changes.
115
+ - Passing a new `groups` array redraws the chart; groups that are no longer present are removed.
116
+
117
+ ### Next.js (App Router)
118
+
119
+ - The 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.
120
+ - 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.
121
+
122
+ ### Testing with Vitest and jsdom
123
+
124
+ 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:
125
+
126
+ ```tsx
127
+ // vitest.setup.tsx
128
+ import { vi } from 'vitest';
129
+ import type { ITICChartMarks, ITICPointClickDetail } from '@circutor/charts-react';
130
+
131
+ vi.mock('@circutor/charts-react', () => ({
132
+ // One button per point, so tests can find points and click them
133
+ IticChart: ({ groups = [], onPointClick, 'aria-label': ariaLabel }: {
134
+ groups?: ITICChartMarks[];
135
+ onPointClick?: (event: CustomEvent<ITICPointClickDetail>) => void;
136
+ 'aria-label'?: string;
137
+ }) => (
138
+ <div role="img" aria-label={ariaLabel} data-testid="itic-chart">
139
+ {groups.flatMap(group =>
140
+ group.points.map((point, index) => (
141
+ <button
142
+ key={`${group.groupName}-${index}`}
143
+ disabled={point.clickable === false}
144
+ onClick={() => onPointClick?.(new CustomEvent('pointClick', { detail: { point, group } }))}
145
+ >
146
+ {group.groupName} {point.ts}
147
+ </button>
148
+ )),
149
+ )}
150
+ </div>
151
+ ),
152
+ }));
120
153
  ```
121
154
 
122
- Remember to consult the documentation for each component for more details and configuration options.
155
+ Then `fireEvent.click(screen.getByText('L1 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/components/itic-chart.js";
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;AAG1B,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,mBAAmB,IAAI,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAIvI,MAAM,CAAC,MAAM,SAAS,GAAiG,eAAe,CAA0D;IAC5L,OAAO,EAAE,YAAY;IACrB,YAAY,EAAE,gBAAgB;IAE9B,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,EAAqB;IAC7B,mBAAmB,EAAE,eAAe;CACvC,CAAC,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 { Components } from "@circutor/charts/dist/components";
3
- import { IticChart as IticChartElement } from "@circutor/charts/dist/components/itic-chart.js";
4
- export type IticChartEvents = NonNullable<unknown>;
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>;
@@ -1 +1,2 @@
1
1
  export * from './components/stencil-generated/components';
2
+ export type { ITICChartMarks, ITICPoint, ITICPointClickDetail, ITICTooltipFormatter } from '@circutor/charts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circutor/charts-react",
3
- "version": "0.1.0",
3
+ "version": "0.2.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.1.0",
27
+ "@circutor/charts": "^0.2.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": "44a514e1c49b34d4ab2a9e52d1354c3753c76e0b"
37
+ "gitHead": "8a3014c8d5a5691aa784640a5ad5008c661e7a57"
38
38
  }