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.
- package/README.md +543 -0
- package/ionic/index.ts +16 -0
- package/ionic/ra-chart.component.html +15 -0
- package/ionic/ra-chart.component.ts +254 -0
- package/ionic/ra-chart.loader.ts +63 -0
- package/ionic/ra-chart.module.ts +23 -0
- package/ionic/ra-chart.types.ts +136 -0
- package/package.json +54 -0
- package/plugin.xml +45 -0
- package/types/index.d.ts +218 -0
- package/www/ra-chart.css +324 -0
- package/www/ra-chart.js +2838 -0
package/www/ra-chart.js
ADDED
|
@@ -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 + " → " + 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) + " – " + 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] + " × " + 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
|
+
}));
|