cordova-plugin-ra-chart 1.0.0

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.
@@ -0,0 +1,2838 @@
1
+ /*!
2
+ * raChart Cordova v1.0.0 — mobile SVG chart engine
3
+ * RemoteApps UI Components
4
+ *
5
+ * Zero dependencies. No jQuery, no chart library, no build step.
6
+ * Works in Cordova, Ionic (Angular/React/Vue), or any browser.
7
+ *
8
+ * var chart = new RaChart(el, { type: 'column', ... });
9
+ *
10
+ * Mobile features: tap/drag inspect, pinch-to-zoom, drag-to-pan,
11
+ * haptic feedback, bottom-sheet tooltips, coarse-pointer hit targets,
12
+ * orientation-aware resize, Ionic dark-mode theming, reduced-motion.
13
+ */
14
+ (function (root, factory) {
15
+ "use strict";
16
+ if (typeof module === "object" && module.exports) module.exports = factory();
17
+ else if (typeof define === "function" && define.amd) define([], factory);
18
+ else root.RaChart = factory();
19
+ }(typeof self !== "undefined" ? self : this, function () {
20
+ "use strict";
21
+
22
+ var NS = "http://www.w3.org/2000/svg";
23
+ var VERSION = "1.0.0";
24
+
25
+ /* ============================================================
26
+ Palettes — CVD-validated order, do not shuffle
27
+ ============================================================ */
28
+
29
+ var PALETTE_LIGHT = ["#2a78d6", "#008300", "#e87ba4", "#eda100", "#1baf7a", "#eb6834", "#4a3aa7", "#e34948"];
30
+ var PALETTE_DARK = ["#3987e5", "#008300", "#d55181", "#c98500", "#199e70", "#d95926", "#9085e9", "#e66767"];
31
+ var SEQ_LIGHT = ["#cde2fb", "#9ec5f4", "#6da7ec", "#3987e5", "#256abf", "#184f95", "#0d366b"];
32
+ var SEQ_DARK = ["#0d366b", "#184f95", "#256abf", "#3987e5", "#6da7ec", "#9ec5f4", "#cde2fb"];
33
+
34
+ var DEFAULTS = {
35
+ type: "column",
36
+ data: [],
37
+ category: "category",
38
+ series: [],
39
+ colors: null, // null = auto (theme-aware palette)
40
+ stacked: false,
41
+ fullStacked: false,
42
+ smooth: false,
43
+ stepped: false,
44
+ semi: false,
45
+ depth: 0,
46
+ fields: null,
47
+ xField: "x",
48
+ sizeField: "size",
49
+ min: 0,
50
+ max: null,
51
+ bands: null,
52
+ upColor: "#0ca30c",
53
+ downColor: "#d03b3b",
54
+ zoom: false, // range-slider zoom (XY + candlestick)
55
+ pinchZoom: true, // two-finger pinch + drag pan when zoom is on
56
+ legend: true,
57
+ height: 260,
58
+ haptics: true, // vibrate on touch-select (Capacitor Haptics or navigator.vibrate)
59
+ sheetTooltip: "auto", // true | false | "auto" (auto = dock to bottom under 480px)
60
+ tooltipHold: 2600, // ms a touch tooltip stays after finger lift (0 = until next tap)
61
+ animation: { duration: 800, delay: 70 }
62
+ };
63
+
64
+ var MORPH_MS = 550;
65
+ var PIE_TILT = 0.55;
66
+
67
+ /* ============================================================
68
+ Environment
69
+ ============================================================ */
70
+
71
+ var COARSE = typeof window !== "undefined" && window.matchMedia
72
+ ? window.matchMedia("(pointer: coarse)").matches : false;
73
+
74
+ function reducedMotion() {
75
+ return typeof window !== "undefined" && window.matchMedia
76
+ ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
77
+ }
78
+
79
+ function haptic() {
80
+ try {
81
+ var cap = window.Capacitor;
82
+ if (cap && cap.Plugins && cap.Plugins.Haptics && cap.Plugins.Haptics.impact) {
83
+ cap.Plugins.Haptics.impact({ style: "LIGHT" });
84
+ return;
85
+ }
86
+ if (window.navigator && navigator.vibrate) navigator.vibrate(8);
87
+ } catch (e) { /* haptics are best-effort */ }
88
+ }
89
+
90
+ /* ============================================================
91
+ Small utils
92
+ ============================================================ */
93
+
94
+ function isObj(v) { return v && typeof v === "object" && !Array.isArray(v); }
95
+
96
+ function merge(target, src) {
97
+ for (var k in src) {
98
+ if (!Object.prototype.hasOwnProperty.call(src, k)) continue;
99
+ var v = src[k];
100
+ if (isObj(v) && isObj(target[k])) merge(target[k], v);
101
+ else target[k] = v;
102
+ }
103
+ return target;
104
+ }
105
+
106
+ var measureCtx = null;
107
+ function textW(s, font) {
108
+ if (!measureCtx) measureCtx = document.createElement("canvas").getContext("2d");
109
+ measureCtx.font = font || "500 11.5px system-ui, -apple-system, 'Segoe UI', sans-serif";
110
+ return measureCtx.measureText(String(s)).width;
111
+ }
112
+
113
+ function fmt(n) {
114
+ if (n == null || isNaN(n)) return "";
115
+ var r = Math.abs(n) >= 100 ? Math.round(n) : Math.round(n * 10) / 10;
116
+ return r.toLocaleString("en-US");
117
+ }
118
+
119
+ function parseVal(v) {
120
+ if (typeof v === "number") return { n: v, isDate: false };
121
+ if (v instanceof Date) return { n: v.getTime(), isDate: true };
122
+ var num = parseFloat(v);
123
+ if (!isNaN(num) && String(num).length === String(v).trim().length) return { n: num, isDate: false };
124
+ var d = Date.parse(v);
125
+ if (!isNaN(d)) return { n: d, isDate: true };
126
+ return { n: 0, isDate: false };
127
+ }
128
+
129
+ function fmtDateShort(n) {
130
+ return new Date(n).toLocaleDateString("en-US", { month: "short", day: "numeric" });
131
+ }
132
+
133
+ var EASE = {
134
+ outCubic: function (t) { return 1 - Math.pow(1 - t, 3); },
135
+ inOutCubic: function (t) { return t < .5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; },
136
+ linear: function (t) { return t; }
137
+ };
138
+
139
+ function lerp(a, b, t) { return a + (b - a) * t; }
140
+ function clamp01(t) { return t < 0 ? 0 : t > 1 ? 1 : t; }
141
+ function clamp(v, lo, hi) { return v < lo ? lo : v > hi ? hi : v; }
142
+
143
+ function shade(hex, pct) {
144
+ if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return hex;
145
+ var n = parseInt(hex.slice(1), 16);
146
+ var r = n >> 16, g = (n >> 8) & 255, b = n & 255;
147
+ var t = pct < 0 ? 0 : 255, p = Math.abs(pct);
148
+ r = Math.round(lerp(r, t, p));
149
+ g = Math.round(lerp(g, t, p));
150
+ b = Math.round(lerp(b, t, p));
151
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
152
+ }
153
+
154
+ function rampColor(ramp, t) {
155
+ t = clamp01(t);
156
+ var pos = t * (ramp.length - 1);
157
+ var i = Math.min(ramp.length - 2, Math.floor(pos));
158
+ var f = pos - i;
159
+ var a = parseInt(ramp[i].slice(1), 16), b = parseInt(ramp[i + 1].slice(1), 16);
160
+ var r = Math.round(lerp(a >> 16, b >> 16, f));
161
+ var g = Math.round(lerp((a >> 8) & 255, (b >> 8) & 255, f));
162
+ var bl = Math.round(lerp(a & 255, b & 255, f));
163
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + bl).toString(16).slice(1);
164
+ }
165
+
166
+ function niceScale(maxVal, min) {
167
+ var span = maxVal - min;
168
+ if (span <= 0) span = 1;
169
+ var raw = span / 5;
170
+ var mag = Math.pow(10, Math.floor(Math.log(raw) / Math.LN10));
171
+ var norm = raw / mag;
172
+ var step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 2.5 ? 2.5 : norm <= 5 ? 5 : 10) * mag;
173
+ return { max: min + Math.ceil(span / step) * step, step: step };
174
+ }
175
+
176
+ /* ============================================================
177
+ DOM / SVG helpers
178
+ ============================================================ */
179
+
180
+ function el(tag, cls, parent) {
181
+ var n = document.createElement(tag);
182
+ if (cls) n.className = cls;
183
+ if (parent) parent.appendChild(n);
184
+ return n;
185
+ }
186
+
187
+ function empty(node) {
188
+ while (node && node.firstChild) node.removeChild(node.firstChild);
189
+ }
190
+
191
+ function svgEl(tag, attrs, parent) {
192
+ var n = document.createElementNS(NS, tag);
193
+ if (attrs) for (var k in attrs) n.setAttribute(k, attrs[k]);
194
+ if (parent) parent.appendChild(n);
195
+ return n;
196
+ }
197
+
198
+ function svgText(parent, x, y, text, anchor, size, color, weight) {
199
+ var n = svgEl("text", {
200
+ x: x, y: y, "text-anchor": anchor || "middle",
201
+ "font-size": size || 11.5, fill: color || "#898781",
202
+ "font-family": "system-ui, -apple-system, 'Segoe UI', sans-serif",
203
+ "font-weight": weight || 500
204
+ }, parent);
205
+ n.appendChild(document.createTextNode(text));
206
+ return n;
207
+ }
208
+
209
+ function setText(node, s) {
210
+ if (node && node.firstChild) node.firstChild.nodeValue = s;
211
+ }
212
+
213
+ /* ============================================================
214
+ Path builders (pure geometry — shared with the desktop engine)
215
+ ============================================================ */
216
+
217
+ function colPath(x, y, w, h, r, horizontal) {
218
+ if (w <= 0 || h <= 0) return "M0 0";
219
+ if (horizontal) {
220
+ r = Math.min(r, h / 2, w);
221
+ return "M" + x + " " + y +
222
+ " L" + (x + w - r) + " " + y +
223
+ " Q" + (x + w) + " " + y + " " + (x + w) + " " + (y + r) +
224
+ " L" + (x + w) + " " + (y + h - r) +
225
+ " Q" + (x + w) + " " + (y + h) + " " + (x + w - r) + " " + (y + h) +
226
+ " L" + x + " " + (y + h) + " Z";
227
+ }
228
+ r = Math.min(r, w / 2, h);
229
+ return "M" + x + " " + (y + h) +
230
+ " L" + x + " " + (y + r) +
231
+ " Q" + x + " " + y + " " + (x + r) + " " + y +
232
+ " L" + (x + w - r) + " " + y +
233
+ " Q" + (x + w) + " " + y + " " + (x + w) + " " + (y + r) +
234
+ " L" + (x + w) + " " + (y + h) + " Z";
235
+ }
236
+
237
+ function extrude(x, y, w, h, d) {
238
+ if (w <= 0 || h <= 0) return { front: "M0 0", top: "M0 0", side: "M0 0" };
239
+ var d2 = d * 0.5;
240
+ return {
241
+ front: "M" + x + " " + y + " L" + (x + w) + " " + y + " L" + (x + w) + " " + (y + h) + " L" + x + " " + (y + h) + " Z",
242
+ top: "M" + x + " " + y + " L" + (x + d) + " " + (y - d2) + " L" + (x + w + d) + " " + (y - d2) + " L" + (x + w) + " " + y + " Z",
243
+ side: "M" + (x + w) + " " + y + " L" + (x + w + d) + " " + (y - d2) + " L" + (x + w + d) + " " + (y + h - d2) + " L" + (x + w) + " " + (y + h) + " Z"
244
+ };
245
+ }
246
+
247
+ function arcPath(cx, cy, r0, r1, a0, a1, tilt) {
248
+ tilt = tilt || 1;
249
+ var sweep = a1 - a0;
250
+ if (sweep <= 0.0004) return "M0 0";
251
+ if (sweep >= Math.PI * 2) { a1 = a0 + Math.PI * 2 - 0.0006; sweep = a1 - a0; }
252
+ var large = sweep > Math.PI ? 1 : 0;
253
+ function pt(r, a) { return (cx + r * Math.cos(a)).toFixed(2) + " " + (cy + r * tilt * Math.sin(a)).toFixed(2); }
254
+ if (r0 < 0.5) {
255
+ return "M" + cx + " " + cy + " L" + pt(r1, a0) +
256
+ " A" + r1 + " " + (r1 * tilt) + " 0 " + large + " 1 " + pt(r1, a1) + " Z";
257
+ }
258
+ return "M" + pt(r1, a0) +
259
+ " A" + r1 + " " + (r1 * tilt) + " 0 " + large + " 1 " + pt(r1, a1) +
260
+ " L" + pt(r0, a1) +
261
+ " A" + r0 + " " + (r0 * tilt) + " 0 " + large + " 0 " + pt(r0, a0) + " Z";
262
+ }
263
+
264
+ function wallPath(r, b0, b1, g) {
265
+ if (b1 - b0 <= 0.002) return "";
266
+ function pt(a, dy) { return (g.cx + r * Math.cos(a)).toFixed(2) + " " + (g.cy + r * g.tilt * Math.sin(a) + dy).toFixed(2); }
267
+ var large = (b1 - b0) > Math.PI ? 1 : 0;
268
+ return "M" + pt(b0, 0) +
269
+ " A" + r + " " + (r * g.tilt) + " 0 " + large + " 1 " + pt(b1, 0) +
270
+ " L" + pt(b1, g.d) +
271
+ " A" + r + " " + (r * g.tilt) + " 0 " + large + " 0 " + pt(b0, g.d) + " Z";
272
+ }
273
+
274
+ function linePath(pts, smooth) {
275
+ if (!pts.length) return "M0 0";
276
+ var d = "M" + pts[0][0] + " " + pts[0][1];
277
+ if (!smooth || pts.length < 3) {
278
+ for (var i = 1; i < pts.length; i++) d += " L" + pts[i][0] + " " + pts[i][1];
279
+ return d;
280
+ }
281
+ for (var j = 1; j < pts.length; j++) {
282
+ var p0 = pts[j - 2] || pts[j - 1], p1 = pts[j - 1], p2 = pts[j], p3 = pts[j + 1] || p2;
283
+ var c1x = p1[0] + (p2[0] - p0[0]) / 6, c1y = p1[1] + (p2[1] - p0[1]) / 6;
284
+ var c2x = p2[0] - (p3[0] - p1[0]) / 6, c2y = p2[1] - (p3[1] - p1[1]) / 6;
285
+ d += " C" + c1x.toFixed(2) + " " + c1y.toFixed(2) + " " + c2x.toFixed(2) + " " + c2y.toFixed(2) + " " + p2[0] + " " + p2[1];
286
+ }
287
+ return d;
288
+ }
289
+
290
+ function steppedPath(pts) {
291
+ var d = "M" + pts[0][0] + " " + pts[0][1];
292
+ for (var i = 1; i < pts.length; i++) {
293
+ var mx = ((pts[i - 1][0] + pts[i][0]) / 2).toFixed(2);
294
+ d += " L" + mx + " " + pts[i - 1][1] + " L" + mx + " " + pts[i][1] + " L" + pts[i][0] + " " + pts[i][1];
295
+ }
296
+ return d;
297
+ }
298
+
299
+ function treemapLayout(values, x, y, w, h) {
300
+ var total = 0;
301
+ values.forEach(function (v) { total += v > 0 ? v : 0; });
302
+ var rects = [];
303
+ if (!total || w <= 0 || h <= 0) return rects;
304
+ var scale = w * h / total;
305
+ var items = [];
306
+ values.forEach(function (v, i) { if (v > 0) items.push({ v: v, i: i }); });
307
+ items.sort(function (a, b) { return b.v - a.v; });
308
+ var ax = x, ay = y, aw = w, ah = h;
309
+ var row = [], rowSum = 0;
310
+
311
+ function worst(r, s) {
312
+ var len = Math.min(aw, ah) || 1;
313
+ var thick = s * scale / len, m = 0;
314
+ r.forEach(function (it) {
315
+ var l = it.v * scale / thick;
316
+ var asp = Math.max(thick / l, l / thick);
317
+ if (asp > m) m = asp;
318
+ });
319
+ return m;
320
+ }
321
+ function layoutRow(r, s) {
322
+ var len = Math.min(aw, ah) || 1;
323
+ var thick = s * scale / len, off = 0;
324
+ r.forEach(function (it) {
325
+ var l = it.v * scale / thick;
326
+ if (aw < ah) rects[it.i] = { x: ax + off, y: ay, w: l, h: thick };
327
+ else rects[it.i] = { x: ax, y: ay + off, w: thick, h: l };
328
+ off += l;
329
+ });
330
+ if (aw < ah) { ay += thick; ah -= thick; }
331
+ else { ax += thick; aw -= thick; }
332
+ }
333
+ items.forEach(function (it) {
334
+ if (row.length && worst(row.concat([it]), rowSum + it.v) > worst(row, rowSum)) {
335
+ layoutRow(row, rowSum);
336
+ row = []; rowSum = 0;
337
+ }
338
+ row.push(it); rowSum += it.v;
339
+ });
340
+ if (row.length) layoutRow(row, rowSum);
341
+ return rects;
342
+ }
343
+
344
+ var KINDS = {
345
+ column: "xy", bar: "xy", line: "xy", area: "xy", lollipop: "xy",
346
+ pie: "pie", donut: "pie", radar: "radar",
347
+ treemap: "treemap", sankey: "sankey", wordcloud: "cloud", venn: "venn",
348
+ candlestick: "candle", timeline: "timeline", gantt: "gantt",
349
+ scatter: "scatter", bubble: "scatter",
350
+ funnel: "funnel", pyramid: "funnel",
351
+ gauge: "gauge", heatmap: "heat"
352
+ };
353
+
354
+ var UID = 0;
355
+
356
+ /* ============================================================
357
+ RaChart
358
+ ============================================================ */
359
+
360
+ function RaChart(host, options) {
361
+ if (!(this instanceof RaChart)) return new RaChart(host, options);
362
+ if (typeof host === "string") host = document.querySelector(host);
363
+ if (!host) throw new Error("raChart: host element not found");
364
+
365
+ this.el = host;
366
+ this.uid = ++UID;
367
+ this.opts = merge(merge({}, DEFAULTS), options || {});
368
+ this.hidden = {};
369
+ this.zoomF = [0, 1];
370
+ this._raf = null;
371
+ this._destroyed = false;
372
+
373
+ if (host.className.indexOf("ra-chart") < 0) {
374
+ host.className = (host.className ? host.className + " " : "") + "ra-chart";
375
+ }
376
+
377
+ this._observeResize();
378
+ this._watchVisibility();
379
+ this.build(true);
380
+ this._emit("raChartReady", { chart: this });
381
+ }
382
+
383
+ /* ---------- theming ---------- */
384
+
385
+ RaChart.prototype._readTheme = function () {
386
+ var cs = window.getComputedStyle(this.el);
387
+ function v(name, fb) {
388
+ var x = cs.getPropertyValue(name);
389
+ return (x && x.trim()) || fb;
390
+ }
391
+ var dark = v("--ra-chart-scheme", "light").indexOf("dark") >= 0;
392
+ this.dark = dark;
393
+ this.theme = {
394
+ dark: dark,
395
+ surface: v("--ra-chart-surface", dark ? "#1a1a19" : "#ffffff"),
396
+ text: v("--ra-chart-text", dark ? "#ffffff" : "#1f2937"),
397
+ label: v("--ra-chart-label", dark ? "#9d9c95" : "#898781"),
398
+ muted: v("--ra-chart-muted", dark ? "#8a8a84" : "#9ca3af"),
399
+ grid: v("--ra-chart-grid", dark ? "#2c2c2a" : "#e1e0d9"),
400
+ baseline: v("--ra-chart-baseline", dark ? "#383835" : "#c3c2b7"),
401
+ body: v("--ra-chart-body", dark ? "#c3c2b7" : "#4b5563")
402
+ };
403
+ this.palette = this.opts.colors || (dark ? PALETTE_DARK : PALETTE_LIGHT);
404
+ this.seq = dark ? SEQ_DARK : SEQ_LIGHT;
405
+ };
406
+
407
+ RaChart.prototype._color = function (i) {
408
+ return this.palette[i % this.palette.length];
409
+ };
410
+
411
+ /* ---------- lifecycle ---------- */
412
+
413
+ RaChart.prototype._observeResize = function () {
414
+ var self = this;
415
+ this._onResize = function () {
416
+ clearTimeout(self._rt);
417
+ self._rt = setTimeout(function () {
418
+ if (!self._destroyed && self.el.clientWidth) self._rebuildBody();
419
+ }, 160);
420
+ };
421
+ if (typeof ResizeObserver !== "undefined") {
422
+ this._ro = new ResizeObserver(function (entries) {
423
+ var w = entries[0] && entries[0].contentRect ? Math.round(entries[0].contentRect.width) : 0;
424
+ if (w && w !== self._lastWidth) { self._lastWidth = w; self._onResize(); }
425
+ });
426
+ this._ro.observe(this.el);
427
+ } else {
428
+ window.addEventListener("resize", this._onResize);
429
+ window.addEventListener("orientationchange", this._onResize);
430
+ }
431
+ };
432
+
433
+ RaChart.prototype.destroy = function () {
434
+ this._destroyed = true;
435
+ this._stopTween();
436
+ this._finishTween = null;
437
+ clearTimeout(this._rt);
438
+ clearTimeout(this._tipTimer);
439
+ if (this._onVisibility) {
440
+ document.removeEventListener("visibilitychange", this._onVisibility);
441
+ this._onVisibility = null;
442
+ }
443
+ if (this._ro) { this._ro.disconnect(); this._ro = null; }
444
+ else {
445
+ window.removeEventListener("resize", this._onResize);
446
+ window.removeEventListener("orientationchange", this._onResize);
447
+ }
448
+ this._unbindDoc();
449
+ empty(this.el);
450
+ };
451
+
452
+ RaChart.prototype._stopTween = function () {
453
+ if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; }
454
+ };
455
+
456
+ /**
457
+ * Mobile apps get backgrounded mid-animation all the time. rAF stops firing
458
+ * the moment that happens, so settle any in-flight tween on its last frame
459
+ * rather than leaving half-grown bars behind when the user comes back.
460
+ */
461
+ RaChart.prototype._watchVisibility = function () {
462
+ var self = this;
463
+ this._onVisibility = function () {
464
+ if (document.hidden && self._finishTween) self._finishTween();
465
+ };
466
+ document.addEventListener("visibilitychange", this._onVisibility);
467
+ };
468
+
469
+ RaChart.prototype._tween = function (duration, ease, step, done) {
470
+ var self = this;
471
+ this._stopTween();
472
+
473
+ // Jump to the final frame when animating would be wrong or impossible:
474
+ // - the user asked for reduced motion
475
+ // - the page is hidden, so requestAnimationFrame is throttled to a halt
476
+ // (backgrounded app, inactive tab, off-screen Ionic page). Without this
477
+ // the chart would sit at frame zero until something forced a redraw.
478
+ if (reducedMotion() || document.hidden) {
479
+ step(1, 1);
480
+ if (done) done();
481
+ return;
482
+ }
483
+
484
+ var t0 = null;
485
+ this._finishTween = function () { // called if we get hidden mid-flight
486
+ self._stopTween();
487
+ step(1, 1);
488
+ self._finishTween = null;
489
+ if (done) done();
490
+ };
491
+ function frame(ts) {
492
+ if (self._destroyed) return;
493
+ if (t0 === null) t0 = ts;
494
+ var p = clamp01((ts - t0) / duration);
495
+ step(ease(p), p);
496
+ if (p < 1) self._raf = requestAnimationFrame(frame);
497
+ else {
498
+ self._raf = null;
499
+ self._finishTween = null;
500
+ if (done) done();
501
+ }
502
+ }
503
+ this._raf = requestAnimationFrame(frame);
504
+ };
505
+
506
+ RaChart.prototype._emit = function (name, detail) {
507
+ var ev;
508
+ try {
509
+ ev = new CustomEvent(name, { detail: detail, bubbles: true });
510
+ } catch (e) { // old Android WebView
511
+ ev = document.createEvent("CustomEvent");
512
+ ev.initCustomEvent(name, true, false, detail);
513
+ }
514
+ this.el.dispatchEvent(ev);
515
+ };
516
+
517
+ /* ---------- scaffold ---------- */
518
+
519
+ RaChart.prototype.build = function (entrance) {
520
+ this._stopTween();
521
+ this._unbindDoc();
522
+ empty(this.el);
523
+ this.zoomBar = null;
524
+ this._readTheme();
525
+
526
+ var o = this.opts;
527
+ this.kind = KINDS[o.type] || "xy";
528
+ this.zoomable = !!o.zoom && (this.kind === "xy" || this.kind === "candle");
529
+
530
+ if (this.zoomable) this._buildZoomBar();
531
+ this._scaffoldBody();
532
+ this._dispatchBuild(entrance);
533
+ };
534
+
535
+ RaChart.prototype._scaffoldBody = function () {
536
+ var o = this.opts;
537
+ this.stage = el("div", "ra-chart__stage", this.el);
538
+ this.stage.style.height = o.height + "px";
539
+ this.w = this.el.clientWidth || this.stage.clientWidth || 320;
540
+ this.h = o.height;
541
+ this._lastWidth = this.w;
542
+
543
+ this.svg = svgEl("svg", {
544
+ viewBox: "0 0 " + this.w + " " + this.h,
545
+ preserveAspectRatio: "none"
546
+ });
547
+ this.stage.appendChild(this.svg);
548
+
549
+ this.tip = el("div", "ra-chart__tip", this.el);
550
+ this.legendEl = el("div", "ra-chart__legend", this.el);
551
+
552
+ // page scroll must keep working; only lock the axis we actually use
553
+ this.stage.style.touchAction = (this.zoomable && this.opts.pinchZoom) ? "pan-y pinch-zoom" : "auto";
554
+
555
+ this._bindPointer();
556
+ if (this.zoomable && this.opts.pinchZoom) this._bindPinch();
557
+ };
558
+
559
+ RaChart.prototype._dispatchBuild = function (entrance) {
560
+ var k = this.kind;
561
+ if (k === "pie") this._buildPie(entrance);
562
+ else if (k === "radar") this._buildRadar(entrance);
563
+ else if (k === "treemap") this._buildTreemap(entrance);
564
+ else if (k === "sankey") this._buildSankey(entrance);
565
+ else if (k === "cloud") this._buildCloud(entrance);
566
+ else if (k === "venn") this._buildVenn(entrance);
567
+ else if (k === "candle") this._buildCandle(entrance);
568
+ else if (k === "timeline") this._buildTimeline(entrance);
569
+ else if (k === "gantt") this._buildGantt(entrance);
570
+ else if (k === "scatter") this._buildScatter(entrance);
571
+ else if (k === "funnel") this._buildFunnel(entrance);
572
+ else if (k === "gauge") this._buildGauge(entrance);
573
+ else if (k === "heat") this._buildHeat(entrance);
574
+ else this._buildXY(entrance);
575
+ };
576
+
577
+ RaChart.prototype._rebuildBody = function () {
578
+ if (this._destroyed) return;
579
+ this._stopTween();
580
+ if (!this.stage) { this.build(false); return; }
581
+ this.stage.parentNode && this.stage.parentNode.removeChild(this.stage);
582
+ this.tip.parentNode && this.tip.parentNode.removeChild(this.tip);
583
+ this.legendEl.parentNode && this.legendEl.parentNode.removeChild(this.legendEl);
584
+ this._scaffoldBody();
585
+ this._dispatchBuild(false);
586
+ };
587
+
588
+ /* ============================================================
589
+ Pointer layer — mouse hover AND touch tap/drag
590
+ ============================================================ */
591
+
592
+ RaChart.prototype._hitAt = function (clientX, clientY) {
593
+ var node = document.elementFromPoint(clientX, clientY);
594
+ if (!node) return null;
595
+ if (node.correspondingUseElement) node = node.correspondingUseElement;
596
+ while (node && node !== this.el) {
597
+ if (node.classList && node.classList.contains("ra-hit")) return node;
598
+ node = node.parentNode;
599
+ }
600
+ return null;
601
+ };
602
+
603
+ RaChart.prototype._bindPointer = function () {
604
+ var self = this;
605
+ var stage = this.stage;
606
+
607
+ function local(e) {
608
+ var r = self.el.getBoundingClientRect();
609
+ return { x: e.clientX - r.left, y: e.clientY - r.top };
610
+ }
611
+
612
+ // ---- mouse (desktop / hybrid) ----
613
+ this._onMove = function (e) {
614
+ if (self._touching) return;
615
+ var hit = self._hitAt(e.clientX, e.clientY);
616
+ var p = local(e);
617
+ if (hit) self._activate(hit, p.x, p.y, false);
618
+ else self._deactivate();
619
+ };
620
+ this._onLeave = function () {
621
+ if (!self._touching) self._deactivate();
622
+ };
623
+ stage.addEventListener("mousemove", this._onMove);
624
+ stage.addEventListener("mouseleave", this._onLeave);
625
+
626
+ // ---- touch: tap to inspect, drag to scrub ----
627
+ this._onTouchStart = function (e) {
628
+ if (e.touches.length !== 1) return;
629
+ self._touching = true;
630
+ clearTimeout(self._tipTimer);
631
+ var t = e.touches[0];
632
+ var hit = self._hitAt(t.clientX, t.clientY);
633
+ var p = local(t);
634
+ if (hit) {
635
+ self._activate(hit, p.x, p.y, true);
636
+ if (self.opts.haptics) haptic();
637
+ } else {
638
+ self._deactivate();
639
+ }
640
+ };
641
+ this._onTouchMove = function (e) {
642
+ if (!self._touching || e.touches.length !== 1) return;
643
+ var t = e.touches[0];
644
+ var hit = self._hitAt(t.clientX, t.clientY);
645
+ var p = local(t);
646
+ if (hit) {
647
+ if (hit !== self._activeNode && self.opts.haptics) haptic();
648
+ self._activate(hit, p.x, p.y, true);
649
+ // scrubbing across marks: keep the finger on the chart, not the page
650
+ if (e.cancelable) e.preventDefault();
651
+ }
652
+ };
653
+ this._onTouchEnd = function () {
654
+ self._touching = false;
655
+ var hold = self.opts.tooltipHold;
656
+ if (hold > 0) {
657
+ clearTimeout(self._tipTimer);
658
+ self._tipTimer = setTimeout(function () { self._deactivate(); }, hold);
659
+ }
660
+ };
661
+ stage.addEventListener("touchstart", this._onTouchStart, { passive: true });
662
+ stage.addEventListener("touchmove", this._onTouchMove, { passive: false });
663
+ stage.addEventListener("touchend", this._onTouchEnd, { passive: true });
664
+ stage.addEventListener("touchcancel", this._onTouchEnd, { passive: true });
665
+ };
666
+
667
+ // subclasses of behaviour are registered per chart type
668
+ RaChart.prototype._activate = function (node, x, y, isTouch) {
669
+ if (!this._hitHandler) return;
670
+ this._activeNode = node;
671
+ this._hitHandler(node, x, y, isTouch);
672
+ };
673
+
674
+ RaChart.prototype._deactivate = function () {
675
+ if (this._activeNode || this._tipOn) {
676
+ this._activeNode = null;
677
+ if (this._leaveHandler) this._leaveHandler();
678
+ this._tipHide();
679
+ }
680
+ };
681
+
682
+ /* ---------- tooltip ---------- */
683
+
684
+ RaChart.prototype._sheetMode = function () {
685
+ var s = this.opts.sheetTooltip;
686
+ if (s === true) return true;
687
+ if (s === false) return false;
688
+ return COARSE && this.w < 480;
689
+ };
690
+
691
+ RaChart.prototype._tipShow = function (html, x, y) {
692
+ this._tipOn = true;
693
+ this.tip.innerHTML = html;
694
+ this.tip.classList.add("is-on");
695
+ if (this._sheetMode()) {
696
+ this.tip.classList.add("ra-chart__tip--sheet");
697
+ this.tip.style.left = "";
698
+ this.tip.style.top = "";
699
+ return;
700
+ }
701
+ this.tip.classList.remove("ra-chart__tip--sheet");
702
+ var tw = this.tip.offsetWidth;
703
+ this.tip.style.left = clamp(x, tw / 2 + 4, Math.max(tw / 2 + 4, this.w - tw / 2 - 4)) + "px";
704
+ this.tip.style.top = Math.max(4, y) + "px";
705
+ };
706
+
707
+ RaChart.prototype._tipHide = function () {
708
+ this._tipOn = false;
709
+ this.tip.classList.remove("is-on");
710
+ };
711
+
712
+ RaChart.prototype._dot = function (color) {
713
+ return '<span class="ra-chart__tip-dot" style="background:' + color + '"></span>';
714
+ };
715
+
716
+ /* ---------- legend ---------- */
717
+
718
+ RaChart.prototype._buildLegend = function (items, onToggle, onHover) {
719
+ var self = this;
720
+ empty(this.legendEl);
721
+ if (!items.length) { this.legendEl.style.display = "none"; return; }
722
+ this.legendEl.style.display = "";
723
+
724
+ items.forEach(function (it, i) {
725
+ var item = el("span", "ra-chart__legend-item", self.legendEl);
726
+ if (self.hidden[i]) item.classList.add("is-off");
727
+ var sw = el("span", "ra-chart__legend-swatch", item);
728
+ sw.style.background = it.color;
729
+ var lb = el("span", "ra-chart__legend-label", item);
730
+ lb.textContent = it.name;
731
+ if (it.value != null) {
732
+ var vv = el("span", "ra-chart__legend-value", item);
733
+ vv.textContent = "(" + fmt(it.value) + ")";
734
+ }
735
+ item.setAttribute("role", "button");
736
+ item.setAttribute("tabindex", "0");
737
+
738
+ function toggle() {
739
+ var visible = items.filter(function (_, j) { return !self.hidden[j]; }).length;
740
+ if (!self.hidden[i] && visible <= 1) return;
741
+ self.hidden[i] = !self.hidden[i];
742
+ item.classList.toggle("is-off", !!self.hidden[i]);
743
+ if (self.opts.haptics) haptic();
744
+ if (onHover) onHover(-1);
745
+ onToggle();
746
+ self._emit("raChartToggle", { index: i, name: it.name, hidden: !!self.hidden[i] });
747
+ }
748
+ item.addEventListener("click", toggle);
749
+ item.addEventListener("keydown", function (e) {
750
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle(); }
751
+ });
752
+ if (onHover && !COARSE) {
753
+ item.addEventListener("mouseenter", function () { if (!self.hidden[i]) onHover(i); });
754
+ item.addEventListener("mouseleave", function () { onHover(-1); });
755
+ }
756
+ });
757
+ };
758
+
759
+ RaChart.prototype._dimGroups = function (i) {
760
+ (this.nodes.groups || []).forEach(function (gr, j) {
761
+ gr.style.opacity = (i >= 0 && j !== i) ? .35 : "";
762
+ });
763
+ };
764
+
765
+ RaChart.prototype._dimSlices = function (i) {
766
+ (this.nodes.slices || []).forEach(function (s, j) {
767
+ var op = (i >= 0 && j !== i) ? .55 : "";
768
+ [s.top, s.outer, s.inner].forEach(function (p) { if (p) p.style.opacity = op; });
769
+ });
770
+ };
771
+
772
+ /* ============================================================
773
+ Zoom — slider + pinch/pan
774
+ ============================================================ */
775
+
776
+ RaChart.prototype._zoomWindow = function (n) {
777
+ if (!this.zoomable || n < 1) return [0, Math.max(0, n - 1)];
778
+ var i0 = Math.min(n - 1, Math.floor(this.zoomF[0] * n));
779
+ var i1 = Math.max(i0, Math.ceil(this.zoomF[1] * n) - 1);
780
+ return [i0, i1];
781
+ };
782
+
783
+ RaChart.prototype._zoomApply = function (force) {
784
+ var n = (this.opts.data || []).length;
785
+ var w = this._zoomWindow(n);
786
+ if (!force && this._lastW && this._lastW[0] === w[0] && this._lastW[1] === w[1]) return;
787
+ this._lastW = w;
788
+ this._rebuildBody();
789
+ this._emit("raChartZoom", {
790
+ from: this.zoomF[0], to: this.zoomF[1],
791
+ startIndex: w[0], endIndex: w[1]
792
+ });
793
+ };
794
+
795
+ RaChart.prototype._zoomApplyThrottled = function () {
796
+ var self = this;
797
+ if (this._zRaf) return;
798
+ this._zRaf = requestAnimationFrame(function () {
799
+ self._zRaf = null;
800
+ self._zoomApply();
801
+ });
802
+ };
803
+
804
+ RaChart.prototype._syncZoomBar = function () {
805
+ if (!this.zoomBar) return;
806
+ var f = this.zoomF;
807
+ this.zoomBar.sel.style.left = (f[0] * 100) + "%";
808
+ this.zoomBar.sel.style.width = ((f[1] - f[0]) * 100) + "%";
809
+ this.zoomBar.h0.style.left = (f[0] * 100) + "%";
810
+ this.zoomBar.h1.style.left = (f[1] * 100) + "%";
811
+ };
812
+
813
+ RaChart.prototype._unbindDoc = function () {
814
+ if (this._docMove) {
815
+ document.removeEventListener("mousemove", this._docMove);
816
+ document.removeEventListener("mouseup", this._docUp);
817
+ document.removeEventListener("touchmove", this._docMove, { passive: false });
818
+ document.removeEventListener("touchend", this._docUp);
819
+ this._docMove = this._docUp = null;
820
+ }
821
+ };
822
+
823
+ RaChart.prototype._buildZoomBar = function () {
824
+ var self = this;
825
+ var bar = el("div", "ra-chart__zoom", this.el);
826
+ var track = el("div", "ra-chart__zoom-track", bar);
827
+ var sel = el("div", "ra-chart__zoom-sel", track);
828
+ var h0 = el("div", "ra-chart__zoom-handle", track);
829
+ var h1 = el("div", "ra-chart__zoom-handle", track);
830
+ h0.setAttribute("role", "slider");
831
+ h1.setAttribute("role", "slider");
832
+ this.zoomBar = { bar: bar, track: track, sel: sel, h0: h0, h1: h1 };
833
+ this._syncZoomBar();
834
+
835
+ var MIN_SPAN = 0.05;
836
+ function pageX(e) {
837
+ return e.touches && e.touches.length ? e.touches[0].clientX : e.clientX;
838
+ }
839
+ function begin(mode, e) {
840
+ var r = track.getBoundingClientRect();
841
+ self._drag = {
842
+ mode: mode, startX: pageX(e),
843
+ f0: self.zoomF[0], f1: self.zoomF[1],
844
+ tx: r.left, tw: r.width || 1
845
+ };
846
+ if (e.cancelable) e.preventDefault();
847
+ }
848
+
849
+ [[h0, 0], [h1, 1], [sel, "pan"]].forEach(function (pair) {
850
+ pair[0].addEventListener("mousedown", function (e) { begin(pair[1], e); });
851
+ pair[0].addEventListener("touchstart", function (e) { begin(pair[1], e); }, { passive: false });
852
+ });
853
+
854
+ this._docMove = function (e) {
855
+ var d = self._drag;
856
+ if (!d) return;
857
+ var x = pageX(e);
858
+ if (d.mode === "pan") {
859
+ var span = d.f1 - d.f0;
860
+ var f0 = clamp(d.f0 + (x - d.startX) / d.tw, 0, 1 - span);
861
+ self.zoomF = [f0, f0 + span];
862
+ } else {
863
+ var f = clamp01((x - d.tx) / d.tw);
864
+ if (d.mode === 0) self.zoomF = [Math.min(f, d.f1 - MIN_SPAN), d.f1];
865
+ else self.zoomF = [d.f0, Math.max(f, d.f0 + MIN_SPAN)];
866
+ }
867
+ self._syncZoomBar();
868
+ self._zoomApplyThrottled();
869
+ if (e.cancelable && e.type === "touchmove") e.preventDefault();
870
+ };
871
+ this._docUp = function () { self._drag = null; };
872
+
873
+ document.addEventListener("mousemove", this._docMove);
874
+ document.addEventListener("mouseup", this._docUp);
875
+ document.addEventListener("touchmove", this._docMove, { passive: false });
876
+ document.addEventListener("touchend", this._docUp);
877
+ };
878
+
879
+ // two-finger pinch to zoom, one-finger drag to pan (only while zoomed in)
880
+ RaChart.prototype._bindPinch = function () {
881
+ var self = this, stage = this.stage;
882
+ var g = null;
883
+
884
+ function dist(t) {
885
+ var dx = t[0].clientX - t[1].clientX, dy = t[0].clientY - t[1].clientY;
886
+ return Math.sqrt(dx * dx + dy * dy) || 1;
887
+ }
888
+ function centerFrac(t, rect) {
889
+ var cx = t.length > 1 ? (t[0].clientX + t[1].clientX) / 2 : t[0].clientX;
890
+ return clamp01((cx - rect.left) / (rect.width || 1));
891
+ }
892
+
893
+ stage.addEventListener("touchstart", function (e) {
894
+ var span = self.zoomF[1] - self.zoomF[0];
895
+ if (e.touches.length === 2) {
896
+ var rect = stage.getBoundingClientRect();
897
+ g = {
898
+ mode: "pinch", d0: dist(e.touches), f: self.zoomF.slice(),
899
+ anchor: self.zoomF[0] + centerFrac(e.touches, rect) * span
900
+ };
901
+ self._touching = false;
902
+ self._deactivate();
903
+ } else if (e.touches.length === 1 && span < 0.999) {
904
+ g = { mode: "pan", x0: e.touches[0].clientX, f: self.zoomF.slice(), w: stage.clientWidth || 1 };
905
+ } else g = null;
906
+ }, { passive: true });
907
+
908
+ stage.addEventListener("touchmove", function (e) {
909
+ if (!g) return;
910
+ if (g.mode === "pinch" && e.touches.length === 2) {
911
+ var scale = dist(e.touches) / g.d0;
912
+ var span = clamp((g.f[1] - g.f[0]) / scale, 0.05, 1);
913
+ var f0 = clamp(g.anchor - span * ((g.anchor - g.f[0]) / ((g.f[1] - g.f[0]) || 1)), 0, 1 - span);
914
+ self.zoomF = [f0, f0 + span];
915
+ self._syncZoomBar();
916
+ self._zoomApplyThrottled();
917
+ if (e.cancelable) e.preventDefault();
918
+ } else if (g.mode === "pan" && e.touches.length === 1) {
919
+ var sp = g.f[1] - g.f[0];
920
+ var dx = (e.touches[0].clientX - g.x0) / g.w;
921
+ var nf0 = clamp(g.f[0] - dx * sp, 0, 1 - sp);
922
+ // only hijack the gesture once it is clearly horizontal
923
+ if (Math.abs(dx) > 0.01) {
924
+ self.zoomF = [nf0, nf0 + sp];
925
+ self._syncZoomBar();
926
+ self._zoomApplyThrottled();
927
+ if (e.cancelable) e.preventDefault();
928
+ }
929
+ }
930
+ }, { passive: false });
931
+
932
+ stage.addEventListener("touchend", function (e) {
933
+ if (e.touches.length === 0) g = null;
934
+ }, { passive: true });
935
+ };
936
+
937
+ /* ============================================================
938
+ XY — column / bar / line / area / lollipop
939
+ ============================================================ */
940
+
941
+ RaChart.prototype._xyVals = function (D) {
942
+ var o = this.opts, self = this;
943
+ var m = o.series.map(function (s) { return D.map(function (d) { return +d[s.field] || 0; }); });
944
+ if (o.fullStacked && o.type !== "line" && o.type !== "area") {
945
+ for (var c = 0; c < D.length; c++) {
946
+ var tot = 0;
947
+ m.forEach(function (row, si) { if (!self.hidden[si]) tot += row[c]; });
948
+ m.forEach(function (row, si) { row[c] = (tot && !self.hidden[si]) ? row[c] / tot * 100 : 0; });
949
+ }
950
+ }
951
+ return m;
952
+ };
953
+
954
+ RaChart.prototype._buildXY = function (entrance) {
955
+ var self = this, o = this.opts, T = this.theme;
956
+ if (o.fullStacked && o.type !== "line" && o.type !== "area") o.stacked = true;
957
+ var horizontal = o.type === "bar";
958
+ var isLine = o.type === "line" || o.type === "area";
959
+ var isLolli = o.type === "lollipop";
960
+ var depth = (!isLine && !isLolli && o.depth > 0) ? o.depth : 0;
961
+
962
+ var vw = this._zoomWindow((o.data || []).length);
963
+ var data = (o.data || []).slice(vw[0], vw[1] + 1);
964
+ this.viewData = data;
965
+
966
+ var cats = data.map(function (d) { return String(d[o.category]); });
967
+ var nS = o.series.length, nC = cats.length;
968
+
969
+ this.cur = { kind: "xy", vals: this._xyVals(data), scaleMax: null };
970
+
971
+ var g = { horizontal: horizontal, isLine: isLine, isLolli: isLolli, depth: depth, cats: cats, nS: nS, nC: nC };
972
+ this.geom = g;
973
+
974
+ var scale = this._xyScale();
975
+ var yLabels = [];
976
+ for (var v = o.min; v <= scale.max + 1e-9; v += scale.step) yLabels.push(fmt(v));
977
+ var maxYLabelW = Math.max.apply(null, yLabels.map(function (s) { return textW(s); }).concat([12]));
978
+ var maxCatW = Math.max.apply(null, cats.map(function (s) { return textW(s); }).concat([12]));
979
+
980
+ g.padL = (horizontal ? Math.min(maxCatW, this.w * 0.34) : maxYLabelW) + 12;
981
+ g.padR = 8 + depth;
982
+ g.padT = 8 + depth * 0.5;
983
+ g.padB = 24;
984
+ g.plotX = g.padL;
985
+ g.plotY = g.padT;
986
+ g.plotW = Math.max(40, this.w - g.padL - g.padR);
987
+ g.plotH = Math.max(40, this.h - g.padT - g.padB);
988
+ g.band = (horizontal ? g.plotH : g.plotW) / Math.max(1, nC);
989
+
990
+ this.gGrid = svgEl("g", null, this.svg);
991
+ this.gMarks = svgEl("g", null, this.svg);
992
+ this.gUI = svgEl("g", null, this.svg);
993
+ this._xyDrawGrid(scale);
994
+
995
+ this.nodes = { rects: [], paths: [], areas: [], bullets: [], groups: [] };
996
+
997
+ o.series.forEach(function (s, si) {
998
+ var color = self._color(si);
999
+ var gS = svgEl("g", { opacity: self.hidden[si] ? 0 : 1 }, self.gMarks);
1000
+ gS.style.transition = "opacity .18s";
1001
+
1002
+ if (isLine) {
1003
+ var area = svgEl("path", { fill: color, "fill-opacity": o.type === "area" ? 0.16 : 0, stroke: "none" }, gS);
1004
+ var path = svgEl("path", {
1005
+ fill: "none", stroke: color, "stroke-width": 2.2,
1006
+ "stroke-linejoin": "round", "stroke-linecap": "round"
1007
+ }, gS);
1008
+ var bl = [];
1009
+ for (var ci = 0; ci < nC; ci++) {
1010
+ bl.push(svgEl("circle", {
1011
+ r: 4, fill: color, stroke: T.surface, "stroke-width": 2
1012
+ }, gS));
1013
+ }
1014
+ self.nodes.paths[si] = path;
1015
+ self.nodes.areas[si] = area;
1016
+ self.nodes.bullets[si] = bl;
1017
+ self.nodes.rects[si] = null;
1018
+ } else {
1019
+ var rl = [];
1020
+ for (var cj = 0; cj < nC; cj++) {
1021
+ var node;
1022
+ if (isLolli) {
1023
+ node = svgEl("g", { "class": "ra-hit", "data-s": si, "data-c": cj }, gS);
1024
+ node._line = svgEl("line", { stroke: color, "stroke-width": 2, "stroke-linecap": "round" }, node);
1025
+ node._dot = svgEl("circle", { r: COARSE ? 7 : 6, fill: color, stroke: T.surface, "stroke-width": 2 }, node);
1026
+ } else if (depth > 0) {
1027
+ node = svgEl("g", { "class": "ra-hit", "data-s": si, "data-c": cj }, gS);
1028
+ node._t = svgEl("path", { fill: shade(color, .22), stroke: "none" }, node);
1029
+ node._s = svgEl("path", { fill: shade(color, -.18), stroke: "none" }, node);
1030
+ var fA = { fill: color };
1031
+ if (o.stacked) { fA.stroke = T.surface; fA["stroke-width"] = 2; }
1032
+ node._f = svgEl("path", fA, node);
1033
+ } else {
1034
+ var attrs = { fill: color, "class": "ra-hit", "data-s": si, "data-c": cj };
1035
+ if (o.stacked) { attrs.stroke = T.surface; attrs["stroke-width"] = 2; }
1036
+ node = svgEl("path", attrs, gS);
1037
+ }
1038
+ node.style.transition = "transform .18s, opacity .18s";
1039
+ rl.push(node);
1040
+ }
1041
+ self.nodes.rects[si] = rl;
1042
+ self.nodes.paths[si] = null; self.nodes.areas[si] = null; self.nodes.bullets[si] = null;
1043
+ }
1044
+ self.nodes.groups[si] = gS;
1045
+ });
1046
+
1047
+ // line/area: a crosshair driven by band-wide invisible hit columns (touch friendly)
1048
+ if (isLine) {
1049
+ this.crossLine = svgEl("line", {
1050
+ y1: g.plotY, y2: g.plotY + g.plotH,
1051
+ stroke: T.baseline, "stroke-width": 1, "stroke-dasharray": "4 3", opacity: 0
1052
+ }, this.gUI);
1053
+ for (var hc = 0; hc < nC; hc++) {
1054
+ svgEl("rect", {
1055
+ x: g.plotX + g.band * hc, y: g.plotY, width: g.band, height: g.plotH,
1056
+ fill: "transparent", "class": "ra-hit", "data-s": -1, "data-c": hc
1057
+ }, this.gUI);
1058
+ }
1059
+ }
1060
+
1061
+ var sfx = o.fullStacked ? "%" : "";
1062
+ this._hitHandler = function (node, x, y) {
1063
+ var si = +node.getAttribute("data-s"), ci = +node.getAttribute("data-c");
1064
+ if (si < 0) { self._xyCross(ci); return; } // line/area band
1065
+ var val = self.cur.vals[si][ci];
1066
+ node.style.transform = horizontal ? "translate(3px,0)" : "translate(0,-4px)";
1067
+ if (nS > 1) self._dimGroups(si);
1068
+ self._tipShow(
1069
+ '<span class="ra-chart__tip-cat">' + cats[ci] + "</span>" + self._dot(self._color(si)) +
1070
+ o.series[si].name + ": <b>" + fmt(val) + sfx + "</b>", x, y - 10);
1071
+ self._emit("raChartSelect", {
1072
+ seriesIndex: si, categoryIndex: ci, series: o.series[si].name,
1073
+ category: cats[ci], value: val, row: data[ci]
1074
+ });
1075
+ };
1076
+ this._leaveHandler = function () {
1077
+ o.series.forEach(function (s, si) {
1078
+ (self.nodes.rects[si] || []).forEach(function (n) { n.style.transform = ""; });
1079
+ });
1080
+ self._dimGroups(-1);
1081
+ if (self.crossLine) self.crossLine.setAttribute("opacity", 0);
1082
+ self._xyBulletReset();
1083
+ };
1084
+
1085
+ if (o.legend && nS > 1) {
1086
+ this._buildLegend(
1087
+ o.series.map(function (s, i) { return { name: s.name, color: self._color(i) }; }),
1088
+ function () { self._xyTransition(); },
1089
+ function (i) { self._dimGroups(i); });
1090
+ } else this.legendEl.style.display = "none";
1091
+
1092
+ if (entrance) this._xyEntrance();
1093
+ else this._xyFrame(this.cur.vals, 1, null);
1094
+ };
1095
+
1096
+ RaChart.prototype._xyScale = function () {
1097
+ var o = this.opts, self = this;
1098
+ if (o.fullStacked && o.type !== "line" && o.type !== "area") {
1099
+ this.cur.scaleMax = 100;
1100
+ return { max: 100, step: 20 };
1101
+ }
1102
+ var D = this.viewData || o.data || [];
1103
+ var maxV = 0;
1104
+ for (var ci = 0; ci < D.length; ci++) {
1105
+ var sum = 0;
1106
+ o.series.forEach(function (s, si) {
1107
+ if (self.hidden[si]) return;
1108
+ var v = +D[ci][s.field] || 0;
1109
+ if (o.stacked && o.type !== "line" && o.type !== "area") sum += v;
1110
+ else if (v > maxV) maxV = v;
1111
+ });
1112
+ if (sum > maxV) maxV = sum;
1113
+ }
1114
+ var ns = niceScale(maxV || 1, o.min);
1115
+ this.cur.scaleMax = ns.max;
1116
+ return ns;
1117
+ };
1118
+
1119
+ RaChart.prototype._xyDrawGrid = function (scale) {
1120
+ var o = this.opts, g = this.geom, T = this.theme;
1121
+ empty(this.gGrid);
1122
+ var i, v, pos;
1123
+
1124
+ if (g.horizontal) {
1125
+ for (v = o.min; v <= scale.max + 1e-9; v += scale.step) {
1126
+ pos = g.plotX + (v - o.min) / (scale.max - o.min) * g.plotW;
1127
+ svgEl("line", { x1: pos, x2: pos, y1: g.plotY, y2: g.plotY + g.plotH, stroke: T.grid, "stroke-width": 1 }, this.gGrid);
1128
+ svgText(this.gGrid, pos, g.plotY + g.plotH + 16, fmt(v), "middle", 11, T.label);
1129
+ }
1130
+ for (i = 0; i < g.nC; i++) {
1131
+ svgText(this.gGrid, g.plotX - 7, g.plotY + g.band * (i + .5) + 4, g.cats[i], "end", 11.5, T.label);
1132
+ }
1133
+ } else {
1134
+ for (v = o.min; v <= scale.max + 1e-9; v += scale.step) {
1135
+ pos = g.plotY + g.plotH - (v - o.min) / (scale.max - o.min) * g.plotH;
1136
+ svgEl("line", {
1137
+ x1: g.plotX, x2: g.plotX + g.plotW, y1: pos, y2: pos,
1138
+ stroke: v === o.min ? T.baseline : T.grid, "stroke-width": 1
1139
+ }, this.gGrid);
1140
+ svgText(this.gGrid, g.plotX - 7, pos + 4, fmt(v), "end", 11, T.label);
1141
+ }
1142
+ var skip = Math.max(1, Math.ceil(32 / g.band));
1143
+ for (i = 0; i < g.nC; i++) {
1144
+ if (i % skip) continue;
1145
+ svgText(this.gGrid, g.plotX + g.band * (i + .5), g.plotY + g.plotH + 16, g.cats[i], "middle", 11, T.label);
1146
+ }
1147
+ }
1148
+ };
1149
+
1150
+ RaChart.prototype._xyFrame = function (vals, alphaT, growth) {
1151
+ var o = this.opts, g = this.geom, self = this;
1152
+ var min = o.min, range = (this.cur.scaleMax - min) || 1;
1153
+
1154
+ var visIdx = [];
1155
+ o.series.forEach(function (_, j) { if (!self.hidden[j]) visIdx.push(j); });
1156
+ var lastVis = visIdx[visIdx.length - 1];
1157
+
1158
+ o.series.forEach(function (s, si) {
1159
+ var target = self.hidden[si] ? 0 : 1;
1160
+ var gEl = self.nodes.groups[si];
1161
+ var a = self._alphas ? lerp(self._alphas.from[si], target, alphaT) : target;
1162
+ gEl.setAttribute("opacity", a);
1163
+
1164
+ if (g.isLine) {
1165
+ var pts = [];
1166
+ for (var ci = 0; ci < g.nC; ci++) {
1167
+ var vv = vals[si][ci] * (growth ? growth[ci] : 1);
1168
+ var px = g.plotX + g.band * (ci + .5);
1169
+ var py = g.plotY + g.plotH - (vv - min) / range * g.plotH;
1170
+ pts.push([+px.toFixed(2), +py.toFixed(2)]);
1171
+ var b = self.nodes.bullets[si][ci];
1172
+ b.setAttribute("cx", px); b.setAttribute("cy", py);
1173
+ }
1174
+ var d = o.stepped ? steppedPath(pts) : linePath(pts, o.smooth);
1175
+ self.nodes.paths[si].setAttribute("d", d);
1176
+ if (o.type === "area") {
1177
+ var base = g.plotY + g.plotH;
1178
+ self.nodes.areas[si].setAttribute("d",
1179
+ d + " L" + pts[pts.length - 1][0] + " " + base + " L" + pts[0][0] + " " + base + " Z");
1180
+ }
1181
+ } else {
1182
+ var slot = visIdx.indexOf(si);
1183
+ var nVis = Math.max(1, visIdx.length);
1184
+
1185
+ for (var cj = 0; cj < g.nC; cj++) {
1186
+ var v2 = vals[si][cj] * (growth ? growth[cj] : 1);
1187
+ var len = (v2 - min) / range * (g.horizontal ? g.plotW : g.plotH);
1188
+ if (len < 0) len = 0;
1189
+ var node = self.nodes.rects[si][cj];
1190
+ var rx, ry, rw, rh, showTop = true, showSide = true;
1191
+
1192
+ if (o.stacked) {
1193
+ var offset = 0;
1194
+ for (var k = 0; k < si; k++) {
1195
+ if (self.hidden[k]) continue;
1196
+ offset += (vals[k][cj] * (growth ? growth[cj] : 1) - min) / range * (g.horizontal ? g.plotW : g.plotH);
1197
+ }
1198
+ var thick = g.band * 0.55;
1199
+ if (g.horizontal) {
1200
+ rx = g.plotX + offset; ry = g.plotY + g.band * cj + (g.band - thick) / 2;
1201
+ rw = len; rh = thick; showSide = (si === lastVis);
1202
+ } else {
1203
+ rx = g.plotX + g.band * cj + (g.band - thick) / 2; ry = g.plotY + g.plotH - offset - len;
1204
+ rw = thick; rh = len; showTop = (si === lastVis);
1205
+ }
1206
+ } else {
1207
+ var usable = g.band * (nVis > 1 ? 0.72 : 0.55);
1208
+ var gap = nVis > 1 ? 3 : 0;
1209
+ var each = (usable - gap * (nVis - 1)) / nVis;
1210
+ var start = g.band * cj + (g.band - usable) / 2 + (slot < 0 ? 0 : slot) * (each + gap);
1211
+ if (g.horizontal) { rx = g.plotX; ry = g.plotY + start; rw = len; rh = each; }
1212
+ else { rx = g.plotX + start; ry = g.plotY + g.plotH - len; rw = each; rh = len; }
1213
+ }
1214
+
1215
+ if (g.isLolli) {
1216
+ if (g.horizontal) {
1217
+ var lcy = ry + rh / 2, tipX = rx + rw;
1218
+ node._line.setAttribute("x1", g.plotX); node._line.setAttribute("y1", lcy);
1219
+ node._line.setAttribute("x2", tipX); node._line.setAttribute("y2", lcy);
1220
+ node._dot.setAttribute("cx", tipX); node._dot.setAttribute("cy", lcy);
1221
+ } else {
1222
+ var lcx = rx + rw / 2;
1223
+ node._line.setAttribute("x1", lcx); node._line.setAttribute("y1", g.plotY + g.plotH);
1224
+ node._line.setAttribute("x2", lcx); node._line.setAttribute("y2", ry);
1225
+ node._dot.setAttribute("cx", lcx); node._dot.setAttribute("cy", ry);
1226
+ }
1227
+ } else if (g.depth > 0) {
1228
+ var f = extrude(rx, ry, rw, rh, g.depth);
1229
+ node._f.setAttribute("d", f.front);
1230
+ node._t.setAttribute("d", showTop ? f.top : "M0 0");
1231
+ node._s.setAttribute("d", showSide ? f.side : "M0 0");
1232
+ } else {
1233
+ var rr = o.stacked ? (si === lastVis ? 4 : 0) : 4;
1234
+ node.setAttribute("d", colPath(rx, ry, rw, rh, rr, g.horizontal));
1235
+ }
1236
+ }
1237
+ }
1238
+ });
1239
+ };
1240
+
1241
+ RaChart.prototype._xyEntrance = function () {
1242
+ var self = this, o = this.opts, g = this.geom;
1243
+ var dur = o.animation.duration, delay = o.animation.delay;
1244
+
1245
+ if (g.isLine) {
1246
+ this._xyFrame(this.cur.vals, 1, null);
1247
+ o.series.forEach(function (s, si) {
1248
+ var p = self.nodes.paths[si];
1249
+ var L = p.getTotalLength ? p.getTotalLength() : 0;
1250
+ p.setAttribute("stroke-dasharray", L + " " + L);
1251
+ p.setAttribute("stroke-dashoffset", L);
1252
+ if (self.nodes.areas[si]) self.nodes.areas[si].setAttribute("opacity", 0);
1253
+ self.nodes.bullets[si].forEach(function (b) { b.setAttribute("r", 0); });
1254
+ });
1255
+ var total = dur + delay + 260;
1256
+ this._tween(total, EASE.outCubic, function (e, raw) {
1257
+ o.series.forEach(function (s, si) {
1258
+ var p = self.nodes.paths[si];
1259
+ var L = p.getTotalLength ? p.getTotalLength() : 0;
1260
+ p.setAttribute("stroke-dashoffset", L * (1 - e));
1261
+ if (self.nodes.areas[si]) self.nodes.areas[si].setAttribute("opacity", clamp01((raw - .35) / .5));
1262
+ self.nodes.bullets[si].forEach(function (b, ci) {
1263
+ b.setAttribute("r", 4 * EASE.outCubic(clamp01((raw * total - ci * 36 - 180) / 240)));
1264
+ });
1265
+ });
1266
+ }, function () {
1267
+ o.series.forEach(function (s, si) {
1268
+ self.nodes.paths[si].removeAttribute("stroke-dasharray");
1269
+ self.nodes.paths[si].removeAttribute("stroke-dashoffset");
1270
+ self.nodes.bullets[si].forEach(function (b) { b.setAttribute("r", 4); });
1271
+ });
1272
+ });
1273
+ } else {
1274
+ var totalMs = dur + delay * Math.min(g.nC, 10);
1275
+ this._tween(totalMs, EASE.linear, function (e, raw) {
1276
+ var growth = [];
1277
+ for (var ci = 0; ci < g.nC; ci++) {
1278
+ growth.push(EASE.outCubic(clamp01((raw * totalMs - ci * delay) / dur)));
1279
+ }
1280
+ self._xyFrame(self.cur.vals, 1, growth);
1281
+ });
1282
+ }
1283
+ };
1284
+
1285
+ RaChart.prototype._xyTransition = function () {
1286
+ var self = this;
1287
+ var D = this.viewData || this.opts.data;
1288
+ var from = this.cur.vals.map(function (r) { return r.slice(); });
1289
+ var to = this._xyVals(D);
1290
+ this._alphas = { from: this.nodes.groups.map(function (gr) { return +gr.getAttribute("opacity"); }) };
1291
+ this._xyDrawGrid(this._xyScale());
1292
+ this._tween(MORPH_MS, EASE.inOutCubic, function (e) {
1293
+ var vals = from.map(function (row, si) {
1294
+ return row.map(function (v, ci) { return lerp(v, to[si][ci], e); });
1295
+ });
1296
+ self._xyFrame(vals, e, null);
1297
+ self.cur.vals = vals;
1298
+ }, function () {
1299
+ self.cur.vals = to;
1300
+ self._alphas = null;
1301
+ });
1302
+ };
1303
+
1304
+ RaChart.prototype._xyCross = function (ci) {
1305
+ var o = this.opts, g = this.geom, self = this;
1306
+ var x = g.plotX + g.band * (ci + .5);
1307
+ this.crossLine.setAttribute("x1", x);
1308
+ this.crossLine.setAttribute("x2", x);
1309
+ this.crossLine.setAttribute("opacity", 1);
1310
+ this._xyBulletReset();
1311
+ var rows = "";
1312
+ o.series.forEach(function (s, si) {
1313
+ if (self.hidden[si]) return;
1314
+ self.nodes.bullets[si][ci].setAttribute("r", 6);
1315
+ rows += self._dot(self._color(si)) + s.name + ": <b>" + fmt(self.cur.vals[si][ci]) + "</b>";
1316
+ });
1317
+ this._tipShow('<span class="ra-chart__tip-cat">' + g.cats[ci] + "</span>" + rows, x, g.plotY + 2);
1318
+ this._emit("raChartSelect", {
1319
+ seriesIndex: -1, categoryIndex: ci, category: g.cats[ci],
1320
+ row: (this.viewData || [])[ci]
1321
+ });
1322
+ };
1323
+
1324
+ RaChart.prototype._xyBulletReset = function () {
1325
+ (this.nodes.bullets || []).forEach(function (bl) {
1326
+ if (bl) bl.forEach(function (b) { b.setAttribute("r", 4); });
1327
+ });
1328
+ };
1329
+
1330
+ /* ============================================================
1331
+ Pie / Donut (+ semi, + 3D)
1332
+ ============================================================ */
1333
+
1334
+ RaChart.prototype._buildPie = function (entrance) {
1335
+ var self = this, o = this.opts, T = this.theme;
1336
+ var field = o.series.length ? o.series[0].field : "value";
1337
+ var cats = o.data.map(function (d) { return String(d[o.category]); });
1338
+ var vals = o.data.map(function (d) { return +d[field] || 0; });
1339
+
1340
+ var d = o.depth > 0 ? o.depth : 0;
1341
+ var tilt = d > 0 ? PIE_TILT : 1;
1342
+ var semi = !!o.semi;
1343
+ var cx = this.w / 2, cy, R;
1344
+ if (semi) {
1345
+ R = Math.min(this.w / 2 - 8, (this.h - 40 - d) / tilt);
1346
+ cy = this.h - 20 - d;
1347
+ } else if (d > 0) {
1348
+ R = Math.min(this.w / 2 - 8, (this.h - d - 20) / (2 * tilt));
1349
+ cy = (this.h - d) / 2;
1350
+ } else {
1351
+ R = Math.min(this.w, this.h) / 2 - 8;
1352
+ cy = this.h / 2;
1353
+ }
1354
+ var r0 = o.type === "donut" ? R * 0.62 : 0;
1355
+ this.geom = { cx: cx, cy: cy, R: R, r0: r0, d: d, tilt: tilt, semi: semi, cats: cats, vals: vals, field: field };
1356
+
1357
+ this.gMarks = svgEl("g", null, this.svg);
1358
+ var gInner = svgEl("g", null, this.gMarks);
1359
+ var gOuter = svgEl("g", null, this.gMarks);
1360
+ var gTop = svgEl("g", null, this.gMarks);
1361
+ this.nodes = { slices: [] };
1362
+
1363
+ vals.forEach(function (v, i) {
1364
+ var color = self._color(i);
1365
+ var inner = (d > 0 && r0 > 0) ? svgEl("path", { fill: shade(color, -.3), stroke: "none" }, gInner) : null;
1366
+ var outer = d > 0 ? svgEl("path", { fill: shade(color, -.18), stroke: "none" }, gOuter) : null;
1367
+ var top = svgEl("path", {
1368
+ fill: color, stroke: T.surface, "stroke-width": 2,
1369
+ "class": "ra-hit", "data-i": i
1370
+ }, gTop);
1371
+ [top, outer, inner].forEach(function (p) {
1372
+ if (p) p.style.transition = "transform .18s, opacity .18s";
1373
+ });
1374
+ self.nodes.slices.push({ top: top, outer: outer, inner: inner });
1375
+ });
1376
+
1377
+ if (r0 > 0) {
1378
+ this.centerSub = svgText(this.svg, cx, semi ? cy - 30 : cy - 7, "Total", "middle", 11, T.muted, 700);
1379
+ this.centerVal = svgText(this.svg, cx, semi ? cy - 6 : cy + 16, "", "middle", 21, T.text, 800);
1380
+ }
1381
+
1382
+ this._hitHandler = function (node, x, y) {
1383
+ var i = +node.getAttribute("data-i");
1384
+ var a = self.cur.angles[i];
1385
+ var mid = a.start + a.sweep / 2;
1386
+ var dx = Math.cos(mid) * 8, dy = Math.sin(mid) * 8 * tilt;
1387
+ var s = self.nodes.slices[i];
1388
+ [s.top, s.outer, s.inner].forEach(function (p) {
1389
+ if (p) p.style.transform = "translate(" + dx + "px," + dy + "px)";
1390
+ });
1391
+ self._dimSlices(i);
1392
+ var total = self._pieTotal();
1393
+ var pct = total > 0 ? (vals[i] / total * 100) : 0;
1394
+ self._tipShow(self._dot(self._color(i)) + cats[i] + ": <b>" + fmt(vals[i]) +
1395
+ '</b> <span class="ra-chart__tip-muted">(' + pct.toFixed(1) + "%)</span>", x, y - 10);
1396
+ self._emit("raChartSelect", { index: i, category: cats[i], value: vals[i], percent: pct, row: o.data[i] });
1397
+ };
1398
+ this._leaveHandler = function () {
1399
+ self.nodes.slices.forEach(function (s) {
1400
+ [s.top, s.outer, s.inner].forEach(function (p) { if (p) p.style.transform = ""; });
1401
+ });
1402
+ self._dimSlices(-1);
1403
+ };
1404
+
1405
+ if (o.legend) {
1406
+ this._buildLegend(
1407
+ cats.map(function (c, i) { return { name: c, color: self._color(i), value: vals[i] }; }),
1408
+ function () { self._pieTransition(); },
1409
+ function (i) { self._dimSlices(i); });
1410
+ } else this.legendEl.style.display = "none";
1411
+
1412
+ var target = this._pieAngles();
1413
+ this.cur = { kind: "pie", angles: target.map(function (a) { return { start: a.start, sweep: a.sweep }; }) };
1414
+
1415
+ if (entrance) {
1416
+ var start = semi ? -Math.PI : -Math.PI / 2;
1417
+ var span = semi ? Math.PI : Math.PI * 2;
1418
+ this._tween(o.animation.duration + o.animation.delay, EASE.outCubic, function (e) {
1419
+ var sweepAll = e * span;
1420
+ var angles = target.map(function (a) {
1421
+ return { start: a.start, sweep: clamp01((sweepAll - (a.start - start)) / (a.sweep || 1e-9)) * a.sweep };
1422
+ });
1423
+ self._pieFrame(angles);
1424
+ self.cur.angles = angles;
1425
+ }, function () {
1426
+ self.cur.angles = target;
1427
+ self._pieFrame(target);
1428
+ });
1429
+ } else {
1430
+ this._pieFrame(target);
1431
+ this.cur.angles = target;
1432
+ }
1433
+ this._pieCenter();
1434
+ };
1435
+
1436
+ RaChart.prototype._pieTotal = function () {
1437
+ var self = this, t = 0;
1438
+ this.geom.vals.forEach(function (v, i) { if (!self.hidden[i]) t += v; });
1439
+ return t;
1440
+ };
1441
+
1442
+ RaChart.prototype._pieAngles = function () {
1443
+ var self = this;
1444
+ var total = this._pieTotal() || 1;
1445
+ var semi = this.geom && this.geom.semi;
1446
+ var span = semi ? Math.PI : Math.PI * 2;
1447
+ var a = semi ? -Math.PI : -Math.PI / 2;
1448
+ return this.geom.vals.map(function (v, i) {
1449
+ var sweep = self.hidden[i] ? 0 : (v / total) * span;
1450
+ var out = { start: a, sweep: sweep };
1451
+ a += sweep;
1452
+ return out;
1453
+ });
1454
+ };
1455
+
1456
+ RaChart.prototype._pieFrame = function (angles) {
1457
+ var g = this.geom;
1458
+ for (var i = 0; i < angles.length; i++) {
1459
+ var a0 = angles[i].start, a1 = a0 + angles[i].sweep;
1460
+ var s = this.nodes.slices[i];
1461
+ s.top.setAttribute("d", arcPath(g.cx, g.cy, g.r0, g.R, a0, a1, g.tilt));
1462
+ if (g.d > 0) {
1463
+ if (s.outer) s.outer.setAttribute("d", wallPath(g.R, Math.max(a0, 0), Math.min(a1, Math.PI), g) || "M0 0");
1464
+ if (s.inner) {
1465
+ var innerD = wallPath(g.r0, a0, Math.min(a1, 0), g) + wallPath(g.r0, Math.max(a0, Math.PI), a1, g);
1466
+ s.inner.setAttribute("d", innerD || "M0 0");
1467
+ }
1468
+ }
1469
+ }
1470
+ };
1471
+
1472
+ RaChart.prototype._pieCenter = function () {
1473
+ if (this.centerVal) setText(this.centerVal, fmt(this._pieTotal()));
1474
+ };
1475
+
1476
+ RaChart.prototype._pieTransition = function () {
1477
+ var self = this;
1478
+ var from = this.cur.angles.map(function (a) { return { start: a.start, sweep: a.sweep }; });
1479
+ var to = this._pieAngles();
1480
+ this._tween(MORPH_MS, EASE.inOutCubic, function (e) {
1481
+ var angles = from.map(function (a, i) {
1482
+ return { start: lerp(a.start, to[i].start, e), sweep: lerp(a.sweep, to[i].sweep, e) };
1483
+ });
1484
+ self._pieFrame(angles);
1485
+ self.cur.angles = angles;
1486
+ }, function () { self.cur.angles = to; });
1487
+ this._pieCenter();
1488
+ };
1489
+
1490
+ /* ============================================================
1491
+ Radar
1492
+ ============================================================ */
1493
+
1494
+ RaChart.prototype._buildRadar = function (entrance) {
1495
+ var self = this, o = this.opts, T = this.theme;
1496
+ var cats = o.data.map(function (d) { return String(d[o.category]); });
1497
+ var nC = cats.length, nS = o.series.length;
1498
+
1499
+ var cx = this.w / 2, cy = this.h / 2 + 2;
1500
+ var R = Math.min(this.w, this.h) / 2 - 30;
1501
+ this.geom = { cx: cx, cy: cy, R: R, cats: cats, nC: nC };
1502
+ this.cur = { kind: "radar", vals: o.series.map(function (s) { return o.data.map(function (d) { return +d[s.field] || 0; }); }) };
1503
+
1504
+ var maxV = 0;
1505
+ this.cur.vals.forEach(function (row, si) {
1506
+ if (self.hidden[si]) return;
1507
+ row.forEach(function (v) { if (v > maxV) maxV = v; });
1508
+ });
1509
+ var ns = niceScale(maxV || 1, o.min);
1510
+ this.cur.scaleMax = ns.max;
1511
+
1512
+ function angle(i) { return -Math.PI / 2 + i * Math.PI * 2 / nC; }
1513
+ function pt(i, r) { return [cx + Math.cos(angle(i)) * r, cy + Math.sin(angle(i)) * r]; }
1514
+ this._radarPt = pt;
1515
+
1516
+ this.gGrid = svgEl("g", null, this.svg);
1517
+ this.gMarks = svgEl("g", null, this.svg);
1518
+
1519
+ for (var v = o.min + ns.step; v <= ns.max + 1e-9; v += ns.step) {
1520
+ var rr = (v - o.min) / (ns.max - o.min) * R;
1521
+ var ringPts = [];
1522
+ for (var i = 0; i < nC; i++) ringPts.push(pt(i, rr).map(function (n) { return n.toFixed(1); }).join(","));
1523
+ svgEl("polygon", { points: ringPts.join(" "), fill: "none", stroke: T.grid, "stroke-width": 1 }, this.gGrid);
1524
+ svgText(this.gGrid, cx + 4, cy - rr - 3, fmt(v), "start", 10, T.label);
1525
+ }
1526
+ for (var j = 0; j < nC; j++) {
1527
+ var e2 = pt(j, R);
1528
+ svgEl("line", { x1: cx, y1: cy, x2: e2[0], y2: e2[1], stroke: T.grid, "stroke-width": 1 }, this.gGrid);
1529
+ var lp = pt(j, R + 14);
1530
+ var ca = Math.cos(angle(j)), sa = Math.sin(angle(j));
1531
+ svgText(this.gGrid, lp[0], lp[1] + (sa > .3 ? 8 : sa < -.3 ? -1 : 4), cats[j],
1532
+ ca > .3 ? "start" : ca < -.3 ? "end" : "middle", 11, T.body, 600);
1533
+ }
1534
+
1535
+ this.nodes = { polys: [], bullets: [], groups: [] };
1536
+ o.series.forEach(function (s, si) {
1537
+ var color = self._color(si);
1538
+ var gS = svgEl("g", { opacity: self.hidden[si] ? 0 : 1 }, self.gMarks);
1539
+ gS.style.transition = "opacity .18s";
1540
+ var poly = svgEl("polygon", {
1541
+ fill: color, "fill-opacity": 0.15, stroke: color,
1542
+ "stroke-width": 2, "stroke-linejoin": "round"
1543
+ }, gS);
1544
+ var bl = [];
1545
+ for (var ci = 0; ci < nC; ci++) {
1546
+ var b = svgEl("circle", {
1547
+ r: COARSE ? 5 : 4, fill: color, stroke: T.surface, "stroke-width": 2,
1548
+ "class": "ra-hit", "data-s": si, "data-c": ci
1549
+ }, gS);
1550
+ bl.push(b);
1551
+ }
1552
+ self.nodes.polys.push(poly);
1553
+ self.nodes.bullets.push(bl);
1554
+ self.nodes.groups.push(gS);
1555
+ });
1556
+
1557
+ this._hitHandler = function (node, x, y) {
1558
+ var si = +node.getAttribute("data-s"), ci = +node.getAttribute("data-c");
1559
+ node.setAttribute("r", COARSE ? 7 : 6);
1560
+ if (nS > 1) self._dimGroups(si);
1561
+ self._tipShow('<span class="ra-chart__tip-cat">' + cats[ci] + "</span>" +
1562
+ self._dot(self._color(si)) + o.series[si].name + ": <b>" + fmt(self.cur.vals[si][ci]) + "</b>", x, y - 10);
1563
+ self._emit("raChartSelect", {
1564
+ seriesIndex: si, categoryIndex: ci, series: o.series[si].name,
1565
+ category: cats[ci], value: self.cur.vals[si][ci], row: o.data[ci]
1566
+ });
1567
+ };
1568
+ this._leaveHandler = function () {
1569
+ self.nodes.bullets.forEach(function (bl) {
1570
+ bl.forEach(function (b) { b.setAttribute("r", COARSE ? 5 : 4); });
1571
+ });
1572
+ self._dimGroups(-1);
1573
+ };
1574
+
1575
+ if (o.legend && nS > 1) {
1576
+ this._buildLegend(
1577
+ o.series.map(function (s, i) { return { name: s.name, color: self._color(i) }; }),
1578
+ function () { self._radarTransition(); },
1579
+ function (i) { self._dimGroups(i); });
1580
+ } else this.legendEl.style.display = "none";
1581
+
1582
+ if (entrance) {
1583
+ this._tween(o.animation.duration + o.animation.delay, EASE.outCubic, function (e) {
1584
+ self._radarFrame(self.cur.vals, e, 1);
1585
+ });
1586
+ } else this._radarFrame(this.cur.vals, 1, 1);
1587
+ };
1588
+
1589
+ RaChart.prototype._radarFrame = function (vals, factor, alphaT) {
1590
+ var self = this, o = this.opts, g = this.geom;
1591
+ var min = o.min, range = (this.cur.scaleMax - min) || 1;
1592
+ o.series.forEach(function (s, si) {
1593
+ var target = self.hidden[si] ? 0 : 1;
1594
+ var a = self._alphas ? lerp(self._alphas.from[si], target, alphaT) : target;
1595
+ self.nodes.groups[si].setAttribute("opacity", a);
1596
+ var pts = [];
1597
+ for (var ci = 0; ci < g.nC; ci++) {
1598
+ var r = (vals[si][ci] * factor - min * factor) / range * g.R;
1599
+ if (r < 0) r = 0;
1600
+ var p = self._radarPt(ci, r);
1601
+ pts.push(p[0].toFixed(1) + "," + p[1].toFixed(1));
1602
+ self.nodes.bullets[si][ci].setAttribute("cx", p[0]);
1603
+ self.nodes.bullets[si][ci].setAttribute("cy", p[1]);
1604
+ }
1605
+ self.nodes.polys[si].setAttribute("points", pts.join(" "));
1606
+ });
1607
+ };
1608
+
1609
+ RaChart.prototype._radarTransition = function () {
1610
+ var self = this, o = this.opts;
1611
+ var from = this.cur.vals.map(function (r) { return r.slice(); });
1612
+ var to = o.series.map(function (s) { return o.data.map(function (d) { return +d[s.field] || 0; }); });
1613
+ this._alphas = { from: this.nodes.groups.map(function (gr) { return +gr.getAttribute("opacity"); }) };
1614
+ this._tween(MORPH_MS, EASE.inOutCubic, function (e) {
1615
+ var vals = from.map(function (row, si) {
1616
+ return row.map(function (v, ci) { return lerp(v, to[si][ci], e); });
1617
+ });
1618
+ self.cur.vals = vals;
1619
+ self._radarFrame(vals, 1, e);
1620
+ }, function () {
1621
+ self.cur.vals = to;
1622
+ self._alphas = null;
1623
+ });
1624
+ };
1625
+
1626
+ /* ============================================================
1627
+ Treemap
1628
+ ============================================================ */
1629
+
1630
+ RaChart.prototype._buildTreemap = function (entrance) {
1631
+ var self = this, o = this.opts, T = this.theme;
1632
+ var field = o.series.length ? o.series[0].field : "value";
1633
+ var cats = o.data.map(function (d) { return String(d[o.category]); });
1634
+ var rawVals = o.data.map(function (d) { return +d[field] || 0; });
1635
+ var vals = rawVals.map(function (v, i) { return self.hidden[i] ? 0 : v; });
1636
+ this.cur = { kind: "treemap" };
1637
+
1638
+ var rects = treemapLayout(vals, 2, 2, this.w - 4, this.h - 4);
1639
+ this.gMarks = svgEl("g", null, this.svg);
1640
+ this.nodes = { cells: [] };
1641
+ var total = 0;
1642
+ vals.forEach(function (v) { total += v; });
1643
+
1644
+ vals.forEach(function (v, i) {
1645
+ var r = rects[i];
1646
+ if (!r) { self.nodes.cells.push(null); return; }
1647
+ var gC = svgEl("g", { "class": "ra-hit", "data-i": i }, self.gMarks);
1648
+ gC.style.transition = "opacity .18s";
1649
+ var rect = svgEl("rect", { fill: self._color(i), stroke: T.surface, "stroke-width": 2, rx: 3 }, gC);
1650
+ var lbl = null, lblV = null;
1651
+ if (r.w > 54 && r.h > 36) {
1652
+ lbl = svgText(gC, 0, 0, cats[i], "start", 12, "#ffffff", 700);
1653
+ lblV = svgText(gC, 0, 0, fmt(v), "start", 10.5, "rgba(255,255,255,.85)", 600);
1654
+ }
1655
+ self.nodes.cells.push({ g: gC, rect: rect, lbl: lbl, lblV: lblV, r: r });
1656
+ });
1657
+
1658
+ function drawCell(c, e) {
1659
+ var r = c.r;
1660
+ var wq = Math.max(0, r.w * e), hq = Math.max(0, r.h * e);
1661
+ c.rect.setAttribute("x", r.x + (r.w - wq) / 2);
1662
+ c.rect.setAttribute("y", r.y + (r.h - hq) / 2);
1663
+ c.rect.setAttribute("width", wq);
1664
+ c.rect.setAttribute("height", hq);
1665
+ if (c.lbl) {
1666
+ c.lbl.setAttribute("x", r.x + 8); c.lbl.setAttribute("y", r.y + 19);
1667
+ c.lbl.setAttribute("opacity", e);
1668
+ c.lblV.setAttribute("x", r.x + 8); c.lblV.setAttribute("y", r.y + 34);
1669
+ c.lblV.setAttribute("opacity", e * .9);
1670
+ }
1671
+ }
1672
+
1673
+ this._hitHandler = function (node, x, y) {
1674
+ var i = +node.getAttribute("data-i");
1675
+ self.nodes.cells.forEach(function (c, j) { if (c) c.g.style.opacity = j === i ? "" : .55; });
1676
+ var pct = total > 0 ? (vals[i] / total * 100) : 0;
1677
+ self._tipShow(self._dot(self._color(i)) + cats[i] + ": <b>" + fmt(vals[i]) +
1678
+ '</b> <span class="ra-chart__tip-muted">(' + pct.toFixed(1) + "%)</span>", x, y - 10);
1679
+ self._emit("raChartSelect", { index: i, category: cats[i], value: vals[i], percent: pct, row: o.data[i] });
1680
+ };
1681
+ this._leaveHandler = function () {
1682
+ self.nodes.cells.forEach(function (c) { if (c) c.g.style.opacity = ""; });
1683
+ };
1684
+
1685
+ if (o.legend) {
1686
+ this._buildLegend(
1687
+ cats.map(function (c, i) { return { name: c, color: self._color(i), value: rawVals[i] }; }),
1688
+ function () { self.build(true); },
1689
+ function (i) {
1690
+ self.nodes.cells.forEach(function (c, j) { if (c) c.g.style.opacity = (i >= 0 && j !== i) ? .55 : ""; });
1691
+ });
1692
+ } else this.legendEl.style.display = "none";
1693
+
1694
+ if (entrance) {
1695
+ var dur = o.animation.duration * 0.6, delay = 42;
1696
+ var totalMs = dur + delay * this.nodes.cells.length;
1697
+ this._tween(totalMs, EASE.linear, function (e, raw) {
1698
+ self.nodes.cells.forEach(function (c, i) {
1699
+ if (c) drawCell(c, EASE.outCubic(clamp01((raw * totalMs - i * delay) / dur)));
1700
+ });
1701
+ });
1702
+ } else {
1703
+ this.nodes.cells.forEach(function (c) { if (c) drawCell(c, 1); });
1704
+ }
1705
+ };
1706
+
1707
+ /* ============================================================
1708
+ Sankey
1709
+ ============================================================ */
1710
+
1711
+ RaChart.prototype._buildSankey = function (entrance) {
1712
+ var self = this, o = this.opts, T = this.theme;
1713
+ var F = merge({ from: "from", to: "to", value: "value" }, o.fields || {});
1714
+ var links = (o.data || []).map(function (d) {
1715
+ return { s: String(d[F.from]), t: String(d[F.to]), v: +d[F.value] || 0 };
1716
+ }).filter(function (l) { return l.v > 0 && l.s !== l.t; });
1717
+
1718
+ this.cur = { kind: "sankey" };
1719
+ this.gMarks = svgEl("g", null, this.svg);
1720
+ this.nodes = { links: [], rects: [] };
1721
+ this.legendEl.style.display = "none";
1722
+ if (!links.length) return;
1723
+
1724
+ var names = [];
1725
+ links.forEach(function (l) {
1726
+ if (names.indexOf(l.s) < 0) names.push(l.s);
1727
+ if (names.indexOf(l.t) < 0) names.push(l.t);
1728
+ });
1729
+ var idx = {};
1730
+ names.forEach(function (n, i) { idx[n] = i; });
1731
+ var N = names.length;
1732
+
1733
+ var layer = names.map(function () { return 0; });
1734
+ for (var it = 0; it < N; it++) {
1735
+ var changed = false;
1736
+ links.forEach(function (l) {
1737
+ if (layer[idx[l.t]] < layer[idx[l.s]] + 1) { layer[idx[l.t]] = layer[idx[l.s]] + 1; changed = true; }
1738
+ });
1739
+ if (!changed) break;
1740
+ }
1741
+ var maxLayer = Math.max.apply(null, layer);
1742
+
1743
+ var inSum = names.map(function () { return 0; }), outSum = names.map(function () { return 0; });
1744
+ links.forEach(function (l) { outSum[idx[l.s]] += l.v; inSum[idx[l.t]] += l.v; });
1745
+ var flow = names.map(function (n, i) { return Math.max(inSum[i], outSum[i]); });
1746
+
1747
+ var layers = [];
1748
+ for (var L = 0; L <= maxLayer; L++) layers.push([]);
1749
+ names.forEach(function (n, i) { layers[layer[i]].push(i); });
1750
+
1751
+ var padT = 10, padB = 12, gapN = 10, nodeW = 12;
1752
+ var availH = this.h - padT - padB;
1753
+ var scale = Infinity;
1754
+ layers.forEach(function (col) {
1755
+ var tf = 0;
1756
+ col.forEach(function (i) { tf += flow[i]; });
1757
+ var s = (availH - gapN * Math.max(0, col.length - 1)) / (tf || 1);
1758
+ if (s < scale) scale = s;
1759
+ });
1760
+ if (!isFinite(scale)) scale = 1;
1761
+
1762
+ var labelPad = 52;
1763
+ var x0 = 6, x1 = this.w - 6 - nodeW - (maxLayer > 0 ? labelPad : 0);
1764
+ var pos = [];
1765
+ layers.forEach(function (col, L2) {
1766
+ var tf = 0;
1767
+ col.forEach(function (i) { tf += flow[i]; });
1768
+ var colH = tf * scale + gapN * Math.max(0, col.length - 1);
1769
+ var yy = padT + (availH - colH) / 2;
1770
+ var lx = maxLayer ? x0 + (x1 - x0) * L2 / maxLayer : (x0 + x1) / 2;
1771
+ col.forEach(function (i) {
1772
+ pos[i] = { x: lx, y: yy, h: Math.max(2, flow[i] * scale), layer: L2 };
1773
+ yy += flow[i] * scale + gapN;
1774
+ });
1775
+ });
1776
+
1777
+ var gLinks = svgEl("g", null, this.gMarks);
1778
+ var gNodes = svgEl("g", null, this.gMarks);
1779
+ var outOff = names.map(function () { return 0; }), inOff = names.map(function () { return 0; });
1780
+ var sorted = links.slice().sort(function (a, b) {
1781
+ return pos[idx[a.t]].y - pos[idx[b.t]].y || pos[idx[a.s]].y - pos[idx[b.s]].y;
1782
+ });
1783
+
1784
+ sorted.forEach(function (l) {
1785
+ var si = idx[l.s], ti = idx[l.t];
1786
+ var lw = Math.max(1.5, l.v * scale);
1787
+ var sx = pos[si].x + nodeW, sy = pos[si].y + outOff[si] + l.v * scale / 2;
1788
+ var tx = pos[ti].x, ty = pos[ti].y + inOff[ti] + l.v * scale / 2;
1789
+ outOff[si] += l.v * scale;
1790
+ inOff[ti] += l.v * scale;
1791
+ var mx = (sx + tx) / 2;
1792
+ var p = svgEl("path", {
1793
+ d: "M" + sx + " " + sy + " C" + mx + " " + sy + " " + mx + " " + ty + " " + tx + " " + ty,
1794
+ fill: "none", stroke: self._color(si), "stroke-width": lw,
1795
+ "stroke-opacity": entrance ? 0 : 0.32,
1796
+ "class": "ra-hit", "data-kind": "link", "data-i": self.nodes.links.length
1797
+ }, gLinks);
1798
+ self.nodes.links.push({ p: p, l: l, sLayer: pos[si].layer });
1799
+ });
1800
+
1801
+ names.forEach(function (n, i) {
1802
+ var gN = svgEl("g", {
1803
+ "class": "ra-hit", "data-kind": "node", "data-i": i, opacity: entrance ? 0 : 1
1804
+ }, gNodes);
1805
+ svgEl("rect", {
1806
+ x: pos[i].x, y: pos[i].y, width: nodeW, height: pos[i].h, rx: 3, fill: self._color(i)
1807
+ }, gN);
1808
+ var right = pos[i].layer < maxLayer;
1809
+ svgText(gN, right ? pos[i].x + nodeW + 6 : pos[i].x - 6, pos[i].y + pos[i].h / 2 + 4,
1810
+ n, right ? "start" : "end", 11, T.body, 600);
1811
+ self.nodes.rects.push({ g: gN, layer: pos[i].layer, name: n, flow: flow[i] });
1812
+ });
1813
+
1814
+ this._hitHandler = function (node, x, y) {
1815
+ var kind = node.getAttribute("data-kind"), i = +node.getAttribute("data-i");
1816
+ var html;
1817
+ if (kind === "link") {
1818
+ var lk = self.nodes.links[i];
1819
+ self.nodes.links.forEach(function (l3, j) { l3.p.setAttribute("stroke-opacity", j === i ? .65 : .1); });
1820
+ html = lk.l.s + " &rarr; " + lk.l.t + ": <b>" + fmt(lk.l.v) + "</b>";
1821
+ self._emit("raChartSelect", { kind: "link", from: lk.l.s, to: lk.l.t, value: lk.l.v });
1822
+ } else {
1823
+ var nd = self.nodes.rects[i], nm = nd.name;
1824
+ self.nodes.links.forEach(function (l3) {
1825
+ l3.p.setAttribute("stroke-opacity", (l3.l.s === nm || l3.l.t === nm) ? .55 : .1);
1826
+ });
1827
+ html = nm + ": <b>" + fmt(nd.flow) + "</b>";
1828
+ self._emit("raChartSelect", { kind: "node", name: nm, value: nd.flow });
1829
+ }
1830
+ self._tipShow(html, x, y - 10);
1831
+ };
1832
+ this._leaveHandler = function () {
1833
+ self.nodes.links.forEach(function (l3) { l3.p.setAttribute("stroke-opacity", .32); });
1834
+ };
1835
+
1836
+ function finish() {
1837
+ self.nodes.links.forEach(function (l3) { l3.p.style.transition = "stroke-opacity .18s"; });
1838
+ }
1839
+ if (entrance) {
1840
+ var totalMs = o.animation.duration + maxLayer * 180;
1841
+ this._tween(totalMs, EASE.linear, function (e, raw) {
1842
+ self.nodes.rects.forEach(function (nd) {
1843
+ nd.g.setAttribute("opacity", EASE.outCubic(clamp01((raw * totalMs - nd.layer * 180) / 420)));
1844
+ });
1845
+ self.nodes.links.forEach(function (l3) {
1846
+ l3.p.setAttribute("stroke-opacity", .32 * EASE.outCubic(clamp01((raw * totalMs - l3.sLayer * 180 - 140) / 420)));
1847
+ });
1848
+ }, finish);
1849
+ } else finish();
1850
+ };
1851
+
1852
+ /* ============================================================
1853
+ Word cloud
1854
+ ============================================================ */
1855
+
1856
+ RaChart.prototype._buildCloud = function (entrance) {
1857
+ var self = this, o = this.opts;
1858
+ var field = o.series.length ? o.series[0].field : "value";
1859
+ var items = (o.data || []).map(function (d) { return { w: String(d[o.category]), v: +d[field] || 0 }; })
1860
+ .filter(function (it) { return it.v > 0; })
1861
+ .sort(function (a, b) { return b.v - a.v; });
1862
+ var vmax = items.length ? items[0].v : 1;
1863
+ var cx = this.w / 2, cy = this.h / 2, W = this.w, H = this.h;
1864
+
1865
+ this.cur = { kind: "cloud" };
1866
+ this.gMarks = svgEl("g", null, this.svg);
1867
+ this.nodes = { words: [] };
1868
+ this.legendEl.style.display = "none";
1869
+ var placed = [];
1870
+ var base = Math.max(11, Math.min(16, W / 26));
1871
+ var range = Math.max(14, Math.min(30, W / 13));
1872
+
1873
+ items.forEach(function (it, i) {
1874
+ var size = base + range * Math.sqrt(it.v / vmax);
1875
+ var font = "700 " + size + "px system-ui, -apple-system, 'Segoe UI', sans-serif";
1876
+ var tw = textW(it.w, font) + 6;
1877
+ var th = size * 1.1;
1878
+ var px = cx, py = cy, ok = false;
1879
+ for (var t = 0; t < 2400; t++) {
1880
+ var r = t * 0.18, a = t * 0.33;
1881
+ px = cx + r * Math.cos(a) * 1.4;
1882
+ py = cy + r * Math.sin(a);
1883
+ var box = { x: px - tw / 2, y: py - th / 2, w: tw, h: th };
1884
+ if (box.x < 2 || box.y < 2 || box.x + box.w > W - 2 || box.y + box.h > H - 2) continue;
1885
+ var hit = placed.some(function (b) {
1886
+ return !(box.x + box.w < b.x || b.x + b.w < box.x || box.y + box.h < b.y || b.y + b.h < box.y);
1887
+ });
1888
+ if (!hit) { ok = true; placed.push(box); break; }
1889
+ }
1890
+ if (!ok) return;
1891
+ var txt = svgEl("text", {
1892
+ x: px, y: py, "text-anchor": "middle", "dominant-baseline": "middle",
1893
+ "font-size": size, fill: self._color(i),
1894
+ "font-family": "system-ui, -apple-system, 'Segoe UI', sans-serif", "font-weight": 700,
1895
+ "class": "ra-hit", "data-i": self.nodes.words.length, opacity: entrance ? 0 : 1
1896
+ }, self.gMarks);
1897
+ txt.appendChild(document.createTextNode(it.w));
1898
+ self.nodes.words.push({ node: txt, it: it, size: size });
1899
+ });
1900
+
1901
+ this._hitHandler = function (node, x, y) {
1902
+ var i = +node.getAttribute("data-i");
1903
+ self.nodes.words.forEach(function (w2, j) {
1904
+ w2.node.style.opacity = j === i ? "" : .35;
1905
+ w2.node.style.fontSize = j === i ? (w2.size * 1.14) + "px" : "";
1906
+ });
1907
+ var w3 = self.nodes.words[i];
1908
+ self._tipShow(w3.it.w + ": <b>" + fmt(w3.it.v) + "</b>", x, y - 14);
1909
+ self._emit("raChartSelect", { index: i, category: w3.it.w, value: w3.it.v });
1910
+ };
1911
+ this._leaveHandler = function () {
1912
+ self.nodes.words.forEach(function (w2) {
1913
+ w2.node.style.opacity = "";
1914
+ w2.node.style.fontSize = "";
1915
+ });
1916
+ };
1917
+
1918
+ function finish() {
1919
+ self.nodes.words.forEach(function (w2) { w2.node.style.transition = "opacity .18s, font-size .18s"; });
1920
+ }
1921
+ if (entrance) {
1922
+ var dur = 320, delay = 50;
1923
+ var totalMs = dur + delay * this.nodes.words.length;
1924
+ this._tween(totalMs, EASE.linear, function (e, raw) {
1925
+ self.nodes.words.forEach(function (w2, i) {
1926
+ w2.node.setAttribute("opacity", EASE.outCubic(clamp01((raw * totalMs - i * delay) / dur)));
1927
+ });
1928
+ }, finish);
1929
+ } else finish();
1930
+ };
1931
+
1932
+ /* ============================================================
1933
+ Venn
1934
+ ============================================================ */
1935
+
1936
+ RaChart.prototype._buildVenn = function (entrance) {
1937
+ var self = this, o = this.opts, T = this.theme;
1938
+ var field = o.series.length ? o.series[0].field : "value";
1939
+ var all = (o.data || []).slice(0, 3).map(function (d, i) {
1940
+ return { label: String(d[o.category]), v: +d[field] || 0, di: i };
1941
+ });
1942
+ var items = all.filter(function (it) { return !self.hidden[it.di] && it.v > 0; });
1943
+ var n = items.length;
1944
+
1945
+ this.cur = { kind: "venn" };
1946
+ this.gMarks = svgEl("g", null, this.svg);
1947
+ this.nodes = { circles: [] };
1948
+
1949
+ var cx = this.w / 2, cy = this.h / 2;
1950
+ var vmax = Math.max.apply(null, items.map(function (i2) { return i2.v; }).concat([1]));
1951
+ var Rmax = Math.min(this.w, this.h) / (n <= 1 ? 2.6 : 3.1);
1952
+ var radii = items.map(function (it) { return Math.max(22, Rmax * Math.sqrt(it.v / vmax)); });
1953
+ var centers;
1954
+ if (n === 1) centers = [[cx, cy]];
1955
+ else if (n === 2) {
1956
+ var d2 = (radii[0] + radii[1]) * 0.6;
1957
+ centers = [[cx - d2 / 2, cy], [cx + d2 / 2, cy]];
1958
+ } else if (n === 3) {
1959
+ var dAB = (radii[0] + radii[1]) * 0.6;
1960
+ var yTop = cy - Rmax * 0.42;
1961
+ centers = [
1962
+ [cx - dAB / 2, yTop], [cx + dAB / 2, yTop],
1963
+ [cx, yTop + ((radii[0] + radii[2]) * 0.6 + (radii[1] + radii[2]) * 0.6) / 2 * 0.82]
1964
+ ];
1965
+ } else centers = [];
1966
+
1967
+ items.forEach(function (it, i) {
1968
+ var color = self._color(it.di);
1969
+ var c = svgEl("circle", {
1970
+ cx: centers[i][0], cy: centers[i][1], r: entrance ? 0 : radii[i],
1971
+ fill: color, "fill-opacity": .38, stroke: color, "stroke-width": 2,
1972
+ "class": "ra-hit", "data-i": i
1973
+ }, self.gMarks);
1974
+ c.style.transition = "fill-opacity .18s";
1975
+ var mx = 0, my = 0;
1976
+ centers.forEach(function (p) { mx += p[0] / centers.length; my += p[1] / centers.length; });
1977
+ var dx = centers[i][0] - mx, dy = centers[i][1] - my;
1978
+ var dl = Math.sqrt(dx * dx + dy * dy) || 1;
1979
+ var lx = n === 1 ? centers[i][0] : centers[i][0] + dx / dl * radii[i] * 0.45;
1980
+ var ly = n === 1 ? centers[i][1] : centers[i][1] + dy / dl * radii[i] * 0.45;
1981
+ var t1 = svgText(self.gMarks, lx, ly, it.label, "middle", 12.5, T.text, 700);
1982
+ var t2 = svgText(self.gMarks, lx, ly + 15, fmt(it.v), "middle", 11, T.body, 600);
1983
+ if (entrance) { t1.setAttribute("opacity", 0); t2.setAttribute("opacity", 0); }
1984
+ self.nodes.circles.push({ c: c, r: radii[i], t1: t1, t2: t2, it: it });
1985
+ });
1986
+
1987
+ this._hitHandler = function (node, x, y) {
1988
+ var i = +node.getAttribute("data-i");
1989
+ self.nodes.circles.forEach(function (c2, j) { c2.c.setAttribute("fill-opacity", j === i ? .58 : .16); });
1990
+ var c3 = self.nodes.circles[i];
1991
+ self._tipShow(self._dot(self._color(c3.it.di)) + c3.it.label + ": <b>" + fmt(c3.it.v) + "</b>", x, y - 10);
1992
+ self._emit("raChartSelect", { index: c3.it.di, category: c3.it.label, value: c3.it.v });
1993
+ };
1994
+ this._leaveHandler = function () {
1995
+ self.nodes.circles.forEach(function (c2) { c2.c.setAttribute("fill-opacity", .38); });
1996
+ };
1997
+
1998
+ if (o.legend && all.length > 1) {
1999
+ this._buildLegend(
2000
+ all.map(function (it) { return { name: it.label, color: self._color(it.di), value: it.v }; }),
2001
+ function () { self.build(true); },
2002
+ function (i) {
2003
+ self.nodes.circles.forEach(function (c2) {
2004
+ c2.c.setAttribute("fill-opacity", (i >= 0 && c2.it.di !== i) ? .16 : .38);
2005
+ });
2006
+ });
2007
+ } else this.legendEl.style.display = "none";
2008
+
2009
+ if (entrance) {
2010
+ var dur = o.animation.duration * 0.7, delay = 130;
2011
+ var totalMs = dur + delay * n;
2012
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2013
+ self.nodes.circles.forEach(function (c2, i) {
2014
+ var p = EASE.outCubic(clamp01((raw * totalMs - i * delay) / dur));
2015
+ c2.c.setAttribute("r", c2.r * p);
2016
+ c2.t1.setAttribute("opacity", p);
2017
+ c2.t2.setAttribute("opacity", p * .9);
2018
+ });
2019
+ });
2020
+ }
2021
+ };
2022
+
2023
+ /* ============================================================
2024
+ Candlestick
2025
+ ============================================================ */
2026
+
2027
+ RaChart.prototype._buildCandle = function (entrance) {
2028
+ var self = this, o = this.opts, T = this.theme;
2029
+ var F = merge({ open: "open", high: "high", low: "low", close: "close" }, o.fields || {});
2030
+ var vw = this._zoomWindow((o.data || []).length);
2031
+ var data = (o.data || []).slice(vw[0], vw[1] + 1);
2032
+ this.viewData = data;
2033
+ var cats = data.map(function (d) { return String(d[o.category]); });
2034
+ var n = cats.length;
2035
+ this.cur = { kind: "candle" };
2036
+ this.legendEl.style.display = "none";
2037
+
2038
+ var lo = Infinity, hi = -Infinity;
2039
+ var rows = data.map(function (d) {
2040
+ var r = { o: +d[F.open] || 0, h: +d[F.high] || 0, l: +d[F.low] || 0, c: +d[F.close] || 0 };
2041
+ if (r.l < lo) lo = r.l;
2042
+ if (r.h > hi) hi = r.h;
2043
+ return r;
2044
+ });
2045
+ if (!isFinite(lo)) { lo = 0; hi = 1; }
2046
+ if (hi === lo) hi = lo + 1;
2047
+ var padV = (hi - lo) * 0.08;
2048
+ var aMin = lo - padV, aMax = hi + padV;
2049
+ var step = niceScale(aMax, aMin).step;
2050
+
2051
+ var yLabels = [];
2052
+ for (var v = Math.ceil(aMin / step) * step; v <= aMax + 1e-9; v += step) yLabels.push(fmt(v));
2053
+ var padL = Math.max.apply(null, yLabels.map(function (s) { return textW(s); }).concat([12])) + 12;
2054
+ var g = { plotX: padL, plotY: 8, plotW: Math.max(40, this.w - padL - 8), plotH: Math.max(40, this.h - 42) };
2055
+ g.band = g.plotW / Math.max(1, n);
2056
+ this.geom = g;
2057
+
2058
+ this.gGrid = svgEl("g", null, this.svg);
2059
+ this.gMarks = svgEl("g", null, this.svg);
2060
+ function y(val) { return g.plotY + g.plotH - (val - aMin) / (aMax - aMin) * g.plotH; }
2061
+
2062
+ for (var v2 = Math.ceil(aMin / step) * step; v2 <= aMax + 1e-9; v2 += step) {
2063
+ var pos = y(v2);
2064
+ svgEl("line", { x1: g.plotX, x2: g.plotX + g.plotW, y1: pos, y2: pos, stroke: T.grid, "stroke-width": 1 }, this.gGrid);
2065
+ svgText(this.gGrid, g.plotX - 7, pos + 4, fmt(v2), "end", 11, T.label);
2066
+ }
2067
+ var skip = Math.max(1, Math.ceil(42 / g.band));
2068
+ for (var i = 0; i < n; i++) {
2069
+ if (i % skip) continue;
2070
+ svgText(this.gGrid, g.plotX + g.band * (i + .5), g.plotY + g.plotH + 16, cats[i], "middle", 11, T.label);
2071
+ }
2072
+
2073
+ this.nodes = { candles: [] };
2074
+ rows.forEach(function (r, i2) {
2075
+ var up = r.c >= r.o;
2076
+ var color = up ? o.upColor : o.downColor;
2077
+ var ccx = g.plotX + g.band * (i2 + .5);
2078
+ var gC = svgEl("g", { "class": "ra-hit", "data-i": i2 }, self.gMarks);
2079
+ gC.style.transition = "opacity .18s";
2080
+ var wick = svgEl("line", { x1: ccx, x2: ccx, stroke: color, "stroke-width": 1.5 }, gC);
2081
+ var bw = Math.max(3, Math.min(18, g.band * 0.55));
2082
+ var body = svgEl("rect", { x: ccx - bw / 2, width: bw, fill: color, rx: 1.5 }, gC);
2083
+ // wide invisible hit target so a fingertip can pick a thin candle
2084
+ svgEl("rect", {
2085
+ x: g.plotX + g.band * i2, y: g.plotY, width: g.band, height: g.plotH, fill: "transparent"
2086
+ }, gC);
2087
+ self.nodes.candles.push({ g: gC, wick: wick, body: body, r: r, up: up });
2088
+ });
2089
+
2090
+ function drawCandle(c, e) {
2091
+ var mid = (c.r.h + c.r.l) / 2;
2092
+ function iy(val) { return y(mid + (val - mid) * e); }
2093
+ c.wick.setAttribute("y1", iy(c.r.h));
2094
+ c.wick.setAttribute("y2", iy(c.r.l));
2095
+ var top = iy(Math.max(c.r.o, c.r.c)), bot = iy(Math.min(c.r.o, c.r.c));
2096
+ c.body.setAttribute("y", top);
2097
+ c.body.setAttribute("height", Math.max(1, bot - top));
2098
+ }
2099
+
2100
+ this._hitHandler = function (node, x, y2) {
2101
+ var i2 = +node.getAttribute("data-i");
2102
+ var c = self.nodes.candles[i2];
2103
+ self.nodes.candles.forEach(function (cc, j) { cc.g.style.opacity = j === i2 ? "" : .45; });
2104
+ self._tipShow(
2105
+ '<span class="ra-chart__tip-cat">' + cats[i2] + "</span>" +
2106
+ self._dot(c.up ? o.upColor : o.downColor) +
2107
+ "O <b>" + fmt(c.r.o) + "</b> H <b>" + fmt(c.r.h) + "</b><br>" +
2108
+ "L <b>" + fmt(c.r.l) + "</b> C <b>" + fmt(c.r.c) + "</b>", x, y2 - 10);
2109
+ self._emit("raChartSelect", { index: i2, category: cats[i2], ohlc: c.r, row: data[i2] });
2110
+ };
2111
+ this._leaveHandler = function () {
2112
+ self.nodes.candles.forEach(function (cc) { cc.g.style.opacity = ""; });
2113
+ };
2114
+
2115
+ if (entrance) {
2116
+ var dur = o.animation.duration * 0.45, delay = 26;
2117
+ var totalMs = dur + delay * n;
2118
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2119
+ self.nodes.candles.forEach(function (c, i2) {
2120
+ drawCandle(c, EASE.outCubic(clamp01((raw * totalMs - i2 * delay) / dur)));
2121
+ });
2122
+ });
2123
+ } else {
2124
+ this.nodes.candles.forEach(function (c) { drawCandle(c, 1); });
2125
+ }
2126
+ };
2127
+
2128
+ /* ============================================================
2129
+ Timeline
2130
+ ============================================================ */
2131
+
2132
+ RaChart.prototype._buildTimeline = function (entrance) {
2133
+ var self = this, o = this.opts, T = this.theme;
2134
+ var field = o.series.length ? o.series[0].field : "date";
2135
+ var items = (o.data || []).map(function (d) {
2136
+ var p = parseVal(d[field]);
2137
+ return { label: String(d[o.category]), n: p.n, isDate: p.isDate };
2138
+ });
2139
+ var isDate = items.some(function (it) { return it.isDate; });
2140
+
2141
+ this.cur = { kind: "timeline" };
2142
+ this.gMarks = svgEl("g", null, this.svg);
2143
+ this.nodes = { events: [] };
2144
+ this.legendEl.style.display = "none";
2145
+ if (!items.length) return;
2146
+
2147
+ var lo = Infinity, hi = -Infinity;
2148
+ items.forEach(function (it) {
2149
+ if (it.n < lo) lo = it.n;
2150
+ if (it.n > hi) hi = it.n;
2151
+ });
2152
+ if (hi === lo) hi = lo + 1;
2153
+ var g = { plotX: 34, plotW: this.w - 68, cy: this.h / 2 };
2154
+ function x(nv) { return g.plotX + (nv - lo) / (hi - lo) * g.plotW; }
2155
+ function fmtV(nv) { return isDate ? fmtDateShort(nv) : fmt(nv); }
2156
+
2157
+ var axis = svgEl("line", {
2158
+ x1: g.plotX, y1: g.cy, x2: entrance ? g.plotX : g.plotX + g.plotW, y2: g.cy,
2159
+ stroke: T.baseline, "stroke-width": 2, "stroke-linecap": "round"
2160
+ }, this.gMarks);
2161
+
2162
+ items.forEach(function (it, i) {
2163
+ var ex = x(it.n);
2164
+ var up = i % 2 === 0;
2165
+ var stem = 36 + (Math.floor(i / 2) % 2 ? 26 : 0);
2166
+ var ey = g.cy + (up ? -stem : stem);
2167
+ var gE = svgEl("g", { "class": "ra-hit", "data-i": i, opacity: entrance ? 0 : 1 }, self.gMarks);
2168
+ svgEl("line", { x1: ex, y1: g.cy, x2: ex, y2: ey, stroke: T.grid, "stroke-width": 1.5 }, gE);
2169
+ var dot = svgEl("circle", {
2170
+ cx: ex, cy: g.cy, r: COARSE ? 7 : 6, fill: self._color(i), stroke: T.surface, "stroke-width": 2
2171
+ }, gE);
2172
+ svgText(gE, ex, ey + (up ? -16 : 19), it.label, "middle", 12, T.text, 700);
2173
+ svgText(gE, ex, ey + (up ? -3 : 32), fmtV(it.n), "middle", 10.5, T.muted, 600);
2174
+ self.nodes.events.push({ g: gE, dot: dot, it: it });
2175
+ });
2176
+
2177
+ this._hitHandler = function (node, x2, y2) {
2178
+ var i = +node.getAttribute("data-i");
2179
+ self.nodes.events.forEach(function (ev, j) {
2180
+ ev.g.style.opacity = j === i ? "" : .35;
2181
+ ev.dot.setAttribute("r", j === i ? (COARSE ? 9 : 8) : (COARSE ? 7 : 6));
2182
+ });
2183
+ var ev2 = self.nodes.events[i];
2184
+ self._tipShow(ev2.it.label + ' <span class="ra-chart__tip-muted">— ' + fmtV(ev2.it.n) + "</span>", x2, y2 - 14);
2185
+ self._emit("raChartSelect", { index: i, category: ev2.it.label, value: ev2.it.n, row: o.data[i] });
2186
+ };
2187
+ this._leaveHandler = function () {
2188
+ self.nodes.events.forEach(function (ev) {
2189
+ ev.g.style.opacity = "";
2190
+ ev.dot.setAttribute("r", COARSE ? 7 : 6);
2191
+ });
2192
+ };
2193
+
2194
+ function finish() {
2195
+ self.nodes.events.forEach(function (ev) { ev.g.style.transition = "opacity .18s"; });
2196
+ }
2197
+ if (entrance) {
2198
+ var totalMs = 460 + items.length * 80 + 320;
2199
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2200
+ axis.setAttribute("x2", g.plotX + g.plotW * EASE.outCubic(clamp01(raw * totalMs / 460)));
2201
+ self.nodes.events.forEach(function (ev, i) {
2202
+ ev.g.setAttribute("opacity", EASE.outCubic(clamp01((raw * totalMs - 300 - i * 80) / 320)));
2203
+ });
2204
+ }, finish);
2205
+ } else finish();
2206
+ };
2207
+
2208
+ /* ============================================================
2209
+ Gantt
2210
+ ============================================================ */
2211
+
2212
+ RaChart.prototype._buildGantt = function (entrance) {
2213
+ var self = this, o = this.opts, T = this.theme;
2214
+ var F = merge({ start: "start", end: "end", progress: "progress" }, o.fields || {});
2215
+ var tasks = (o.data || []).map(function (d) {
2216
+ var s = parseVal(d[F.start]), e = parseVal(d[F.end]);
2217
+ return {
2218
+ label: String(d[o.category]), s: s.n, e: e.n,
2219
+ isDate: s.isDate || e.isDate,
2220
+ prog: d[F.progress] != null ? +d[F.progress] : null
2221
+ };
2222
+ });
2223
+
2224
+ this.cur = { kind: "gantt" };
2225
+ this.gGrid = svgEl("g", null, this.svg);
2226
+ this.gMarks = svgEl("g", null, this.svg);
2227
+ this.nodes = { bars: [] };
2228
+ this.legendEl.style.display = "none";
2229
+ if (!tasks.length) return;
2230
+
2231
+ var isDate = tasks.some(function (t) { return t.isDate; });
2232
+ var lo = Infinity, hi = -Infinity;
2233
+ tasks.forEach(function (t) {
2234
+ if (t.s < lo) lo = t.s;
2235
+ if (t.e > hi) hi = t.e;
2236
+ });
2237
+ if (hi === lo) hi = lo + 1;
2238
+ var padL = Math.min(this.w * 0.36,
2239
+ Math.max.apply(null, tasks.map(function (t) { return textW(t.label); }).concat([12])) + 12);
2240
+ var g = { plotX: padL, plotY: 8, plotW: Math.max(40, this.w - padL - 10), plotH: Math.max(40, this.h - 36) };
2241
+ g.band = g.plotH / tasks.length;
2242
+ function x(nv) { return g.plotX + (nv - lo) / (hi - lo) * g.plotW; }
2243
+ function fmtV(nv) { return isDate ? fmtDateShort(nv) : fmt(nv); }
2244
+
2245
+ var ticks = this.w < 400 ? 2 : 4;
2246
+ for (var k = 0; k <= ticks; k++) {
2247
+ var tv = lo + (hi - lo) * k / ticks;
2248
+ var tx = x(tv);
2249
+ svgEl("line", { x1: tx, x2: tx, y1: g.plotY, y2: g.plotY + g.plotH, stroke: T.grid, "stroke-width": 1 }, this.gGrid);
2250
+ svgText(this.gGrid, tx, g.plotY + g.plotH + 16, fmtV(tv), "middle", 10.5, T.label);
2251
+ }
2252
+ tasks.forEach(function (t, i) {
2253
+ svgText(self.gGrid, g.plotX - 7, g.plotY + g.band * (i + .5) + 4, t.label, "end", 11, T.body, 600);
2254
+ });
2255
+
2256
+ tasks.forEach(function (t, i) {
2257
+ var color = self._color(i);
2258
+ var by = g.plotY + g.band * i + g.band * 0.22;
2259
+ var bh = g.band * 0.56;
2260
+ var rx2 = Math.min(5, bh / 2);
2261
+ var gB = svgEl("g", { "class": "ra-hit", "data-i": i }, self.gMarks);
2262
+ gB.style.transition = "opacity .18s, transform .18s";
2263
+ var bar = svgEl("rect", {
2264
+ x: x(t.s), y: by, height: bh, width: 0, rx: rx2,
2265
+ fill: color, "fill-opacity": t.prog != null ? .35 : 1
2266
+ }, gB);
2267
+ var progBar = t.prog != null
2268
+ ? svgEl("rect", { x: x(t.s), y: by, height: bh, width: 0, rx: rx2, fill: color }, gB)
2269
+ : null;
2270
+ self.nodes.bars.push({ g: gB, bar: bar, prog: progBar, t: t });
2271
+ });
2272
+
2273
+ function drawBar(b, e) {
2274
+ var w2 = Math.max(0, (x(b.t.e) - x(b.t.s)) * e);
2275
+ b.bar.setAttribute("width", w2);
2276
+ if (b.prog) b.prog.setAttribute("width", w2 * clamp01((b.t.prog || 0) / 100));
2277
+ }
2278
+
2279
+ this._hitHandler = function (node, x2, y2) {
2280
+ var i = +node.getAttribute("data-i");
2281
+ self.nodes.bars.forEach(function (b, j) { b.g.style.opacity = j === i ? "" : .4; });
2282
+ node.style.transform = "translate(0,-2px)";
2283
+ var b2 = self.nodes.bars[i];
2284
+ self._tipShow(b2.t.label + ": <b>" + fmtV(b2.t.s) + " &ndash; " + fmtV(b2.t.e) + "</b>" +
2285
+ (b2.t.prog != null ? ' <span class="ra-chart__tip-muted">(' + b2.t.prog + "%)</span>" : ""), x2, y2 - 10);
2286
+ self._emit("raChartSelect", {
2287
+ index: i, category: b2.t.label, start: b2.t.s, end: b2.t.e,
2288
+ progress: b2.t.prog, row: o.data[i]
2289
+ });
2290
+ };
2291
+ this._leaveHandler = function () {
2292
+ self.nodes.bars.forEach(function (b) { b.g.style.opacity = ""; b.g.style.transform = ""; });
2293
+ };
2294
+
2295
+ if (entrance) {
2296
+ var dur = o.animation.duration * 0.6, delay = 62;
2297
+ var totalMs = dur + delay * tasks.length;
2298
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2299
+ self.nodes.bars.forEach(function (b, i) {
2300
+ drawBar(b, EASE.outCubic(clamp01((raw * totalMs - i * delay) / dur)));
2301
+ });
2302
+ });
2303
+ } else {
2304
+ this.nodes.bars.forEach(function (b) { drawBar(b, 1); });
2305
+ }
2306
+ };
2307
+
2308
+ /* ============================================================
2309
+ Scatter / Bubble
2310
+ ============================================================ */
2311
+
2312
+ RaChart.prototype._buildScatter = function (entrance) {
2313
+ var self = this, o = this.opts, T = this.theme;
2314
+ var isBubble = o.type === "bubble";
2315
+ var xf = o.xField || "x", sf = o.sizeField || "size";
2316
+ var D = o.data || [];
2317
+ var nS = o.series.length;
2318
+ this.cur = { kind: "scatter" };
2319
+
2320
+ var xLo = Infinity, xHi = -Infinity, yLo = Infinity, yHi = -Infinity, sMax = 0;
2321
+ D.forEach(function (d) {
2322
+ var xv = +d[xf] || 0;
2323
+ if (xv < xLo) xLo = xv;
2324
+ if (xv > xHi) xHi = xv;
2325
+ o.series.forEach(function (s) {
2326
+ var yv = +d[s.field] || 0;
2327
+ if (yv < yLo) yLo = yv;
2328
+ if (yv > yHi) yHi = yv;
2329
+ });
2330
+ var sv = +d[sf] || 0;
2331
+ if (sv > sMax) sMax = sv;
2332
+ });
2333
+ if (!isFinite(xLo)) { xLo = 0; xHi = 1; }
2334
+ if (xHi === xLo) xHi = xLo + 1;
2335
+ if (!isFinite(yLo)) { yLo = 0; yHi = 1; }
2336
+ if (yHi === yLo) yHi = yLo + 1;
2337
+ xLo -= (xHi - xLo) * 0.06; xHi += (xHi - xLo) * 0.06;
2338
+ yLo -= (yHi - yLo) * 0.1; yHi += (yHi - yLo) * 0.1;
2339
+ var xStep = niceScale(xHi, xLo).step, yStep = niceScale(yHi, yLo).step;
2340
+
2341
+ var yLabels = [];
2342
+ for (var v = Math.ceil(yLo / yStep) * yStep; v <= yHi + 1e-9; v += yStep) yLabels.push(fmt(v));
2343
+ var padL = Math.max.apply(null, yLabels.map(function (s) { return textW(s); }).concat([12])) + 12;
2344
+ var g = { plotX: padL, plotY: 8, plotW: Math.max(40, this.w - padL - 10), plotH: Math.max(40, this.h - 42) };
2345
+ function fx(val) { return g.plotX + (val - xLo) / (xHi - xLo) * g.plotW; }
2346
+ function fy(val) { return g.plotY + g.plotH - (val - yLo) / (yHi - yLo) * g.plotH; }
2347
+
2348
+ this.gGrid = svgEl("g", null, this.svg);
2349
+ this.gMarks = svgEl("g", null, this.svg);
2350
+
2351
+ for (var v2 = Math.ceil(yLo / yStep) * yStep; v2 <= yHi + 1e-9; v2 += yStep) {
2352
+ svgEl("line", { x1: g.plotX, x2: g.plotX + g.plotW, y1: fy(v2), y2: fy(v2), stroke: T.grid, "stroke-width": 1 }, this.gGrid);
2353
+ svgText(this.gGrid, g.plotX - 7, fy(v2) + 4, fmt(v2), "end", 11, T.label);
2354
+ }
2355
+ for (var v3 = Math.ceil(xLo / xStep) * xStep; v3 <= xHi + 1e-9; v3 += xStep) {
2356
+ svgEl("line", { x1: fx(v3), x2: fx(v3), y1: g.plotY, y2: g.plotY + g.plotH, stroke: T.grid, "stroke-width": 1 }, this.gGrid);
2357
+ svgText(this.gGrid, fx(v3), g.plotY + g.plotH + 16, fmt(v3), "middle", 11, T.label);
2358
+ }
2359
+
2360
+ this.nodes = { groups: [], dots: [] };
2361
+ o.series.forEach(function (s, si) {
2362
+ var color = self._color(si);
2363
+ var gS = svgEl("g", { opacity: self.hidden[si] ? 0 : 1 }, self.gMarks);
2364
+ gS.style.transition = "opacity .18s";
2365
+ var dl = [];
2366
+ D.forEach(function (d, ci) {
2367
+ var sv = +d[sf] || 0;
2368
+ var r = isBubble ? 4 + 14 * Math.sqrt(sv / (sMax || 1)) : (COARSE ? 6 : 5);
2369
+ var cxp = fx(+d[xf] || 0), cyp = fy(+d[s.field] || 0);
2370
+ var dot = svgEl("circle", {
2371
+ cx: cxp, cy: cyp, r: entrance ? 0 : r,
2372
+ fill: color, "fill-opacity": isBubble ? .4 : .85,
2373
+ stroke: isBubble ? color : T.surface, "stroke-width": isBubble ? 1.5 : 2
2374
+ }, gS);
2375
+ // finger-sized transparent hit ring over each point
2376
+ svgEl("circle", {
2377
+ cx: cxp, cy: cyp, r: Math.max(r, COARSE ? 16 : 9), fill: "transparent",
2378
+ "class": "ra-hit", "data-s": si, "data-c": ci
2379
+ }, gS);
2380
+ dl.push({ node: dot, r: r, x: +d[xf] || 0, y: +d[s.field] || 0, s: sv });
2381
+ });
2382
+ self.nodes.groups.push(gS);
2383
+ self.nodes.dots.push(dl);
2384
+ });
2385
+
2386
+ this._hitHandler = function (node, x, y) {
2387
+ var si = +node.getAttribute("data-s"), ci = +node.getAttribute("data-c");
2388
+ var dt = self.nodes.dots[si][ci];
2389
+ dt.node.setAttribute("r", dt.r * 1.3);
2390
+ if (nS > 1) self._dimGroups(si);
2391
+ self._tipShow(self._dot(self._color(si)) + o.series[si].name +
2392
+ ' <span class="ra-chart__tip-muted">(' + fmt(dt.x) + ", " + fmt(dt.y) +
2393
+ (isBubble ? ", " + fmt(dt.s) : "") + ")</span>", x, y - 12);
2394
+ self._emit("raChartSelect", {
2395
+ seriesIndex: si, index: ci, series: o.series[si].name,
2396
+ x: dt.x, y: dt.y, size: dt.s, row: D[ci]
2397
+ });
2398
+ };
2399
+ this._leaveHandler = function () {
2400
+ self.nodes.dots.forEach(function (dl) {
2401
+ dl.forEach(function (dt) { dt.node.setAttribute("r", dt.r); });
2402
+ });
2403
+ self._dimGroups(-1);
2404
+ };
2405
+
2406
+ if (o.legend && nS > 1) {
2407
+ this._buildLegend(
2408
+ o.series.map(function (s, i) { return { name: s.name, color: self._color(i) }; }),
2409
+ function () {
2410
+ o.series.forEach(function (s, si) {
2411
+ self.nodes.groups[si].setAttribute("opacity", self.hidden[si] ? 0 : 1);
2412
+ });
2413
+ },
2414
+ function (i) { self._dimGroups(i); });
2415
+ } else this.legendEl.style.display = "none";
2416
+
2417
+ if (entrance) {
2418
+ var dur = 280, delay = 22;
2419
+ var totalMs = dur + delay * D.length;
2420
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2421
+ self.nodes.dots.forEach(function (dl) {
2422
+ dl.forEach(function (dt, ci) {
2423
+ dt.node.setAttribute("r", dt.r * EASE.outCubic(clamp01((raw * totalMs - ci * delay) / dur)));
2424
+ });
2425
+ });
2426
+ });
2427
+ }
2428
+ };
2429
+
2430
+ /* ============================================================
2431
+ Funnel / Pyramid
2432
+ ============================================================ */
2433
+
2434
+ RaChart.prototype._buildFunnel = function (entrance) {
2435
+ var self = this, o = this.opts, T = this.theme;
2436
+ var isPyr = o.type === "pyramid";
2437
+ var field = o.series.length ? o.series[0].field : "value";
2438
+ var all = (o.data || []).map(function (d, i) {
2439
+ return { label: String(d[o.category]), v: +d[field] || 0, di: i };
2440
+ });
2441
+ var items = all.filter(function (it) { return !self.hidden[it.di] && it.v > 0; });
2442
+
2443
+ this.cur = { kind: "funnel" };
2444
+ this.gMarks = svgEl("g", null, this.svg);
2445
+ this.nodes = { slices: [] };
2446
+ if (!items.length) { this.legendEl.style.display = "none"; return; }
2447
+
2448
+ var labelW = Math.min(this.w * 0.42,
2449
+ Math.max.apply(null, items.map(function (it) { return textW(it.label); }).concat([30])) + 54);
2450
+ var fw = Math.max(60, this.w - labelW - 22);
2451
+ var cx = 11 + fw / 2;
2452
+ var top = 6, H = this.h - 12;
2453
+ var total = 0, vmax = 0;
2454
+ items.forEach(function (it) { total += it.v; if (it.v > vmax) vmax = it.v; });
2455
+
2456
+ var widths = [], ys = [top];
2457
+ if (isPyr) {
2458
+ var cum = 0;
2459
+ widths.push(0);
2460
+ items.forEach(function (it) {
2461
+ cum += it.v;
2462
+ ys.push(top + H * cum / total);
2463
+ widths.push(fw * cum / total);
2464
+ });
2465
+ } else {
2466
+ var hS = H / items.length;
2467
+ items.forEach(function (it, i) {
2468
+ widths.push(fw * it.v / vmax);
2469
+ ys.push(top + hS * (i + 1));
2470
+ });
2471
+ widths.push(fw * items[items.length - 1].v / vmax);
2472
+ }
2473
+
2474
+ items.forEach(function (it, i) {
2475
+ var gSl = svgEl("g", { "class": "ra-hit", "data-i": i }, self.gMarks);
2476
+ gSl.style.transition = "opacity .18s, transform .18s";
2477
+ var path = svgEl("path", { fill: self._color(it.di), stroke: T.surface, "stroke-width": 2 }, gSl);
2478
+ var ty = (ys[i] + ys[i + 1]) / 2;
2479
+ var pct = isPyr ? it.v / total * 100 : it.v / items[0].v * 100;
2480
+ var t1 = svgText(gSl, 11 + fw + 12, ty, it.label, "start", 12, T.text, 700);
2481
+ var t2 = svgText(gSl, 11 + fw + 12, ty + 14, fmt(it.v) + " (" + pct.toFixed(0) + "%)", "start", 10.5, T.muted, 600);
2482
+ self.nodes.slices.push({
2483
+ g: gSl, path: path, w0: widths[i], w1: widths[i + 1],
2484
+ y0: ys[i], y1: ys[i + 1], it: it, t1: t1, t2: t2
2485
+ });
2486
+ });
2487
+
2488
+ function draw(s, e) {
2489
+ var w0 = s.w0 * e, w1 = s.w1 * e;
2490
+ s.path.setAttribute("d",
2491
+ "M" + (cx - w0 / 2).toFixed(2) + " " + s.y0.toFixed(2) +
2492
+ " L" + (cx + w0 / 2).toFixed(2) + " " + s.y0.toFixed(2) +
2493
+ " L" + (cx + w1 / 2).toFixed(2) + " " + s.y1.toFixed(2) +
2494
+ " L" + (cx - w1 / 2).toFixed(2) + " " + s.y1.toFixed(2) + " Z");
2495
+ s.t1.setAttribute("opacity", e);
2496
+ s.t2.setAttribute("opacity", e * .9);
2497
+ }
2498
+
2499
+ this._hitHandler = function (node, x, y) {
2500
+ var i = +node.getAttribute("data-i");
2501
+ node.style.transform = "translate(6px,0)";
2502
+ self.nodes.slices.forEach(function (s, j) { s.g.style.opacity = j === i ? "" : .45; });
2503
+ var s2 = self.nodes.slices[i];
2504
+ self._tipShow(self._dot(self._color(s2.it.di)) + s2.it.label + ": <b>" + fmt(s2.it.v) + "</b>", x, y - 10);
2505
+ self._emit("raChartSelect", { index: s2.it.di, category: s2.it.label, value: s2.it.v });
2506
+ };
2507
+ this._leaveHandler = function () {
2508
+ self.nodes.slices.forEach(function (s) { s.g.style.opacity = ""; s.g.style.transform = ""; });
2509
+ };
2510
+
2511
+ if (o.legend && all.length > 1) {
2512
+ this._buildLegend(
2513
+ all.map(function (it) { return { name: it.label, color: self._color(it.di), value: it.v }; }),
2514
+ function () { self.build(true); },
2515
+ function (i) {
2516
+ self.nodes.slices.forEach(function (s) { s.g.style.opacity = (i >= 0 && s.it.di !== i) ? .45 : ""; });
2517
+ });
2518
+ } else this.legendEl.style.display = "none";
2519
+
2520
+ if (entrance) {
2521
+ var dur = o.animation.duration * 0.5, delay = 80;
2522
+ var totalMs = dur + delay * items.length;
2523
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2524
+ self.nodes.slices.forEach(function (s, i) {
2525
+ draw(s, EASE.outCubic(clamp01((raw * totalMs - i * delay) / dur)));
2526
+ });
2527
+ });
2528
+ } else {
2529
+ this.nodes.slices.forEach(function (s) { draw(s, 1); });
2530
+ }
2531
+ };
2532
+
2533
+ /* ============================================================
2534
+ Gauge
2535
+ ============================================================ */
2536
+
2537
+ RaChart.prototype._buildGauge = function (entrance) {
2538
+ var self = this, o = this.opts, T = this.theme;
2539
+ var field = o.series.length ? o.series[0].field : "value";
2540
+ var val = (o.data && o.data.length) ? (+o.data[0][field] || 0) : 0;
2541
+ var min = o.min || 0;
2542
+ var max = o.max != null ? o.max : 100;
2543
+ if (max <= min) max = min + 1;
2544
+ var frac = clamp01((val - min) / (max - min));
2545
+
2546
+ this.cur = { kind: "gauge" };
2547
+ this.gMarks = svgEl("g", null, this.svg);
2548
+ this.nodes = {};
2549
+ this.legendEl.style.display = "none";
2550
+
2551
+ var cx = this.w / 2;
2552
+ var R = Math.max(40, Math.min(this.w / 2 - 34, this.h - 62));
2553
+ var cy = (this.h + R) / 2 - 4;
2554
+ var LW = Math.max(9, R * 0.14);
2555
+ var A0 = Math.PI;
2556
+
2557
+ function pt(a, r) { return (cx + r * Math.cos(a)).toFixed(2) + " " + (cy + r * Math.sin(a)).toFixed(2); }
2558
+ function arc(a0, a1, r) { return "M" + pt(a0, r) + " A" + r + " " + r + " 0 0 1 " + pt(a1, r); }
2559
+
2560
+ svgEl("path", {
2561
+ d: arc(A0, Math.PI * 2, R), fill: "none",
2562
+ stroke: T.dark ? "#2c2c2a" : "#eceff3", "stroke-width": LW, "stroke-linecap": "round"
2563
+ }, this.gMarks);
2564
+
2565
+ (o.bands || []).forEach(function (b) {
2566
+ var f0 = clamp01(((b.from != null ? b.from : min) - min) / (max - min));
2567
+ var f1 = clamp01(((b.to != null ? b.to : max) - min) / (max - min));
2568
+ if (f1 <= f0) return;
2569
+ svgEl("path", {
2570
+ d: arc(A0 + f0 * Math.PI, A0 + f1 * Math.PI, R + LW / 2 + 6),
2571
+ fill: "none", stroke: b.color || T.grid, "stroke-width": 4
2572
+ }, self.gMarks);
2573
+ });
2574
+
2575
+ var vColor = this._color(0);
2576
+ (o.bands || []).forEach(function (b) {
2577
+ var bf = b.from != null ? b.from : min, bt = b.to != null ? b.to : max;
2578
+ if (val >= bf && val <= bt && b.color) vColor = b.color;
2579
+ });
2580
+
2581
+ var varc = svgEl("path", { fill: "none", stroke: vColor, "stroke-width": LW, "stroke-linecap": "round" }, this.gMarks);
2582
+ var needle = svgEl("line", { stroke: T.text, "stroke-width": 3, "stroke-linecap": "round" }, this.gMarks);
2583
+ svgEl("circle", { cx: cx, cy: cy, r: 6, fill: T.text }, this.gMarks);
2584
+ svgText(this.gMarks, cx - R, cy + 18, fmt(min), "middle", 11, T.label);
2585
+ svgText(this.gMarks, cx + R, cy + 18, fmt(max), "middle", 11, T.label);
2586
+ var valText = svgText(this.gMarks, cx, cy - R * 0.3, "", "middle", 28, T.text, 800);
2587
+ if (o.series.length && o.series[0].name) {
2588
+ svgText(this.gMarks, cx, cy - R * 0.3 + 20, o.series[0].name, "middle", 11.5, T.muted, 600);
2589
+ }
2590
+
2591
+ function draw(fr) {
2592
+ var a = A0 + Math.max(0.001, fr * Math.PI);
2593
+ varc.setAttribute("d", arc(A0, a, R));
2594
+ var nr = R - LW / 2 - 10;
2595
+ needle.setAttribute("x1", cx + 14 * Math.cos(a + Math.PI));
2596
+ needle.setAttribute("y1", cy + 14 * Math.sin(a + Math.PI));
2597
+ needle.setAttribute("x2", cx + nr * Math.cos(a));
2598
+ needle.setAttribute("y2", cy + nr * Math.sin(a));
2599
+ setText(valText, fmt(min + (max - min) * fr));
2600
+ }
2601
+ if (entrance) this._tween(o.animation.duration + 180, EASE.outCubic, function (e) { draw(frac * e); });
2602
+ else draw(frac);
2603
+ };
2604
+
2605
+ /* ============================================================
2606
+ Heatmap
2607
+ ============================================================ */
2608
+
2609
+ RaChart.prototype._buildHeat = function (entrance) {
2610
+ var self = this, o = this.opts, T = this.theme;
2611
+ var rows = o.data || [];
2612
+ var cols = o.series || [];
2613
+ var catL = rows.map(function (d) { return String(d[o.category]); });
2614
+
2615
+ this.cur = { kind: "heat" };
2616
+ this.gMarks = svgEl("g", null, this.svg);
2617
+ this.nodes = { cells: [] };
2618
+ this.legendEl.style.display = "none";
2619
+ if (!rows.length || !cols.length) return;
2620
+
2621
+ var vmin = Infinity, vmax = -Infinity;
2622
+ rows.forEach(function (d) {
2623
+ cols.forEach(function (s) {
2624
+ var v = +d[s.field] || 0;
2625
+ if (v < vmin) vmin = v;
2626
+ if (v > vmax) vmax = v;
2627
+ });
2628
+ });
2629
+ if (!isFinite(vmin)) { vmin = 0; vmax = 1; }
2630
+ if (vmax === vmin) vmax = vmin + 1;
2631
+
2632
+ var padL = Math.min(this.w * 0.28,
2633
+ Math.max.apply(null, catL.map(function (s) { return textW(s); }).concat([12])) + 10);
2634
+ var g = { plotX: padL, plotY: 4, plotW: Math.max(40, this.w - padL - 4), plotH: Math.max(40, this.h - 28) };
2635
+ var cw = g.plotW / cols.length, ch = g.plotH / rows.length;
2636
+
2637
+ rows.forEach(function (d, ri) {
2638
+ svgText(self.gMarks, g.plotX - 6, g.plotY + ch * (ri + .5) + 4, catL[ri], "end", 11, T.label);
2639
+ });
2640
+ cols.forEach(function (s, ci) {
2641
+ svgText(self.gMarks, g.plotX + cw * (ci + .5), g.plotY + g.plotH + 16, s.name, "middle", 10.5, T.label);
2642
+ });
2643
+
2644
+ rows.forEach(function (d, ri) {
2645
+ cols.forEach(function (s, ci) {
2646
+ var v = +d[s.field] || 0;
2647
+ var t = (v - vmin) / (vmax - vmin);
2648
+ var rect = svgEl("rect", {
2649
+ x: g.plotX + cw * ci + 1, y: g.plotY + ch * ri + 1,
2650
+ width: Math.max(0, cw - 2), height: Math.max(0, ch - 2),
2651
+ rx: 3, fill: rampColor(self.seq, t), "class": "ra-hit",
2652
+ "data-r": ri, "data-c": ci, opacity: entrance ? 0 : 1
2653
+ }, self.gMarks);
2654
+ var txt = null;
2655
+ if (cw > 38 && ch > 20) {
2656
+ var lightOnDark = self.dark ? t < .45 : t > .55;
2657
+ txt = svgText(self.gMarks, g.plotX + cw * (ci + .5), g.plotY + ch * (ri + .5) + 4,
2658
+ fmt(v), "middle", 10.5, lightOnDark ? "#ffffff" : "#1f2937", 600);
2659
+ if (entrance) txt.setAttribute("opacity", 0);
2660
+ txt.style.pointerEvents = "none";
2661
+ }
2662
+ self.nodes.cells.push({ rect: rect, txt: txt, v: v, ri: ri, ci: ci });
2663
+ });
2664
+ });
2665
+
2666
+ this._hitHandler = function (node, x, y) {
2667
+ var ri = +node.getAttribute("data-r"), ci = +node.getAttribute("data-c");
2668
+ self.nodes.cells.forEach(function (c) { c.rect.removeAttribute("stroke"); });
2669
+ node.setAttribute("stroke", T.text);
2670
+ node.setAttribute("stroke-width", 1.5);
2671
+ var cell = self.nodes.cells[ri * cols.length + ci];
2672
+ self._tipShow(catL[ri] + " &times; " + cols[ci].name + ": <b>" + fmt(cell.v) + "</b>", x, y - 10);
2673
+ self._emit("raChartSelect", {
2674
+ row: ri, col: ci, category: catL[ri], series: cols[ci].name, value: cell.v
2675
+ });
2676
+ };
2677
+ this._leaveHandler = function () {
2678
+ self.nodes.cells.forEach(function (c) { c.rect.removeAttribute("stroke"); });
2679
+ };
2680
+
2681
+ if (entrance) {
2682
+ var delay = 22, dur = 240;
2683
+ var totalMs = dur + delay * (rows.length + cols.length);
2684
+ this._tween(totalMs, EASE.linear, function (e, raw) {
2685
+ self.nodes.cells.forEach(function (c) {
2686
+ var p = EASE.outCubic(clamp01((raw * totalMs - (c.ri + c.ci) * delay) / dur));
2687
+ c.rect.setAttribute("opacity", p);
2688
+ if (c.txt) c.txt.setAttribute("opacity", p);
2689
+ });
2690
+ });
2691
+ }
2692
+ };
2693
+
2694
+ /* ============================================================
2695
+ Public API
2696
+ ============================================================ */
2697
+
2698
+ RaChart.prototype.setData = function (data) {
2699
+ this.opts.data = data || [];
2700
+ this._lastW = null;
2701
+ if (this.zoomable) { this.build(true); return this; }
2702
+
2703
+ if (this.kind === "xy" && this.cur && this.cur.kind === "xy" && data.length === this.geom.nC) {
2704
+ this.viewData = data;
2705
+ this.geom.cats = data.map(function (d) { return String(d[this.opts.category]); }, this);
2706
+ this._xyDrawGrid(this._xyScale());
2707
+ this._xyTransition();
2708
+ } else if (this.kind === "pie" && this.cur && this.cur.kind === "pie" && data.length === this.geom.vals.length) {
2709
+ var field = this.geom.field;
2710
+ this.geom.vals = data.map(function (d) { return +d[field] || 0; });
2711
+ this._pieTransition();
2712
+ } else if (this.kind === "radar" && this.cur && this.cur.kind === "radar" && data.length === this.geom.nC) {
2713
+ this._radarTransition();
2714
+ } else {
2715
+ this.build(true);
2716
+ }
2717
+ return this;
2718
+ };
2719
+
2720
+ RaChart.prototype.setType = function (type) {
2721
+ this.opts.type = type;
2722
+ this.hidden = {};
2723
+ this.zoomF = [0, 1];
2724
+ this._lastW = null;
2725
+ this.build(true);
2726
+ return this;
2727
+ };
2728
+
2729
+ RaChart.prototype.update = function (partial) {
2730
+ var o = this.opts;
2731
+ partial = partial || {};
2732
+ if (partial.colors !== undefined) o.colors = partial.colors;
2733
+ if (partial.series) o.series = partial.series;
2734
+ if (partial.data) o.data = partial.data;
2735
+ for (var k in partial) {
2736
+ if (!Object.prototype.hasOwnProperty.call(partial, k)) continue;
2737
+ if (k === "colors" || k === "series" || k === "data") continue;
2738
+ if (k === "animation") merge(o.animation, partial[k]);
2739
+ else o[k] = partial[k];
2740
+ }
2741
+ this.hidden = {};
2742
+ this._lastW = null;
2743
+ this.build(true);
2744
+ return this;
2745
+ };
2746
+
2747
+ RaChart.prototype.replay = function () {
2748
+ this.build(true);
2749
+ return this;
2750
+ };
2751
+
2752
+ RaChart.prototype.resize = function () {
2753
+ this._rebuildBody();
2754
+ return this;
2755
+ };
2756
+
2757
+ /** Zoom to a fraction window (0..1) or, with integers, a data index range. */
2758
+ RaChart.prototype.zoomTo = function (from, to) {
2759
+ if (!this.zoomable) return this;
2760
+ var n = (this.opts.data || []).length || 1;
2761
+ if (from > 1 || to > 1) { from = from / n; to = to / n; } // index form
2762
+ this.zoomF = [clamp01(Math.min(from, to)), clamp01(Math.max(from, to))];
2763
+ if (this.zoomF[1] - this.zoomF[0] < 0.05) this.zoomF[1] = Math.min(1, this.zoomF[0] + 0.05);
2764
+ this._syncZoomBar();
2765
+ this._zoomApply(true);
2766
+ return this;
2767
+ };
2768
+
2769
+ RaChart.prototype.resetZoom = function () {
2770
+ if (!this.zoomable) return this;
2771
+ this.zoomF = [0, 1];
2772
+ this._syncZoomBar();
2773
+ this._zoomApply(true);
2774
+ return this;
2775
+ };
2776
+
2777
+ RaChart.prototype.getZoom = function () {
2778
+ var n = (this.opts.data || []).length;
2779
+ var w = this._zoomWindow(n);
2780
+ return { from: this.zoomF[0], to: this.zoomF[1], startIndex: w[0], endIndex: w[1] };
2781
+ };
2782
+
2783
+ RaChart.prototype.toggleSeries = function (index, hidden) {
2784
+ this.hidden[index] = hidden === undefined ? !this.hidden[index] : !!hidden;
2785
+ this.build(false);
2786
+ return this;
2787
+ };
2788
+
2789
+ /** Serialise the current chart to an SVG string (for share/export). */
2790
+ RaChart.prototype.toSVG = function () {
2791
+ if (!this.svg) return "";
2792
+ var clone = this.svg.cloneNode(true);
2793
+ clone.setAttribute("xmlns", NS);
2794
+ clone.setAttribute("width", this.w);
2795
+ clone.setAttribute("height", this.h);
2796
+ var bg = document.createElementNS(NS, "rect");
2797
+ bg.setAttribute("width", this.w);
2798
+ bg.setAttribute("height", this.h);
2799
+ bg.setAttribute("fill", this.theme.surface);
2800
+ clone.insertBefore(bg, clone.firstChild);
2801
+ return new XMLSerializer().serializeToString(clone);
2802
+ };
2803
+
2804
+ /* ---------- statics ---------- */
2805
+
2806
+ RaChart.version = VERSION;
2807
+ RaChart.defaults = DEFAULTS;
2808
+ RaChart.palette = PALETTE_LIGHT;
2809
+ RaChart.paletteDark = PALETTE_DARK;
2810
+ RaChart.types = Object.keys(KINDS);
2811
+ RaChart.create = function (host, options) { return new RaChart(host, options); };
2812
+
2813
+ /** Optional jQuery bridge — only if jQuery happens to be present. */
2814
+ if (typeof window !== "undefined" && window.jQuery) {
2815
+ (function ($) {
2816
+ $.fn.raChart = function (options) {
2817
+ if (typeof options === "string") {
2818
+ var args = Array.prototype.slice.call(arguments, 1);
2819
+ var out = this;
2820
+ this.each(function () {
2821
+ var inst = $(this).data("raChart");
2822
+ if (!inst) return;
2823
+ if (options === "instance") { out = inst; return; }
2824
+ if (typeof inst[options] === "function") inst[options].apply(inst, args);
2825
+ });
2826
+ return out;
2827
+ }
2828
+ return this.each(function () {
2829
+ var prev = $(this).data("raChart");
2830
+ if (prev) prev.destroy();
2831
+ $(this).data("raChart", new RaChart(this, options));
2832
+ });
2833
+ };
2834
+ })(window.jQuery);
2835
+ }
2836
+
2837
+ return RaChart;
2838
+ }));