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 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
- **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.
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,
@@ -63,6 +63,7 @@ export interface RaChartOptions {
63
63
  bands?: RaBand[] | null;
64
64
  upColor?: string;
65
65
  downColor?: string;
66
+ labelRotation?: 'auto' | number;
66
67
  zoom?: boolean;
67
68
  pinchZoom?: boolean;
68
69
  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.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
@@ -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.5">
19
19
 
20
20
  <name>raChart</name>
21
21
  <description>
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.4 — mobile SVG chart engine
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.4";
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
- g.plotH = Math.max(40, this.h - g.padT - g.padB);
1101
- g.band = (horizontal ? g.plotH : g.plotW) / Math.max(1, nC);
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
- svgText(this.gGrid, g.plotX - 7, g.plotY + g.band * (i + .5) + 4, g.cats[i], "end", 11.5, T.label);
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 skip = Math.max(1, Math.ceil(32 / g.band));
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
- svgText(this.gGrid, g.plotX + g.band * (i + .5), g.plotY + g.plotH + 16, g.cats[i], "middle", 11, T.label);
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
  };