cordova-plugin-ra-chart 1.0.5 → 1.0.6
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 +11 -1
- package/ionic/ra-chart.component.ts +3 -0
- package/ionic/ra-chart.types.ts +1 -0
- package/package.json +1 -1
- package/plugin.xml +1 -1
- package/types/index.d.ts +8 -0
- package/www/ra-chart.js +102 -8
package/README.md
CHANGED
|
@@ -391,6 +391,7 @@ 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 |
|
|
394
395
|
| `legend` | `true` | Tappable; auto-hidden for single-series XY |
|
|
395
396
|
| `haptics` | `true` | Vibrate on touch-select |
|
|
396
397
|
| `sheetTooltip` | `'auto'` | Dock tooltip to bottom; auto = touch device under 480px |
|
|
@@ -675,7 +676,16 @@ To silence the diagnostics in production: `RaChart.warnings = false;`
|
|
|
675
676
|
|
|
676
677
|
**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
678
|
|
|
678
|
-
**
|
|
679
|
+
**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`:
|
|
680
|
+
|
|
681
|
+
```html
|
|
682
|
+
<ra-chart labelRotation="0" ...></ra-chart> <!-- force flat (thins labels out) -->
|
|
683
|
+
<ra-chart [labelRotation]="-90" ...></ra-chart> <!-- force vertical -->
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Prefer short category names where you can — tilted labels cost up to a third of the chart height.
|
|
687
|
+
|
|
688
|
+
**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
689
|
|
|
680
690
|
**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
691
|
|
|
@@ -63,6 +63,8 @@ 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';
|
|
66
68
|
@Input() zoom = false;
|
|
67
69
|
@Input() pinchZoom = true;
|
|
68
70
|
@Input() legend = true;
|
|
@@ -197,6 +199,7 @@ export class RaChartComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|
|
197
199
|
min: this.min,
|
|
198
200
|
max: this.max,
|
|
199
201
|
bands: this.bands,
|
|
202
|
+
labelRotation: this.labelRotation,
|
|
200
203
|
zoom: this.zoom,
|
|
201
204
|
pinchZoom: this.pinchZoom,
|
|
202
205
|
legend: this.legend,
|
package/ionic/ra-chart.types.ts
CHANGED
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.6",
|
|
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,14 @@ 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
|
+
|
|
94
102
|
/** Range-slider zoom (XY types + candlestick). */
|
|
95
103
|
zoom?: boolean;
|
|
96
104
|
/** 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.5 — 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.5";
|
|
24
24
|
|
|
25
25
|
/* ============================================================
|
|
26
26
|
Palettes — CVD-validated order, do not shuffle
|
|
@@ -54,6 +54,7 @@
|
|
|
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
|
|
57
58
|
zoom: false, // range-slider zoom (XY + candlestick)
|
|
58
59
|
pinchZoom: true, // two-finger pinch + drag pan when zoom is on
|
|
59
60
|
legend: true,
|
|
@@ -198,6 +199,59 @@
|
|
|
198
199
|
return n;
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
var LABEL_SIZE = 11;
|
|
203
|
+
function labelFont(size) {
|
|
204
|
+
return "500 " + (size || LABEL_SIZE) + "px system-ui, -apple-system, 'Segoe UI', sans-serif";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Shorten to fit maxPx, ending in an ellipsis. */
|
|
208
|
+
function truncate(s, maxPx, size) {
|
|
209
|
+
s = String(s);
|
|
210
|
+
if (!isFinite(maxPx)) return s;
|
|
211
|
+
var font = labelFont(size);
|
|
212
|
+
if (textW(s, font) <= maxPx) return s;
|
|
213
|
+
var lo = 1, hi = s.length;
|
|
214
|
+
while (lo < hi) { // longest prefix that still fits
|
|
215
|
+
var mid = (lo + hi + 1) >> 1;
|
|
216
|
+
if (textW(s.slice(0, mid) + "…", font) <= maxPx) lo = mid; else hi = mid - 1;
|
|
217
|
+
}
|
|
218
|
+
return s.slice(0, lo) + "…";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Decide how category labels sit under a vertical axis.
|
|
223
|
+
*
|
|
224
|
+
* Long names ("Material Status") are wider than their column, so laying them
|
|
225
|
+
* flat makes them collide — and simply dropping every other one hides real
|
|
226
|
+
* categories. Instead measure the text and tilt it when it cannot fit, going
|
|
227
|
+
* to vertical only when the columns get genuinely narrow. Returns the bottom
|
|
228
|
+
* padding the chosen layout needs.
|
|
229
|
+
*/
|
|
230
|
+
function labelLayout(cats, band, totalH, forced) {
|
|
231
|
+
var flat = { angle: 0, maxPx: Infinity, pad: 24 };
|
|
232
|
+
if (!cats.length) return flat;
|
|
233
|
+
var font = labelFont(LABEL_SIZE);
|
|
234
|
+
var maxW = 0;
|
|
235
|
+
for (var i = 0; i < cats.length; i++) {
|
|
236
|
+
var wd = textW(cats[i], font);
|
|
237
|
+
if (wd > maxW) maxW = wd;
|
|
238
|
+
}
|
|
239
|
+
var angle;
|
|
240
|
+
if (typeof forced === "number") angle = forced;
|
|
241
|
+
else if (forced === 0) angle = 0;
|
|
242
|
+
else if (maxW <= band * 0.92) return flat; // fits flat — leave it alone
|
|
243
|
+
else angle = band < 26 ? -90 : -35;
|
|
244
|
+
if (!angle) return { angle: 0, maxPx: Infinity, pad: 24 };
|
|
245
|
+
|
|
246
|
+
// cap how much of the chart the labels may consume
|
|
247
|
+
var cap = Math.max(40, Math.min(totalH * 0.34, 96));
|
|
248
|
+
var rad = Math.abs(angle) * Math.PI / 180;
|
|
249
|
+
var sin = Math.sin(rad) || 1;
|
|
250
|
+
var maxPx = Math.min(maxW, (cap - 12) / sin);
|
|
251
|
+
var pad = Math.min(cap, Math.max(24, Math.ceil(maxPx * sin) + 14));
|
|
252
|
+
return { angle: angle, maxPx: maxPx, pad: pad };
|
|
253
|
+
}
|
|
254
|
+
|
|
201
255
|
function svgText(parent, x, y, text, anchor, size, color, weight) {
|
|
202
256
|
var n = svgEl("text", {
|
|
203
257
|
x: x, y: y, "text-anchor": anchor || "middle",
|
|
@@ -1093,12 +1147,27 @@
|
|
|
1093
1147
|
g.padL = (horizontal ? Math.min(maxCatW, this.w * 0.34) : maxYLabelW) + 12;
|
|
1094
1148
|
g.padR = 8 + depth;
|
|
1095
1149
|
g.padT = 8 + depth * 0.5;
|
|
1096
|
-
g.padB = 24;
|
|
1097
1150
|
g.plotX = g.padL;
|
|
1098
1151
|
g.plotY = g.padT;
|
|
1099
1152
|
g.plotW = Math.max(40, this.w - g.padL - g.padR);
|
|
1100
|
-
|
|
1101
|
-
|
|
1153
|
+
|
|
1154
|
+
if (horizontal) {
|
|
1155
|
+
// category labels live in the left gutter, so the bottom stays shallow
|
|
1156
|
+
g.padB = 24;
|
|
1157
|
+
g.plotH = Math.max(40, this.h - g.padT - g.padB);
|
|
1158
|
+
g.band = g.plotH / Math.max(1, nC);
|
|
1159
|
+
g.labelAngle = 0;
|
|
1160
|
+
g.labelMaxPx = Math.max(24, g.padL - 12);
|
|
1161
|
+
} else {
|
|
1162
|
+
// band does not depend on padB here, so measure the labels first and let
|
|
1163
|
+
// them reserve the bottom space they actually need
|
|
1164
|
+
g.band = g.plotW / Math.max(1, nC);
|
|
1165
|
+
var lay = labelLayout(cats, g.band, this.h, o.labelRotation);
|
|
1166
|
+
g.labelAngle = lay.angle;
|
|
1167
|
+
g.labelMaxPx = lay.maxPx;
|
|
1168
|
+
g.padB = lay.pad;
|
|
1169
|
+
g.plotH = Math.max(40, this.h - g.padT - g.padB);
|
|
1170
|
+
}
|
|
1102
1171
|
|
|
1103
1172
|
this.gGrid = svgEl("g", null, this.svg);
|
|
1104
1173
|
this.gMarks = svgEl("g", null, this.svg);
|
|
@@ -1241,7 +1310,15 @@
|
|
|
1241
1310
|
svgText(this.gGrid, pos, g.plotY + g.plotH + 16, fmt(v), "middle", 11, T.label);
|
|
1242
1311
|
}
|
|
1243
1312
|
for (i = 0; i < g.nC; i++) {
|
|
1244
|
-
|
|
1313
|
+
// the gutter is capped at a third of the width, so clip to fit it
|
|
1314
|
+
var fullY = g.cats[i];
|
|
1315
|
+
var shownY = truncate(fullY, g.labelMaxPx, 11.5);
|
|
1316
|
+
var nodeY = svgText(this.gGrid, g.plotX - 7, g.plotY + g.band * (i + .5) + 4,
|
|
1317
|
+
shownY, "end", 11.5, T.label);
|
|
1318
|
+
if (shownY !== fullY) {
|
|
1319
|
+
var ttY = svgEl("title", null, nodeY);
|
|
1320
|
+
ttY.appendChild(document.createTextNode(fullY));
|
|
1321
|
+
}
|
|
1245
1322
|
}
|
|
1246
1323
|
} else {
|
|
1247
1324
|
for (v = o.min; v <= scale.max + 1e-9; v += scale.step) {
|
|
@@ -1252,10 +1329,27 @@
|
|
|
1252
1329
|
}, this.gGrid);
|
|
1253
1330
|
svgText(this.gGrid, g.plotX - 7, pos + 4, fmt(v), "end", 11, T.label);
|
|
1254
1331
|
}
|
|
1255
|
-
var
|
|
1332
|
+
var ang = g.labelAngle || 0;
|
|
1333
|
+
// rotation removes the collision, so only thin out when even tilted
|
|
1334
|
+
// labels would stack on top of each other
|
|
1335
|
+
var skip = ang ? Math.max(1, Math.ceil(11 / g.band)) : 1;
|
|
1336
|
+
if (!ang) {
|
|
1337
|
+
var widest = 0;
|
|
1338
|
+
for (i = 0; i < g.nC; i++) widest = Math.max(widest, textW(g.cats[i]));
|
|
1339
|
+
skip = Math.max(1, Math.ceil((widest + 8) / g.band));
|
|
1340
|
+
}
|
|
1256
1341
|
for (i = 0; i < g.nC; i++) {
|
|
1257
1342
|
if (i % skip) continue;
|
|
1258
|
-
|
|
1343
|
+
var lx = g.plotX + g.band * (i + .5);
|
|
1344
|
+
var ly = g.plotY + g.plotH + (ang ? 14 : 16);
|
|
1345
|
+
var full = g.cats[i];
|
|
1346
|
+
var shown = ang ? truncate(full, g.labelMaxPx) : full;
|
|
1347
|
+
var node = svgText(this.gGrid, lx, ly, shown, ang ? "end" : "middle", 11, T.label);
|
|
1348
|
+
if (ang) node.setAttribute("transform", "rotate(" + ang + " " + lx + " " + ly + ")");
|
|
1349
|
+
if (shown !== full) {
|
|
1350
|
+
var tt = svgEl("title", null, node); // full text still reachable
|
|
1351
|
+
tt.appendChild(document.createTextNode(full));
|
|
1352
|
+
}
|
|
1259
1353
|
}
|
|
1260
1354
|
}
|
|
1261
1355
|
};
|