cordova-plugin-ra-chart 1.0.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 +543 -0
- package/ionic/index.ts +16 -0
- package/ionic/ra-chart.component.html +15 -0
- package/ionic/ra-chart.component.ts +254 -0
- package/ionic/ra-chart.loader.ts +63 -0
- package/ionic/ra-chart.module.ts +23 -0
- package/ionic/ra-chart.types.ts +136 -0
- package/package.json +54 -0
- package/plugin.xml +45 -0
- package/types/index.d.ts +218 -0
- package/www/ra-chart.css +324 -0
- package/www/ra-chart.js +2838 -0
package/README.md
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# raChart Cordova
|
|
2
|
+
|
|
3
|
+
A dependency-free SVG chart engine for **Cordova** and **Ionic**. 21 chart types, built for touch: tap to inspect, drag to scrub, pinch to zoom, with haptics, dark mode and offline rendering.
|
|
4
|
+
|
|
5
|
+
No jQuery. No chart library. No native code. No build step. One 60 KB JS file and one CSS file.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ra-chart-cordova/
|
|
9
|
+
├── plugin.xml Cordova plugin descriptor
|
|
10
|
+
├── package.json
|
|
11
|
+
├── www/
|
|
12
|
+
│ ├── ra-chart.js the engine (window.RaChart)
|
|
13
|
+
│ └── ra-chart.css styles + theme variables
|
|
14
|
+
├── types/index.d.ts TypeScript definitions
|
|
15
|
+
├── ionic/ Angular wrapper
|
|
16
|
+
│ ├── ra-chart.component.ts/.html <ra-chart>
|
|
17
|
+
│ ├── ra-chart.module.ts NgModule for non-standalone apps
|
|
18
|
+
│ ├── ra-chart.loader.ts lazy script loader
|
|
19
|
+
│ ├── ra-chart.types.ts
|
|
20
|
+
│ └── index.ts
|
|
21
|
+
└── demo/
|
|
22
|
+
├── index.html plain mobile demo (open in a browser)
|
|
23
|
+
└── dashboard.page.ts/.html Ionic page example
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Table of contents
|
|
29
|
+
|
|
30
|
+
1. [Install](#install)
|
|
31
|
+
2. [Quick start](#quick-start)
|
|
32
|
+
3. [Ionic / Angular usage](#ionic--angular-usage)
|
|
33
|
+
4. [Chart types](#chart-types)
|
|
34
|
+
5. [Data shapes](#data-shapes)
|
|
35
|
+
6. [Options](#options)
|
|
36
|
+
7. [Methods](#methods)
|
|
37
|
+
8. [Events](#events)
|
|
38
|
+
9. [Mobile behaviour](#mobile-behaviour)
|
|
39
|
+
10. [Theming and dark mode](#theming-and-dark-mode)
|
|
40
|
+
11. [Recipes](#recipes)
|
|
41
|
+
12. [Troubleshooting](#troubleshooting)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
### Option A — Cordova plugin
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cordova plugin add ./ra-chart-cordova
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`window.RaChart` is available once `deviceready` fires. Cordova does not inject CSS, so link it yourself in `index.html`:
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<link rel="stylesheet" href="plugins/cordova-plugin-ra-chart/www/ra-chart.css">
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Option B — Ionic app (recommended)
|
|
60
|
+
|
|
61
|
+
Copy the two runtime files into your assets and the wrapper into your source tree:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
src/assets/ra-chart/ra-chart.js ← from www/
|
|
65
|
+
src/assets/ra-chart/ra-chart.css ← from www/
|
|
66
|
+
src/app/shared/ra-chart/ ← the whole ionic/ folder
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Add the stylesheet to `angular.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
"styles": [
|
|
73
|
+
"src/theme/variables.scss",
|
|
74
|
+
"src/global.scss",
|
|
75
|
+
"src/assets/ra-chart/ra-chart.css"
|
|
76
|
+
]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The component lazy-loads `ra-chart.js` from `assets/ra-chart/ra-chart.js` on first use. To bundle it eagerly instead, add it to `angular.json` → `"scripts"`; the loader will detect `window.RaChart` and skip the fetch.
|
|
80
|
+
|
|
81
|
+
To load it from somewhere else:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// main.ts
|
|
85
|
+
import { RA_CHART_SCRIPT_URL } from './app/shared/ra-chart';
|
|
86
|
+
|
|
87
|
+
bootstrapApplication(AppComponent, {
|
|
88
|
+
providers: [
|
|
89
|
+
{ provide: RA_CHART_SCRIPT_URL, useValue: 'assets/vendor/ra-chart.js' }
|
|
90
|
+
]
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Option C — plain browser
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<link rel="stylesheet" href="ra-chart.css">
|
|
98
|
+
<script src="ra-chart.js"></script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Quick start
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<div id="sales"></div>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
var chart = new RaChart('#sales', {
|
|
111
|
+
type: 'column',
|
|
112
|
+
category: 'month',
|
|
113
|
+
series: [
|
|
114
|
+
{ name: 'Revenue', field: 'revenue' },
|
|
115
|
+
{ name: 'Target', field: 'target' }
|
|
116
|
+
],
|
|
117
|
+
data: [
|
|
118
|
+
{ month: 'Jan', revenue: 42, target: 38 },
|
|
119
|
+
{ month: 'Feb', revenue: 55, target: 45 }
|
|
120
|
+
],
|
|
121
|
+
height: 240
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
chart.setData(newRows); // animates to the new values
|
|
125
|
+
chart.destroy(); // always clean up
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
In Cordova, wait for the platform first:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
document.addEventListener('deviceready', function () {
|
|
132
|
+
new RaChart('#sales', { /* ... */ });
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Ionic / Angular usage
|
|
139
|
+
|
|
140
|
+
### Standalone component (Angular 16+)
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { Component } from '@angular/core';
|
|
144
|
+
import { RaChartComponent } from '../shared/ra-chart';
|
|
145
|
+
import type { RaSeries, RaSelectEvent } from '../shared/ra-chart';
|
|
146
|
+
|
|
147
|
+
@Component({
|
|
148
|
+
selector: 'app-sales',
|
|
149
|
+
standalone: true,
|
|
150
|
+
imports: [RaChartComponent],
|
|
151
|
+
templateUrl: './sales.page.html'
|
|
152
|
+
})
|
|
153
|
+
export class SalesPage {
|
|
154
|
+
series: RaSeries[] = [{ name: 'Revenue', field: 'revenue' }];
|
|
155
|
+
rows = [{ month: 'Jan', revenue: 42 }, { month: 'Feb', revenue: 55 }];
|
|
156
|
+
|
|
157
|
+
onSelect(e: RaSelectEvent) {
|
|
158
|
+
console.log(e.category, e.value);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<ra-chart type="column"
|
|
165
|
+
category="month"
|
|
166
|
+
[series]="series"
|
|
167
|
+
[data]="rows"
|
|
168
|
+
[zoom]="true"
|
|
169
|
+
[height]="240"
|
|
170
|
+
(select)="onSelect($event)">
|
|
171
|
+
</ra-chart>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### NgModule apps
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { RaChartModule } from './shared/ra-chart';
|
|
178
|
+
|
|
179
|
+
@NgModule({
|
|
180
|
+
imports: [RaChartModule] // or RaChartModule.forRoot({ scriptUrl: '...' })
|
|
181
|
+
})
|
|
182
|
+
export class SalesPageModule {}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Calling methods
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
@ViewChild('chart') chart?: RaChartComponent;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<ra-chart #chart ...></ra-chart>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
this.chart?.replay();
|
|
197
|
+
this.chart?.zoomTo(0.5, 1);
|
|
198
|
+
this.chart?.resetZoom();
|
|
199
|
+
const svg = this.chart?.toSVG();
|
|
200
|
+
this.chart?.instance?.setData(rows); // raw engine handle
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Change detection
|
|
204
|
+
|
|
205
|
+
The component is `OnPush` and the engine runs **outside** Angular's zone, so touch scrubbing and animations never trigger change detection. Replace arrays instead of mutating them so `ngOnChanges` fires:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
this.rows = [...this.rows, newRow]; // ✅ detected
|
|
209
|
+
this.rows.push(newRow); // ❌ ignored
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A change to `data` alone animates the values in place. Changing any other input rebuilds the chart.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Chart types
|
|
217
|
+
|
|
218
|
+
| `type` | Shape | Notes |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| `column` | vertical bars | `stacked`, `fullStacked`, `depth` |
|
|
221
|
+
| `bar` | horizontal bars | same options |
|
|
222
|
+
| `line` | line | `smooth`, `stepped` |
|
|
223
|
+
| `area` | filled line | `smooth`, `stepped` |
|
|
224
|
+
| `lollipop` | stem + dot | good for sparse mobile series |
|
|
225
|
+
| `pie` | pie | `semi`, `depth` |
|
|
226
|
+
| `donut` | donut with live centre total | `semi`, `depth` |
|
|
227
|
+
| `radar` | multi-axis polygon | |
|
|
228
|
+
| `treemap` | squarified hierarchy | |
|
|
229
|
+
| `sankey` | flow diagram | uses `from`/`to`/`value` rows |
|
|
230
|
+
| `wordcloud` | spiral-packed words | |
|
|
231
|
+
| `venn` | up to 3 sets | |
|
|
232
|
+
| `candlestick` | OHLC | `upColor`, `downColor`, `zoom` |
|
|
233
|
+
| `timeline` | events on an axis | accepts dates |
|
|
234
|
+
| `gantt` | task bars with progress | accepts dates |
|
|
235
|
+
| `scatter` | numeric X/Y | `xField` |
|
|
236
|
+
| `bubble` | X/Y + size | `xField`, `sizeField` |
|
|
237
|
+
| `funnel` | conversion stages | |
|
|
238
|
+
| `pyramid` | proportional triangle | |
|
|
239
|
+
| `gauge` | needle + bands | `max`, `bands` |
|
|
240
|
+
| `heatmap` | matrix on a sequential ramp | |
|
|
241
|
+
|
|
242
|
+
`RaChart.types` returns this list at runtime.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Data shapes
|
|
247
|
+
|
|
248
|
+
Everything takes an **array of plain objects**. `category` names the label field; `series` maps display names to value fields.
|
|
249
|
+
|
|
250
|
+
**Most types** — `column`, `bar`, `line`, `area`, `lollipop`, `radar`, `heatmap`:
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
category: 'month',
|
|
254
|
+
series: [{ name: 'Revenue', field: 'revenue' }, { name: 'Cost', field: 'cost' }],
|
|
255
|
+
data: [{ month: 'Jan', revenue: 42, cost: 30 }]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Single-value types** — `pie`, `donut`, `treemap`, `wordcloud`, `venn`, `funnel`, `pyramid`, `gauge` (first series only):
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
category: 'source',
|
|
262
|
+
series: [{ name: 'Share', field: 'value' }],
|
|
263
|
+
data: [{ source: 'Direct', value: 42 }]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Candlestick:**
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
category: 'day',
|
|
270
|
+
data: [{ day: '1', open: 102, high: 108, low: 100, close: 106 }]
|
|
271
|
+
// rename columns: fields: { open: 'o', high: 'h', low: 'l', close: 'c' }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Gantt** — dates or numbers, `progress` is optional (0–100):
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
category: 'task',
|
|
278
|
+
data: [{ task: 'Design', start: '2026-01-20', end: '2026-03-01', progress: 100 }]
|
|
279
|
+
// fields: { start: 'from', end: 'until', progress: 'pct' }
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Timeline:**
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
category: 'milestone',
|
|
286
|
+
series: [{ name: 'Date', field: 'date' }],
|
|
287
|
+
data: [{ milestone: 'Launch', date: '2026-07-01' }]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Sankey** — no `category`/`series`, just links:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
data: [{ from: 'Solar', to: 'Grid', value: 30 }]
|
|
294
|
+
// fields: { from: 'src', to: 'dst', value: 'amount' }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Scatter / bubble:**
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
xField: 'price', sizeField: 'reach',
|
|
301
|
+
series: [{ name: 'Brand A', field: 'rating' }],
|
|
302
|
+
data: [{ price: 12, rating: 3.8, reach: 20 }]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Options
|
|
308
|
+
|
|
309
|
+
### Data
|
|
310
|
+
|
|
311
|
+
| Option | Default | Description |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `type` | `'column'` | Any type from the table above |
|
|
314
|
+
| `data` | `[]` | Array of plain rows |
|
|
315
|
+
| `category` | `'category'` | Row field used for labels |
|
|
316
|
+
| `series` | `[]` | `[{ name, field }]` |
|
|
317
|
+
| `colors` | `null` | Hex array; `null` picks the theme-aware palette |
|
|
318
|
+
| `fields` | `null` | Rename columns per type (see above) |
|
|
319
|
+
| `xField` / `sizeField` | `'x'` / `'size'` | Scatter and bubble |
|
|
320
|
+
|
|
321
|
+
### Shape
|
|
322
|
+
|
|
323
|
+
| Option | Default | Description |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| `stacked` | `false` | Stack column/bar series |
|
|
326
|
+
| `fullStacked` | `false` | 100% stacked; tooltips switch to % |
|
|
327
|
+
| `smooth` | `false` | Curved line/area |
|
|
328
|
+
| `stepped` | `false` | Stepped line/area |
|
|
329
|
+
| `semi` | `false` | Half-circle pie/donut |
|
|
330
|
+
| `depth` | `0` | 3D extrusion px (column, bar, pie, donut) |
|
|
331
|
+
|
|
332
|
+
### Scale
|
|
333
|
+
|
|
334
|
+
| Option | Default | Description |
|
|
335
|
+
|---|---|---|
|
|
336
|
+
| `min` | `0` | Value-axis minimum |
|
|
337
|
+
| `max` | `null` | Gauge maximum |
|
|
338
|
+
| `bands` | `null` | Gauge zones `[{ from, to, color }]` |
|
|
339
|
+
| `upColor` / `downColor` | green / red | Candlestick bull/bear |
|
|
340
|
+
|
|
341
|
+
### Interaction
|
|
342
|
+
|
|
343
|
+
| Option | Default | Description |
|
|
344
|
+
|---|---|---|
|
|
345
|
+
| `zoom` | `false` | Range slider (XY types + candlestick) |
|
|
346
|
+
| `pinchZoom` | `true` | Pinch and pan when `zoom` is on |
|
|
347
|
+
| `legend` | `true` | Tappable; auto-hidden for single-series XY |
|
|
348
|
+
| `haptics` | `true` | Vibrate on touch-select |
|
|
349
|
+
| `sheetTooltip` | `'auto'` | Dock tooltip to bottom; auto = touch device under 480px |
|
|
350
|
+
| `tooltipHold` | `2600` | ms the tooltip stays after finger lift; `0` = until next tap |
|
|
351
|
+
|
|
352
|
+
### Presentation
|
|
353
|
+
|
|
354
|
+
| Option | Default | Description |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `height` | `260` | Plot height px; legend and zoom bar sit outside it |
|
|
357
|
+
| `animation` | `{duration: 800, delay: 70}` | Entrance timing; data changes morph over 550 ms |
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Methods
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
chart.setData(rows) // morph to new values (rebuilds if the shape changed)
|
|
365
|
+
chart.setType('donut') // switch type; resets zoom and hidden series
|
|
366
|
+
chart.update({ depth: 12 }) // merge options and rebuild
|
|
367
|
+
chart.replay() // replay the entrance animation
|
|
368
|
+
chart.resize() // re-measure (automatic on rotate/resize)
|
|
369
|
+
|
|
370
|
+
chart.zoomTo(0.25, 0.75) // fraction window
|
|
371
|
+
chart.zoomTo(5, 10) // integers = data index range
|
|
372
|
+
chart.resetZoom()
|
|
373
|
+
chart.getZoom() // { from, to, startIndex, endIndex }
|
|
374
|
+
|
|
375
|
+
chart.toggleSeries(1) // hide/show series or slice by index
|
|
376
|
+
chart.toggleSeries(1, true) // explicitly hide
|
|
377
|
+
|
|
378
|
+
chart.toSVG() // SVG string, background included — for share/export
|
|
379
|
+
chart.destroy() // remove listeners and observers
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Statics: `RaChart.version`, `RaChart.defaults`, `RaChart.palette`, `RaChart.paletteDark`, `RaChart.types`, `RaChart.create(el, opts)`.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Events
|
|
387
|
+
|
|
388
|
+
The engine dispatches **DOM CustomEvents** on the host element. The Angular component re-emits them as `@Output()`s.
|
|
389
|
+
|
|
390
|
+
| DOM event | Angular output | Detail |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| `raChartReady` | `(ready)` | The chart instance |
|
|
393
|
+
| `raChartSelect` | `(select)` | The tapped mark |
|
|
394
|
+
| `raChartZoom` | `(zoomChange)` | `{ from, to, startIndex, endIndex }` |
|
|
395
|
+
| `raChartToggle` | `(toggle)` | `{ index, name, hidden }` |
|
|
396
|
+
|
|
397
|
+
```js
|
|
398
|
+
document.getElementById('sales')
|
|
399
|
+
.addEventListener('raChartSelect', function (e) {
|
|
400
|
+
console.log(e.detail.category, e.detail.value);
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`raChartSelect` detail varies by type — `category`/`series`/`value` for most, plus `ohlc` for candlestick, `start`/`end`/`progress` for gantt, `x`/`y`/`size` for scatter and bubble, `kind`/`from`/`to` for sankey. `row` carries the original data object.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Mobile behaviour
|
|
409
|
+
|
|
410
|
+
**Touch.** Tap any mark to open its tooltip; drag without lifting to scrub across marks, each one snapping the tooltip along. The tooltip stays `tooltipHold` ms after you lift so it is readable, then fades. Tapping empty space dismisses it.
|
|
411
|
+
|
|
412
|
+
**Pinch and pan.** With `zoom: true`, two fingers on the plot zoom around the pinch midpoint and one finger pans once you are zoomed in. Panning only takes over the gesture after clearly horizontal movement, so vertical page scrolling still works. The range slider stays in sync.
|
|
413
|
+
|
|
414
|
+
**Hit targets.** On coarse pointers, dots and points get invisible finger-sized hit rings, thin candlesticks get full-band hit columns, zoom handles grow to 26 px, and legend rows carry 44 px of padded height.
|
|
415
|
+
|
|
416
|
+
**Haptics.** Selecting a mark or toggling a legend entry fires a light impact through Capacitor Haptics when available, falling back to `navigator.vibrate`, and silently doing nothing otherwise. Disable with `haptics: false`.
|
|
417
|
+
|
|
418
|
+
**Bottom-sheet tooltips.** Under 480 px on touch devices the tooltip docks to the bottom edge instead of floating, so your thumb never covers the value.
|
|
419
|
+
|
|
420
|
+
**Rotation.** A `ResizeObserver` on the host redraws after orientation changes, debounced 160 ms, without re-running entrance animations.
|
|
421
|
+
|
|
422
|
+
**Backgrounding.** Browsers freeze `requestAnimationFrame` while a page is hidden, which would otherwise leave a chart stuck mid-animation when the user switches apps. Charts built while hidden skip straight to their final frame, and a tween interrupted by backgrounding settles on its last frame instead of resuming half-drawn.
|
|
423
|
+
|
|
424
|
+
**Reduced motion.** With `prefers-reduced-motion: reduce`, animations resolve to their final frame immediately.
|
|
425
|
+
|
|
426
|
+
**Offline.** Everything renders locally — no network, no fonts, no tiles.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Theming and dark mode
|
|
431
|
+
|
|
432
|
+
Every colour is a CSS custom property, and the engine reads the same variables when it paints SVG. Restyle from your Ionic theme file without touching JS:
|
|
433
|
+
|
|
434
|
+
```css
|
|
435
|
+
:root {
|
|
436
|
+
--ra-chart-surface: #ffffff;
|
|
437
|
+
--ra-chart-text: #1f2937;
|
|
438
|
+
--ra-chart-label: #898781;
|
|
439
|
+
--ra-chart-grid: #e1e0d9;
|
|
440
|
+
--ra-chart-zoom-handle-border: #534ab7;
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Dark mode activates automatically from `prefers-color-scheme`, Ionic 8's `.ion-palette-dark`, or a legacy `body.dark`. The series palette swaps to steps validated against a dark surface — it is a re-stepped palette, not an inverted one.
|
|
445
|
+
|
|
446
|
+
Force a mode per chart with a class:
|
|
447
|
+
|
|
448
|
+
```html
|
|
449
|
+
<div class="ra-chart ra-chart--light"></div>
|
|
450
|
+
<div class="ra-chart ra-chart--dark"></div>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
The default 8-colour palette is ordered so adjacent series stay distinguishable for colourblind viewers. If you pass your own `colors`, keep that in mind. Charts also carry non-colour cues — legends, direct labels, tooltips.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Recipes
|
|
458
|
+
|
|
459
|
+
**Live data**
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
this.sub = interval(5000).subscribe(() => {
|
|
463
|
+
this.rows = [...fetchLatest()]; // new array reference
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Switch type from an ion-segment**
|
|
468
|
+
|
|
469
|
+
```html
|
|
470
|
+
<ion-segment [value]="type" (ionChange)="type = $any($event.detail.value)">
|
|
471
|
+
<ion-segment-button value="column"><ion-label>Bars</ion-label></ion-segment-button>
|
|
472
|
+
<ion-segment-button value="donut"><ion-label>Donut</ion-label></ion-segment-button>
|
|
473
|
+
</ion-segment>
|
|
474
|
+
|
|
475
|
+
<ra-chart [type]="type" ...></ra-chart>
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Share a chart as an image**
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
import { Share } from '@capacitor/share';
|
|
482
|
+
import { Filesystem, Directory } from '@capacitor/filesystem';
|
|
483
|
+
|
|
484
|
+
async share() {
|
|
485
|
+
const svg = this.chart!.toSVG();
|
|
486
|
+
const file = await Filesystem.writeFile({
|
|
487
|
+
path: 'chart.svg',
|
|
488
|
+
data: btoa(unescape(encodeURIComponent(svg))),
|
|
489
|
+
directory: Directory.Cache
|
|
490
|
+
});
|
|
491
|
+
await Share.share({ title: 'Chart', url: file.uri });
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Brand colours**
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
colors = ['#534ab7', '#2196f3', '#f0334b', '#eda100'];
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
```html
|
|
502
|
+
<ra-chart [colors]="colors" ...></ra-chart>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Long series on a small screen** — show a window and let the user pinch:
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
ready(chart: RaChartInstance) {
|
|
509
|
+
chart.zoomTo(Math.max(0, this.rows.length - 12), this.rows.length);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
```html
|
|
514
|
+
<ra-chart [zoom]="true" (ready)="ready($event)" ...></ra-chart>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Troubleshooting
|
|
520
|
+
|
|
521
|
+
**"Chart engine failed to load"** — `ra-chart.js` is not at the loader's path. Confirm it sits in `src/assets/ra-chart/`, that `angular.json` copies `src/assets`, and override `RA_CHART_SCRIPT_URL` if you keep it elsewhere.
|
|
522
|
+
|
|
523
|
+
**Chart is blank but no error** — the host had zero width at build time (a hidden tab, a collapsed accordion). Call `chart.resize()` or the component's `resize()` when the container becomes visible.
|
|
524
|
+
|
|
525
|
+
**Nothing happens when data changes** — you mutated the array. Assign a new reference.
|
|
526
|
+
|
|
527
|
+
**Page will not scroll over the chart** — only happens with `zoom: true`; vertical scrolling is preserved and horizontal drags pan. Set `pinchZoom: false` to disable gesture capture entirely.
|
|
528
|
+
|
|
529
|
+
**Labels are cut off** — increase `height`, or shorten category labels. Horizontal `bar` and `gantt` cap the label gutter at about a third of the width.
|
|
530
|
+
|
|
531
|
+
**Tooltip is clipped inside `ion-card`** — the tooltip is absolutely positioned inside the chart element; give the card content enough padding, or set `sheetTooltip: true` to dock it.
|
|
532
|
+
|
|
533
|
+
**No haptics on device** — install `@capacitor/haptics`, or rely on the `navigator.vibrate` fallback (Android only; iOS Safari does not support it).
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Browser and platform support
|
|
538
|
+
|
|
539
|
+
Android WebView 60+ (Android 8+), iOS 12+, and all modern desktop browsers. `ResizeObserver` falls back to window resize/orientation listeners; `CustomEvent` falls back to `createEvent` for old WebViews.
|
|
540
|
+
|
|
541
|
+
## Licence
|
|
542
|
+
|
|
543
|
+
MIT — RemoteApps.
|
package/ionic/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* raChart Cordova — Ionic/Angular entry point.
|
|
3
|
+
*
|
|
4
|
+
* Standalone (Angular 16+):
|
|
5
|
+
* import { RaChartComponent } from 'src/app/shared/ra-chart';
|
|
6
|
+
* @Component({ imports: [RaChartComponent], ... })
|
|
7
|
+
*
|
|
8
|
+
* NgModule apps:
|
|
9
|
+
* import { RaChartModule } from 'src/app/shared/ra-chart';
|
|
10
|
+
* @NgModule({ imports: [RaChartModule], ... })
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export * from './ra-chart.types';
|
|
14
|
+
export * from './ra-chart.loader';
|
|
15
|
+
export * from './ra-chart.component';
|
|
16
|
+
export * from './ra-chart.module';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- The engine renders into this element and adds its own .ra-chart class. -->
|
|
2
|
+
<div #host class="ra-chart" [class.ra-chart--busy]="loading || isEmpty"></div>
|
|
3
|
+
|
|
4
|
+
<div class="ra-chart__loading" *ngIf="loading" [style.height.px]="height">
|
|
5
|
+
<span class="ra-chart__spinner"></span>
|
|
6
|
+
<span>{{ loadingText }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="ra-chart__empty" *ngIf="!loading && isEmpty" [style.height.px]="height">
|
|
10
|
+
<span>{{ emptyText }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="ra-chart__empty" *ngIf="error" [style.height.px]="height">
|
|
14
|
+
<span>{{ error }}</span>
|
|
15
|
+
</div>
|