traderead-charts 1.3.2 → 1.3.5
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/CHANGELOG.md +44 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/traderead-charts.js +105 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.5 — 2026-10-04
|
|
4
|
+
|
|
5
|
+
- **Price lines can carry a trade plan.** `addPriceLine(price, color, label,
|
|
6
|
+
opts)` now writes the label on the line (the price stays on the axis), and
|
|
7
|
+
takes `opts.fit` and `opts.group`:
|
|
8
|
+
- `fit: true` widens the price scale to keep the line on screen, up to 0.6×
|
|
9
|
+
the visible bars' own range — a far second target used to squeeze the
|
|
10
|
+
candles into half the pane, so beyond that it is left out;
|
|
11
|
+
- a labelled line still outside the frame is pinned to the pane's edge with
|
|
12
|
+
an arrow, so a plan never silently loses a level;
|
|
13
|
+
- `group: "name"` lets `clearPriceLines("name")` remove just that set;
|
|
14
|
+
`clearPriceLines()` with no argument still clears every line.
|
|
15
|
+
- Unlabelled lines behave exactly as before.
|
|
16
|
+
|
|
17
|
+
## 1.3.4 — 2026-09-30
|
|
18
|
+
|
|
19
|
+
- **A pinch on a phone zooms the chart, not the page.** The plot now always
|
|
20
|
+
carries `touch-action: none`. 1.3.3 cancelled the pinch in `touchmove`,
|
|
21
|
+
but the browser decides to zoom the page at `touchstart`, before that
|
|
22
|
+
handler can stop it — on phones the whole page still grew. Nothing else
|
|
23
|
+
changes: every one-finger move on the plot was already the chart's, so the
|
|
24
|
+
page never scrolled from there.
|
|
25
|
+
- **The version inside the file matches the package again**: the banner read
|
|
26
|
+
1.3.0 and `TRCharts.version` 1.0.0 through 1.3.3.
|
|
27
|
+
- **Zoom out reaches as far on a phone as on a laptop.** The narrowest bar was
|
|
28
|
+
1.5 px, so a 310-px phone plot held 200 bars while a laptop held 700; the
|
|
29
|
+
floor is now whatever fits 750 bars in the plot, when that is smaller than
|
|
30
|
+
1.5 px (never under 0.25 px). Laptops are practically unchanged.
|
|
31
|
+
|
|
32
|
+
## 1.3.3 — 2026-09-29
|
|
33
|
+
|
|
34
|
+
- **The view can be dragged past the last bar by half the plot**, not by ten
|
|
35
|
+
bars. On a phone at 2 px a bar ten bars were 20 px: the latest candles
|
|
36
|
+
stayed pinned to the price axis and could not be brought to the middle to
|
|
37
|
+
zoom on them. The limit is now in pixels; wide bars keep the old ten.
|
|
38
|
+
- **Pinch zooms around the fingers**, and the bar under them stays under
|
|
39
|
+
them. It used to zoom from the right edge only.
|
|
40
|
+
- **Pinch measures the real distance between the fingers.** A pinch made
|
|
41
|
+
with one finger above the other has almost no horizontal spread; measuring
|
|
42
|
+
only that made the zoom jump.
|
|
43
|
+
- **A pinch on the chart never zooms the page instead.** iOS ignores
|
|
44
|
+
`user-scalable=no`: the touch handler now claims the gesture, and Safari's
|
|
45
|
+
own `gesturestart` / `gesturechange` are cancelled on the chart.
|
|
46
|
+
|
|
3
47
|
## 1.3.2 — 2026-09-20
|
|
4
48
|
|
|
5
49
|
- **README install example unpinned from 1.2.0.** The copy-paste snippet on
|
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ npm install traderead-charts
|
|
|
58
58
|
or straight from a CDN, no build step:
|
|
59
59
|
|
|
60
60
|
```html
|
|
61
|
-
<script src="https://cdn.jsdelivr.net/npm/traderead-charts@1.3.
|
|
61
|
+
<script src="https://cdn.jsdelivr.net/npm/traderead-charts@1.3.3/src/traderead-charts.js"></script>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
## Quick start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "traderead-charts",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Complete financial charting widget — candlesticks, indicators with a pro-style panel, drawing tools, symbol search, themes. Batteries included, zero dependencies, in production on traderead.ai.",
|
|
5
5
|
"main": "src/traderead-charts.js",
|
|
6
6
|
"files": [
|
package/src/traderead-charts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* TradeRead Charts v1.3.
|
|
2
|
+
* TradeRead Charts v1.3.5
|
|
3
3
|
* Copyright (c) 2026 Marcel Todea / TradeRead — traderead.ai
|
|
4
4
|
* Original work, written from first principles.
|
|
5
5
|
* TradeRead Community License 1.0 (see LICENSE.md): free to use, including
|
|
@@ -125,6 +125,21 @@
|
|
|
125
125
|
if (v < min) min = v;
|
|
126
126
|
if (v > max) max = v;
|
|
127
127
|
}
|
|
128
|
+
// Price lines added with { fit: true } — a trade plan's entry, stop and
|
|
129
|
+
// targets — widen the price scale so the plan is on screen. One sitting
|
|
130
|
+
// more than 0.6x the bars' own range away is left out: a far Target 2
|
|
131
|
+
// squeezed the candles into half the pane (4 Oct 2026). It is pinned to
|
|
132
|
+
// the pane's edge instead (see the price-line paint).
|
|
133
|
+
if (this.kind === "price" && isFinite(min) && isFinite(max)) {
|
|
134
|
+
var span = max - min, pls = this.chart.priceLines, fmin = min, fmax = max;
|
|
135
|
+
for (i = 0; i < pls.length; i++) {
|
|
136
|
+
v = pls[i].price;
|
|
137
|
+
if (!pls[i].fit || !isNum(v) || v < min - 0.6 * span || v > max + 0.6 * span) continue;
|
|
138
|
+
if (v < fmin) fmin = v;
|
|
139
|
+
if (v > fmax) fmax = v;
|
|
140
|
+
}
|
|
141
|
+
min = fmin; max = fmax;
|
|
142
|
+
}
|
|
128
143
|
if (!isFinite(min) || !isFinite(max)) { min = 0; max = 1; }
|
|
129
144
|
if (min === max) { min -= 0.5; max += 0.5; }
|
|
130
145
|
var pad = (max - min) * this.chart.opt.autoScalePadPct;
|
|
@@ -178,6 +193,9 @@
|
|
|
178
193
|
c.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;display:block;";
|
|
179
194
|
container.appendChild(c);
|
|
180
195
|
});
|
|
196
|
+
// from the first touch: the browser reads touch-action when the finger
|
|
197
|
+
// lands, so it must be in place before any gesture (see _touchLock)
|
|
198
|
+
this.overlay.style.touchAction = "none";
|
|
181
199
|
this.ctx = this.canvas.getContext("2d");
|
|
182
200
|
this.dctx = this.drawCanvas.getContext("2d");
|
|
183
201
|
this.octx = this.overlay.getContext("2d");
|
|
@@ -758,6 +776,22 @@
|
|
|
758
776
|
Chart.prototype.xToIndex = function (x) {
|
|
759
777
|
return this.rightIndex + this.opt.rightPadBars - (this._plotW() - x) / this.barSpacing;
|
|
760
778
|
};
|
|
779
|
+
// How far past the last bar the view may be dragged: the pad, or half the
|
|
780
|
+
// plot, whichever is more — in pixels, not bars. Ten bars of slack was 20 px
|
|
781
|
+
// on a phone at 2 px a bar: the latest candles stayed pinned to the price
|
|
782
|
+
// axis and could not be brought to the middle to zoom on them (owner's
|
|
783
|
+
// find, 29 Sep 2026).
|
|
784
|
+
// Zoomed all the way out, a phone shows as many bars as a laptop: the floor
|
|
785
|
+
// is the option (1.5 px a bar) or whatever fits 750 bars in the plot,
|
|
786
|
+
// whichever is smaller — a 310-px phone plot held 200 bars at 1.5 px
|
|
787
|
+
// (owner's find, 30 Sep 2026: "pe mobil pot să fac un zoom out limitat").
|
|
788
|
+
// Never under 0.25 px; each bar is still drawn at least a pixel wide.
|
|
789
|
+
Chart.prototype._minSpacing = function () {
|
|
790
|
+
return Math.max(0.25, Math.min(this.opt.minBarSpacing, this._plotW() / 750));
|
|
791
|
+
};
|
|
792
|
+
Chart.prototype._maxRight = function () {
|
|
793
|
+
return this.bars.length - 1 + Math.max(this.opt.rightPadBars * 2, (this._plotW() * 0.5) / (this.barSpacing || 1));
|
|
794
|
+
};
|
|
761
795
|
Chart.prototype._visibleRange = function () {
|
|
762
796
|
var lo = Math.floor(this.xToIndex(0)), hi = Math.ceil(this.xToIndex(this._plotW()));
|
|
763
797
|
return [clamp(lo, 0, this.bars.length - 1), clamp(hi, 0, this.bars.length - 1)];
|
|
@@ -1227,11 +1261,18 @@
|
|
|
1227
1261
|
};
|
|
1228
1262
|
|
|
1229
1263
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1264
|
+
// opts.fit: widen the price scale to keep the line in view (see the pane
|
|
1265
|
+
// scale). opts.group: a name, so clearPriceLines(name) removes only that
|
|
1266
|
+
// set. The label is written on the line; the price stays on the axis.
|
|
1267
|
+
Chart.prototype.addPriceLine = function (price, color, label, opts) {
|
|
1268
|
+
opts = opts || {};
|
|
1269
|
+
this.priceLines.push({ price: price, color: color || "#8b949e", label: label, fit: !!opts.fit, group: opts.group || null });
|
|
1270
|
+
this._paint();
|
|
1271
|
+
};
|
|
1272
|
+
Chart.prototype.clearPriceLines = function (group) {
|
|
1273
|
+
this.priceLines = group ? this.priceLines.filter(function (l) { return l.group !== group; }) : [];
|
|
1232
1274
|
this._paint();
|
|
1233
1275
|
};
|
|
1234
|
-
Chart.prototype.clearPriceLines = function () { this.priceLines = []; this._paint(); };
|
|
1235
1276
|
Chart.prototype.onCrosshair = function (cb) { this.crosshairCb = cb; };
|
|
1236
1277
|
// cb({firstVisible, lastVisible, barsLeft}) after every pan/zoom; fires at
|
|
1237
1278
|
// most once per paint. barsLeft small = time to prepend history.
|
|
@@ -1260,11 +1301,16 @@
|
|
|
1260
1301
|
this.el.style.cursor = t ? "crosshair" : "";
|
|
1261
1302
|
this._touchLock();
|
|
1262
1303
|
};
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1304
|
+
// The plot owns every touch, always. Without touch-action none the browser
|
|
1305
|
+
// steals the gesture: mid-draw it fires a cancel and kills the line (paid
|
|
1306
|
+
// for once in production), and a pinch zoomed the whole PAGE instead of
|
|
1307
|
+
// the chart on phones (owner's find, 30 Sep 2026) — the browser decides
|
|
1308
|
+
// that at touchstart, before any preventDefault in touchmove can stop it.
|
|
1309
|
+
// Nothing is lost: every one-finger move on the plot was already the
|
|
1310
|
+
// chart's (touchmove prevents the default), so the page never scrolled
|
|
1311
|
+
// from here anyway.
|
|
1266
1312
|
Chart.prototype._touchLock = function () {
|
|
1267
|
-
this.overlay.style.touchAction =
|
|
1313
|
+
this.overlay.style.touchAction = "none";
|
|
1268
1314
|
};
|
|
1269
1315
|
Chart.prototype.onToolDone = function (cb) { this._toolDoneCb = cb; };
|
|
1270
1316
|
Chart.prototype.clearDrawings = function () { this.drawings = []; this._draft = null; this._selected = null; this._persist(); this._paintDrawings(); };
|
|
@@ -1831,16 +1877,37 @@
|
|
|
1831
1877
|
for (i = 0; i < tags.length; i++) {
|
|
1832
1878
|
var t = tags[i];
|
|
1833
1879
|
var ty = pp.toY(t.price);
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1880
|
+
// A labelled level outside the frame (a far target) is pinned to the
|
|
1881
|
+
// pane's edge with an arrow, so the plan never silently loses a level;
|
|
1882
|
+
// an unlabelled line outside the frame stays hidden, as before.
|
|
1883
|
+
var offAbove = ty < pp.y0, offBelow = ty > pp.y0 + pp.h;
|
|
1884
|
+
if ((offAbove || offBelow) && !(t.label && !t._last)) continue;
|
|
1885
|
+
if (offAbove) ty = pp.y0 + 10;
|
|
1886
|
+
else if (offBelow) ty = pp.y0 + pp.h - 10;
|
|
1887
|
+
if (!offAbove && !offBelow) {
|
|
1888
|
+
c.strokeStyle = t.color; c.lineWidth = 1; c.setLineDash([4, 3]);
|
|
1889
|
+
c.beginPath(); c.moveTo(0, Math.round(ty) + 0.5); c.lineTo(W, Math.round(ty) + 0.5); c.stroke();
|
|
1890
|
+
c.setLineDash([]);
|
|
1891
|
+
}
|
|
1838
1892
|
var txt = fmtPrice(t.price, dec);
|
|
1839
1893
|
c.fillStyle = t._last ? t.color : o.tagBg;
|
|
1840
1894
|
c.fillRect(W, ty - 9, o.priceAxisWidth, 18);
|
|
1841
1895
|
c.fillStyle = t._last ? "#0d1117" : o.tagText;
|
|
1842
1896
|
c.textBaseline = "middle";
|
|
1843
1897
|
c.fillText(txt, W + 5, ty);
|
|
1898
|
+
// the line says what it is ("TP1", "SL"), just above it at the right edge
|
|
1899
|
+
if (t.label && !t._last) {
|
|
1900
|
+
c.save();
|
|
1901
|
+
c.font = "700 10.5px -apple-system, 'Segoe UI', sans-serif";
|
|
1902
|
+
var lbl = offAbove ? "\u25B2 " + t.label : offBelow ? "\u25BC " + t.label : t.label;
|
|
1903
|
+
var lw = c.measureText(lbl).width;
|
|
1904
|
+
c.globalAlpha = 0.85; c.fillStyle = o.tagBg;
|
|
1905
|
+
c.fillRect(W - lw - 14, ty - 16, lw + 10, 14);
|
|
1906
|
+
c.globalAlpha = 1; c.fillStyle = t.color;
|
|
1907
|
+
c.textAlign = "right"; c.textBaseline = "middle";
|
|
1908
|
+
c.fillText(lbl, W - 9, offAbove ? ty + 1 : ty - 9);
|
|
1909
|
+
c.restore();
|
|
1910
|
+
}
|
|
1844
1911
|
}
|
|
1845
1912
|
|
|
1846
1913
|
// markers
|
|
@@ -1938,7 +2005,7 @@
|
|
|
1938
2005
|
function step(now) {
|
|
1939
2006
|
var dt = now - last; last = now;
|
|
1940
2007
|
self.rightIndex = clamp(self.rightIndex - glide.v * dt / self.barSpacing,
|
|
1941
|
-
0, self.
|
|
2008
|
+
0, self._maxRight());
|
|
1942
2009
|
self._paint();
|
|
1943
2010
|
glide.v *= Math.pow(0.94, dt / 16.7);
|
|
1944
2011
|
if (Math.abs(glide.v) >= 0.05) glide.raf = requestAnimationFrame(step);
|
|
@@ -1960,7 +2027,7 @@
|
|
|
1960
2027
|
if (self._axisDrag) {
|
|
1961
2028
|
var ad = self._axisDrag;
|
|
1962
2029
|
if (ad.axis === "price") self._vZoom = clamp(ad.z0 * Math.exp((self._cross.y - ad.y0) * 0.004), 0.15, 8);
|
|
1963
|
-
else self.barSpacing = clamp(ad.s0 * Math.exp((self._cross.x - ad.x0) * 0.004), self.
|
|
2030
|
+
else self.barSpacing = clamp(ad.s0 * Math.exp((self._cross.x - ad.x0) * 0.004), self._minSpacing(), self.opt.maxBarSpacing);
|
|
1964
2031
|
self._paint();
|
|
1965
2032
|
return;
|
|
1966
2033
|
}
|
|
@@ -2003,7 +2070,7 @@
|
|
|
2003
2070
|
}
|
|
2004
2071
|
glide.lastX = nowX; glide.lastT = nowT;
|
|
2005
2072
|
self.rightIndex = drag.right0 + (drag.x0 - nowX) / self.barSpacing;
|
|
2006
|
-
self.rightIndex = clamp(self.rightIndex, 0, self.
|
|
2073
|
+
self.rightIndex = clamp(self.rightIndex, 0, self._maxRight());
|
|
2007
2074
|
if (drag.vpan) {
|
|
2008
2075
|
var us = self.panes[0].h * (1 - self.opt.volumeHeightPct * 0.35) || 1;
|
|
2009
2076
|
self._vOff = drag.off0 + (e.clientY - r.top - drag.y0) / us;
|
|
@@ -2152,13 +2219,13 @@
|
|
|
2152
2219
|
// way every charting tool treats it. Vertical wheel stays zoom.
|
|
2153
2220
|
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
|
|
2154
2221
|
self.rightIndex = clamp(self.rightIndex + e.deltaX / self.barSpacing,
|
|
2155
|
-
0, self.
|
|
2222
|
+
0, self._maxRight());
|
|
2156
2223
|
self._paint();
|
|
2157
2224
|
return;
|
|
2158
2225
|
}
|
|
2159
2226
|
var anchor = self.xToIndex(x);
|
|
2160
2227
|
var k = Math.exp(-e.deltaY * 0.0015);
|
|
2161
|
-
self.barSpacing = clamp(self.barSpacing * k, self.
|
|
2228
|
+
self.barSpacing = clamp(self.barSpacing * k, self._minSpacing(), self.opt.maxBarSpacing);
|
|
2162
2229
|
self.rightIndex = anchor - (self.xToIndex(x) - self.rightIndex);
|
|
2163
2230
|
self._paint();
|
|
2164
2231
|
}, { passive: false });
|
|
@@ -2209,8 +2276,12 @@
|
|
|
2209
2276
|
if (self._draft) { self.drawings.push(self._draft); self._selected = self._draft.id; self._draft = null; self._persist(); self._paintDrawings(); }
|
|
2210
2277
|
self._dragDraw = null; self._sepDrag = null; drag = null;
|
|
2211
2278
|
self._touchLock();
|
|
2212
|
-
|
|
2213
|
-
|
|
2279
|
+
// Zoom around the fingers, on their real distance: a pinch made with
|
|
2280
|
+
// one finger above the other has almost no horizontal spread, and
|
|
2281
|
+
// measuring only that made the zoom jump.
|
|
2282
|
+
var f0 = e.touches[0], f1 = e.touches[1];
|
|
2283
|
+
pinch = { d0: Math.hypot(f0.clientX - f1.clientX, f0.clientY - f1.clientY) || 1, spacing0: self.barSpacing,
|
|
2284
|
+
anchor: self.xToIndex((f0.clientX + f1.clientX) / 2 - r.left) };
|
|
2214
2285
|
}
|
|
2215
2286
|
}, { passive: true });
|
|
2216
2287
|
el.addEventListener("touchmove", function (e) {
|
|
@@ -2219,7 +2290,7 @@
|
|
|
2219
2290
|
var aax = e.touches[0].clientX - r.left, aay = e.touches[0].clientY - r.top;
|
|
2220
2291
|
var ad2 = self._axisDrag;
|
|
2221
2292
|
if (ad2.axis === "price") self._vZoom = clamp(ad2.z0 * Math.exp((aay - ad2.y0) * 0.004), 0.15, 8);
|
|
2222
|
-
else self.barSpacing = clamp(ad2.s0 * Math.exp((aax - ad2.x0) * 0.004), self.
|
|
2293
|
+
else self.barSpacing = clamp(ad2.s0 * Math.exp((aax - ad2.x0) * 0.004), self._minSpacing(), self.opt.maxBarSpacing);
|
|
2223
2294
|
self._paint();
|
|
2224
2295
|
e.preventDefault();
|
|
2225
2296
|
return;
|
|
@@ -2254,9 +2325,16 @@
|
|
|
2254
2325
|
return;
|
|
2255
2326
|
}
|
|
2256
2327
|
if (pinch && e.touches.length === 2) {
|
|
2257
|
-
var
|
|
2258
|
-
|
|
2328
|
+
var g0 = e.touches[0], g1 = e.touches[1];
|
|
2329
|
+
var pd = Math.hypot(g0.clientX - g1.clientX, g0.clientY - g1.clientY) || 1;
|
|
2330
|
+
var pmx = (g0.clientX + g1.clientX) / 2 - r.left;
|
|
2331
|
+
self.barSpacing = clamp(pinch.spacing0 * pd / pinch.d0, self._minSpacing(), self.opt.maxBarSpacing);
|
|
2332
|
+
// the bar that was under the fingers stays under them (and follows
|
|
2333
|
+
// them: two fingers also pan)
|
|
2334
|
+
self.rightIndex = clamp(pinch.anchor - (self.xToIndex(pmx) - self.rightIndex), 0, self._maxRight());
|
|
2259
2335
|
self._paint();
|
|
2336
|
+
// the page must not zoom instead: iOS ignores user-scalable=no
|
|
2337
|
+
e.preventDefault();
|
|
2260
2338
|
} else if (drag && e.touches.length === 1) {
|
|
2261
2339
|
var tNowT = performance.now(), tNowX = e.touches[0].clientX - r.left;
|
|
2262
2340
|
if (glide.lastT) {
|
|
@@ -2265,7 +2343,7 @@
|
|
|
2265
2343
|
}
|
|
2266
2344
|
glide.lastX = tNowX; glide.lastT = tNowT;
|
|
2267
2345
|
self.rightIndex = drag.right0 + (drag.x0 - tNowX) / self.barSpacing;
|
|
2268
|
-
self.rightIndex = clamp(self.rightIndex, 0, self.
|
|
2346
|
+
self.rightIndex = clamp(self.rightIndex, 0, self._maxRight());
|
|
2269
2347
|
if (drag.vpan) {
|
|
2270
2348
|
var us2 = self.panes[0].h * (1 - self.opt.volumeHeightPct * 0.35) || 1;
|
|
2271
2349
|
self._vOff = drag.off0 + (e.touches[0].clientY - r.top - drag.y0) / us2;
|
|
@@ -2291,6 +2369,9 @@
|
|
|
2291
2369
|
self._touchLock();
|
|
2292
2370
|
}
|
|
2293
2371
|
el.addEventListener("touchend", endTouch, { passive: true });
|
|
2372
|
+
// Safari's own pinch events: without this it zooms the whole page.
|
|
2373
|
+
el.addEventListener("gesturestart", function (e) { e.preventDefault(); });
|
|
2374
|
+
el.addEventListener("gesturechange", function (e) { e.preventDefault(); });
|
|
2294
2375
|
// a browser-fired cancel must COMMIT the in-flight shape, not destroy it
|
|
2295
2376
|
el.addEventListener("touchcancel", endTouch, { passive: true });
|
|
2296
2377
|
};
|
|
@@ -2343,7 +2424,7 @@
|
|
|
2343
2424
|
}
|
|
2344
2425
|
|
|
2345
2426
|
global.TRCharts = {
|
|
2346
|
-
version: "1.
|
|
2427
|
+
version: "1.3.5",
|
|
2347
2428
|
themes: THEMES,
|
|
2348
2429
|
resample: resample,
|
|
2349
2430
|
createChart: function (el, options) { return new Chart(el, options); },
|