cordova-plugin-ra-chart 1.0.5 → 1.0.7
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 +28 -1
- package/ionic/ra-chart.component.ts +9 -0
- package/ionic/ra-chart.types.ts +3 -0
- package/package.json +1 -1
- package/plugin.xml +1 -1
- package/types/index.d.ts +19 -0
- package/www/ra-chart.js +142 -10
package/README.md
CHANGED
|
@@ -391,6 +391,9 @@ data: [{ price: 12, rating: 3.8, reach: 20 }]
|
|
|
391
391
|
|---|---|---|
|
|
392
392
|
| `zoom` | `false` | Range slider (XY types + candlestick) |
|
|
393
393
|
| `pinchZoom` | `true` | Pinch and pan when `zoom` is on |
|
|
394
|
+
| `labelRotation` | `'auto'` | Category-label tilt. `'auto'` keeps labels flat while they fit, tilts to −35° when they would collide, −90° once columns get narrow. A number forces an angle; `0` forces flat |
|
|
395
|
+
| `labelMaxLength` | `0` | Hard character cap on category labels; `0` disables. Full text stays in the tooltip |
|
|
396
|
+
| `labelEllipsis` | `'…'` | Suffix appended when a label is shortened, e.g. `'....'` |
|
|
394
397
|
| `legend` | `true` | Tappable; auto-hidden for single-series XY |
|
|
395
398
|
| `haptics` | `true` | Vibrate on touch-select |
|
|
396
399
|
| `sheetTooltip` | `'auto'` | Dock tooltip to bottom; auto = touch device under 480px |
|
|
@@ -675,7 +678,31 @@ To silence the diagnostics in production: `RaChart.warnings = false;`
|
|
|
675
678
|
|
|
676
679
|
**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.
|
|
677
680
|
|
|
678
|
-
**
|
|
681
|
+
**Arabic / Hebrew: tilted labels overlap the plot** — fixed in v1.0.6. SVG resolves `text-anchor` against the inline direction, so under `dir="rtl"` an `"end"` anchor flips to the opposite side and tilted labels swept *upward into* the chart instead of down away from it; axis numbers drifted toward the plot for the same reason. The drawing surface is now pinned to `direction: ltr`, which fixes anchoring while the bidi algorithm still shapes each Arabic run right-to-left. Nothing to configure — RTL pages work as they are.
|
|
682
|
+
|
|
683
|
+
RTL notes: the HTML legend follows the page direction, so series read right-to-left (correct mirroring). Arabic glyphs sit taller than Latin, and the bottom padding now accounts for that, so labels are never clipped.
|
|
684
|
+
|
|
685
|
+
**Category labels overlap or run together** — fixed in v1.0.5. Long names like "Material Status" are wider than their column, and the old spacing logic assumed a fixed 32px per label so it never thinned them out. Labels now tilt to −35° automatically (−90° when columns get narrow), the bottom padding grows to fit, and anything still too long is ellipsised with the full text kept in an SVG `<title>`. Override with `labelRotation`:
|
|
686
|
+
|
|
687
|
+
```html
|
|
688
|
+
<ra-chart labelRotation="0" ...></ra-chart> <!-- force flat (thins labels out) -->
|
|
689
|
+
<ra-chart [labelRotation]="-90" ...></ra-chart> <!-- force vertical -->
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Prefer short category names where you can — tilted labels cost up to a third of the chart height.
|
|
693
|
+
|
|
694
|
+
**Shortening long labels.** `labelMaxLength` caps them at a character count and appends `labelEllipsis`:
|
|
695
|
+
|
|
696
|
+
```html
|
|
697
|
+
<ra-chart [labelMaxLength]="5" labelEllipsis="...." ...></ra-chart>
|
|
698
|
+
<!-- "Total Employees" → "Total...." -->
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
The cap is applied *before* layout, so shortening labels enough to fit lets them stay flat and hands the vertical space back to the plot — in a five-category chart that is 72px of bottom padding down to 24px. The full text is still shown in the tooltip and kept in an SVG `<title>`.
|
|
702
|
+
|
|
703
|
+
Set `labelEllipsis` to `''` for a hard cut with no suffix. Both options apply to vertical category axes and to the left gutter of horizontal `bar` charts.
|
|
704
|
+
|
|
705
|
+
**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; longer names are ellipsised with the full text in a `<title>`.
|
|
679
706
|
|
|
680
707
|
**Legend reads "RevenueTarget" with no colour swatches** — the legend HTML is unstyled, meaning `ra-chart.css` did not reach it. Almost always because it was added to a component's `styleUrls` instead of `angular.json` → `styles`; view encapsulation scopes it to `[_ngcontent-*]` and the runtime-created legend has no such attribute. Fixed by default in v1.0.3 (the engine injects its own structural CSS), so updating the engine resolves it. To theme, move `ra-chart.css` to the global styles array.
|
|
681
708
|
|
|
@@ -63,6 +63,12 @@ export class RaChartComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|
|
63
63
|
@Input() downColor?: string;
|
|
64
64
|
|
|
65
65
|
// ---- interaction ----
|
|
66
|
+
/** 'auto' tilts long category labels instead of letting them collide. */
|
|
67
|
+
@Input() labelRotation: 'auto' | number = 'auto';
|
|
68
|
+
/** Hard character cap on category labels; 0 disables it. */
|
|
69
|
+
@Input() labelMaxLength = 0;
|
|
70
|
+
/** Suffix appended when a label is shortened, e.g. '…' or '....'. */
|
|
71
|
+
@Input() labelEllipsis = '…';
|
|
66
72
|
@Input() zoom = false;
|
|
67
73
|
@Input() pinchZoom = true;
|
|
68
74
|
@Input() legend = true;
|
|
@@ -197,6 +203,9 @@ export class RaChartComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|
|
197
203
|
min: this.min,
|
|
198
204
|
max: this.max,
|
|
199
205
|
bands: this.bands,
|
|
206
|
+
labelRotation: this.labelRotation,
|
|
207
|
+
labelMaxLength: this.labelMaxLength,
|
|
208
|
+
labelEllipsis: this.labelEllipsis,
|
|
200
209
|
zoom: this.zoom,
|
|
201
210
|
pinchZoom: this.pinchZoom,
|
|
202
211
|
legend: this.legend,
|
package/ionic/ra-chart.types.ts
CHANGED
|
@@ -63,6 +63,9 @@ export interface RaChartOptions {
|
|
|
63
63
|
bands?: RaBand[] | null;
|
|
64
64
|
upColor?: string;
|
|
65
65
|
downColor?: string;
|
|
66
|
+
labelRotation?: 'auto' | number;
|
|
67
|
+
labelMaxLength?: number;
|
|
68
|
+
labelEllipsis?: string;
|
|
66
69
|
zoom?: boolean;
|
|
67
70
|
pinchZoom?: boolean;
|
|
68
71
|
legend?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cordova-plugin-ra-chart",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Dependency-free SVG chart engine for Cordova and Ionic — 21 chart types with touch gestures, pinch-to-zoom, haptics and dark-mode theming. No native code, no build step.",
|
|
5
5
|
"main": "www/ra-chart.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
package/plugin.xml
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -91,6 +91,25 @@ export interface RaChartOptions {
|
|
|
91
91
|
/** Candlestick bear colour. */
|
|
92
92
|
downColor?: string;
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Category-label tilt on vertical charts. `'auto'` (default) keeps labels
|
|
96
|
+
* flat while they fit, tilts to -35° when they would collide, and goes to
|
|
97
|
+
* -90° once columns get narrow. Pass a number to force an angle, or `0` to
|
|
98
|
+
* force flat (labels are then thinned out to avoid overlap).
|
|
99
|
+
*/
|
|
100
|
+
labelRotation?: 'auto' | number;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Hard character cap on category labels. `0` (default) leaves them alone and
|
|
104
|
+
* relies on tilting. Applied before layout, so capping labels short enough
|
|
105
|
+
* lets them stay flat and gives the plot back its vertical space. The full
|
|
106
|
+
* text stays available in the tooltip and an SVG `<title>`.
|
|
107
|
+
*/
|
|
108
|
+
labelMaxLength?: number;
|
|
109
|
+
|
|
110
|
+
/** Suffix appended when a label is shortened. Default `'…'`; e.g. `'....'`. */
|
|
111
|
+
labelEllipsis?: string;
|
|
112
|
+
|
|
94
113
|
/** Range-slider zoom (XY types + candlestick). */
|
|
95
114
|
zoom?: boolean;
|
|
96
115
|
/** Pinch-to-zoom and drag-to-pan when `zoom` is on. Default true. */
|
package/www/ra-chart.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* raChart Cordova v1.0.
|
|
2
|
+
* raChart Cordova v1.0.7 — mobile SVG chart engine
|
|
3
3
|
* RemoteApps UI Components
|
|
4
4
|
*
|
|
5
5
|
* Zero dependencies. No jQuery, no chart library, no build step.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"use strict";
|
|
21
21
|
|
|
22
22
|
var NS = "http://www.w3.org/2000/svg";
|
|
23
|
-
var VERSION = "1.0.
|
|
23
|
+
var VERSION = "1.0.7";
|
|
24
24
|
|
|
25
25
|
/* ============================================================
|
|
26
26
|
Palettes — CVD-validated order, do not shuffle
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
bands: null,
|
|
55
55
|
upColor: "#0ca30c",
|
|
56
56
|
downColor: "#d03b3b",
|
|
57
|
+
labelRotation: "auto", // "auto" tilts long category labels; or a number of degrees
|
|
58
|
+
labelMaxLength: 0, // hard character cap on category labels (0 = off)
|
|
59
|
+
labelEllipsis: "…", // suffix appended when a label is shortened
|
|
57
60
|
zoom: false, // range-slider zoom (XY + candlestick)
|
|
58
61
|
pinchZoom: true, // two-finger pinch + drag pan when zoom is on
|
|
59
62
|
legend: true,
|
|
@@ -198,6 +201,73 @@
|
|
|
198
201
|
return n;
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
var LABEL_SIZE = 11;
|
|
205
|
+
function labelFont(size) {
|
|
206
|
+
return "500 " + (size || LABEL_SIZE) + "px system-ui, -apple-system, 'Segoe UI', sans-serif";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Shorten to fit maxPx, ending in `ell`. */
|
|
210
|
+
function truncate(s, maxPx, size, ell) {
|
|
211
|
+
s = String(s);
|
|
212
|
+
ell = ell == null ? "…" : ell;
|
|
213
|
+
if (!isFinite(maxPx)) return s;
|
|
214
|
+
var font = labelFont(size);
|
|
215
|
+
if (textW(s, font) <= maxPx) return s;
|
|
216
|
+
var lo = 1, hi = s.length;
|
|
217
|
+
while (lo < hi) { // longest prefix that still fits
|
|
218
|
+
var mid = (lo + hi + 1) >> 1;
|
|
219
|
+
if (textW(s.slice(0, mid) + ell, font) <= maxPx) lo = mid; else hi = mid - 1;
|
|
220
|
+
}
|
|
221
|
+
return s.slice(0, lo).replace(/\s+$/, "") + ell;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Hard character cap, applied before any pixel-based fitting. */
|
|
225
|
+
function clipChars(s, max, ell) {
|
|
226
|
+
s = String(s);
|
|
227
|
+
if (!max || max <= 0 || s.length <= max) return s;
|
|
228
|
+
return s.slice(0, max).replace(/\s+$/, "") + (ell == null ? "…" : ell);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Decide how category labels sit under a vertical axis.
|
|
233
|
+
*
|
|
234
|
+
* Long names ("Material Status") are wider than their column, so laying them
|
|
235
|
+
* flat makes them collide — and simply dropping every other one hides real
|
|
236
|
+
* categories. Instead measure the text and tilt it when it cannot fit, going
|
|
237
|
+
* to vertical only when the columns get genuinely narrow. Returns the bottom
|
|
238
|
+
* padding the chosen layout needs.
|
|
239
|
+
*/
|
|
240
|
+
function labelLayout(cats, band, totalH, forced) {
|
|
241
|
+
var flat = { angle: 0, maxPx: Infinity, pad: 24 };
|
|
242
|
+
if (!cats.length) return flat;
|
|
243
|
+
var font = labelFont(LABEL_SIZE);
|
|
244
|
+
var maxW = 0;
|
|
245
|
+
for (var i = 0; i < cats.length; i++) {
|
|
246
|
+
var wd = textW(cats[i], font);
|
|
247
|
+
if (wd > maxW) maxW = wd;
|
|
248
|
+
}
|
|
249
|
+
var angle;
|
|
250
|
+
if (typeof forced === "number") angle = forced;
|
|
251
|
+
else if (forced === 0) angle = 0;
|
|
252
|
+
else if (maxW <= band * 0.92) return flat; // fits flat — leave it alone
|
|
253
|
+
else angle = band < 26 ? -90 : -35;
|
|
254
|
+
if (!angle) return { angle: 0, maxPx: Infinity, pad: 24 };
|
|
255
|
+
|
|
256
|
+
// cap how much of the chart the labels may consume
|
|
257
|
+
var cap = Math.max(40, Math.min(totalH * 0.34, 96));
|
|
258
|
+
var rad = Math.abs(angle) * Math.PI / 180;
|
|
259
|
+
var sin = Math.sin(rad) || 1;
|
|
260
|
+
var cos = Math.cos(rad);
|
|
261
|
+
// A tilted line of text is as tall as (length x sin) PLUS its own glyph box
|
|
262
|
+
// laid on the diagonal (lineH x cos). Dropping that second term clips
|
|
263
|
+
// descenders — very visible in Arabic, which sits taller than Latin.
|
|
264
|
+
var lineH = LABEL_SIZE * 1.35;
|
|
265
|
+
var room = cap - 8 - lineH * cos;
|
|
266
|
+
var maxPx = Math.min(maxW, Math.max(24, room / sin));
|
|
267
|
+
var pad = Math.min(cap, Math.max(24, Math.ceil(maxPx * sin + lineH * cos) + 8));
|
|
268
|
+
return { angle: angle, maxPx: maxPx, pad: pad };
|
|
269
|
+
}
|
|
270
|
+
|
|
201
271
|
function svgText(parent, x, y, text, anchor, size, color, weight) {
|
|
202
272
|
var n = svgEl("text", {
|
|
203
273
|
x: x, y: y, "text-anchor": anchor || "middle",
|
|
@@ -657,6 +727,12 @@
|
|
|
657
727
|
viewBox: "0 0 " + this.w + " " + this.h,
|
|
658
728
|
preserveAspectRatio: "none"
|
|
659
729
|
});
|
|
730
|
+
// Pin the drawing surface to LTR. text-anchor is resolved against the
|
|
731
|
+
// inline direction, so under an RTL page "end" would flip to the opposite
|
|
732
|
+
// side and tilted labels would sweep up into the plot instead of down away
|
|
733
|
+
// from it. Glyph shaping is unaffected: the bidi algorithm still lays each
|
|
734
|
+
// Arabic/Hebrew run out right-to-left inside the element.
|
|
735
|
+
this.svg.style.direction = "ltr";
|
|
660
736
|
this.stage.appendChild(this.svg);
|
|
661
737
|
|
|
662
738
|
this.tip = el("div", "ra-chart__tip", this.el);
|
|
@@ -1077,28 +1153,54 @@
|
|
|
1077
1153
|
this.viewData = data;
|
|
1078
1154
|
|
|
1079
1155
|
var cats = data.map(function (d) { return String(d[o.category]); });
|
|
1156
|
+
// display copy: the character cap is applied up front so the layout below
|
|
1157
|
+
// measures what will actually be drawn, and can keep labels flat when the
|
|
1158
|
+
// cap makes them short enough to fit
|
|
1159
|
+
var catLabels = cats.map(function (s) {
|
|
1160
|
+
return clipChars(s, o.labelMaxLength, o.labelEllipsis);
|
|
1161
|
+
});
|
|
1080
1162
|
var nS = o.series.length, nC = cats.length;
|
|
1081
1163
|
|
|
1082
1164
|
this.cur = { kind: "xy", vals: this._xyVals(data), scaleMax: null };
|
|
1083
1165
|
|
|
1084
|
-
var g = {
|
|
1166
|
+
var g = {
|
|
1167
|
+
horizontal: horizontal, isLine: isLine, isLolli: isLolli, depth: depth,
|
|
1168
|
+
cats: cats, // full text — tooltips and events
|
|
1169
|
+
catLabels: catLabels, // what the axis draws
|
|
1170
|
+
nS: nS, nC: nC
|
|
1171
|
+
};
|
|
1085
1172
|
this.geom = g;
|
|
1086
1173
|
|
|
1087
1174
|
var scale = this._xyScale();
|
|
1088
1175
|
var yLabels = [];
|
|
1089
1176
|
for (var v = o.min; v <= scale.max + 1e-9; v += scale.step) yLabels.push(fmt(v));
|
|
1090
1177
|
var maxYLabelW = Math.max.apply(null, yLabels.map(function (s) { return textW(s); }).concat([12]));
|
|
1091
|
-
var maxCatW = Math.max.apply(null,
|
|
1178
|
+
var maxCatW = Math.max.apply(null, catLabels.map(function (s) { return textW(s); }).concat([12]));
|
|
1092
1179
|
|
|
1093
1180
|
g.padL = (horizontal ? Math.min(maxCatW, this.w * 0.34) : maxYLabelW) + 12;
|
|
1094
1181
|
g.padR = 8 + depth;
|
|
1095
1182
|
g.padT = 8 + depth * 0.5;
|
|
1096
|
-
g.padB = 24;
|
|
1097
1183
|
g.plotX = g.padL;
|
|
1098
1184
|
g.plotY = g.padT;
|
|
1099
1185
|
g.plotW = Math.max(40, this.w - g.padL - g.padR);
|
|
1100
|
-
|
|
1101
|
-
|
|
1186
|
+
|
|
1187
|
+
if (horizontal) {
|
|
1188
|
+
// category labels live in the left gutter, so the bottom stays shallow
|
|
1189
|
+
g.padB = 24;
|
|
1190
|
+
g.plotH = Math.max(40, this.h - g.padT - g.padB);
|
|
1191
|
+
g.band = g.plotH / Math.max(1, nC);
|
|
1192
|
+
g.labelAngle = 0;
|
|
1193
|
+
g.labelMaxPx = Math.max(24, g.padL - 12);
|
|
1194
|
+
} else {
|
|
1195
|
+
// band does not depend on padB here, so measure the labels first and let
|
|
1196
|
+
// them reserve the bottom space they actually need
|
|
1197
|
+
g.band = g.plotW / Math.max(1, nC);
|
|
1198
|
+
var lay = labelLayout(catLabels, g.band, this.h, o.labelRotation);
|
|
1199
|
+
g.labelAngle = lay.angle;
|
|
1200
|
+
g.labelMaxPx = lay.maxPx;
|
|
1201
|
+
g.padB = lay.pad;
|
|
1202
|
+
g.plotH = Math.max(40, this.h - g.padT - g.padB);
|
|
1203
|
+
}
|
|
1102
1204
|
|
|
1103
1205
|
this.gGrid = svgEl("g", null, this.svg);
|
|
1104
1206
|
this.gMarks = svgEl("g", null, this.svg);
|
|
@@ -1241,7 +1343,15 @@
|
|
|
1241
1343
|
svgText(this.gGrid, pos, g.plotY + g.plotH + 16, fmt(v), "middle", 11, T.label);
|
|
1242
1344
|
}
|
|
1243
1345
|
for (i = 0; i < g.nC; i++) {
|
|
1244
|
-
|
|
1346
|
+
// the gutter is capped at a third of the width, so clip to fit it
|
|
1347
|
+
var fullY = g.cats[i];
|
|
1348
|
+
var shownY = truncate(g.catLabels[i], g.labelMaxPx, 11.5, o.labelEllipsis);
|
|
1349
|
+
var nodeY = svgText(this.gGrid, g.plotX - 7, g.plotY + g.band * (i + .5) + 4,
|
|
1350
|
+
shownY, "end", 11.5, T.label);
|
|
1351
|
+
if (shownY !== fullY) {
|
|
1352
|
+
var ttY = svgEl("title", null, nodeY);
|
|
1353
|
+
ttY.appendChild(document.createTextNode(fullY));
|
|
1354
|
+
}
|
|
1245
1355
|
}
|
|
1246
1356
|
} else {
|
|
1247
1357
|
for (v = o.min; v <= scale.max + 1e-9; v += scale.step) {
|
|
@@ -1252,10 +1362,32 @@
|
|
|
1252
1362
|
}, this.gGrid);
|
|
1253
1363
|
svgText(this.gGrid, g.plotX - 7, pos + 4, fmt(v), "end", 11, T.label);
|
|
1254
1364
|
}
|
|
1255
|
-
var
|
|
1365
|
+
var ang = g.labelAngle || 0;
|
|
1366
|
+
// rotation removes the collision, so only thin out when even tilted
|
|
1367
|
+
// labels would stack on top of each other
|
|
1368
|
+
var skip = ang ? Math.max(1, Math.ceil(11 / g.band)) : 1;
|
|
1369
|
+
if (!ang) {
|
|
1370
|
+
var widest = 0;
|
|
1371
|
+
for (i = 0; i < g.nC; i++) widest = Math.max(widest, textW(g.catLabels[i]));
|
|
1372
|
+
// Same fit rule labelLayout used to choose "flat", so a layout that
|
|
1373
|
+
// decided the labels fit never has them silently dropped here. Only
|
|
1374
|
+
// a forced labelRotation:0 that genuinely overflows gets thinned.
|
|
1375
|
+
skip = widest <= g.band * 0.92 ? 1 : Math.max(1, Math.ceil((widest + 8) / g.band));
|
|
1376
|
+
}
|
|
1256
1377
|
for (i = 0; i < g.nC; i++) {
|
|
1257
1378
|
if (i % skip) continue;
|
|
1258
|
-
|
|
1379
|
+
var lx = g.plotX + g.band * (i + .5);
|
|
1380
|
+
var ly = g.plotY + g.plotH + (ang ? 14 : 16);
|
|
1381
|
+
var full = g.cats[i];
|
|
1382
|
+
var shown = ang
|
|
1383
|
+
? truncate(g.catLabels[i], g.labelMaxPx, LABEL_SIZE, o.labelEllipsis)
|
|
1384
|
+
: g.catLabels[i];
|
|
1385
|
+
var node = svgText(this.gGrid, lx, ly, shown, ang ? "end" : "middle", 11, T.label);
|
|
1386
|
+
if (ang) node.setAttribute("transform", "rotate(" + ang + " " + lx + " " + ly + ")");
|
|
1387
|
+
if (shown !== full) {
|
|
1388
|
+
var tt = svgEl("title", null, node); // full text still reachable
|
|
1389
|
+
tt.appendChild(document.createTextNode(full));
|
|
1390
|
+
}
|
|
1259
1391
|
}
|
|
1260
1392
|
}
|
|
1261
1393
|
};
|