traderead-charts 1.2.0 → 1.3.1
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 +19 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/traderead-charts.js +61 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.1 — 2026-09-20
|
|
4
|
+
|
|
5
|
+
- **`homepage` now points at traderead.ai**, not at the GitHub Pages docs
|
|
6
|
+
host. No code change — the engine is byte-identical to 1.3.0. The npm
|
|
7
|
+
listing ranks for the brand name and was sending people to GitHub rather
|
|
8
|
+
than to the site the library was built for and runs in production on.
|
|
9
|
+
The documentation stays where it is and is linked from the README and
|
|
10
|
+
from <https://traderead.ai/charts>, which is now the library's own page.
|
|
11
|
+
|
|
12
|
+
## 1.3.0 — 2026-09-19
|
|
13
|
+
|
|
14
|
+
- **Measure tool** (ruler, in the drawing toolbar): drag between two points
|
|
15
|
+
and the box reports the move three ways at once — absolute price change,
|
|
16
|
+
percentage, and the span in bars and in time (`+563.22 (+0.85%)` /
|
|
17
|
+
`39 bars · 1d 14h`). Green up, red down, with an arrow following the drag,
|
|
18
|
+
so the sign reads from the direction you dragged rather than from the
|
|
19
|
+
chart. Selectable, movable and deletable like any other drawing, and it
|
|
20
|
+
works with touch.
|
|
21
|
+
|
|
3
22
|
## 1.2.0 — 2026-09-13
|
|
4
23
|
|
|
5
24
|
- **In-widget timeframe picker** (`timeframes: {list, active, onSelect}`):
|
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.
|
|
61
|
+
<script src="https://cdn.jsdelivr.net/npm/traderead-charts@1.2.0/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
|
+
"version": "1.3.1",
|
|
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": [
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"widget"
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": true,
|
|
31
|
-
"homepage": "https://
|
|
31
|
+
"homepage": "https://traderead.ai",
|
|
32
32
|
"bugs": {
|
|
33
33
|
"url": "https://github.com/marceltodeamt-lang/traderead-charts/issues"
|
|
34
34
|
}
|
package/src/traderead-charts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* TradeRead Charts v1.
|
|
2
|
+
* TradeRead Charts v1.3.0
|
|
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
|
|
@@ -203,6 +203,7 @@
|
|
|
203
203
|
cursor: SVGI('<path d="M4 2 L12 8.5 L8.2 9.3 L6.2 13.5 Z" fill="currentColor" stroke="none"/>'),
|
|
204
204
|
trend: SVGI('<line x1="3" y1="13" x2="13" y2="3"/><circle cx="3" cy="13" r="1.4" fill="currentColor" stroke="none"/><circle cx="13" cy="3" r="1.4" fill="currentColor" stroke="none"/>'),
|
|
205
205
|
hline: SVGI('<line x1="2" y1="8" x2="14" y2="8"/><circle cx="8" cy="8" r="1.4" fill="currentColor" stroke="none"/>'),
|
|
206
|
+
measure: SVGI('<rect x="2.5" y="5.5" width="11" height="5" rx="1"/><line x1="5" y1="5.5" x2="5" y2="8"/><line x1="8" y1="5.5" x2="8" y2="9"/><line x1="11" y1="5.5" x2="11" y2="8"/>'),
|
|
206
207
|
rect: SVGI('<rect x="3" y="4.5" width="10" height="7" rx="1"/>'),
|
|
207
208
|
fib: SVGI('<line x1="2.5" y1="4" x2="13.5" y2="4"/><line x1="2.5" y1="8" x2="13.5" y2="8" opacity="0.55"/><line x1="2.5" y1="12" x2="13.5" y2="12"/>'),
|
|
208
209
|
text: SVGI('<path d="M4 4 H12 M8 4 V12.5" />'),
|
|
@@ -255,7 +256,8 @@
|
|
|
255
256
|
}
|
|
256
257
|
var toolBtns = {};
|
|
257
258
|
[[ICONS.cursor, "Select / move", null], [ICONS.trend, "Trend line", "trend"], [ICONS.hline, "Horizontal line", "hline"],
|
|
258
|
-
[ICONS.rect, "Rectangle", "rect"], [ICONS.fib, "Fibonacci retracement", "fib"], [ICONS.text, "Text", "text"]
|
|
259
|
+
[ICONS.rect, "Rectangle", "rect"], [ICONS.fib, "Fibonacci retracement", "fib"], [ICONS.text, "Text", "text"],
|
|
260
|
+
[ICONS.measure, "Measure \u2014 price move, % and span", "measure"]].forEach(function (t) {
|
|
259
261
|
toolBtns[t[2] || "cursor"] = btn(t[0], t[1], function (b) { self.setTool(t[2]); mark(t[2] ? b : toolBtns.cursor); });
|
|
260
262
|
});
|
|
261
263
|
mark(toolBtns.cursor);
|
|
@@ -1405,6 +1407,61 @@
|
|
|
1405
1407
|
} else if (d.type === "trend") {
|
|
1406
1408
|
c.beginPath(); c.moveTo(x1, y1); c.lineTo(x2, y2); c.stroke();
|
|
1407
1409
|
if (sel) { c.fillRect(x1 - 3, y1 - 3, 6, 6); c.fillRect(x2 - 3, y2 - 3, 6, 6); }
|
|
1410
|
+
} else if (d.type === "measure") {
|
|
1411
|
+
// A ruler answers three questions at once — how far, how far in
|
|
1412
|
+
// percent, and over how long — so all three are on the box. The
|
|
1413
|
+
// percentage is measured from the FIRST point: dragging from the low
|
|
1414
|
+
// to the high is a rise, and the sign follows the drag, not the chart.
|
|
1415
|
+
var mUp = d.p2 >= d.p1;
|
|
1416
|
+
var mCol = mUp ? (this.opt.upColor || "#22c55e") : (this.opt.downColor || "#ef4444");
|
|
1417
|
+
var mx = Math.min(x1, x2), my = Math.min(y1, y2);
|
|
1418
|
+
var mw = Math.abs(x2 - x1), mh = Math.abs(y2 - y1);
|
|
1419
|
+
c.save();
|
|
1420
|
+
c.fillStyle = mUp ? "rgba(34,197,94,0.13)" : "rgba(239,68,68,0.13)";
|
|
1421
|
+
c.fillRect(mx, my, mw, mh);
|
|
1422
|
+
c.strokeStyle = mCol; c.lineWidth = 1;
|
|
1423
|
+
c.strokeRect(Math.round(mx) + 0.5, Math.round(my) + 0.5, Math.round(mw), Math.round(mh));
|
|
1424
|
+
// vertical arrow through the middle, pointing the way the move went
|
|
1425
|
+
var amx = mx + mw / 2;
|
|
1426
|
+
c.beginPath(); c.moveTo(amx, y1); c.lineTo(amx, y2); c.stroke();
|
|
1427
|
+
var ah = y2 >= y1 ? -5 : 5;
|
|
1428
|
+
c.beginPath(); c.moveTo(amx, y2); c.lineTo(amx - 4, y2 + ah); c.lineTo(amx + 4, y2 + ah);
|
|
1429
|
+
c.closePath(); c.fillStyle = mCol; c.fill();
|
|
1430
|
+
|
|
1431
|
+
var dPrice = d.p2 - d.p1;
|
|
1432
|
+
var dPct = d.p1 ? (dPrice / Math.abs(d.p1)) * 100 : 0;
|
|
1433
|
+
var digits = Math.abs(d.p1) >= 1000 ? 2 : (Math.abs(d.p1) >= 1 ? 2 : 5);
|
|
1434
|
+
var nBars = Math.abs(Math.round(this.xToIndex(x2) - this.xToIndex(x1)));
|
|
1435
|
+
var secs = Math.abs((d.t2 || 0) - (d.t1 || 0));
|
|
1436
|
+
var span = "";
|
|
1437
|
+
if (secs >= 86400) span = Math.floor(secs / 86400) + "d " + Math.floor((secs % 86400) / 3600) + "h";
|
|
1438
|
+
else if (secs >= 3600) span = Math.floor(secs / 3600) + "h " + Math.floor((secs % 3600) / 60) + "m";
|
|
1439
|
+
else span = Math.max(1, Math.round(secs / 60)) + "m";
|
|
1440
|
+
var sign = dPrice >= 0 ? "+" : "\u2212";
|
|
1441
|
+
var l1 = sign + fmtPrice(Math.abs(dPrice), digits) + " (" + sign + Math.abs(dPct).toFixed(2) + "%)";
|
|
1442
|
+
var l2 = nBars + (nBars === 1 ? " bar" : " bars") + " \u00b7 " + span;
|
|
1443
|
+
|
|
1444
|
+
c.font = "700 11px -apple-system, 'Segoe UI', sans-serif";
|
|
1445
|
+
var w1 = c.measureText(l1).width;
|
|
1446
|
+
c.font = "600 10px -apple-system, 'Segoe UI', sans-serif";
|
|
1447
|
+
var w2 = c.measureText(l2).width;
|
|
1448
|
+
var bw = Math.max(w1, w2) + 16, bh = 32;
|
|
1449
|
+
// sit above the end point, and flip below when there is no room
|
|
1450
|
+
var bx = clamp(amx - bw / 2, 2, Math.max(2, this._plotW() - bw - 2));
|
|
1451
|
+
var by = (y2 <= y1) ? y2 - bh - 6 : y2 + 6;
|
|
1452
|
+
if (by < pp.y0 + 2) by = y2 + 6;
|
|
1453
|
+
if (by + bh > pp.y0 + pp.h - 2) by = Math.max(pp.y0 + 2, y2 - bh - 6);
|
|
1454
|
+
c.fillStyle = mCol;
|
|
1455
|
+
if (c.roundRect) { c.beginPath(); c.roundRect(bx, by, bw, bh, 5); c.fill(); }
|
|
1456
|
+
else c.fillRect(bx, by, bw, bh);
|
|
1457
|
+
c.fillStyle = "#fff";
|
|
1458
|
+
c.font = "700 11px -apple-system, 'Segoe UI', sans-serif";
|
|
1459
|
+
c.fillText(l1, bx + 8, by + 14);
|
|
1460
|
+
c.font = "600 10px -apple-system, 'Segoe UI', sans-serif";
|
|
1461
|
+
c.globalAlpha = 0.85;
|
|
1462
|
+
c.fillText(l2, bx + 8, by + 26);
|
|
1463
|
+
c.restore();
|
|
1464
|
+
if (sel) { c.fillStyle = mCol; c.fillRect(x1 - 3, y1 - 3, 6, 6); c.fillRect(x2 - 3, y2 - 3, 6, 6); }
|
|
1408
1465
|
} else if (d.type === "rect") {
|
|
1409
1466
|
c.save(); c.globalAlpha = 0.12;
|
|
1410
1467
|
c.fillRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1));
|
|
@@ -1455,7 +1512,7 @@
|
|
|
1455
1512
|
var x1 = isNum(d.t1) ? this.timeToX(d.t1) : null, y1 = isNum(d.p1) ? pp.toY(d.p1) : null;
|
|
1456
1513
|
var x2 = isNum(d.t2) ? this.timeToX(d.t2) : null, y2 = isNum(d.p2) ? pp.toY(d.p2) : null;
|
|
1457
1514
|
if (d.id === this._selected && x2 !== null && Math.abs(x - x2) < 7 && Math.abs(y - y2) < 7 &&
|
|
1458
|
-
(d.type === "rect" || d.type === "fib" || d.type === "trend")) return { d: d, handle: "p2" };
|
|
1515
|
+
(d.type === "rect" || d.type === "fib" || d.type === "trend" || d.type === "measure")) return { d: d, handle: "p2" };
|
|
1459
1516
|
if (d.id === this._selected && d.type === "text") {
|
|
1460
1517
|
var fsz = d.size || 12, tw2 = d._tw || (d.text || "").length * fsz * 0.62;
|
|
1461
1518
|
if (Math.abs(x - (x1 + tw2 + 6)) < 8 && Math.abs(y - y1) < 8) return { d: d, handle: "size" };
|
|
@@ -1463,7 +1520,7 @@
|
|
|
1463
1520
|
if (d.id === this._selected && d.type === "trend" && Math.abs(x - x1) < 7 && Math.abs(y - y1) < 7) return { d: d, handle: "p1" };
|
|
1464
1521
|
if (d.type === "hline") { if (Math.abs(y - y1) < 6) return { d: d, handle: null }; }
|
|
1465
1522
|
else if (d.type === "trend") { if (distToSeg(x, y, { x: x1, y: y1 }, { x: x2, y: y2 }) < 6) return { d: d, handle: null }; }
|
|
1466
|
-
else if (d.type === "rect" || d.type === "fib") {
|
|
1523
|
+
else if (d.type === "rect" || d.type === "fib" || d.type === "measure") {
|
|
1467
1524
|
if (x >= Math.min(x1, x2) - 4 && x <= Math.max(x1, x2) + 4 &&
|
|
1468
1525
|
y >= Math.min(y1, y2) - 4 && y <= Math.max(y1, y2) + 4) return { d: d, handle: null };
|
|
1469
1526
|
} else if (d.type === "text") {
|