cordova-plugin-ra-chart 1.0.2 → 1.0.4

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
@@ -82,6 +82,14 @@ Add the stylesheet to `angular.json`:
82
82
  ]
83
83
  ```
84
84
 
85
+ > **`ra-chart.css` must be global — never a component's `styleUrls`.** Angular's
86
+ > view encapsulation scopes those rules to `[_ngcontent-*]`, which never matches
87
+ > the legend, tooltip and zoom bar because the engine creates them at runtime.
88
+ > The classic symptom is a legend that reads `RevenueTarget` with no colour
89
+ > swatches. Since v1.0.3 the engine injects its own structural styles so this
90
+ > renders correctly regardless, and `ra-chart.css` is optional — it only supplies
91
+ > theming. Load it globally to theme; skip it entirely and charts still look right.
92
+
85
93
  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.
86
94
 
87
95
  To load it from somewhere else:
@@ -104,6 +112,31 @@ bootstrapApplication(AppComponent, {
104
112
  <script src="ra-chart.js"></script>
105
113
  ```
106
114
 
115
+ ### The `<ra-chart>` custom element
116
+
117
+ Loading `ra-chart.js` registers `<ra-chart>` as a real custom element, so the tag
118
+ works with no framework at all:
119
+
120
+ ```html
121
+ <ra-chart type="column" category="month"></ra-chart>
122
+ <script>
123
+ document.querySelector('ra-chart').data = rows;
124
+ document.querySelector('ra-chart').series = [{ name: 'Revenue', field: 'revenue' }];
125
+ </script>
126
+ ```
127
+
128
+ Scalar options are attributes (`type`, `category`, `height`, `zoom`, `stacked`,
129
+ `smooth`, `semi`, `depth`, `min`, `max`, `x-field`, `size-field`, …; dash-case maps
130
+ to camelCase). Object options are properties (`data`, `series`, `colors`, `bands`,
131
+ `fields`, `animation`).
132
+
133
+ This also acts as a safety net in Angular: if `RaChartComponent` is missing from
134
+ `imports`, the element still renders rather than failing silently. When the Angular
135
+ component *is* present it renders its own child first and the custom element stands
136
+ down, so the two never double-render. Disable with
137
+ `RaChart.defineElement = function () { return false; }` before load, or register
138
+ under a different tag with `RaChart.defineElement('my-chart')`.
139
+
107
140
  ---
108
141
 
109
142
  ## Quick start
