traderead-charts 1.3.3 → 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 +29 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/traderead-charts.js +76 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
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
|
+
|
|
3
32
|
## 1.3.3 — 2026-09-29
|
|
4
33
|
|
|
5
34
|
- **The view can be dragged past the last bar by half the plot**, not by ten
|
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");
|
|
@@ -763,6 +781,14 @@
|
|
|
763
781
|
// on a phone at 2 px a bar: the latest candles stayed pinned to the price
|
|
764
782
|
// axis and could not be brought to the middle to zoom on them (owner's
|
|
765
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
|
+
};
|
|
766
792
|
Chart.prototype._maxRight = function () {
|
|
767
793
|
return this.bars.length - 1 + Math.max(this.opt.rightPadBars * 2, (this._plotW() * 0.5) / (this.barSpacing || 1));
|
|
768
794
|
};
|
|
@@ -1235,11 +1261,18 @@
|
|
|
1235
1261
|
};
|
|
1236
1262
|
|
|
1237
1263
|
|
|
1238
|
-
|
|
1239
|
-
|
|
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; }) : [];
|
|
1240
1274
|
this._paint();
|
|
1241
1275
|
};
|
|
1242
|
-
Chart.prototype.clearPriceLines = function () { this.priceLines = []; this._paint(); };
|
|
1243
1276
|
Chart.prototype.onCrosshair = function (cb) { this.crosshairCb = cb; };
|
|
1244
1277
|
// cb({firstVisible, lastVisible, barsLeft}) after every pan/zoom; fires at
|
|
1245
1278
|
// most once per paint. barsLeft small = time to prepend history.
|
|
@@ -1268,11 +1301,16 @@
|
|
|
1268
1301
|
this.el.style.cursor = t ? "crosshair" : "";
|
|
1269
1302
|
this._touchLock();
|
|
1270
1303
|
};
|
|
1271
|
-
//
|
|
1272
|
-
//
|
|
1273
|
-
//
|
|
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.
|
|
1274
1312
|
Chart.prototype._touchLock = function () {
|
|
1275
|
-
this.overlay.style.touchAction =
|
|
1313
|
+
this.overlay.style.touchAction = "none";
|
|
1276
1314
|
};
|
|
1277
1315
|
Chart.prototype.onToolDone = function (cb) { this._toolDoneCb = cb; };
|
|
1278
1316
|
Chart.prototype.clearDrawings = function () { this.drawings = []; this._draft = null; this._selected = null; this._persist(); this._paintDrawings(); };
|
|
@@ -1839,16 +1877,37 @@
|
|
|
1839
1877
|
for (i = 0; i < tags.length; i++) {
|
|
1840
1878
|
var t = tags[i];
|
|
1841
1879
|
var ty = pp.toY(t.price);
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
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
|
+
}
|
|
1846
1892
|
var txt = fmtPrice(t.price, dec);
|
|
1847
1893
|
c.fillStyle = t._last ? t.color : o.tagBg;
|
|
1848
1894
|
c.fillRect(W, ty - 9, o.priceAxisWidth, 18);
|
|
1849
1895
|
c.fillStyle = t._last ? "#0d1117" : o.tagText;
|
|
1850
1896
|
c.textBaseline = "middle";
|
|
1851
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
|
+
}
|
|
1852
1911
|
}
|
|
1853
1912
|
|
|
1854
1913
|
// markers
|
|
@@ -1968,7 +2027,7 @@
|
|
|
1968
2027
|
if (self._axisDrag) {
|
|
1969
2028
|
var ad = self._axisDrag;
|
|
1970
2029
|
if (ad.axis === "price") self._vZoom = clamp(ad.z0 * Math.exp((self._cross.y - ad.y0) * 0.004), 0.15, 8);
|
|
1971
|
-
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);
|
|
1972
2031
|
self._paint();
|
|
1973
2032
|
return;
|
|
1974
2033
|
}
|
|
@@ -2166,7 +2225,7 @@
|
|
|
2166
2225
|
}
|
|
2167
2226
|
var anchor = self.xToIndex(x);
|
|
2168
2227
|
var k = Math.exp(-e.deltaY * 0.0015);
|
|
2169
|
-
self.barSpacing = clamp(self.barSpacing * k, self.
|
|
2228
|
+
self.barSpacing = clamp(self.barSpacing * k, self._minSpacing(), self.opt.maxBarSpacing);
|
|
2170
2229
|
self.rightIndex = anchor - (self.xToIndex(x) - self.rightIndex);
|
|
2171
2230
|
self._paint();
|
|
2172
2231
|
}, { passive: false });
|
|
@@ -2231,7 +2290,7 @@
|
|
|
2231
2290
|
var aax = e.touches[0].clientX - r.left, aay = e.touches[0].clientY - r.top;
|
|
2232
2291
|
var ad2 = self._axisDrag;
|
|
2233
2292
|
if (ad2.axis === "price") self._vZoom = clamp(ad2.z0 * Math.exp((aay - ad2.y0) * 0.004), 0.15, 8);
|
|
2234
|
-
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);
|
|
2235
2294
|
self._paint();
|
|
2236
2295
|
e.preventDefault();
|
|
2237
2296
|
return;
|
|
@@ -2269,7 +2328,7 @@
|
|
|
2269
2328
|
var g0 = e.touches[0], g1 = e.touches[1];
|
|
2270
2329
|
var pd = Math.hypot(g0.clientX - g1.clientX, g0.clientY - g1.clientY) || 1;
|
|
2271
2330
|
var pmx = (g0.clientX + g1.clientX) / 2 - r.left;
|
|
2272
|
-
self.barSpacing = clamp(pinch.spacing0 * pd / pinch.d0, self.
|
|
2331
|
+
self.barSpacing = clamp(pinch.spacing0 * pd / pinch.d0, self._minSpacing(), self.opt.maxBarSpacing);
|
|
2273
2332
|
// the bar that was under the fingers stays under them (and follows
|
|
2274
2333
|
// them: two fingers also pan)
|
|
2275
2334
|
self.rightIndex = clamp(pinch.anchor - (self.xToIndex(pmx) - self.rightIndex), 0, self._maxRight());
|
|
@@ -2365,7 +2424,7 @@
|
|
|
2365
2424
|
}
|
|
2366
2425
|
|
|
2367
2426
|
global.TRCharts = {
|
|
2368
|
-
version: "1.
|
|
2427
|
+
version: "1.3.5",
|
|
2369
2428
|
themes: THEMES,
|
|
2370
2429
|
resample: resample,
|
|
2371
2430
|
createChart: function (el, options) { return new Chart(el, options); },
|