@trdrs/quickcharts 4.0.0 → 4.1.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/CHANGELOG.md +16 -0
- package/README.md +49 -20
- package/dist/adapters/rest.d.ts +1 -1
- package/dist/{ar-FXZ5QJUR.js → ar-VILLYPAA.js} +4 -1
- package/dist/ar-VILLYPAA.js.map +1 -0
- package/dist/{assets-Gg-UquDA.d.ts → assets-BF-NDyHl.d.ts} +7 -2
- package/dist/{ca_ES-3WVKFBIF.js → ca_ES-EJXLAXOA.js} +4 -1
- package/dist/ca_ES-EJXLAXOA.js.map +1 -0
- package/dist/{chunk-C4MEVUNK.js → chunk-TBOVLJAK.js} +908 -266
- package/dist/chunk-TBOVLJAK.js.map +1 -0
- package/dist/{de-V72Q3OI7.js → de-PHID4SVN.js} +4 -1
- package/dist/de-PHID4SVN.js.map +1 -0
- package/dist/drawings.d.ts +18 -7
- package/dist/drawings.js +7 -1
- package/dist/{es-CNQ3OJ6E.js → es-KD4FBLOP.js} +4 -1
- package/dist/es-KD4FBLOP.js.map +1 -0
- package/dist/feature-manifest.json +1 -1
- package/dist/{fr-5WQBCGR4.js → fr-KTTX3FF3.js} +4 -1
- package/dist/fr-KTTX3FF3.js.map +1 -0
- package/dist/{he_IL-WXKA77UE.js → he_IL-Z3DXMAKZ.js} +4 -1
- package/dist/he_IL-Z3DXMAKZ.js.map +1 -0
- package/dist/{id_ID-XPBAYSWK.js → id_ID-FFX6X4C5.js} +4 -1
- package/dist/id_ID-FFX6X4C5.js.map +1 -0
- package/dist/index.d.ts +94 -77
- package/dist/index.js +180 -84
- package/dist/index.js.map +1 -1
- package/dist/{it-DR2EBNWX.js → it-X2GFCLGU.js} +4 -1
- package/dist/it-X2GFCLGU.js.map +1 -0
- package/dist/{ja-URB2CPXO.js → ja-TG3WO2KH.js} +4 -1
- package/dist/ja-TG3WO2KH.js.map +1 -0
- package/dist/{ko-OE47NYVP.js → ko-4HHHO7KV.js} +4 -1
- package/dist/ko-4HHHO7KV.js.map +1 -0
- package/dist/{ms_MY-BKCFRK2K.js → ms_MY-HPVQKVPY.js} +4 -1
- package/dist/ms_MY-HPVQKVPY.js.map +1 -0
- package/dist/{pl-3IKQRG3Y.js → pl-2W2HP34O.js} +4 -1
- package/dist/pl-2W2HP34O.js.map +1 -0
- package/dist/{pt-PZHV5EZE.js → pt-XQUBSXO7.js} +4 -1
- package/dist/pt-XQUBSXO7.js.map +1 -0
- package/dist/quickcharts.css +41 -10
- package/dist/{resources-Le6flcEn.d.ts → resources-C0cYryO9.d.ts} +1 -1
- package/dist/{ru-BAL6RQUV.js → ru-XKFQPFDM.js} +4 -1
- package/dist/ru-XKFQPFDM.js.map +1 -0
- package/dist/{sv-Y6GBGDHM.js → sv-ZMEGAPCR.js} +4 -1
- package/dist/sv-ZMEGAPCR.js.map +1 -0
- package/dist/{th-MAMLMZKU.js → th-4735BUT4.js} +4 -1
- package/dist/th-4735BUT4.js.map +1 -0
- package/dist/{tr-3LQHINUZ.js → tr-PG3CW2L3.js} +4 -1
- package/dist/tr-PG3CW2L3.js.map +1 -0
- package/dist/{vi-GR4PSDKP.js → vi-YPRIAJ33.js} +4 -1
- package/dist/vi-YPRIAJ33.js.map +1 -0
- package/dist/{zh-2RJ5VNAI.js → zh-RVHEWNSW.js} +4 -1
- package/dist/zh-RVHEWNSW.js.map +1 -0
- package/dist/{zh_TW-XEBSACHU.js → zh_TW-ZFJMDA64.js} +4 -1
- package/dist/zh_TW-ZFJMDA64.js.map +1 -0
- package/package.json +1 -1
- package/dist/ar-FXZ5QJUR.js.map +0 -1
- package/dist/ca_ES-3WVKFBIF.js.map +0 -1
- package/dist/chunk-C4MEVUNK.js.map +0 -1
- package/dist/de-V72Q3OI7.js.map +0 -1
- package/dist/es-CNQ3OJ6E.js.map +0 -1
- package/dist/fr-5WQBCGR4.js.map +0 -1
- package/dist/he_IL-WXKA77UE.js.map +0 -1
- package/dist/id_ID-XPBAYSWK.js.map +0 -1
- package/dist/it-DR2EBNWX.js.map +0 -1
- package/dist/ja-URB2CPXO.js.map +0 -1
- package/dist/ko-OE47NYVP.js.map +0 -1
- package/dist/ms_MY-BKCFRK2K.js.map +0 -1
- package/dist/pl-3IKQRG3Y.js.map +0 -1
- package/dist/pt-PZHV5EZE.js.map +0 -1
- package/dist/ru-BAL6RQUV.js.map +0 -1
- package/dist/sv-Y6GBGDHM.js.map +0 -1
- package/dist/th-MAMLMZKU.js.map +0 -1
- package/dist/tr-3LQHINUZ.js.map +0 -1
- package/dist/vi-GR4PSDKP.js.map +0 -1
- package/dist/zh-2RJ5VNAI.js.map +0 -1
- package/dist/zh_TW-XEBSACHU.js.map +0 -1
|
@@ -679,16 +679,19 @@ var DrawingPaneView = class {
|
|
|
679
679
|
drawing2.paintConstruction(ctx, viewport);
|
|
680
680
|
}
|
|
681
681
|
const state = drawing2.state;
|
|
682
|
+
const inks = drawing2.inks();
|
|
683
|
+
const handleInks = { ring: inks.handleRing, center: inks.handleCenter };
|
|
682
684
|
if (state === "selected" || state === "editing") {
|
|
685
|
+
if (drawing2.isValid()) drawing2.paintTextHint(ctx, viewport);
|
|
683
686
|
const points = drawing2.getControlPoints(viewport);
|
|
684
|
-
const inks = drawing2.inks();
|
|
685
|
-
const handleInks = { ring: inks.handleRing, center: inks.handleCenter };
|
|
686
687
|
if (points.length > 0) paintHandles(ctx, points, handleInks, drawing2.handleShape(), hoveredIndex(points, drawing2.pointer, HANDLE_REACH));
|
|
687
688
|
const grips = drawing2.resizeHandles(viewport);
|
|
688
689
|
const gripShape = drawing2.gripShape();
|
|
689
690
|
if (grips.length > 0 && gripShape) paintHandles(ctx, grips, handleInks, gripShape, hoveredIndex(grips, drawing2.pointer, GRIP_REACH));
|
|
690
691
|
else if (grips.length > 0) paintResizeGrips(ctx, grips, drawing2.style.lineColor);
|
|
691
|
-
|
|
692
|
+
} else if (drawing2.hovered && drawing2.handlesOnHover() && drawing2.isValid()) {
|
|
693
|
+
const points = drawing2.getControlPoints(viewport);
|
|
694
|
+
if (points.length > 0) paintHandles(ctx, points, handleInks, drawing2.handleShape(), null, "thin");
|
|
692
695
|
}
|
|
693
696
|
} finally {
|
|
694
697
|
ctx.restore();
|
|
@@ -698,6 +701,66 @@ var DrawingPaneView = class {
|
|
|
698
701
|
}
|
|
699
702
|
};
|
|
700
703
|
|
|
704
|
+
// src/internal/drawings/render/axis-view.ts
|
|
705
|
+
var AxisLabel = class {
|
|
706
|
+
_source;
|
|
707
|
+
constructor(source) {
|
|
708
|
+
this._source = source;
|
|
709
|
+
}
|
|
710
|
+
coordinate() {
|
|
711
|
+
return this._source.coordinate() ?? -1e6;
|
|
712
|
+
}
|
|
713
|
+
text() {
|
|
714
|
+
return this._source.text();
|
|
715
|
+
}
|
|
716
|
+
textColor() {
|
|
717
|
+
return this._source.textColor?.() ?? "#ffffff";
|
|
718
|
+
}
|
|
719
|
+
backColor() {
|
|
720
|
+
return this._source.color();
|
|
721
|
+
}
|
|
722
|
+
visible() {
|
|
723
|
+
return this._source.visible() && this._source.coordinate() !== null;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var AxisBandView = class {
|
|
727
|
+
_source;
|
|
728
|
+
_axis;
|
|
729
|
+
constructor(source, axis) {
|
|
730
|
+
this._source = source;
|
|
731
|
+
this._axis = axis;
|
|
732
|
+
}
|
|
733
|
+
zOrder() {
|
|
734
|
+
return "bottom";
|
|
735
|
+
}
|
|
736
|
+
renderer() {
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
draw(target) {
|
|
740
|
+
const span = this._source.span();
|
|
741
|
+
if (!span || span.coordinates.length === 0) return;
|
|
742
|
+
target.useMediaCoordinateSpace(({ context: ctx, mediaSize }) => {
|
|
743
|
+
paintAxisBand(ctx, this._axis, span.coordinates, span.color, mediaSize);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
function paintAxisBand(ctx, axis, coordinates, color, size) {
|
|
748
|
+
const low = Math.min(...coordinates);
|
|
749
|
+
const high = Math.max(...coordinates);
|
|
750
|
+
ctx.save();
|
|
751
|
+
ctx.fillStyle = color;
|
|
752
|
+
if (axis === "price") {
|
|
753
|
+
const top = Math.round(low) + 1;
|
|
754
|
+
const bottom = Math.round(high) - 1;
|
|
755
|
+
if (bottom > top) ctx.fillRect(1, top, size.width, bottom - top);
|
|
756
|
+
else ctx.fillRect(1, Math.round(high) - 1, size.width, 1);
|
|
757
|
+
} else {
|
|
758
|
+
const left = Math.floor(low);
|
|
759
|
+
ctx.fillRect(left, 0, Math.max(1, Math.ceil(high) - left), size.height);
|
|
760
|
+
}
|
|
761
|
+
ctx.restore();
|
|
762
|
+
}
|
|
763
|
+
|
|
701
764
|
// src/i18n/en/drawing.ts
|
|
702
765
|
var drawing = {
|
|
703
766
|
// The toolbar itself, and its tool GROUPS (a button each) with the SECTION headings inside their
|
|
@@ -808,6 +871,8 @@ var drawing = {
|
|
|
808
871
|
"drawing.modifierCommand": "Cmd",
|
|
809
872
|
"drawing.hintClone": "{modifier} + Drag",
|
|
810
873
|
"drawing.hintCopy": "{modifier} + C",
|
|
874
|
+
"drawing.hintRemove": "Del",
|
|
875
|
+
"drawing.drawingMenu": "Drawing menu",
|
|
811
876
|
"drawing.hintPaste": "{modifier} + V",
|
|
812
877
|
"drawing.deleteWithKey": "Delete (Del)",
|
|
813
878
|
"drawing.statusDrawingsHidden": "Drawings hidden",
|
|
@@ -902,6 +967,7 @@ var drawing = {
|
|
|
902
967
|
"drawing.sectionInfo": "Info",
|
|
903
968
|
"drawing.priceRange": "Price range",
|
|
904
969
|
"drawing.barsRange": "Bars range",
|
|
970
|
+
"drawing.distance": "Distance",
|
|
905
971
|
"drawing.angle": "Angle",
|
|
906
972
|
"drawing.statsPosition": "Stats position",
|
|
907
973
|
"drawing.statsHidden": "Hidden",
|
|
@@ -1279,6 +1345,13 @@ function viewportOf(chart, series) {
|
|
|
1279
1345
|
};
|
|
1280
1346
|
}
|
|
1281
1347
|
var UNRESOLVED_PRICE_TEXT = (price) => price.toFixed(2);
|
|
1348
|
+
function plainTime(time) {
|
|
1349
|
+
const t = Number(time);
|
|
1350
|
+
if (!Number.isFinite(t)) return String(time);
|
|
1351
|
+
const d = new Date(t * 1e3);
|
|
1352
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1353
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
1354
|
+
}
|
|
1282
1355
|
var Drawing = class {
|
|
1283
1356
|
id;
|
|
1284
1357
|
/** Set when this drawing belongs to ONE surface; undefined when every surface showing the symbol
|
|
@@ -1296,6 +1369,8 @@ var Drawing = class {
|
|
|
1296
1369
|
_series = null;
|
|
1297
1370
|
_requestUpdate = null;
|
|
1298
1371
|
_paneViews;
|
|
1372
|
+
_priceBands;
|
|
1373
|
+
_timeBands;
|
|
1299
1374
|
constructor(id, anchors = [], style = {}, options = {}, props = {}) {
|
|
1300
1375
|
this.id = id;
|
|
1301
1376
|
this._anchors = this.upgradeAnchors(anchors.map((a) => ({ ...a })));
|
|
@@ -1303,6 +1378,8 @@ var Drawing = class {
|
|
|
1303
1378
|
this._options = normalizeOptions(options);
|
|
1304
1379
|
this._props = { ...this.defaultProps(), ...this.upgradeProps(props) };
|
|
1305
1380
|
this._paneViews = [new DrawingPaneView(this)];
|
|
1381
|
+
this._priceBands = [new AxisBandView({ span: () => this.bandSpan("price") }, "price")];
|
|
1382
|
+
this._timeBands = [new AxisBandView({ span: () => this.bandSpan("time") }, "time")];
|
|
1306
1383
|
}
|
|
1307
1384
|
/** Tool-specific defaults. Must not read instance fields (runs during construction). */
|
|
1308
1385
|
defaultProps() {
|
|
@@ -1365,6 +1442,39 @@ var Drawing = class {
|
|
|
1365
1442
|
timeViews() {
|
|
1366
1443
|
return [];
|
|
1367
1444
|
}
|
|
1445
|
+
priceAxisPaneViews() {
|
|
1446
|
+
return this._priceBands;
|
|
1447
|
+
}
|
|
1448
|
+
timeAxisPaneViews() {
|
|
1449
|
+
return this._timeBands;
|
|
1450
|
+
}
|
|
1451
|
+
/** The prices and times a selected drawing marks on the axes with a band across their span, in
|
|
1452
|
+
* the accent at a quarter; none by default. */
|
|
1453
|
+
axisSpans() {
|
|
1454
|
+
return { prices: null, times: null };
|
|
1455
|
+
}
|
|
1456
|
+
/** The band a selected drawing lays on an axis: the coordinates its marked prices or times stand
|
|
1457
|
+
* at, or null where it lays none. */
|
|
1458
|
+
bandSpan(axis) {
|
|
1459
|
+
if (this._state !== "selected" && this._state !== "editing" || !this.isVisibleNow()) return null;
|
|
1460
|
+
const viewport = this.getViewport();
|
|
1461
|
+
if (!viewport) return null;
|
|
1462
|
+
const spans = this.axisSpans();
|
|
1463
|
+
const raw = axis === "price" ? (spans.prices ?? []).map((p) => viewport.yOf(p)) : (spans.times ?? []).map((t) => viewport.xOf(t));
|
|
1464
|
+
const coordinates = raw.filter((c) => c !== null && Number.isFinite(c));
|
|
1465
|
+
return coordinates.length > 0 ? { coordinates, color: withAlpha(this.inks().accent, 0.25) } : null;
|
|
1466
|
+
}
|
|
1467
|
+
/** A time as the chart writes it on its time axis: through the chart's own time formatter, or
|
|
1468
|
+
* its plain UTC date and time where the chart has none. */
|
|
1469
|
+
formatTime(time) {
|
|
1470
|
+
if (time === void 0) return "";
|
|
1471
|
+
try {
|
|
1472
|
+
const formatter = this._chart?.options().localization?.timeFormatter;
|
|
1473
|
+
if (formatter) return formatter(time);
|
|
1474
|
+
} catch {
|
|
1475
|
+
}
|
|
1476
|
+
return plainTime(time);
|
|
1477
|
+
}
|
|
1368
1478
|
hitTest(x, y) {
|
|
1369
1479
|
if (!this.isVisibleNow()) return null;
|
|
1370
1480
|
const viewport = this.getViewport();
|
|
@@ -1376,6 +1486,11 @@ var Drawing = class {
|
|
|
1376
1486
|
zOrder: "normal"
|
|
1377
1487
|
};
|
|
1378
1488
|
}
|
|
1489
|
+
/** The cursor a point over the drawing wears: a handle's, its words', or null for the plain
|
|
1490
|
+
* pointer. */
|
|
1491
|
+
cursorFor(point, viewport) {
|
|
1492
|
+
return this.cursorAt(point, viewport);
|
|
1493
|
+
}
|
|
1379
1494
|
/** Position-specific hover cursor (a table divider's col-resize); null = the default pointer. */
|
|
1380
1495
|
cursorAt(_point, _viewport) {
|
|
1381
1496
|
return null;
|
|
@@ -1561,6 +1676,11 @@ var Drawing = class {
|
|
|
1561
1676
|
resizeTo(_handleIndex, _point, _viewport) {
|
|
1562
1677
|
}
|
|
1563
1678
|
_textHint = null;
|
|
1679
|
+
/** Note where the invitation a paint just laid stands, where a press opens the words' editor; null
|
|
1680
|
+
* where none stands. */
|
|
1681
|
+
noteTextHint(region) {
|
|
1682
|
+
this._textHint = region;
|
|
1683
|
+
}
|
|
1564
1684
|
/** An inline text editor is open on this drawing (transient view state — never serialized). */
|
|
1565
1685
|
textEditing = false;
|
|
1566
1686
|
/**
|
|
@@ -1688,6 +1808,11 @@ var Drawing = class {
|
|
|
1688
1808
|
gripShape() {
|
|
1689
1809
|
return null;
|
|
1690
1810
|
}
|
|
1811
|
+
/** Whether the drawing shows its handles, in their thin form, while the pointer rests on it
|
|
1812
|
+
* unselected; a press on one then moves its point and selects the drawing. */
|
|
1813
|
+
handlesOnHover() {
|
|
1814
|
+
return false;
|
|
1815
|
+
}
|
|
1691
1816
|
_hovered = false;
|
|
1692
1817
|
_pointer = null;
|
|
1693
1818
|
/** Whether the pointer rests on this drawing (transient view state, never serialized). */
|
|
@@ -2143,28 +2268,207 @@ function cachedImageBitmap(key) {
|
|
|
2143
2268
|
return hit.complete && hit.naturalWidth > 0 ? hit : null;
|
|
2144
2269
|
}
|
|
2145
2270
|
|
|
2146
|
-
// src/internal/drawings/
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2271
|
+
// src/internal/drawings/core/savedLook.ts
|
|
2272
|
+
function endSavedLook(patch) {
|
|
2273
|
+
return "savedLook" in patch || Object.keys(patch).length === 0 ? patch : { ...patch, savedLook: null };
|
|
2274
|
+
}
|
|
2275
|
+
function savedLevels(look, key = "levels") {
|
|
2276
|
+
const levels = look[key];
|
|
2277
|
+
if (!Array.isArray(levels)) return [];
|
|
2278
|
+
return levels.filter((l) => !!l && typeof l === "object" && typeof l.value === "number").map((l) => ({
|
|
2279
|
+
value: l.value,
|
|
2280
|
+
visible: l.visible !== false,
|
|
2281
|
+
...typeof l.color === "string" ? { color: l.color } : {},
|
|
2282
|
+
...typeof l.text === "string" ? { text: l.text } : {}
|
|
2283
|
+
}));
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// src/internal/drawings/render/lineWords.ts
|
|
2287
|
+
var wordsOffset = (fontSize) => fontSize / 2 + 4;
|
|
2288
|
+
var plusSize = (fontSize) => fontSize / 2 + 5;
|
|
2289
|
+
var plusGap = (fontSize) => fontSize / 2 - 3;
|
|
2290
|
+
function placeAt(place, along, across) {
|
|
2291
|
+
const cos = Math.cos(place.angle);
|
|
2292
|
+
const sin = Math.sin(place.angle);
|
|
2293
|
+
return { x: place.x + along * cos - across * sin, y: place.y + along * sin + across * cos };
|
|
2294
|
+
}
|
|
2295
|
+
function paintInvitation(ctx, words, place, style, measure) {
|
|
2296
|
+
const size = style.fontSize;
|
|
2297
|
+
const plus = plusSize(size);
|
|
2298
|
+
const gap = plusGap(size);
|
|
2299
|
+
const width = plus + gap + measure(words);
|
|
2300
|
+
const left = place.align === "left" ? 0 : place.align === "right" ? -width : -width / 2;
|
|
2301
|
+
const ink = withAlpha(style.textColor, 0.5);
|
|
2302
|
+
ctx.save();
|
|
2303
|
+
ctx.setLineDash([]);
|
|
2304
|
+
ctx.translate(place.x, place.y);
|
|
2305
|
+
if (place.angle) ctx.rotate(place.angle);
|
|
2306
|
+
ctx.font = fontOf(style);
|
|
2307
|
+
ctx.fillStyle = ink;
|
|
2308
|
+
ctx.textAlign = "left";
|
|
2309
|
+
ctx.textBaseline = "middle";
|
|
2310
|
+
ctx.fillText(words, left + plus + gap, 0);
|
|
2311
|
+
let px = left + plus / 2;
|
|
2312
|
+
let py = -0.5;
|
|
2313
|
+
if (!place.angle) {
|
|
2314
|
+
px = Math.round(place.x + px - 0.5) + 0.5 - place.x;
|
|
2315
|
+
py = Math.round(place.y - 1) + 0.5 - place.y;
|
|
2316
|
+
}
|
|
2317
|
+
ctx.strokeStyle = ink;
|
|
2318
|
+
ctx.lineWidth = 1;
|
|
2319
|
+
ctx.lineCap = "butt";
|
|
2320
|
+
ctx.lineJoin = "miter";
|
|
2321
|
+
ctx.beginPath();
|
|
2322
|
+
ctx.moveTo(px - plus / 2, py);
|
|
2323
|
+
ctx.lineTo(px + plus / 2, py);
|
|
2324
|
+
ctx.moveTo(px, py - plus / 2);
|
|
2325
|
+
ctx.lineTo(px, py + plus / 2);
|
|
2326
|
+
ctx.stroke();
|
|
2327
|
+
ctx.restore();
|
|
2328
|
+
const middle = placeAt(place, left + width / 2, 0);
|
|
2329
|
+
return { cx: middle.x, cy: middle.y, angle: place.angle, halfW: width / 2 + 2, halfH: size / 2 + 2 };
|
|
2330
|
+
}
|
|
2331
|
+
function inRegion(point, r) {
|
|
2332
|
+
const dx = point.x - r.cx;
|
|
2333
|
+
const dy = point.y - r.cy;
|
|
2334
|
+
const cos = Math.cos(-r.angle);
|
|
2335
|
+
const sin = Math.sin(-r.angle);
|
|
2336
|
+
return Math.abs(dx * cos - dy * sin) <= r.halfW && Math.abs(dx * sin + dy * cos) <= r.halfH;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/internal/drawings/render/statsBox.ts
|
|
2340
|
+
var STATS_GROUND = "rgba(70, 70, 70, 0.9)";
|
|
2341
|
+
var STATS_INK = "#ffffff";
|
|
2342
|
+
var MARK_INK = "#f9f9f9";
|
|
2343
|
+
var STATS_FONT_SIZE = 12;
|
|
2344
|
+
var PAD_Y = 8;
|
|
2345
|
+
var ROW = 26;
|
|
2346
|
+
var MARK_X = 21;
|
|
2347
|
+
var WORDS_X = 42;
|
|
2348
|
+
var PAD_END = 13;
|
|
2349
|
+
function statsBoxAt(at, rows, measure) {
|
|
2350
|
+
const widest = rows.reduce((w, r) => Math.max(w, measure(r.text)), 0);
|
|
2351
|
+
return { x: at.x, y: at.y, width: Math.round(WORDS_X + widest + PAD_END), height: PAD_Y * 2 + rows.length * ROW };
|
|
2352
|
+
}
|
|
2353
|
+
function paintStatsBox(ctx, box2, rows, font) {
|
|
2354
|
+
ctx.save();
|
|
2355
|
+
ctx.setLineDash([]);
|
|
2356
|
+
ctx.fillStyle = STATS_GROUND;
|
|
2357
|
+
ctx.beginPath();
|
|
2358
|
+
ctx.roundRect(box2.x, box2.y, box2.width, box2.height, 4);
|
|
2359
|
+
ctx.fill();
|
|
2360
|
+
ctx.font = font;
|
|
2361
|
+
ctx.textAlign = "start";
|
|
2362
|
+
ctx.textBaseline = "middle";
|
|
2363
|
+
rows.forEach((row, i) => {
|
|
2364
|
+
const middle = box2.y + PAD_Y + ROW / 2 + i * ROW;
|
|
2365
|
+
paintMark(ctx, row.kind, box2.x + MARK_X, middle);
|
|
2366
|
+
ctx.fillStyle = STATS_INK;
|
|
2367
|
+
ctx.fillText(row.text, box2.x + WORDS_X, middle);
|
|
2368
|
+
});
|
|
2369
|
+
ctx.restore();
|
|
2370
|
+
}
|
|
2371
|
+
function paintMark(ctx, kind, cx, cy) {
|
|
2372
|
+
ctx.strokeStyle = MARK_INK;
|
|
2373
|
+
ctx.lineWidth = 1;
|
|
2374
|
+
ctx.lineCap = "butt";
|
|
2375
|
+
ctx.lineJoin = "miter";
|
|
2376
|
+
ctx.beginPath();
|
|
2377
|
+
if (kind === "price") {
|
|
2378
|
+
for (const dy of [-6.5, 6.5]) {
|
|
2379
|
+
ctx.moveTo(cx - 6, cy + dy);
|
|
2380
|
+
ctx.lineTo(cx + 5, cy + dy);
|
|
2381
|
+
}
|
|
2382
|
+
ctx.moveTo(cx - 0.5, cy - 5);
|
|
2383
|
+
ctx.lineTo(cx - 0.5, cy + 5);
|
|
2384
|
+
for (const s of [-1, 1]) {
|
|
2385
|
+
ctx.moveTo(cx - 3, cy + s * 2);
|
|
2386
|
+
ctx.lineTo(cx - 0.5, cy + s * 4.5);
|
|
2387
|
+
ctx.lineTo(cx + 2, cy + s * 2);
|
|
2388
|
+
}
|
|
2389
|
+
} else if (kind === "span") {
|
|
2390
|
+
for (const x of [-7.5, 5.5]) {
|
|
2391
|
+
ctx.rect(cx + x, cy - 3.5, 2, 8);
|
|
2392
|
+
ctx.moveTo(cx + x + 1, cy - 6);
|
|
2393
|
+
ctx.lineTo(cx + x + 1, cy - 4);
|
|
2394
|
+
ctx.moveTo(cx + x + 1, cy + 5);
|
|
2395
|
+
ctx.lineTo(cx + x + 1, cy + 7);
|
|
2396
|
+
}
|
|
2397
|
+
ctx.moveTo(cx - 3.5, cy + 0.5);
|
|
2398
|
+
ctx.lineTo(cx + 3.5, cy + 0.5);
|
|
2399
|
+
for (const s of [-1, 1]) {
|
|
2400
|
+
ctx.moveTo(cx + s, cy - 2);
|
|
2401
|
+
ctx.lineTo(cx + s * 3.5, cy + 0.5);
|
|
2402
|
+
ctx.lineTo(cx + s, cy + 3);
|
|
2403
|
+
}
|
|
2404
|
+
} else {
|
|
2405
|
+
const corner = { x: cx - 5.5, y: cy + 5.5 };
|
|
2406
|
+
const ray = 64.7 * Math.PI / 180;
|
|
2407
|
+
ctx.moveTo(cx - 6, corner.y);
|
|
2408
|
+
ctx.lineTo(cx + 6, corner.y);
|
|
2409
|
+
ctx.moveTo(corner.x, corner.y);
|
|
2410
|
+
ctx.lineTo(corner.x + 12.5 * Math.cos(ray), corner.y - 12.5 * Math.sin(ray));
|
|
2411
|
+
ctx.moveTo(corner.x + 7, corner.y);
|
|
2412
|
+
ctx.arc(corner.x, corner.y, 7, 0, -ray, true);
|
|
2157
2413
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2414
|
+
ctx.stroke();
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/internal/drawings/render/textEntry.ts
|
|
2418
|
+
var SELECTION_WASH = "rgba(255, 255, 255, 0.2)";
|
|
2419
|
+
var CARET_WIDTH = 2;
|
|
2420
|
+
var lineMiddle = (lineHeight) => lineHeight / 2 + 1;
|
|
2421
|
+
var caretHeight = (font, lineHeight) => Math.min(lineHeight, Number(/(\d+(?:\.\d+)?)px/.exec(font)?.[1]) || lineHeight);
|
|
2422
|
+
function paintTextEntry(ctx, p) {
|
|
2423
|
+
const draft = p.draft;
|
|
2424
|
+
const shown = p.placeholder ? p.placeholder.block : p.block;
|
|
2425
|
+
const lineTop = (i) => p.y + i * p.lineHeight;
|
|
2426
|
+
const left = (block, i) => p.x + lineLeft(block, i, p.align, p.width);
|
|
2427
|
+
ctx.save();
|
|
2428
|
+
ctx.setLineDash([]);
|
|
2429
|
+
ctx.direction = "ltr";
|
|
2430
|
+
if (draft && draft.selectionStart !== draft.selectionEnd && !p.placeholder) {
|
|
2431
|
+
ctx.fillStyle = SELECTION_WASH;
|
|
2432
|
+
for (const span of runSpans(p.block, draft.selectionStart, draft.selectionEnd, p.measure)) {
|
|
2433
|
+
const from = Math.round(left(p.block, span.line) + span.x0);
|
|
2434
|
+
const to = Math.ceil(left(p.block, span.line) + span.x1);
|
|
2435
|
+
if (to > from) ctx.fillRect(from, lineTop(span.line), to - from, p.lineHeight);
|
|
2436
|
+
}
|
|
2160
2437
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2438
|
+
ctx.font = p.font;
|
|
2439
|
+
ctx.fillStyle = p.color;
|
|
2440
|
+
ctx.textAlign = "left";
|
|
2441
|
+
ctx.textBaseline = "middle";
|
|
2442
|
+
const alpha = ctx.globalAlpha;
|
|
2443
|
+
if (p.placeholder) ctx.globalAlpha = alpha * p.placeholder.alpha;
|
|
2444
|
+
shown.lines.forEach((l, i) => {
|
|
2445
|
+
if (l.text === "") return;
|
|
2446
|
+
ctx.fillText(l.text, left(shown, i), lineTop(i) + lineMiddle(p.lineHeight));
|
|
2447
|
+
});
|
|
2448
|
+
ctx.globalAlpha = alpha;
|
|
2449
|
+
if (draft?.composition && !p.placeholder) {
|
|
2450
|
+
for (const span of runSpans(p.block, draft.composition.start, draft.composition.end, p.measure)) {
|
|
2451
|
+
const from = Math.round(left(p.block, span.line) + span.x0);
|
|
2452
|
+
const to = Math.ceil(left(p.block, span.line) + span.x1);
|
|
2453
|
+
if (to > from) ctx.fillRect(from, lineTop(span.line) + p.lineHeight - 1, to - from, 1);
|
|
2454
|
+
}
|
|
2163
2455
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2456
|
+
if (draft?.caret && draft.selectionStart === draft.selectionEnd) {
|
|
2457
|
+
const at = caretPlace(p.block, draft.selectionEnd, p.measure);
|
|
2458
|
+
const tall = caretHeight(p.font, p.lineHeight);
|
|
2459
|
+
ctx.fillRect(Math.round(left(p.block, at.line) + at.x) - CARET_WIDTH / 2, lineTop(at.line) + Math.round((p.lineHeight - tall) / 2), CARET_WIDTH, tall);
|
|
2166
2460
|
}
|
|
2167
|
-
|
|
2461
|
+
ctx.restore();
|
|
2462
|
+
}
|
|
2463
|
+
function paintWordsFrame(ctx, box2, color, alpha) {
|
|
2464
|
+
ctx.save();
|
|
2465
|
+
ctx.setLineDash([]);
|
|
2466
|
+
ctx.globalAlpha = ctx.globalAlpha * alpha;
|
|
2467
|
+
ctx.strokeStyle = color;
|
|
2468
|
+
ctx.lineWidth = 2;
|
|
2469
|
+
ctx.strokeRect(box2.x - 1, box2.y - 1, box2.width + 2, box2.height + 2);
|
|
2470
|
+
ctx.restore();
|
|
2471
|
+
}
|
|
2168
2472
|
|
|
2169
2473
|
// src/internal/drawings/tools/lines.ts
|
|
2170
2474
|
var LINE_PROPS = {
|
|
@@ -2180,39 +2484,227 @@ var LINE_PROPS = {
|
|
|
2180
2484
|
showPipsChange: false,
|
|
2181
2485
|
showBarsRange: false,
|
|
2182
2486
|
showDateTimeRange: false,
|
|
2487
|
+
showDistance: false,
|
|
2183
2488
|
showAngle: false,
|
|
2184
2489
|
statsPosition: "right",
|
|
2185
2490
|
alwaysShowStats: false,
|
|
2186
2491
|
textVAlign: "top",
|
|
2187
|
-
textHAlign: "center"
|
|
2492
|
+
textHAlign: "center",
|
|
2493
|
+
savedLook: null
|
|
2188
2494
|
};
|
|
2189
2495
|
var ACROSS = {
|
|
2190
2496
|
top: { y: -4, baseline: "bottom" },
|
|
2191
2497
|
middle: { y: 0, baseline: "middle" },
|
|
2192
2498
|
bottom: { y: 4, baseline: "top" }
|
|
2193
2499
|
};
|
|
2500
|
+
var LEVEL_WORDS_INSET = 7;
|
|
2501
|
+
var LEVEL_HANDLE_AT = 0.9;
|
|
2194
2502
|
function hitTolerance(lineWidth) {
|
|
2195
2503
|
return Math.max(6, lineWidth / 2 + 4);
|
|
2196
2504
|
}
|
|
2197
|
-
|
|
2505
|
+
function crisp(v, width) {
|
|
2506
|
+
return Math.round(v) + (Math.round(width) % 2 === 1 ? 0.5 : 0);
|
|
2507
|
+
}
|
|
2508
|
+
function openHead(ctx, from, tip, lineWidth) {
|
|
2509
|
+
const length = Math.hypot(tip.x - from.x, tip.y - from.y);
|
|
2510
|
+
if (!(length > 0)) return;
|
|
2511
|
+
const ux = (tip.x - from.x) / length;
|
|
2512
|
+
const uy = (tip.y - from.y) / length;
|
|
2513
|
+
const size = 5 + 2.5 * lineWidth;
|
|
2514
|
+
ctx.moveTo(tip.x - size * ux - size * uy, tip.y - size * uy + size * ux);
|
|
2515
|
+
ctx.lineTo(tip.x, tip.y);
|
|
2516
|
+
ctx.lineTo(tip.x - size * ux + size * uy, tip.y - size * uy - size * ux);
|
|
2517
|
+
}
|
|
2518
|
+
function grouped(n) {
|
|
2519
|
+
const digits = String(Math.abs(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2520
|
+
return n < 0 ? `-${digits}` : digits;
|
|
2521
|
+
}
|
|
2522
|
+
function spanText(seconds) {
|
|
2523
|
+
const d = Math.floor(seconds / 86400);
|
|
2524
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
2525
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
2526
|
+
return d > 0 ? `${d}d ${h}h` : h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
2527
|
+
}
|
|
2528
|
+
function wordsBox(place, block, lineHeight, across) {
|
|
2529
|
+
const lines = Math.max(1, block.lines.length);
|
|
2530
|
+
const width = block.width;
|
|
2531
|
+
const left = place.align === "left" ? 0 : place.align === "right" ? -width : -width / 2;
|
|
2532
|
+
const first = -lineHeight / 2 - 1;
|
|
2533
|
+
const top = across === "top" ? first - (lines - 1) * lineHeight : across === "bottom" ? first : first - (lines - 1) * lineHeight / 2;
|
|
2534
|
+
return { left, top, width, lines };
|
|
2535
|
+
}
|
|
2536
|
+
var LineWords = class extends Drawing {
|
|
2537
|
+
/** Whether the line invites words while it is selected without any. */
|
|
2538
|
+
invites() {
|
|
2539
|
+
return true;
|
|
2540
|
+
}
|
|
2541
|
+
wordsBlock() {
|
|
2542
|
+
return this.shownWords(lineMeasure(this.style));
|
|
2543
|
+
}
|
|
2544
|
+
/** Paint the words, or an open edit's draft with its placeholder at half strength while it is
|
|
2545
|
+
* empty. */
|
|
2546
|
+
paintWords(ctx, viewport) {
|
|
2547
|
+
const draft = this.textDraft;
|
|
2548
|
+
if (!draft && this.props.text === "") return;
|
|
2549
|
+
const place = this.wordsPlace(viewport);
|
|
2550
|
+
if (!place) return;
|
|
2551
|
+
const { block, placeholder } = this.wordsBlock();
|
|
2552
|
+
const shown = draft ? placeholder ?? block : block;
|
|
2553
|
+
const box2 = wordsBox(place, shown, this.style.fontSize, this.wordsAcross());
|
|
2554
|
+
ctx.save();
|
|
2555
|
+
ctx.translate(place.x, place.y);
|
|
2556
|
+
if (place.angle) ctx.rotate(place.angle);
|
|
2557
|
+
paintTextEntry(ctx, {
|
|
2558
|
+
x: box2.left,
|
|
2559
|
+
y: box2.top,
|
|
2560
|
+
width: box2.width,
|
|
2561
|
+
lineHeight: this.style.fontSize,
|
|
2562
|
+
font: fontOf(this.style),
|
|
2563
|
+
color: this.style.textColor,
|
|
2564
|
+
align: place.align,
|
|
2565
|
+
block,
|
|
2566
|
+
placeholder: draft && placeholder ? { block: placeholder, alpha: 0.5 } : null,
|
|
2567
|
+
draft,
|
|
2568
|
+
measure: lineMeasure(this.style)
|
|
2569
|
+
});
|
|
2570
|
+
ctx.restore();
|
|
2571
|
+
}
|
|
2572
|
+
textFrame(viewport) {
|
|
2573
|
+
const place = this.wordsPlace(viewport);
|
|
2574
|
+
if (!place) return null;
|
|
2575
|
+
const { block, placeholder } = this.wordsBlock();
|
|
2576
|
+
const shown = placeholder ?? block;
|
|
2577
|
+
const box2 = wordsBox(place, shown, this.style.fontSize, this.wordsAcross());
|
|
2578
|
+
const at = placeAt(place, box2.left, box2.top);
|
|
2579
|
+
return { x: at.x, y: at.y, width: box2.width, lines: box2.lines, lineHeight: this.style.fontSize, font: fontOf(this.style), align: place.align, wrapWidth: null, angle: place.angle };
|
|
2580
|
+
}
|
|
2581
|
+
/** The region the words cover, turned with them; null without words. */
|
|
2582
|
+
wordsRegion(viewport) {
|
|
2583
|
+
if (this.props.text === "" && !this.textDraft) return null;
|
|
2584
|
+
const place = this.wordsPlace(viewport);
|
|
2585
|
+
if (!place) return null;
|
|
2586
|
+
const { block, placeholder } = this.wordsBlock();
|
|
2587
|
+
const shown = this.textDraft && placeholder ? placeholder : block;
|
|
2588
|
+
const lineHeight = this.style.fontSize;
|
|
2589
|
+
const box2 = wordsBox(place, shown, lineHeight, this.wordsAcross());
|
|
2590
|
+
const middle = placeAt(place, box2.left + box2.width / 2, box2.top + box2.lines * lineHeight / 2);
|
|
2591
|
+
return { cx: middle.x, cy: middle.y, angle: place.angle, halfW: box2.width / 2 + 2, halfH: box2.lines * lineHeight / 2 + 2 };
|
|
2592
|
+
}
|
|
2593
|
+
wordsAt(point, viewport) {
|
|
2594
|
+
const region = this.wordsRegion(viewport);
|
|
2595
|
+
return !!region && inRegion(point, region);
|
|
2596
|
+
}
|
|
2597
|
+
/** The selected line without words invites them where they would stand. */
|
|
2598
|
+
paintTextHint(ctx, viewport) {
|
|
2599
|
+
this.noteTextHint(null);
|
|
2600
|
+
if (this.state !== "selected" || this.textEditing || this.props.text !== "" || !this.invites()) return;
|
|
2601
|
+
const place = this.wordsPlace(viewport);
|
|
2602
|
+
if (!place) return;
|
|
2603
|
+
this.noteTextHint(paintInvitation(ctx, this.textPlaceholder(), place, this.style, lineMeasure(this.style)));
|
|
2604
|
+
}
|
|
2605
|
+
/** Over its words or its invitation the selected line reads as words, and over a handle as the
|
|
2606
|
+
* handle it is. */
|
|
2607
|
+
cursorAt(point, viewport) {
|
|
2608
|
+
const handle = this.handleCursor(point, viewport);
|
|
2609
|
+
if (handle) return handle;
|
|
2610
|
+
if (this.state === "selected" && (this.wordsAt(point, viewport) || this.hitTextHint(point))) return "text";
|
|
2611
|
+
return null;
|
|
2612
|
+
}
|
|
2613
|
+
/** The cursor a handle under a point wears, or null where none is. */
|
|
2614
|
+
handleCursor(point, viewport) {
|
|
2615
|
+
const near = this.getControlPoints(viewport).some((p) => Math.hypot(p.x - point.x, p.y - point.y) <= 6.5);
|
|
2616
|
+
return near ? this.handleCursorName() : null;
|
|
2617
|
+
}
|
|
2618
|
+
handleCursorName() {
|
|
2619
|
+
return "default";
|
|
2620
|
+
}
|
|
2621
|
+
/** The line's handles show in their thin form while the pointer rests on it. */
|
|
2622
|
+
handlesOnHover() {
|
|
2623
|
+
return true;
|
|
2624
|
+
}
|
|
2625
|
+
/** A press on the words or the invitation of the selected line lands on the line. */
|
|
2626
|
+
hitWords(point, viewport) {
|
|
2627
|
+
if (this.wordsAt(point, viewport)) return true;
|
|
2628
|
+
return this.state === "selected" && this.hitTextHint(point);
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
var pointLabel = (drawing2, coordinate, text, shown) => new AxisLabel({
|
|
2632
|
+
coordinate,
|
|
2633
|
+
text,
|
|
2634
|
+
color: () => drawing2.inks().accent,
|
|
2635
|
+
textColor: () => "#ffffff",
|
|
2636
|
+
visible: shown
|
|
2637
|
+
});
|
|
2638
|
+
var TrendLine = class extends LineWords {
|
|
2198
2639
|
type = "trend_line";
|
|
2640
|
+
_priceViews = [0, 1].map(
|
|
2641
|
+
(i) => pointLabel(
|
|
2642
|
+
this,
|
|
2643
|
+
() => {
|
|
2644
|
+
const viewport = this.getViewport();
|
|
2645
|
+
const anchor = this.anchors[i];
|
|
2646
|
+
return viewport && anchor ? viewport.yOf(anchor.price) : null;
|
|
2647
|
+
},
|
|
2648
|
+
() => this.formatPrice(this.anchors[i]?.price ?? 0),
|
|
2649
|
+
() => this.marksAxes() && !!this.anchors[i]
|
|
2650
|
+
)
|
|
2651
|
+
);
|
|
2652
|
+
_timeViews = [0, 1].map(
|
|
2653
|
+
(i) => pointLabel(
|
|
2654
|
+
this,
|
|
2655
|
+
() => {
|
|
2656
|
+
const viewport = this.getViewport();
|
|
2657
|
+
const anchor = this.anchors[i];
|
|
2658
|
+
return viewport && anchor ? viewport.xOf(anchor.time) : null;
|
|
2659
|
+
},
|
|
2660
|
+
() => this.formatTime(this.anchors[i]?.time),
|
|
2661
|
+
() => this.marksAxes() && !!this.anchors[i]
|
|
2662
|
+
)
|
|
2663
|
+
);
|
|
2199
2664
|
defaultProps() {
|
|
2200
2665
|
return { ...LINE_PROPS };
|
|
2201
2666
|
}
|
|
2667
|
+
applyProps(patch) {
|
|
2668
|
+
super.applyProps(endSavedLook(patch));
|
|
2669
|
+
}
|
|
2202
2670
|
requiredAnchors() {
|
|
2203
2671
|
return 2;
|
|
2204
2672
|
}
|
|
2205
|
-
/** The
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2673
|
+
/** The selected line marks its points' prices and times on the axes. */
|
|
2674
|
+
marksAxes() {
|
|
2675
|
+
return (this.state === "selected" || this.state === "editing") && this.isVisibleNow();
|
|
2676
|
+
}
|
|
2677
|
+
axisViews() {
|
|
2678
|
+
return this._priceViews;
|
|
2679
|
+
}
|
|
2680
|
+
timeViews() {
|
|
2681
|
+
return this._timeViews;
|
|
2214
2682
|
}
|
|
2215
|
-
|
|
2683
|
+
axisSpans() {
|
|
2684
|
+
return { prices: this.anchors.map((a) => a.price), times: this.anchors.map((a) => a.time) };
|
|
2685
|
+
}
|
|
2686
|
+
/** While its second point is being placed, the point following the pointer shows no handle. */
|
|
2687
|
+
getControlPoints(viewport) {
|
|
2688
|
+
const points = super.getControlPoints(viewport);
|
|
2689
|
+
return this.state === "editing" ? points.filter((p) => p.index < this.anchors.length - 1) : points;
|
|
2690
|
+
}
|
|
2691
|
+
/** The words ride the segment's slope at its middle or at the end they align to, above, on or
|
|
2692
|
+
* below it. */
|
|
2693
|
+
wordsPlace(viewport) {
|
|
2694
|
+
const [pa, pb] = this.anchorPixels(viewport);
|
|
2695
|
+
if (!pa || !pb) return null;
|
|
2696
|
+
const angle = segmentTextAngle(pa, pb);
|
|
2697
|
+
const [left, right] = pa.x <= pb.x ? [pa, pb] : [pb, pa];
|
|
2698
|
+
const along = this.props.textHAlign;
|
|
2699
|
+
const at = along === "left" ? left : along === "right" ? right : midpoint(pa, pb);
|
|
2700
|
+
const across = this.props.textVAlign === "top" ? -wordsOffset(this.style.fontSize) : this.props.textVAlign === "bottom" ? wordsOffset(this.style.fontSize) : 0;
|
|
2701
|
+
const p = placeAt({ x: at.x, y: at.y, angle, align: along }, 0, across);
|
|
2702
|
+
return { x: p.x, y: p.y, angle, align: along };
|
|
2703
|
+
}
|
|
2704
|
+
wordsAcross() {
|
|
2705
|
+
return this.props.textVAlign;
|
|
2706
|
+
}
|
|
2707
|
+
/** The on-screen segment after extension, the shared basis for painting and hit-testing. */
|
|
2216
2708
|
segment(viewport) {
|
|
2217
2709
|
const [pa, pb] = this.anchorPixels(viewport);
|
|
2218
2710
|
if (!pa || !pb) return null;
|
|
@@ -2222,13 +2714,13 @@ var TrendLine = class extends Drawing {
|
|
|
2222
2714
|
}
|
|
2223
2715
|
/** A format-2 line showed its stats whether or not it was selected and counted no price moves; a
|
|
2224
2716
|
* stats position counted its left and right from the first point, and a save naming none stood
|
|
2225
|
-
* them at the middle. */
|
|
2717
|
+
* them at the middle. Its ends, words and stats paint as format 2 painted them. */
|
|
2226
2718
|
keepSavedLook(saved) {
|
|
2227
2719
|
const position = saved.statsPosition ?? "center";
|
|
2228
2720
|
const [a, b] = this._anchors;
|
|
2229
2721
|
const flipped = !!a && !!b && Number(b.time) < Number(a.time);
|
|
2230
2722
|
const statsPosition = flipped && position === "left" ? "right" : flipped && position === "right" ? "left" : position;
|
|
2231
|
-
this._props = { ...this._props, statsPosition, alwaysShowStats: true, showPipsChange: false };
|
|
2723
|
+
this._props = { ...this._props, statsPosition, alwaysShowStats: true, showPipsChange: false, savedLook: {} };
|
|
2232
2724
|
}
|
|
2233
2725
|
/** The stats stand while the line is selected or edited, or always where the viewer asked. */
|
|
2234
2726
|
statsShown() {
|
|
@@ -2237,15 +2729,24 @@ var TrendLine = class extends Drawing {
|
|
|
2237
2729
|
paint(ctx, viewport) {
|
|
2238
2730
|
const seg = this.segment(viewport);
|
|
2239
2731
|
if (!seg) return;
|
|
2732
|
+
if (this.props.savedLook) {
|
|
2733
|
+
this.paintSavedLook(ctx, viewport, seg);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2240
2736
|
applyStroke(ctx, this.style);
|
|
2241
|
-
|
|
2242
|
-
if (this.props.
|
|
2243
|
-
if (this.props.
|
|
2244
|
-
|
|
2737
|
+
ctx.beginPath();
|
|
2738
|
+
if (this.props.rightEnd === "arrow") openHead(ctx, seg.a, seg.b, this.style.lineWidth);
|
|
2739
|
+
if (this.props.leftEnd === "arrow") openHead(ctx, seg.b, seg.a, this.style.lineWidth);
|
|
2740
|
+
ctx.moveTo(seg.a.x, seg.a.y);
|
|
2741
|
+
ctx.lineTo(seg.b.x, seg.b.y);
|
|
2742
|
+
ctx.stroke();
|
|
2743
|
+
this.paintMarks(ctx, viewport);
|
|
2744
|
+
this.paintWords(ctx, viewport);
|
|
2745
|
+
this.paintStats(ctx, viewport);
|
|
2245
2746
|
this.paintDecorations(ctx, viewport);
|
|
2246
2747
|
}
|
|
2247
|
-
/**
|
|
2248
|
-
|
|
2748
|
+
/** The midpoint marker and the end price pills, as their props ask. */
|
|
2749
|
+
paintMarks(ctx, viewport) {
|
|
2249
2750
|
const [pa, pb] = this.anchorPixels(viewport);
|
|
2250
2751
|
if (!pa || !pb) return;
|
|
2251
2752
|
if (this.props.middlePoint) {
|
|
@@ -2260,48 +2761,128 @@ var TrendLine = class extends Drawing {
|
|
|
2260
2761
|
}
|
|
2261
2762
|
if (this.props.showPriceLabels) {
|
|
2262
2763
|
const [a, b] = this.anchors;
|
|
2263
|
-
paintLabel(ctx, this.formatPrice(a.price), { x: pa.x - 8, y: pa.y }, this.style, {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2764
|
+
paintLabel(ctx, this.formatPrice(a.price), { x: pa.x - 8, y: pa.y }, this.style, { align: "right", background: withAlpha(this.style.lineColor, 0.2) });
|
|
2765
|
+
paintLabel(ctx, this.formatPrice(b.price), { x: pb.x + 8, y: pb.y }, this.style, { background: withAlpha(this.style.lineColor, 0.2) });
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
/** The stats box: rows of the price move, the span and the angle, each led by its mark, standing
|
|
2769
|
+
* off the point along the line the stats position names, below and right of a line that rises
|
|
2770
|
+
* and above and right of one that falls. */
|
|
2771
|
+
paintStats(ctx, viewport) {
|
|
2772
|
+
if (!this.statsShown()) return;
|
|
2773
|
+
const rows = this.statsRows(viewport);
|
|
2774
|
+
if (rows.length === 0) return;
|
|
2775
|
+
const at = this.statsPoint(viewport);
|
|
2776
|
+
if (!at) return;
|
|
2777
|
+
const type = { ...this.style, fontSize: STATS_FONT_SIZE, bold: false, italic: false };
|
|
2778
|
+
const sized = statsBoxAt({ x: 0, y: 0 }, rows, lineMeasure(type));
|
|
2779
|
+
const box2 = { ...sized, x: Math.round(at.point.x + 12), y: Math.round(at.rising ? at.point.y + 11 : at.point.y - 11 - sized.height) };
|
|
2780
|
+
paintStatsBox(ctx, box2, rows, fontOf(type));
|
|
2781
|
+
}
|
|
2782
|
+
/** Where the stats stand along the line, and whether it rises left to right. */
|
|
2783
|
+
statsPoint(viewport) {
|
|
2784
|
+
const [pa, pb] = this.anchorPixels(viewport);
|
|
2785
|
+
if (!pa || !pb) return null;
|
|
2272
2786
|
const [left, right] = pa.x <= pb.x ? [pa, pb] : [pb, pa];
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2787
|
+
const toRight = (f) => ({ x: left.x + (right.x - left.x) * f, y: left.y + (right.y - left.y) * f });
|
|
2788
|
+
const place = this.props.statsPosition;
|
|
2789
|
+
const t = place === "left" ? 0.12 : place === "center" ? 0.5 : place === "auto" && toRight(0.88).x > viewport.width * 0.85 ? 0.12 : 0.88;
|
|
2790
|
+
return { point: toRight(t), rising: right.y <= left.y };
|
|
2791
|
+
}
|
|
2792
|
+
/** The stats rows: the price move with its percent and its count of the symbol's smallest moves;
|
|
2793
|
+
* the bars and the time it spans with its length on the pane; and its angle. A row whose items
|
|
2794
|
+
* are all off is left out. */
|
|
2795
|
+
statsRows(viewport) {
|
|
2796
|
+
const [a, b] = this.anchors;
|
|
2797
|
+
if (!a || !b) return [];
|
|
2798
|
+
const rows = [];
|
|
2799
|
+
const dPrice = b.price - a.price;
|
|
2800
|
+
const price = [];
|
|
2801
|
+
if (this.props.showPriceRange) price.push(this.formatPrice(dPrice));
|
|
2802
|
+
if (this.props.showPercentChange) {
|
|
2803
|
+
const pct = (a.price !== 0 ? dPrice / Math.abs(a.price) * 100 : 0).toFixed(2);
|
|
2804
|
+
price.push(price.length ? `(${pct}%)` : `${pct}%`);
|
|
2282
2805
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
const
|
|
2287
|
-
|
|
2288
|
-
const at = toRight(t);
|
|
2289
|
-
const crowded = !!this.props.text && this.props.textVAlign === "top" && (place === "center" && this.props.textHAlign === "center" || t === 0.12 && this.props.textHAlign === "left" || t === 0.88 && this.props.textHAlign === "right");
|
|
2290
|
-
ctx.save();
|
|
2291
|
-
ctx.translate(at.x, at.y);
|
|
2292
|
-
ctx.rotate(textAngle);
|
|
2293
|
-
paintLabel(ctx, stats, { x: 0, y: crowded ? -32 : -14 }, this.style, { align: "center", background: withAlpha("#1b1f27", 0.92) });
|
|
2294
|
-
ctx.restore();
|
|
2806
|
+
let priceRow = price.join(" ");
|
|
2807
|
+
const tick = this.tickSize();
|
|
2808
|
+
if (this.props.showPipsChange && tick !== null && tick > 0) {
|
|
2809
|
+
const moves = grouped(Math.round(dPrice / tick));
|
|
2810
|
+
priceRow = priceRow ? `${priceRow}, ${moves}` : moves;
|
|
2295
2811
|
}
|
|
2812
|
+
if (priceRow) rows.push({ kind: "price", text: priceRow });
|
|
2813
|
+
const span = [];
|
|
2814
|
+
if (this.props.showBarsRange) {
|
|
2815
|
+
const bars = viewport.barsBetween(a.time, b.time);
|
|
2816
|
+
if (bars !== null) span.push(`${Math.round(bars)} bars`);
|
|
2817
|
+
}
|
|
2818
|
+
if (this.props.showDateTimeRange) {
|
|
2819
|
+
const secs = Math.abs(Number(b.time) - Number(a.time));
|
|
2820
|
+
if (Number.isFinite(secs) && secs > 0) span.push(span.length ? `(${spanText(secs)})` : spanText(secs));
|
|
2821
|
+
}
|
|
2822
|
+
let spanRow = span.join(" ");
|
|
2823
|
+
const [pa, pb] = this.anchorPixels(viewport);
|
|
2824
|
+
if (this.props.showDistance && pa && pb) {
|
|
2825
|
+
const distance = `distance: ${Math.round(Math.hypot(pb.x - pa.x, pb.y - pa.y))} px`;
|
|
2826
|
+
spanRow = spanRow ? `${spanRow}, ${distance}` : distance;
|
|
2827
|
+
}
|
|
2828
|
+
if (spanRow) rows.push({ kind: "span", text: spanRow });
|
|
2829
|
+
if (this.props.showAngle && pa && pb) rows.push({ kind: "angle", text: `${(-angleOf(pa, pb) * 180 / Math.PI).toFixed(1)}\xB0` });
|
|
2830
|
+
return rows;
|
|
2831
|
+
}
|
|
2832
|
+
/** Extra ink beyond the shared prop set: the family variants override. */
|
|
2833
|
+
paintDecorations(_ctx, _viewport) {
|
|
2834
|
+
}
|
|
2835
|
+
testHit(point, viewport) {
|
|
2836
|
+
const seg = this.segment(viewport);
|
|
2837
|
+
if (!seg) return false;
|
|
2838
|
+
return distanceToSegment(point, seg.a, seg.b) <= hitTolerance(this.style.lineWidth) || this.hitWords(point, viewport);
|
|
2839
|
+
}
|
|
2840
|
+
// ============ The format-2 look ============
|
|
2841
|
+
/** A format-2 line: filled arrow heads, its words on a label across it, and its stats on a pill. */
|
|
2842
|
+
paintSavedLook(ctx, viewport, seg) {
|
|
2843
|
+
applyStroke(ctx, this.style);
|
|
2844
|
+
strokeSegment(ctx, seg.a, seg.b);
|
|
2845
|
+
if (this.props.leftEnd === "arrow") paintArrowHead(ctx, seg.b, seg.a, this.style);
|
|
2846
|
+
if (this.props.rightEnd === "arrow") paintArrowHead(ctx, seg.a, seg.b, this.style);
|
|
2847
|
+
this.paintMarks(ctx, viewport);
|
|
2848
|
+
const [pa, pb] = this.anchorPixels(viewport);
|
|
2849
|
+
if (pa && pb) {
|
|
2850
|
+
const textAngle = segmentTextAngle(pa, pb);
|
|
2851
|
+
const [left, right] = pa.x <= pb.x ? [pa, pb] : [pb, pa];
|
|
2852
|
+
if (this.textDraft) this.paintWords(ctx, viewport);
|
|
2853
|
+
else if (this.props.text) {
|
|
2854
|
+
const along = this.props.textHAlign;
|
|
2855
|
+
const at = along === "left" ? left : along === "right" ? right : midpoint(pa, pb);
|
|
2856
|
+
const across = ACROSS[this.props.textVAlign] ?? ACROSS.top;
|
|
2857
|
+
ctx.save();
|
|
2858
|
+
ctx.translate(at.x, at.y);
|
|
2859
|
+
ctx.rotate(textAngle);
|
|
2860
|
+
paintLabel(ctx, this.props.text, { x: 0, y: across.y }, this.style, { align: along === "left" ? "left" : along === "right" ? "right" : "center", baseline: across.baseline });
|
|
2861
|
+
ctx.restore();
|
|
2862
|
+
}
|
|
2863
|
+
const stats = this.statsShown() ? this.savedStatsText(viewport) : null;
|
|
2864
|
+
if (stats) {
|
|
2865
|
+
const place = this.props.statsPosition;
|
|
2866
|
+
const toRight = (f) => ({ x: left.x + (right.x - left.x) * f, y: left.y + (right.y - left.y) * f });
|
|
2867
|
+
const t = place === "left" ? 0.12 : place === "center" ? 0.5 : place === "auto" && toRight(0.88).x > viewport.width * 0.85 ? 0.12 : 0.88;
|
|
2868
|
+
const at = toRight(t);
|
|
2869
|
+
const crowded = !!this.props.text && this.props.textVAlign === "top" && (place === "center" && this.props.textHAlign === "center" || t === 0.12 && this.props.textHAlign === "left" || t === 0.88 && this.props.textHAlign === "right");
|
|
2870
|
+
ctx.save();
|
|
2871
|
+
ctx.translate(at.x, at.y);
|
|
2872
|
+
ctx.rotate(textAngle);
|
|
2873
|
+
paintLabel(ctx, stats, { x: 0, y: crowded ? -32 : -14 }, this.style, { align: "center", background: withAlpha("#1b1f27", 0.92) });
|
|
2874
|
+
ctx.restore();
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
this.paintDecorations(ctx, viewport);
|
|
2296
2878
|
}
|
|
2297
|
-
|
|
2879
|
+
/** A format-2 line's stats on one line: signed moves, the bars, the span and the angle. */
|
|
2880
|
+
savedStatsText(viewport) {
|
|
2298
2881
|
const [a, b] = this.anchors;
|
|
2299
2882
|
if (!a || !b) return null;
|
|
2300
2883
|
const parts = [];
|
|
2301
2884
|
const dPrice = b.price - a.price;
|
|
2302
|
-
if (this.props.showPriceRange) {
|
|
2303
|
-
parts.push(`${dPrice >= 0 ? "+" : ""}${this.formatPrice(dPrice)}`);
|
|
2304
|
-
}
|
|
2885
|
+
if (this.props.showPriceRange) parts.push(`${dPrice >= 0 ? "+" : ""}${this.formatPrice(dPrice)}`);
|
|
2305
2886
|
if (this.props.showPercentChange) {
|
|
2306
2887
|
const pct = a.price !== 0 ? dPrice / Math.abs(a.price) * 100 : 0;
|
|
2307
2888
|
parts.push(`${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`);
|
|
@@ -2319,12 +2900,7 @@ var TrendLine = class extends Drawing {
|
|
|
2319
2900
|
}
|
|
2320
2901
|
if (this.props.showDateTimeRange) {
|
|
2321
2902
|
const secs = Math.abs(Number(b.time) - Number(a.time));
|
|
2322
|
-
if (Number.isFinite(secs) && secs > 0)
|
|
2323
|
-
const d = Math.floor(secs / 86400);
|
|
2324
|
-
const h = Math.floor(secs % 86400 / 3600);
|
|
2325
|
-
const m = Math.floor(secs % 3600 / 60);
|
|
2326
|
-
parts.push(d > 0 ? `${d}d ${h}h` : h > 0 ? `${h}h ${m}m` : `${m}m`);
|
|
2327
|
-
}
|
|
2903
|
+
if (Number.isFinite(secs) && secs > 0) parts.push(spanText(secs));
|
|
2328
2904
|
}
|
|
2329
2905
|
if (this.props.showAngle) {
|
|
2330
2906
|
const [pa, pb] = this.anchorPixels(viewport);
|
|
@@ -2332,14 +2908,6 @@ var TrendLine = class extends Drawing {
|
|
|
2332
2908
|
}
|
|
2333
2909
|
return parts.length ? parts.join(" \xB7 ") : null;
|
|
2334
2910
|
}
|
|
2335
|
-
/** Extra ink beyond the shared prop set — the family variants override. */
|
|
2336
|
-
paintDecorations(_ctx, _viewport) {
|
|
2337
|
-
}
|
|
2338
|
-
testHit(point, viewport) {
|
|
2339
|
-
const seg = this.segment(viewport);
|
|
2340
|
-
if (!seg) return false;
|
|
2341
|
-
return distanceToSegment(point, seg.a, seg.b) <= hitTolerance(this.style.lineWidth);
|
|
2342
|
-
}
|
|
2343
2911
|
};
|
|
2344
2912
|
var Ray = class extends TrendLine {
|
|
2345
2913
|
type = "ray";
|
|
@@ -2358,6 +2926,9 @@ var Arrow = class extends TrendLine {
|
|
|
2358
2926
|
defaultProps() {
|
|
2359
2927
|
return { ...LINE_PROPS, rightEnd: "arrow" };
|
|
2360
2928
|
}
|
|
2929
|
+
cursorAt(point, viewport) {
|
|
2930
|
+
return super.cursorAt(point, viewport) ?? (this.state === "selected" ? "text" : null);
|
|
2931
|
+
}
|
|
2361
2932
|
};
|
|
2362
2933
|
var InfoLine = class extends TrendLine {
|
|
2363
2934
|
type = "info_line";
|
|
@@ -2369,15 +2940,16 @@ var InfoLine = class extends TrendLine {
|
|
|
2369
2940
|
showPipsChange: true,
|
|
2370
2941
|
showBarsRange: true,
|
|
2371
2942
|
showDateTimeRange: true,
|
|
2943
|
+
showDistance: true,
|
|
2372
2944
|
showAngle: true,
|
|
2373
2945
|
statsPosition: "center",
|
|
2374
2946
|
alwaysShowStats: true
|
|
2375
2947
|
};
|
|
2376
2948
|
}
|
|
2377
|
-
/** A format-2 info line that named no percent change or span showed neither. */
|
|
2949
|
+
/** A format-2 info line that named no percent change or span showed neither, and no distance. */
|
|
2378
2950
|
keepSavedLook(saved) {
|
|
2379
2951
|
super.keepSavedLook(saved);
|
|
2380
|
-
const props = {};
|
|
2952
|
+
const props = { showDistance: false };
|
|
2381
2953
|
if (!("showPercentChange" in saved)) props.showPercentChange = false;
|
|
2382
2954
|
if (!("showDateTimeRange" in saved)) props.showDateTimeRange = false;
|
|
2383
2955
|
this._props = { ...this._props, ...props };
|
|
@@ -2385,12 +2957,38 @@ var InfoLine = class extends TrendLine {
|
|
|
2385
2957
|
};
|
|
2386
2958
|
var TrendAngle = class extends TrendLine {
|
|
2387
2959
|
type = "trend_angle";
|
|
2388
|
-
|
|
2389
|
-
|
|
2960
|
+
invites() {
|
|
2961
|
+
return false;
|
|
2390
2962
|
}
|
|
2391
2963
|
paintDecorations(ctx, viewport) {
|
|
2392
2964
|
const [pa, pb] = this.anchorPixels(viewport);
|
|
2393
2965
|
if (!pa || !pb) return;
|
|
2966
|
+
if (this.props.savedLook) {
|
|
2967
|
+
this.paintSavedAngle(ctx, pa, pb);
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
const angle = angleOf(pa, pb);
|
|
2971
|
+
ctx.save();
|
|
2972
|
+
ctx.strokeStyle = this.style.lineColor;
|
|
2973
|
+
ctx.lineWidth = 1;
|
|
2974
|
+
ctx.lineCap = "butt";
|
|
2975
|
+
ctx.lineJoin = "miter";
|
|
2976
|
+
ctx.setLineDash([1, 2]);
|
|
2977
|
+
ctx.beginPath();
|
|
2978
|
+
ctx.moveTo(pa.x, pa.y);
|
|
2979
|
+
ctx.lineTo(pa.x + 50, pa.y);
|
|
2980
|
+
ctx.arc(pa.x, pa.y, 50, 0, angle, angle < 0);
|
|
2981
|
+
ctx.stroke();
|
|
2982
|
+
ctx.setLineDash([]);
|
|
2983
|
+
ctx.font = fontOf(this.style);
|
|
2984
|
+
ctx.fillStyle = this.style.lineColor;
|
|
2985
|
+
ctx.textAlign = "start";
|
|
2986
|
+
ctx.textBaseline = "middle";
|
|
2987
|
+
ctx.fillText(`${(-angle * 180 / Math.PI).toFixed(1)}\xB0`, pa.x + 58.5, pa.y + 1);
|
|
2988
|
+
ctx.restore();
|
|
2989
|
+
}
|
|
2990
|
+
/** A format-2 trend angle's mark: a short dotted level and arc, the angle on a pill. */
|
|
2991
|
+
paintSavedAngle(ctx, pa, pb) {
|
|
2394
2992
|
const angle = angleOf(pa, pb);
|
|
2395
2993
|
const angleDeg = -angle * (180 / Math.PI);
|
|
2396
2994
|
const radius = Math.min(36, Math.hypot(pb.x - pa.x, pb.y - pa.y) / 2);
|
|
@@ -2408,25 +3006,33 @@ var TrendAngle = class extends TrendLine {
|
|
|
2408
3006
|
ctx.stroke();
|
|
2409
3007
|
ctx.restore();
|
|
2410
3008
|
}
|
|
2411
|
-
paintLabel(ctx, `${angleDeg.toFixed(0)}\xB0`, { x: pa.x + radius + 18, y: pa.y }, this.style, {
|
|
2412
|
-
background: withAlpha("#1b1f27", 0.92)
|
|
2413
|
-
});
|
|
3009
|
+
paintLabel(ctx, `${angleDeg.toFixed(0)}\xB0`, { x: pa.x + radius + 18, y: pa.y }, this.style, { background: withAlpha("#1b1f27", 0.92) });
|
|
2414
3010
|
}
|
|
2415
3011
|
};
|
|
2416
|
-
var
|
|
3012
|
+
var levelLabel = (drawing2, coordinate, text, own) => {
|
|
3013
|
+
const selected = () => drawing2.state === "selected" || drawing2.state === "editing";
|
|
3014
|
+
const color = () => own() ? drawing2.style.lineColor : drawing2.inks().accent;
|
|
3015
|
+
return new AxisLabel({
|
|
3016
|
+
coordinate,
|
|
3017
|
+
text,
|
|
3018
|
+
color,
|
|
3019
|
+
textColor: () => inkOn(color()),
|
|
3020
|
+
visible: () => (own() || selected()) && drawing2.isVisibleNow()
|
|
3021
|
+
});
|
|
3022
|
+
};
|
|
3023
|
+
var HorizontalLine = class extends LineWords {
|
|
2417
3024
|
type = "horizontal_line";
|
|
2418
3025
|
_axisViews = [
|
|
2419
|
-
|
|
2420
|
-
|
|
3026
|
+
levelLabel(
|
|
3027
|
+
this,
|
|
3028
|
+
() => {
|
|
2421
3029
|
const anchor = this.anchors[0];
|
|
2422
3030
|
const viewport = this.getViewport();
|
|
2423
|
-
|
|
2424
|
-
return viewport.yOf(anchor.price);
|
|
3031
|
+
return anchor && viewport ? viewport.yOf(anchor.price) : null;
|
|
2425
3032
|
},
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
})
|
|
3033
|
+
() => this.formatPrice(this.anchors[0]?.price ?? 0),
|
|
3034
|
+
() => this.props.showPrice
|
|
3035
|
+
)
|
|
2430
3036
|
];
|
|
2431
3037
|
defaultProps() {
|
|
2432
3038
|
return { text: "", showPrice: true, textVAlign: "middle", textHAlign: "center" };
|
|
@@ -2434,21 +3040,56 @@ var HorizontalLine = class extends Drawing {
|
|
|
2434
3040
|
axisViews() {
|
|
2435
3041
|
return this._axisViews;
|
|
2436
3042
|
}
|
|
3043
|
+
axisSpans() {
|
|
3044
|
+
return { prices: this.anchors.slice(0, 1).map((a) => a.price), times: null };
|
|
3045
|
+
}
|
|
2437
3046
|
requiredAnchors() {
|
|
2438
3047
|
return 1;
|
|
2439
3048
|
}
|
|
2440
|
-
|
|
3049
|
+
handleShape() {
|
|
3050
|
+
return "square";
|
|
3051
|
+
}
|
|
3052
|
+
handleCursorName() {
|
|
3053
|
+
return "ns-resize";
|
|
3054
|
+
}
|
|
3055
|
+
/** The square handle stands nine tenths of the way across the pane at the level. */
|
|
3056
|
+
getControlPoints(viewport) {
|
|
3057
|
+
const y = this.levelY(viewport);
|
|
3058
|
+
return y === null ? [] : [{ index: 0, x: viewport.width * LEVEL_HANDLE_AT, y }];
|
|
3059
|
+
}
|
|
3060
|
+
/** The level's height on the pane, or null off the scale. */
|
|
3061
|
+
levelY(viewport) {
|
|
2441
3062
|
const y = viewport.yOf(this.anchors[0]?.price ?? NaN);
|
|
2442
|
-
|
|
2443
|
-
|
|
3063
|
+
return y === null || !Number.isFinite(y) ? null : y;
|
|
3064
|
+
}
|
|
3065
|
+
paint(ctx, viewport) {
|
|
3066
|
+
const level = this.levelY(viewport);
|
|
3067
|
+
if (level === null) return;
|
|
3068
|
+
const y = crisp(level, this.style.lineWidth);
|
|
2444
3069
|
applyStroke(ctx, this.style);
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
3070
|
+
ctx.lineCap = this.levelCap();
|
|
3071
|
+
ctx.lineJoin = "miter";
|
|
3072
|
+
strokeSegment(ctx, { x: this.leftEdge(viewport), y }, { x: viewport.width, y });
|
|
3073
|
+
this.paintWords(ctx, viewport);
|
|
3074
|
+
}
|
|
3075
|
+
/** The cap the line's ends wear: square across the pane's edges, round at a ray's point. */
|
|
3076
|
+
levelCap() {
|
|
3077
|
+
return "butt";
|
|
3078
|
+
}
|
|
3079
|
+
/** The words stand above, on or below the drawn line, in from its left end, at its middle, or in
|
|
3080
|
+
* from the pane's right edge. */
|
|
3081
|
+
wordsPlace(viewport) {
|
|
3082
|
+
const level = this.levelY(viewport);
|
|
3083
|
+
if (level === null) return null;
|
|
3084
|
+
const y = crisp(level, this.style.lineWidth);
|
|
3085
|
+
const start = this.leftEdge(viewport);
|
|
3086
|
+
const along = this.props.textHAlign;
|
|
3087
|
+
const x = along === "left" ? start + LEVEL_WORDS_INSET : along === "right" ? viewport.width - LEVEL_WORDS_INSET : (start + viewport.width) / 2;
|
|
3088
|
+
const across = this.props.textVAlign === "top" ? -wordsOffset(this.style.fontSize) : this.props.textVAlign === "bottom" ? wordsOffset(this.style.fontSize) : 0;
|
|
3089
|
+
return { x, y: y + across, angle: 0, align: along };
|
|
3090
|
+
}
|
|
3091
|
+
wordsAcross() {
|
|
3092
|
+
return this.props.textVAlign;
|
|
2452
3093
|
}
|
|
2453
3094
|
/** Where the line starts; the ray variant starts at its anchor. */
|
|
2454
3095
|
leftEdge(_viewport) {
|
|
@@ -2459,16 +3100,53 @@ var HorizontalLine = class extends Drawing {
|
|
|
2459
3100
|
this._props = { ...this._props, textVAlign: "top", textHAlign: "left" };
|
|
2460
3101
|
}
|
|
2461
3102
|
testHit(point, viewport) {
|
|
2462
|
-
const y =
|
|
3103
|
+
const y = this.levelY(viewport);
|
|
2463
3104
|
if (y === null) return false;
|
|
2464
|
-
|
|
3105
|
+
const onLine = point.x >= this.leftEdge(viewport) - 4 && Math.abs(point.y - y) <= hitTolerance(this.style.lineWidth);
|
|
3106
|
+
return onLine || this.hitWords(point, viewport);
|
|
2465
3107
|
}
|
|
2466
3108
|
};
|
|
2467
3109
|
var HorizontalRay = class extends HorizontalLine {
|
|
2468
3110
|
type = "horizontal_ray";
|
|
3111
|
+
_rayTimeViews = [
|
|
3112
|
+
pointLabel(
|
|
3113
|
+
this,
|
|
3114
|
+
() => {
|
|
3115
|
+
const anchor = this.anchors[0];
|
|
3116
|
+
const viewport = this.getViewport();
|
|
3117
|
+
return anchor && viewport ? viewport.xOf(anchor.time) : null;
|
|
3118
|
+
},
|
|
3119
|
+
() => this.formatTime(this.anchors[0]?.time),
|
|
3120
|
+
() => (this.state === "selected" || this.state === "editing") && this.isVisibleNow()
|
|
3121
|
+
)
|
|
3122
|
+
];
|
|
2469
3123
|
defaultProps() {
|
|
2470
3124
|
return { text: "", showPrice: true, textVAlign: "bottom", textHAlign: "center" };
|
|
2471
3125
|
}
|
|
3126
|
+
timeViews() {
|
|
3127
|
+
return this._rayTimeViews;
|
|
3128
|
+
}
|
|
3129
|
+
axisSpans() {
|
|
3130
|
+
const anchor = this.anchors[0];
|
|
3131
|
+
return { prices: anchor ? [anchor.price] : null, times: anchor ? [anchor.time] : null };
|
|
3132
|
+
}
|
|
3133
|
+
handleShape() {
|
|
3134
|
+
return "circle";
|
|
3135
|
+
}
|
|
3136
|
+
handleCursorName() {
|
|
3137
|
+
return "default";
|
|
3138
|
+
}
|
|
3139
|
+
getControlPoints(viewport) {
|
|
3140
|
+
const anchor = this.anchors[0];
|
|
3141
|
+
const p = anchor ? this.anchorToPixel(anchor, viewport) : null;
|
|
3142
|
+
return p ? [{ index: 0, x: p.x, y: p.y }] : [];
|
|
3143
|
+
}
|
|
3144
|
+
invites() {
|
|
3145
|
+
return false;
|
|
3146
|
+
}
|
|
3147
|
+
levelCap() {
|
|
3148
|
+
return "round";
|
|
3149
|
+
}
|
|
2472
3150
|
leftEdge(viewport) {
|
|
2473
3151
|
const anchor = this.anchors[0];
|
|
2474
3152
|
if (!anchor) return 0;
|
|
@@ -2476,27 +3154,19 @@ var HorizontalRay = class extends HorizontalLine {
|
|
|
2476
3154
|
return p ? p.x : 0;
|
|
2477
3155
|
}
|
|
2478
3156
|
};
|
|
2479
|
-
|
|
2480
|
-
const t = Number(time);
|
|
2481
|
-
if (!Number.isFinite(t)) return String(time);
|
|
2482
|
-
const d = new Date(t * 1e3);
|
|
2483
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
2484
|
-
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
2485
|
-
}
|
|
2486
|
-
var VerticalLine = class extends Drawing {
|
|
3157
|
+
var VerticalLine = class extends LineWords {
|
|
2487
3158
|
type = "vertical_line";
|
|
2488
3159
|
_timeViews = [
|
|
2489
|
-
|
|
2490
|
-
|
|
3160
|
+
levelLabel(
|
|
3161
|
+
this,
|
|
3162
|
+
() => {
|
|
2491
3163
|
const anchor = this.anchors[0];
|
|
2492
3164
|
const viewport = this.getViewport();
|
|
2493
|
-
|
|
2494
|
-
return viewport.xOf(anchor.time);
|
|
3165
|
+
return anchor && viewport ? viewport.xOf(anchor.time) : null;
|
|
2495
3166
|
},
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
})
|
|
3167
|
+
() => this.formatTime(this.anchors[0]?.time),
|
|
3168
|
+
() => this.props.showTime
|
|
3169
|
+
)
|
|
2500
3170
|
];
|
|
2501
3171
|
defaultProps() {
|
|
2502
3172
|
return { text: "", showTime: true, textVAlign: "middle", textHAlign: "center", textOrientation: "vertical" };
|
|
@@ -2504,76 +3174,94 @@ var VerticalLine = class extends Drawing {
|
|
|
2504
3174
|
timeViews() {
|
|
2505
3175
|
return this._timeViews;
|
|
2506
3176
|
}
|
|
3177
|
+
axisSpans() {
|
|
3178
|
+
return { prices: null, times: this.anchors.slice(0, 1).map((a) => a.time) };
|
|
3179
|
+
}
|
|
2507
3180
|
requiredAnchors() {
|
|
2508
3181
|
return 1;
|
|
2509
3182
|
}
|
|
3183
|
+
handleShape() {
|
|
3184
|
+
return "square";
|
|
3185
|
+
}
|
|
3186
|
+
handleCursorName() {
|
|
3187
|
+
return "ew-resize";
|
|
3188
|
+
}
|
|
3189
|
+
/** The square handle stands on the line nine tenths of the way down the pane. */
|
|
3190
|
+
getControlPoints(viewport) {
|
|
3191
|
+
const x = this.timeX(viewport);
|
|
3192
|
+
return x === null ? [] : [{ index: 0, x, y: viewport.height * LEVEL_HANDLE_AT }];
|
|
3193
|
+
}
|
|
3194
|
+
timeX(viewport) {
|
|
3195
|
+
const anchor = this.anchors[0];
|
|
3196
|
+
return anchor ? viewport.xOf(anchor.time) : null;
|
|
3197
|
+
}
|
|
2510
3198
|
/** A format-2 line's label read across it, to its right at the top of the pane. */
|
|
2511
3199
|
keepSavedLook(_saved) {
|
|
2512
3200
|
this._props = { ...this._props, textOrientation: "horizontal", textVAlign: "top", textHAlign: "right" };
|
|
2513
3201
|
}
|
|
2514
3202
|
paint(ctx, viewport) {
|
|
2515
|
-
const
|
|
2516
|
-
if (
|
|
2517
|
-
const x =
|
|
2518
|
-
if (x === null) return;
|
|
3203
|
+
const time = this.timeX(viewport);
|
|
3204
|
+
if (time === null) return;
|
|
3205
|
+
const x = crisp(time, this.style.lineWidth);
|
|
2519
3206
|
applyStroke(ctx, this.style);
|
|
3207
|
+
ctx.lineCap = "butt";
|
|
3208
|
+
ctx.lineJoin = "miter";
|
|
2520
3209
|
strokeSegment(ctx, { x, y: 0 }, { x, y: viewport.height });
|
|
2521
|
-
|
|
2522
|
-
}
|
|
2523
|
-
/** The
|
|
2524
|
-
* a quarter left, so the line's top is where
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
const
|
|
2528
|
-
if (
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
|
|
3210
|
+
this.paintWords(ctx, viewport);
|
|
3211
|
+
}
|
|
3212
|
+
/** The words stand at the top, the middle or the bottom of the pane, to the line's left, on it or
|
|
3213
|
+
* to its right. Running up the line they turn a quarter left, so the line's top is where they
|
|
3214
|
+
* end; reading across it they stand level. */
|
|
3215
|
+
wordsPlace(viewport) {
|
|
3216
|
+
const time = this.timeX(viewport);
|
|
3217
|
+
if (time === null) return null;
|
|
3218
|
+
const x = crisp(time, this.style.lineWidth);
|
|
3219
|
+
const { textVAlign: along, textHAlign: across, textOrientation } = this.props;
|
|
3220
|
+
const off = wordsOffset(this.style.fontSize);
|
|
3221
|
+
if (textOrientation === "horizontal") {
|
|
3222
|
+
const y2 = along === "top" ? off : along === "bottom" ? viewport.height - off : viewport.height / 2;
|
|
3223
|
+
const at2 = across === "left" ? x - 4 : across === "right" ? x + 4 : x;
|
|
3224
|
+
return { x: at2, y: y2, angle: 0, align: across === "left" ? "right" : across === "right" ? "left" : "center" };
|
|
3225
|
+
}
|
|
3226
|
+
const y = along === "top" ? LEVEL_WORDS_INSET : along === "bottom" ? viewport.height - LEVEL_WORDS_INSET : viewport.height / 2;
|
|
3227
|
+
const at = across === "left" ? x - off : across === "right" ? x + off : x;
|
|
3228
|
+
return { x: at, y, angle: -Math.PI / 2, align: along === "top" ? "right" : along === "bottom" ? "left" : "center" };
|
|
3229
|
+
}
|
|
3230
|
+
wordsAcross() {
|
|
3231
|
+
if (this.props.textOrientation === "horizontal") return this.props.textVAlign === "top" ? "bottom" : this.props.textVAlign === "bottom" ? "top" : "middle";
|
|
3232
|
+
return this.props.textHAlign === "left" ? "top" : this.props.textHAlign === "right" ? "bottom" : "middle";
|
|
2541
3233
|
}
|
|
2542
3234
|
testHit(point, viewport) {
|
|
2543
|
-
const
|
|
2544
|
-
if (
|
|
2545
|
-
|
|
2546
|
-
if (x === null) return false;
|
|
2547
|
-
return Math.abs(point.x - x) <= hitTolerance(this.style.lineWidth);
|
|
3235
|
+
const time = this.timeX(viewport);
|
|
3236
|
+
if (time === null) return false;
|
|
3237
|
+
return Math.abs(point.x - time) <= hitTolerance(this.style.lineWidth) || this.hitWords(point, viewport);
|
|
2548
3238
|
}
|
|
2549
3239
|
};
|
|
2550
3240
|
var CrossLine = class extends Drawing {
|
|
2551
3241
|
type = "cross_line";
|
|
2552
3242
|
_axisViews = [
|
|
2553
|
-
|
|
2554
|
-
|
|
3243
|
+
levelLabel(
|
|
3244
|
+
this,
|
|
3245
|
+
() => {
|
|
2555
3246
|
const anchor = this.anchors[0];
|
|
2556
3247
|
const viewport = this.getViewport();
|
|
2557
|
-
|
|
2558
|
-
return viewport.yOf(anchor.price);
|
|
3248
|
+
return anchor && viewport ? viewport.yOf(anchor.price) : null;
|
|
2559
3249
|
},
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
})
|
|
3250
|
+
() => this.formatPrice(this.anchors[0]?.price ?? 0),
|
|
3251
|
+
() => this.props.showPrice
|
|
3252
|
+
)
|
|
2564
3253
|
];
|
|
2565
3254
|
_timeViews = [
|
|
2566
|
-
|
|
2567
|
-
|
|
3255
|
+
levelLabel(
|
|
3256
|
+
this,
|
|
3257
|
+
() => {
|
|
2568
3258
|
const anchor = this.anchors[0];
|
|
2569
3259
|
const viewport = this.getViewport();
|
|
2570
|
-
|
|
2571
|
-
return viewport.xOf(anchor.time);
|
|
3260
|
+
return anchor && viewport ? viewport.xOf(anchor.time) : null;
|
|
2572
3261
|
},
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
})
|
|
3262
|
+
() => this.formatTime(this.anchors[0]?.time),
|
|
3263
|
+
() => this.props.showTime
|
|
3264
|
+
)
|
|
2577
3265
|
];
|
|
2578
3266
|
defaultProps() {
|
|
2579
3267
|
return { showPrice: true, showTime: true };
|
|
@@ -2584,17 +3272,30 @@ var CrossLine = class extends Drawing {
|
|
|
2584
3272
|
timeViews() {
|
|
2585
3273
|
return this._timeViews;
|
|
2586
3274
|
}
|
|
3275
|
+
axisSpans() {
|
|
3276
|
+
const anchor = this.anchors[0];
|
|
3277
|
+
return { prices: anchor ? [anchor.price] : null, times: anchor ? [anchor.time] : null };
|
|
3278
|
+
}
|
|
2587
3279
|
requiredAnchors() {
|
|
2588
3280
|
return 1;
|
|
2589
3281
|
}
|
|
3282
|
+
handlesOnHover() {
|
|
3283
|
+
return true;
|
|
3284
|
+
}
|
|
3285
|
+
cursorAt(point, viewport) {
|
|
3286
|
+
return this.getControlPoints(viewport).some((p) => Math.hypot(p.x - point.x, p.y - point.y) <= 6.5) ? "default" : null;
|
|
3287
|
+
}
|
|
2590
3288
|
paint(ctx, viewport) {
|
|
2591
3289
|
const anchor = this.anchors[0];
|
|
2592
3290
|
if (!anchor) return;
|
|
2593
3291
|
const p = this.anchorToPixel(anchor, viewport);
|
|
2594
3292
|
if (!p) return;
|
|
3293
|
+
const width = this.style.lineWidth;
|
|
2595
3294
|
applyStroke(ctx, this.style);
|
|
2596
|
-
|
|
2597
|
-
|
|
3295
|
+
ctx.lineCap = "butt";
|
|
3296
|
+
ctx.lineJoin = "miter";
|
|
3297
|
+
strokeSegment(ctx, { x: 0, y: crisp(p.y, width) }, { x: viewport.width, y: crisp(p.y, width) });
|
|
3298
|
+
strokeSegment(ctx, { x: crisp(p.x, width), y: 0 }, { x: crisp(p.x, width), y: viewport.height });
|
|
2598
3299
|
}
|
|
2599
3300
|
testHit(point, viewport) {
|
|
2600
3301
|
const anchor = this.anchors[0];
|
|
@@ -3153,77 +3854,6 @@ function volumeProfile(bars, rows) {
|
|
|
3153
3854
|
return bins;
|
|
3154
3855
|
}
|
|
3155
3856
|
|
|
3156
|
-
// src/internal/drawings/core/savedLook.ts
|
|
3157
|
-
function endSavedLook(patch) {
|
|
3158
|
-
return "savedLook" in patch || Object.keys(patch).length === 0 ? patch : { ...patch, savedLook: null };
|
|
3159
|
-
}
|
|
3160
|
-
function savedLevels(look, key = "levels") {
|
|
3161
|
-
const levels = look[key];
|
|
3162
|
-
if (!Array.isArray(levels)) return [];
|
|
3163
|
-
return levels.filter((l) => !!l && typeof l === "object" && typeof l.value === "number").map((l) => ({
|
|
3164
|
-
value: l.value,
|
|
3165
|
-
visible: l.visible !== false,
|
|
3166
|
-
...typeof l.color === "string" ? { color: l.color } : {},
|
|
3167
|
-
...typeof l.text === "string" ? { text: l.text } : {}
|
|
3168
|
-
}));
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
// src/internal/drawings/render/textEntry.ts
|
|
3172
|
-
var SELECTION_WASH = "rgba(255, 255, 255, 0.2)";
|
|
3173
|
-
var CARET_WIDTH = 2;
|
|
3174
|
-
var lineMiddle = (lineHeight) => lineHeight / 2 + 1;
|
|
3175
|
-
var caretHeight = (font, lineHeight) => Math.min(lineHeight, Number(/(\d+(?:\.\d+)?)px/.exec(font)?.[1]) || lineHeight);
|
|
3176
|
-
function paintTextEntry(ctx, p) {
|
|
3177
|
-
const draft = p.draft;
|
|
3178
|
-
const shown = p.placeholder ? p.placeholder.block : p.block;
|
|
3179
|
-
const lineTop = (i) => p.y + i * p.lineHeight;
|
|
3180
|
-
const left = (block, i) => p.x + lineLeft(block, i, p.align, p.width);
|
|
3181
|
-
ctx.save();
|
|
3182
|
-
ctx.setLineDash([]);
|
|
3183
|
-
ctx.direction = "ltr";
|
|
3184
|
-
if (draft && draft.selectionStart !== draft.selectionEnd && !p.placeholder) {
|
|
3185
|
-
ctx.fillStyle = SELECTION_WASH;
|
|
3186
|
-
for (const span of runSpans(p.block, draft.selectionStart, draft.selectionEnd, p.measure)) {
|
|
3187
|
-
const from = Math.round(left(p.block, span.line) + span.x0);
|
|
3188
|
-
const to = Math.ceil(left(p.block, span.line) + span.x1);
|
|
3189
|
-
if (to > from) ctx.fillRect(from, lineTop(span.line), to - from, p.lineHeight);
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
ctx.font = p.font;
|
|
3193
|
-
ctx.fillStyle = p.color;
|
|
3194
|
-
ctx.textAlign = "left";
|
|
3195
|
-
ctx.textBaseline = "middle";
|
|
3196
|
-
const alpha = ctx.globalAlpha;
|
|
3197
|
-
if (p.placeholder) ctx.globalAlpha = alpha * p.placeholder.alpha;
|
|
3198
|
-
shown.lines.forEach((l, i) => {
|
|
3199
|
-
if (l.text === "") return;
|
|
3200
|
-
ctx.fillText(l.text, left(shown, i), lineTop(i) + lineMiddle(p.lineHeight));
|
|
3201
|
-
});
|
|
3202
|
-
ctx.globalAlpha = alpha;
|
|
3203
|
-
if (draft?.composition && !p.placeholder) {
|
|
3204
|
-
for (const span of runSpans(p.block, draft.composition.start, draft.composition.end, p.measure)) {
|
|
3205
|
-
const from = Math.round(left(p.block, span.line) + span.x0);
|
|
3206
|
-
const to = Math.ceil(left(p.block, span.line) + span.x1);
|
|
3207
|
-
if (to > from) ctx.fillRect(from, lineTop(span.line) + p.lineHeight - 1, to - from, 1);
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
if (draft?.caret && draft.selectionStart === draft.selectionEnd) {
|
|
3211
|
-
const at = caretPlace(p.block, draft.selectionEnd, p.measure);
|
|
3212
|
-
const tall = caretHeight(p.font, p.lineHeight);
|
|
3213
|
-
ctx.fillRect(Math.round(left(p.block, at.line) + at.x) - CARET_WIDTH / 2, lineTop(at.line) + Math.round((p.lineHeight - tall) / 2), CARET_WIDTH, tall);
|
|
3214
|
-
}
|
|
3215
|
-
ctx.restore();
|
|
3216
|
-
}
|
|
3217
|
-
function paintWordsFrame(ctx, box2, color, alpha) {
|
|
3218
|
-
ctx.save();
|
|
3219
|
-
ctx.setLineDash([]);
|
|
3220
|
-
ctx.globalAlpha = ctx.globalAlpha * alpha;
|
|
3221
|
-
ctx.strokeStyle = color;
|
|
3222
|
-
ctx.lineWidth = 2;
|
|
3223
|
-
ctx.strokeRect(box2.x - 1, box2.y - 1, box2.width + 2, box2.height + 2);
|
|
3224
|
-
ctx.restore();
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
3857
|
// src/internal/drawings/render/glyphArtwork.ts
|
|
3228
3858
|
var glyphImages = /* @__PURE__ */ new Map();
|
|
3229
3859
|
function glyphArtwork(url, onLoad) {
|
|
@@ -3528,6 +4158,10 @@ var Note = class extends Drawing {
|
|
|
3528
4158
|
const at = this.place(viewport);
|
|
3529
4159
|
return !!at && inBox(point, at.box);
|
|
3530
4160
|
}
|
|
4161
|
+
/** Over the selected note's box the cursor reads as its words. */
|
|
4162
|
+
cursorAt(point, viewport) {
|
|
4163
|
+
return this.state === "selected" && this.wordsAt(point, viewport) ? "text" : null;
|
|
4164
|
+
}
|
|
3531
4165
|
/** An empty note shows its placeholder in its own box, so it needs no hint above it. */
|
|
3532
4166
|
paintTextHint() {
|
|
3533
4167
|
}
|
|
@@ -4892,7 +5526,7 @@ var RangeMeter = class extends Drawing {
|
|
|
4892
5526
|
}
|
|
4893
5527
|
if (this.props.showDateTimeRange) {
|
|
4894
5528
|
const secs = Number(b.time) - Number(a.time);
|
|
4895
|
-
if (Number.isFinite(secs) && secs !== 0) out.push(
|
|
5529
|
+
if (Number.isFinite(secs) && secs !== 0) out.push(spanText2(Math.abs(secs)));
|
|
4896
5530
|
}
|
|
4897
5531
|
if (this.props.showVolume) {
|
|
4898
5532
|
const range = barsInRange(this.bars(), a.time, b.time);
|
|
@@ -5096,7 +5730,7 @@ var Measure = class extends Drawing {
|
|
|
5096
5730
|
const secs = Math.abs(Number(b.time) - Number(a.time));
|
|
5097
5731
|
const line22 = [];
|
|
5098
5732
|
if (bars !== null) line22.push(`${Math.abs(Math.round(bars))} bars`);
|
|
5099
|
-
if (Number.isFinite(secs) && secs > 0) line22.push(
|
|
5733
|
+
if (Number.isFinite(secs) && secs > 0) line22.push(spanText2(secs));
|
|
5100
5734
|
const lines = line22.length ? [parts[0], line22.join(", ")] : [parts[0]];
|
|
5101
5735
|
ctx.font = `600 ${this.style.fontSize}px ui-sans-serif, system-ui, sans-serif`;
|
|
5102
5736
|
ctx.textAlign = "center";
|
|
@@ -5123,7 +5757,7 @@ var Measure = class extends Drawing {
|
|
|
5123
5757
|
return inBox2(point, box(pa, pb));
|
|
5124
5758
|
}
|
|
5125
5759
|
};
|
|
5126
|
-
function
|
|
5760
|
+
function spanText2(seconds) {
|
|
5127
5761
|
const d = Math.floor(seconds / 86400);
|
|
5128
5762
|
const h = Math.floor(seconds % 86400 / 3600);
|
|
5129
5763
|
const m = Math.floor(seconds % 3600 / 60);
|
|
@@ -9785,18 +10419,19 @@ var TOOL_CATEGORIES = [
|
|
|
9785
10419
|
"annotation",
|
|
9786
10420
|
"content"
|
|
9787
10421
|
];
|
|
10422
|
+
var LINE_WORDS = { inlineText: { clickToType: true, whenEmpty: "keep" }, settingsOnDoubleClick: true };
|
|
9788
10423
|
var DEFINITIONS = [
|
|
9789
10424
|
// Lines
|
|
9790
|
-
tool(TrendLine, { type: "trend_line", name: "Trend line", category: "lines", anchors: 2, style: LINE_LOOK }),
|
|
9791
|
-
tool(Ray, { type: "ray", name: "Ray", category: "lines", anchors: 2, style: LINE_LOOK }),
|
|
9792
|
-
tool(InfoLine, { type: "info_line", name: "Info line", category: "lines", anchors: 2, style: LINE_LOOK }),
|
|
9793
|
-
tool(ExtendedLine, { type: "extended", name: "Extended line", category: "lines", anchors: 2, style: LINE_LOOK }),
|
|
9794
|
-
tool(TrendAngle, { type: "trend_angle", name: "Trend angle", category: "lines", anchors: 2, style: { ...LINE_LOOK, fontSize: 12 } }),
|
|
9795
|
-
tool(HorizontalLine, { type: "horizontal_line", name: "Horizontal line", category: "lines", anchors: 1, style: { ...LINE_LOOK, fontSize: 12 } }),
|
|
9796
|
-
tool(HorizontalRay, { type: "horizontal_ray", name: "Horizontal ray", category: "lines", anchors: 1, style: { ...LINE_LOOK, fontSize: 12 } }),
|
|
9797
|
-
tool(VerticalLine, { type: "vertical_line", name: "Vertical line", category: "lines", anchors: 1, style: LINE_LOOK }),
|
|
9798
|
-
tool(CrossLine, { type: "cross_line", name: "Cross line", category: "lines", anchors: 1, style: LINE_LOOK }),
|
|
9799
|
-
tool(Arrow, { type: "arrow", name: "Arrow", category: "lines", anchors: 2, style: LINE_LOOK }),
|
|
10425
|
+
tool(TrendLine, { type: "trend_line", name: "Trend line", category: "lines", anchors: 2, style: LINE_LOOK, ...LINE_WORDS }),
|
|
10426
|
+
tool(Ray, { type: "ray", name: "Ray", category: "lines", anchors: 2, style: LINE_LOOK, ...LINE_WORDS }),
|
|
10427
|
+
tool(InfoLine, { type: "info_line", name: "Info line", category: "lines", anchors: 2, style: LINE_LOOK, ...LINE_WORDS }),
|
|
10428
|
+
tool(ExtendedLine, { type: "extended", name: "Extended line", category: "lines", anchors: 2, style: LINE_LOOK, ...LINE_WORDS }),
|
|
10429
|
+
tool(TrendAngle, { type: "trend_angle", name: "Trend angle", category: "lines", anchors: 2, settingsOnDoubleClick: true, style: { ...LINE_LOOK, fontSize: 12 } }),
|
|
10430
|
+
tool(HorizontalLine, { type: "horizontal_line", name: "Horizontal line", category: "lines", anchors: 1, style: { ...LINE_LOOK, fontSize: 12 }, ...LINE_WORDS }),
|
|
10431
|
+
tool(HorizontalRay, { type: "horizontal_ray", name: "Horizontal ray", category: "lines", anchors: 1, settingsOnDoubleClick: true, style: { ...LINE_LOOK, fontSize: 12 } }),
|
|
10432
|
+
tool(VerticalLine, { type: "vertical_line", name: "Vertical line", category: "lines", anchors: 1, style: LINE_LOOK, ...LINE_WORDS }),
|
|
10433
|
+
tool(CrossLine, { type: "cross_line", name: "Cross line", category: "lines", anchors: 1, settingsOnDoubleClick: true, style: LINE_LOOK }),
|
|
10434
|
+
tool(Arrow, { type: "arrow", name: "Arrow", category: "lines", anchors: 2, style: LINE_LOOK, ...LINE_WORDS }),
|
|
9800
10435
|
// Shapes
|
|
9801
10436
|
tool(Rectangle, { type: "rectangle", name: "Rectangle", category: "shapes", anchors: 2, style: shapeLook("#9c27b0") }),
|
|
9802
10437
|
tool(RotatedRectangle, { type: "rotated_rectangle", name: "Rotated rectangle", category: "shapes", anchors: 3, style: shapeLook("#4caf50") }),
|
|
@@ -11256,6 +11891,12 @@ var SAVED_LOOK_PROPS = {
|
|
|
11256
11891
|
bars_pattern: ["candles"],
|
|
11257
11892
|
fixed_range_volume_profile: PROFILE,
|
|
11258
11893
|
anchored_volume_profile: PROFILE,
|
|
11894
|
+
trend_line: SAVED_LOOK,
|
|
11895
|
+
ray: SAVED_LOOK,
|
|
11896
|
+
info_line: SAVED_LOOK,
|
|
11897
|
+
extended: SAVED_LOOK,
|
|
11898
|
+
trend_angle: SAVED_LOOK,
|
|
11899
|
+
arrow: SAVED_LOOK,
|
|
11259
11900
|
callout: ["borderWidth", ...SAVED_LOOK],
|
|
11260
11901
|
price_label: SAVED_LOOK,
|
|
11261
11902
|
pin: SAVED_LOOK,
|
|
@@ -11275,7 +11916,7 @@ var SAVED_LOOK_PROPS = {
|
|
|
11275
11916
|
};
|
|
11276
11917
|
var INERT_PROPS = {
|
|
11277
11918
|
...SAVED_LOOK_PROPS,
|
|
11278
|
-
trend_angle: ["text", "leftEnd", "rightEnd", "showDateTimeRange", "showAngle"],
|
|
11919
|
+
trend_angle: ["text", "leftEnd", "rightEnd", "showDateTimeRange", "showDistance", "showAngle", ...SAVED_LOOK],
|
|
11279
11920
|
table: ["cells", "headerRow", ...SAVED_LOOK]
|
|
11280
11921
|
};
|
|
11281
11922
|
var NO_COORDINATES_TAB = /* @__PURE__ */ new Set(["rotated_rectangle", "arc", "polyline", "ellipse", "fib_wedge", "flat_top_bottom", "disjoint_channel", "sector", "bars_pattern", "long_position", "short_position", "anchored_vwap", "text", "table", "brush", "highlighter", "path", "emoji", "sticker", "icon", "image"]);
|
|
@@ -11319,6 +11960,7 @@ export {
|
|
|
11319
11960
|
visibleAt,
|
|
11320
11961
|
DEFAULT_STYLE,
|
|
11321
11962
|
DEFAULT_OPTIONS,
|
|
11963
|
+
SERIAL_VERSION,
|
|
11322
11964
|
drawingInksOf,
|
|
11323
11965
|
alphaOf,
|
|
11324
11966
|
withAlpha,
|
|
@@ -11446,4 +12088,4 @@ export {
|
|
|
11446
12088
|
fitScale,
|
|
11447
12089
|
fittedSize
|
|
11448
12090
|
};
|
|
11449
|
-
//# sourceMappingURL=chunk-
|
|
12091
|
+
//# sourceMappingURL=chunk-TBOVLJAK.js.map
|