@@ -177,6 +210,14 @@ export class SalesPage {
177
210
  </ra-chart>
178
211
  ```
179
212
 
213
+ > **`imports: [RaChartComponent]` is required.** Leave it out and Angular treats
214
+ > `<ra-chart>` as an unknown element. Ionic projects usually include
215
+ > `CUSTOM_ELEMENTS_SCHEMA`, which suppresses the error that would tell you —
216
+ > so the tag appears in the DOM, `@Output()`s never fire, and you get a blank
217
+ > box with a clean console. Since v1.0.2 the built-in custom element renders a
218
+ > chart anyway (see below), but you lose the Angular outputs and inputs typing,
219
+ > so add the import.
220
+
180
221
  ### NgModule apps
181
222
 
182
223
  ```ts
@@ -557,20 +598,22 @@ Your rows have: month, total. Check the `field`/`category` names against your da
557
598
 
558
599
  That means `field: 'revenue'` does not match your rows, so every value read as 0. The `field` in `series` and the `category` string must match your data object keys **exactly**, including case. An API returning `Total` will not bind to `field: 'total'`.
559
600
 
560
- **2. Async data with `<ra-chart>`**fixed in v1.0.1. On v1.0.0 a chart whose `data` was still `[]` when the view initialised never built once the API responded, leaving it permanently blank. Update to v1.0.1. There is no workaround on v1.0.0 other than `*ngIf`-ing the chart until data arrives.
601
+ **2. `RaChartComponent` missing from `imports`**the single most common cause in Ionic. Symptoms: the tag is in the DOM, no console error, and you had to add `display: block` yourself to see it. That last one is the giveaway the component sets `:host { display: block }`, so if you needed to add it manually, the component was never attached. Add it to `imports: []`. Since v1.0.2 the custom element renders anyway and logs an actionable error, but the Angular `@Input()`/`@Output()` API only works with the import.
561
602
 
562
- **3. Mutating the array instead of replacing it.** The component is `OnPush`, so `push()` is invisible to it:
603
+ **3. Async data with `<ra-chart>`** fixed in v1.0.1. On v1.0.0 a chart whose `data` was still `[]` when the view initialised never built once the API responded, leaving it permanently blank. Update to v1.0.1. There is no workaround on v1.0.0 other than `*ngIf`-ing the chart until data arrives.
604
+
605
+ **4. Mutating the array instead of replacing it.** The component is `OnPush`, so `push()` is invisible to it:
563
606
 
564
607
  ```ts
565
608
  this.rows = [...this.rows, row]; // ✅
566
609
  this.rows.push(row); // ❌ nothing redraws
567
610
  ```
568
611
 
569
- **4. Values are strings.** `"42"` from JSON coerces fine, but `"1,234"` or `"42%"` becomes 0. Parse before binding.
612
+ **5. Values are strings.** `"42"` from JSON coerces fine, but `"1,234"` or `"42%"` becomes 0. Parse before binding.
570
613
 
571
- **5. Zero-width container.** A chart built inside a hidden tab, collapsed accordion or `display:none` parent measures 0 and draws nothing. Call `resize()` when it becomes visible.
614
+ **6. Zero-width container.** A chart built inside a hidden tab, collapsed accordion or `display:none` parent measures 0 and draws nothing. Call `resize()` when it becomes visible.
572
615
 
573
- **6. Plain Cordova: building before `deviceready`.** `window.RaChart` does not exist until the plugin bootstraps. Wrap construction in a `deviceready` listener.
616
+ **7. Plain Cordova: building before `deviceready`.** `window.RaChart` does not exist until the plugin bootstraps. Wrap construction in a `deviceready` listener.
574
617
 
575
618
  To silence the diagnostics in production: `RaChart.warnings = false;`
576
619
 
@@ -586,6 +629,8 @@ To silence the diagnostics in production: `RaChart.warnings = false;`
586
629
 
587
630
  **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.
588
631
 
632
+ **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.
633
+
589
634
  **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.
590
635
 
591
636
  **No haptics on device** — install `@capacitor/haptics`, or rely on the `navigator.vibrate` fallback (Android only; iOS Safari does not support it).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordova-plugin-ra-chart",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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.1">
18
+ version="1.0.3">
19
19
 
20
20
  <name>raChart</name>
21
21
  <description>
package/www/ra-chart.css CHANGED
@@ -7,6 +7,14 @@
7
7
  * reads these same variables at build time for SVG fills.
8
8
  */
9
9
 
10
+ /* The custom element / Angular host. Without this the tag is inline and
11
+ collapses to zero height, which reads as "the chart did not render". */
12
+ ra-chart {
13
+ display: block;
14
+ position: relative;
15
+ width: 100%;
16
+ }
17
+
10
18
  .ra-chart {
11
19
  /* ---- palette hooks (override in your theme) ---- */
12
20
  --ra-chart-scheme: light;
package/www/ra-chart.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * raChart Cordova v1.0.1 — mobile SVG chart engine
2
+ * raChart Cordova v1.0.3 — 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.1";
23
+ var VERSION = "1.0.3";
24
24
 
25
25
  /* ============================================================
26
26
  Palettes — CVD-validated order, do not shuffle
@@ -351,6 +351,60 @@
351
351
  gauge: "gauge", heatmap: "heat"
352
352
  };
353
353
 
354
+ /* ============================================================
355
+ Base stylesheet
356
+
357
+ The SVG gets its colours from inline attributes, but the legend, tooltip
358
+ and zoom bar are real HTML and would collapse into unstyled inline text
359
+ without CSS — the classic "legend reads RevenueTarget" symptom. Angular
360
+ makes that easy to hit: ra-chart.css dropped into a component's styleUrls
361
+ is scoped to [_ngcontent-*], which never matches nodes the engine creates
362
+ at runtime.
363
+
364
+ So the structural rules ship in JS and are injected once, at the TOP of
365
+ <head>, leaving ra-chart.css (and any app CSS) free to override them.
366
+ Colours defer to the same custom properties, with fallbacks.
367
+ ============================================================ */
368
+
369
+ var BASE_STYLE_ID = "ra-chart-base-styles";
370
+ var BASE_CSS = [
371
+ 'ra-chart{display:block;position:relative;width:100%}',
372
+ '.ra-chart{position:relative;display:block;width:100%;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;-webkit-tap-highlight-color:transparent}',
373
+ '.ra-chart__stage{position:relative;width:100%;overflow:hidden}',
374
+ '.ra-chart__stage svg{display:block;width:100%;height:100%;overflow:visible;user-select:none;-webkit-user-select:none}',
375
+ '.ra-chart .ra-hit{cursor:pointer}',
376
+ '.ra-chart__legend{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:4px 14px;padding-top:10px;font-size:13px;font-weight:700;line-height:1.4;color:var(--ra-chart-body,#4b5563)}',
377
+ '.ra-chart__legend-item{display:inline-flex;align-items:center;gap:7px;padding:7px 2px;cursor:pointer;user-select:none;-webkit-user-select:none;transition:opacity .18s}',
378
+ '.ra-chart__legend-item.is-off{opacity:.38}',
379
+ '.ra-chart__legend-item.is-off .ra-chart__legend-label{text-decoration:line-through}',
380
+ '.ra-chart__legend-swatch{display:inline-block;width:11px;height:11px;border-radius:4px;flex:0 0 auto}',
381
+ '.ra-chart__legend-value{color:var(--ra-chart-muted,#9ca3af);font-weight:600}',
382
+ '.ra-chart__tip{position:absolute;z-index:5;pointer-events:none;max-width:88%;background:var(--ra-chart-tip-bg,#1f2937);color:var(--ra-chart-tip-text,#f9fafb);font-size:12px;font-weight:600;line-height:1.5;border-radius:10px;padding:8px 12px;box-shadow:0 6px 20px rgba(15,23,42,.28);opacity:0;transform:translate(-50%,-112%) scale(.96);transition:opacity .15s,transform .15s;white-space:nowrap}',
383
+ '.ra-chart__tip.is-on{opacity:1;transform:translate(-50%,-112%) scale(1)}',
384
+ '.ra-chart__tip--sheet{left:50%!important;top:auto!important;bottom:6px;transform:translate(-50%,8px);white-space:normal;text-align:center;padding:10px 16px;border-radius:12px;font-size:13px}',
385
+ '.ra-chart__tip--sheet.is-on{transform:translate(-50%,0)}',
386
+ '.ra-chart__tip-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}',
387
+ '.ra-chart__tip-cat{display:block;color:var(--ra-chart-tip-muted,#9ca3af);font-weight:500;margin-bottom:2px}',
388
+ '.ra-chart__tip-muted{color:var(--ra-chart-tip-muted,#9ca3af);font-weight:500}',
389
+ '.ra-chart__zoom{padding:6px 0 14px;touch-action:none}',
390
+ '.ra-chart__zoom-track{position:relative;height:8px;background:var(--ra-chart-zoom-track,#eceff3);border-radius:999px;margin:0 13px}',
391
+ '.ra-chart__zoom-sel{position:absolute;top:0;height:100%;background:var(--ra-chart-zoom-sel,#c7c2ee);border-radius:999px;cursor:grab}',
392
+ '.ra-chart__zoom-handle{position:absolute;top:50%;width:26px;height:26px;border-radius:50%;background:var(--ra-chart-zoom-handle,#fff);border:2px solid var(--ra-chart-zoom-handle-border,#534ab7);box-shadow:0 1px 5px rgba(15,23,42,.28);transform:translate(-50%,-50%);cursor:ew-resize;touch-action:none}',
393
+ '@media (pointer:fine){.ra-chart__zoom-handle{width:18px;height:18px}}'
394
+ ].join("");
395
+
396
+ function ensureBaseStyles() {
397
+ if (typeof document === "undefined") return;
398
+ if (document.getElementById(BASE_STYLE_ID)) return;
399
+ var head = document.head || document.getElementsByTagName("head")[0];
400
+ if (!head) return;
401
+ var st = document.createElement("style");
402
+ st.id = BASE_STYLE_ID;
403
+ st.appendChild(document.createTextNode(BASE_CSS));
404
+ // first in <head> so ra-chart.css and app styles win on equal specificity
405
+ head.insertBefore(st, head.firstChild);
406
+ }
407
+
354
408
  var UID = 0;
355
409
 
356
410
  /* ============================================================
@@ -362,6 +416,7 @@
362
416
  if (typeof host === "string") host = document.querySelector(host);
363
417
  if (!host) throw new Error("raChart: host element not found");
364
418
 
419
+ ensureBaseStyles();
365
420
  this.el = host;
366
421
  this.uid = ++UID;
367
422
  this.opts = merge(merge({}, DEFAULTS), options || {});
@@ -2855,6 +2910,176 @@
2855
2910
  RaChart.types = Object.keys(KINDS);
2856
2911
  RaChart.create = function (host, options) { return new RaChart(host, options); };
2857
2912
 
2913
+ /* ============================================================
2914
+ <ra-chart> custom element
2915
+
2916
+ Makes the tag work on its own, so a chart still renders when the Angular
2917
+ component was never instantiated — the silent-blank-chart case, which
2918
+ `CUSTOM_ELEMENTS_SCHEMA` hides by suppressing Angular's unknown-element
2919
+ error. Angular property bindings ([data]="rows") land on the element as
2920
+ plain DOM properties, which the accessors below pick up.
2921
+
2922
+ If the Angular component IS attached it renders its own child div first;
2923
+ the element sees that and stands down, so the two never double-render.
2924
+ ============================================================ */
2925
+
2926
+ var EL_ATTRS = [
2927
+ "type", "category", "height", "min", "max", "depth",
2928
+ "x-field", "size-field", "up-color", "down-color", "tooltip-hold",
2929
+ "zoom", "pinch-zoom", "stacked", "full-stacked", "smooth", "stepped",
2930
+ "semi", "legend", "haptics", "sheet-tooltip"
2931
+ ];
2932
+ var EL_PROPS = ["data", "series", "colors", "bands", "fields", "animation"];
2933
+
2934
+ function camel(s) {
2935
+ return s.replace(/-([a-z])/g, function (_, c) { return c.toUpperCase(); });
2936
+ }
2937
+
2938
+ function coerceAttr(v) {
2939
+ if (v === null) return undefined;
2940
+ if (v === "" || v === "true") return true;
2941
+ if (v === "false") return false;
2942
+ if (v === "auto") return "auto";
2943
+ var n = parseFloat(v);
2944
+ return (!isNaN(n) && String(n) === String(v).trim()) ? n : v;
2945
+ }
2946
+
2947
+ function defineElement(tag) {
2948
+ tag = tag || "ra-chart";
2949
+ if (typeof customElements === "undefined" ||
2950
+ typeof Reflect === "undefined" || !Reflect.construct) return false;
2951
+ if (customElements.get(tag)) return false;
2952
+
2953
+ // ES5-compatible subclassing so the file still parses on old WebViews;
2954
+ // the body only ever runs where custom elements exist.
2955
+ function RaChartElement() {
2956
+ return Reflect.construct(HTMLElement, [], Object.getPrototypeOf(this).constructor);
2957
+ }
2958
+ RaChartElement.prototype = Object.create(HTMLElement.prototype);
2959
+ RaChartElement.prototype.constructor = RaChartElement;
2960
+ Object.setPrototypeOf(RaChartElement, HTMLElement);
2961
+
2962
+ Object.defineProperty(RaChartElement, "observedAttributes", {
2963
+ get: function () { return EL_ATTRS; }
2964
+ });
2965
+
2966
+ // object-valued inputs arrive as properties, not attributes
2967
+ EL_PROPS.forEach(function (name) {
2968
+ Object.defineProperty(RaChartElement.prototype, name, {
2969
+ configurable: true,
2970
+ get: function () { return this["_" + name]; },
2971
+ set: function (v) {
2972
+ this["_" + name] = v;
2973
+ this._raSchedule();
2974
+ }
2975
+ });
2976
+ });
2977
+
2978
+ RaChartElement.prototype.connectedCallback = function () {
2979
+ this._raSchedule();
2980
+ };
2981
+
2982
+ RaChartElement.prototype.disconnectedCallback = function () {
2983
+ if (this._raChart) { this._raChart.destroy(); this._raChart = null; }
2984
+ this._raMount = null;
2985
+ };
2986
+
2987
+ RaChartElement.prototype.attributeChangedCallback = function () {
2988
+ this._raSchedule();
2989
+ };
2990
+
2991
+ /** Coalesce the burst of attribute/property writes into one render. */
2992
+ RaChartElement.prototype._raSchedule = function () {
2993
+ var self = this;
2994
+ if (this._raPending) return;
2995
+ this._raPending = true;
2996
+ var run = function () {
2997
+ self._raPending = false;
2998
+ self._raRender();
2999
+ };
3000
+ // a macrotask lets a framework finish rendering its own children first
3001
+ if (typeof setTimeout === "function") setTimeout(run, 0);
3002
+ else run();
3003
+ };
3004
+
3005
+ RaChartElement.prototype._raOptions = function () {
3006
+ var o = {};
3007
+ for (var i = 0; i < EL_ATTRS.length; i++) {
3008
+ var a = EL_ATTRS[i];
3009
+ if (!this.hasAttribute(a)) continue;
3010
+ var v = coerceAttr(this.getAttribute(a));
3011
+ if (v !== undefined) o[camel(a)] = v;
3012
+ }
3013
+ for (var j = 0; j < EL_PROPS.length; j++) {
3014
+ var p = EL_PROPS[j];
3015
+ if (this["_" + p] !== undefined) o[p] = this["_" + p];
3016
+ }
3017
+ return o;
3018
+ };
3019
+
3020
+ RaChartElement.prototype._raRender = function () {
3021
+ if (!this.isConnected) return;
3022
+
3023
+ // Stand down if a framework component owns this element: it renders its
3024
+ // own .ra-chart child and drives the engine itself.
3025
+ var owned = this.querySelector("div.ra-chart");
3026
+ if (owned && owned !== this._raMount) {
3027
+ this._raClaimed = true;
3028
+ return;
3029
+ }
3030
+ if (this._raClaimed) return;
3031
+
3032
+ var o = this._raOptions();
3033
+ var rows = o.data;
3034
+ if (!rows || !rows.length) {
3035
+ // Nothing to draw yet. Only complain once the page has settled, so an
3036
+ // async load in flight does not produce a false alarm.
3037
+ this._raWarnEmpty();
3038
+ return;
3039
+ }
3040
+
3041
+ if (!this._raMount) {
3042
+ this._raMount = document.createElement("div");
3043
+ this._raMount.className = "ra-chart";
3044
+ this.appendChild(this._raMount);
3045
+ }
3046
+ if (this._raChart) this._raChart.destroy();
3047
+ this._raChart = new RaChart(this._raMount, o);
3048
+ this._raWarned = true; // it rendered; nothing to report
3049
+ };
3050
+
3051
+ RaChartElement.prototype._raWarnEmpty = function () {
3052
+ var self = this;
3053
+ if (this._raWarned || this._raWarnTimer) return;
3054
+ this._raWarnTimer = setTimeout(function () {
3055
+ self._raWarnTimer = null;
3056
+ if (self._raChart || self._raClaimed || self._raWarned) return;
3057
+ if (!RaChart.warnings) return;
3058
+ self._raWarned = true;
3059
+ console.error(
3060
+ "[raChart] <ra-chart> is in the DOM but has no data, so nothing was drawn.\n" +
3061
+ " • Angular/Ionic: add RaChartComponent to your component's `imports: []` " +
3062
+ "(or RaChartModule for NgModule apps). Without it, CUSTOM_ELEMENTS_SCHEMA " +
3063
+ "silently swallows the unknown-element error and [data] never reaches the chart.\n" +
3064
+ " • Plain HTML: set the data property, e.g. " +
3065
+ "document.querySelector('ra-chart').data = rows;",
3066
+ self
3067
+ );
3068
+ }, 3000);
3069
+ };
3070
+
3071
+ customElements.define(tag, RaChartElement);
3072
+ return true;
3073
+ }
3074
+
3075
+ RaChart.defineElement = defineElement;
3076
+ /** Inject the built-in structural stylesheet (called automatically). */
3077
+ RaChart.injectStyles = ensureBaseStyles;
3078
+
3079
+ // Auto-register so the tag works with no setup. Harmless when a framework
3080
+ // component already owns the element — the stand-down guard handles it.
3081
+ try { defineElement("ra-chart"); } catch (e) { /* non-fatal */ }
3082
+
2858
3083
  /** Optional jQuery bridge — only if jQuery happens to be present. */
2859
3084
  if (typeof window !== "undefined" && window.jQuery) {
2860
3085
  (function ($) {