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 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
- **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.
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,
@@ -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.5",
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
@@ -15,7 +15,7 @@
15
15
  -->
16
16
  <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
17
17
  id="cordova-plugin-ra-chart"
18
- version="1.0.4">
18
+ version="1.0.7">
19
19
 
20
20
  <name>raChart</name>
21
21
  <description>
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.4 — mobile SVG chart engine
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.4";
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 = { horizontal: horizontal, isLine: isLine, isLolli: isLolli, depth: depth, cats: cats, nS: nS, nC: nC };
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, cats.map(function (s) { return textW(s); }).concat([12]));
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
- g.plotH = Math.max(40, this.h - g.padT - g.padB);
1101
- g.band = (horizontal ? g.plotH : g.plotW) / Math.max(1, nC);
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
- svgText(this.gGrid, g.plotX - 7, g.plotY + g.band * (i + .5) + 4, g.cats[i], "end", 11.5, T.label);
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 skip = Math.max(1, Math.ceil(32 / g.band));
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
- svgText(this.gGrid, g.plotX + g.band * (i + .5), g.plotY + g.plotH + 16, g.cats[i], "middle", 11, T.label);
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
  };