hyperprop-charting-library 0.1.134 → 0.1.136

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.
@@ -2940,8 +2940,11 @@ function createChart(element, options = {}) {
2940
2940
  );
2941
2941
  let activeDrawingTool = null;
2942
2942
  let draftDrawing = null;
2943
+ let measureState = null;
2943
2944
  let drawingDragState = null;
2944
2945
  let drawingsChangeHandler = null;
2946
+ let drawingToolChangeHandler = null;
2947
+ let lastNotifiedDrawingTool = null;
2945
2948
  let drawingSelectHandler = null;
2946
2949
  let drawingDoubleClickHandler = null;
2947
2950
  let drawingEditTextHandler = null;
@@ -3534,6 +3537,13 @@ function createChart(element, options = {}) {
3534
3537
  points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
3535
3538
  };
3536
3539
  }
3540
+ if (measureState) {
3541
+ measureState = {
3542
+ ...measureState,
3543
+ start: reanchorDrawingPoint(measureState.start),
3544
+ end: reanchorDrawingPoint(measureState.end)
3545
+ };
3546
+ }
3537
3547
  };
3538
3548
  const getCandleDirectionByIndex = (index) => {
3539
3549
  const point = data[index];
@@ -3635,6 +3645,22 @@ function createChart(element, options = {}) {
3635
3645
  const pad = (value) => String(value).padStart(2, "0");
3636
3646
  return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
3637
3647
  };
3648
+ const formatTimeSpan = (ms) => {
3649
+ const totalMinutes = Math.max(0, Math.round(ms / 6e4));
3650
+ if (totalMinutes < 1) {
3651
+ return `${Math.max(1, Math.round(ms / 1e3))}s`;
3652
+ }
3653
+ const days = Math.floor(totalMinutes / 1440);
3654
+ const hours = Math.floor(totalMinutes % 1440 / 60);
3655
+ const minutes = totalMinutes % 60;
3656
+ if (days > 0) {
3657
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
3658
+ }
3659
+ if (hours > 0) {
3660
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
3661
+ }
3662
+ return `${minutes}m`;
3663
+ };
3638
3664
  const getRightAxisLabelX = (chartRight) => chartRight + 1;
3639
3665
  const getRightAxisLabelWidth = (chartRight, contentWidth) => {
3640
3666
  const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
@@ -4003,6 +4029,10 @@ function createChart(element, options = {}) {
4003
4029
  let pendingDrawUpdateAutoScale = false;
4004
4030
  let pendingDrawOverlayOnly = true;
4005
4031
  const scheduleDraw = (options2 = {}) => {
4032
+ if (activeDrawingTool !== lastNotifiedDrawingTool) {
4033
+ lastNotifiedDrawingTool = activeDrawingTool;
4034
+ drawingToolChangeHandler?.(activeDrawingTool);
4035
+ }
4006
4036
  armCountdownHeartbeat();
4007
4037
  const overlayOnly = options2.overlayOnly ?? false;
4008
4038
  pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
@@ -4758,6 +4788,216 @@ function createChart(element, options = {}) {
4758
4788
  drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
4759
4789
  }
4760
4790
  }
4791
+ } else if (drawing.type === "ellipse") {
4792
+ const p0 = drawing.points[0];
4793
+ const p1 = drawing.points[1];
4794
+ if (p0 && p1) {
4795
+ const px0 = xFromDrawingPoint(p0);
4796
+ const px1 = xFromDrawingPoint(p1);
4797
+ const y0 = yFromPrice(p0.price);
4798
+ const y1 = yFromPrice(p1.price);
4799
+ const cx = (px0 + px1) / 2;
4800
+ const cy = (y0 + y1) / 2;
4801
+ const rx = Math.max(1, Math.abs(px1 - px0) / 2);
4802
+ const ry = Math.max(1, Math.abs(y1 - y0) / 2);
4803
+ ctx.save();
4804
+ ctx.globalAlpha = draft ? 0.6 : 1;
4805
+ ctx.beginPath();
4806
+ ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
4807
+ ctx.fillStyle = hexToRgba(drawing.color, 0.14);
4808
+ ctx.fill();
4809
+ ctx.lineWidth = Math.max(1, drawing.width);
4810
+ ctx.strokeStyle = drawing.color;
4811
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
4812
+ ctx.stroke();
4813
+ ctx.restore();
4814
+ handleAt(px0, y0, drawing.color);
4815
+ handleAt(px1, y1, drawing.color);
4816
+ handleAt(px0, y1, drawing.color);
4817
+ handleAt(px1, y0, drawing.color);
4818
+ if (drawing.label) {
4819
+ drawDrawingLabel(drawing.label, cx, cy - ry - 4, drawing.color);
4820
+ }
4821
+ }
4822
+ } else if (drawing.type === "parallel-channel") {
4823
+ const p0 = drawing.points[0];
4824
+ const p1 = drawing.points[1];
4825
+ const p2 = drawing.points[2];
4826
+ if (p0 && p1) {
4827
+ const x0 = xFromDrawingPoint(p0);
4828
+ const y0 = yFromPrice(p0.price);
4829
+ const x1 = xFromDrawingPoint(p1);
4830
+ const y1 = yFromPrice(p1.price);
4831
+ ctx.beginPath();
4832
+ ctx.moveTo(x0, y0);
4833
+ ctx.lineTo(x1, y1);
4834
+ ctx.stroke();
4835
+ handleAt(x0, y0, drawing.color);
4836
+ handleAt(x1, y1, drawing.color);
4837
+ if (p2) {
4838
+ const indexSpan = p1.index - p0.index;
4839
+ const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
4840
+ const basePriceAtP2 = p0.price + slope * (p2.index - p0.index);
4841
+ const offset = p2.price - basePriceAtP2;
4842
+ const q0x = x0;
4843
+ const q0y = yFromPrice(p0.price + offset);
4844
+ const q1x = x1;
4845
+ const q1y = yFromPrice(p1.price + offset);
4846
+ ctx.save();
4847
+ ctx.globalAlpha = draft ? 0.5 : 1;
4848
+ ctx.fillStyle = hexToRgba(drawing.color, 0.08);
4849
+ ctx.beginPath();
4850
+ ctx.moveTo(x0, y0);
4851
+ ctx.lineTo(x1, y1);
4852
+ ctx.lineTo(q1x, q1y);
4853
+ ctx.lineTo(q0x, q0y);
4854
+ ctx.closePath();
4855
+ ctx.fill();
4856
+ ctx.restore();
4857
+ ctx.beginPath();
4858
+ ctx.moveTo(q0x, q0y);
4859
+ ctx.lineTo(q1x, q1y);
4860
+ ctx.stroke();
4861
+ ctx.save();
4862
+ ctx.setLineDash([4, 4]);
4863
+ ctx.globalAlpha = draft ? 0.5 : 0.7;
4864
+ ctx.beginPath();
4865
+ ctx.moveTo(x0, (y0 + q0y) / 2);
4866
+ ctx.lineTo(x1, (y1 + q1y) / 2);
4867
+ ctx.stroke();
4868
+ ctx.restore();
4869
+ handleAt(xFromDrawingPoint(p2), yFromPrice(p2.price), drawing.color);
4870
+ }
4871
+ if (drawing.label) {
4872
+ drawDrawingLabel(drawing.label, (x0 + x1) / 2, Math.min(y0, y1) - 4, drawing.color);
4873
+ }
4874
+ }
4875
+ } else if (drawing.type === "arrow") {
4876
+ const first = drawing.points[0];
4877
+ const second = drawing.points[1];
4878
+ if (first && second) {
4879
+ const x0 = xFromDrawingPoint(first);
4880
+ const y0 = yFromPrice(first.price);
4881
+ const x1 = xFromDrawingPoint(second);
4882
+ const y1 = yFromPrice(second.price);
4883
+ const angle = Math.atan2(y1 - y0, x1 - x0);
4884
+ const head = 5 + Math.max(1, drawing.width) * 3;
4885
+ const shaftX = x1 - Math.cos(angle) * head * 0.6;
4886
+ const shaftY = y1 - Math.sin(angle) * head * 0.6;
4887
+ ctx.beginPath();
4888
+ ctx.moveTo(x0, y0);
4889
+ ctx.lineTo(shaftX, shaftY);
4890
+ ctx.stroke();
4891
+ ctx.save();
4892
+ ctx.setLineDash([]);
4893
+ ctx.fillStyle = drawing.color;
4894
+ ctx.beginPath();
4895
+ ctx.moveTo(x1, y1);
4896
+ ctx.lineTo(x1 - Math.cos(angle - Math.PI / 7) * head, y1 - Math.sin(angle - Math.PI / 7) * head);
4897
+ ctx.lineTo(x1 - Math.cos(angle + Math.PI / 7) * head, y1 - Math.sin(angle + Math.PI / 7) * head);
4898
+ ctx.closePath();
4899
+ ctx.fill();
4900
+ ctx.restore();
4901
+ handleAt(x0, y0, drawing.color);
4902
+ handleAt(x1, y1, drawing.color);
4903
+ if (drawing.label) {
4904
+ const midX = (x0 + x1) / 2;
4905
+ const midY = (y0 + y1) / 2;
4906
+ drawDrawingLabel(drawing.label, midX, midY, drawing.color);
4907
+ }
4908
+ }
4909
+ } else if (drawing.type === "brush") {
4910
+ const pts = drawing.points;
4911
+ if (pts.length >= 2) {
4912
+ ctx.save();
4913
+ ctx.lineJoin = "round";
4914
+ ctx.lineCap = "round";
4915
+ ctx.beginPath();
4916
+ const startX = xFromDrawingPoint(pts[0]);
4917
+ const startY = yFromPrice(pts[0].price);
4918
+ ctx.moveTo(startX, startY);
4919
+ if (pts.length === 2) {
4920
+ ctx.lineTo(xFromDrawingPoint(pts[1]), yFromPrice(pts[1].price));
4921
+ } else {
4922
+ for (let i = 1; i < pts.length - 1; i += 1) {
4923
+ const cxp = xFromDrawingPoint(pts[i]);
4924
+ const cyp = yFromPrice(pts[i].price);
4925
+ const nx = xFromDrawingPoint(pts[i + 1]);
4926
+ const ny = yFromPrice(pts[i + 1].price);
4927
+ ctx.quadraticCurveTo(cxp, cyp, (cxp + nx) / 2, (cyp + ny) / 2);
4928
+ }
4929
+ const last = pts[pts.length - 1];
4930
+ ctx.lineTo(xFromDrawingPoint(last), yFromPrice(last.price));
4931
+ }
4932
+ ctx.stroke();
4933
+ ctx.restore();
4934
+ if (isSelected) {
4935
+ handleAt(startX, startY, drawing.color);
4936
+ const last = pts[pts.length - 1];
4937
+ handleAt(xFromDrawingPoint(last), yFromPrice(last.price), drawing.color);
4938
+ }
4939
+ }
4940
+ } else if (drawing.type === "callout") {
4941
+ const anchor = drawing.points[0];
4942
+ const boxPoint = drawing.points[1];
4943
+ if (anchor && boxPoint) {
4944
+ const ax = xFromDrawingPoint(anchor);
4945
+ const ay = yFromPrice(anchor.price);
4946
+ const bx = xFromDrawingPoint(boxPoint);
4947
+ const by = yFromPrice(boxPoint.price);
4948
+ const fontSize = Math.max(6, drawing.fontSize);
4949
+ const prevFont = ctx.font;
4950
+ ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
4951
+ const rawLabel = drawing.label ?? "";
4952
+ const isPlaceholder = rawLabel.trim().length === 0;
4953
+ const lines = (isPlaceholder ? "Text" : rawLabel).split("\n");
4954
+ const textW = Math.max(1, ...lines.map((line) => measureTextWidth(line)));
4955
+ const padX = 10;
4956
+ const padY = 7;
4957
+ const lineH = Math.round(fontSize * 1.35);
4958
+ const blockW = textW + padX * 2;
4959
+ const blockH = lines.length * lineH + padY * 2;
4960
+ const edgeX = Math.max(bx, Math.min(bx + blockW, ax));
4961
+ const edgeY = Math.max(by, Math.min(by + blockH, ay));
4962
+ const insideBox = ax >= bx && ax <= bx + blockW && ay >= by && ay <= by + blockH;
4963
+ if (!insideBox) {
4964
+ ctx.save();
4965
+ ctx.setLineDash([]);
4966
+ ctx.lineWidth = Math.max(1, drawing.width);
4967
+ ctx.beginPath();
4968
+ ctx.moveTo(edgeX, edgeY);
4969
+ ctx.lineTo(ax, ay);
4970
+ ctx.stroke();
4971
+ const angle = Math.atan2(ay - edgeY, ax - edgeX);
4972
+ const head = 7;
4973
+ ctx.fillStyle = drawing.color;
4974
+ ctx.beginPath();
4975
+ ctx.moveTo(ax, ay);
4976
+ ctx.lineTo(ax - Math.cos(angle - Math.PI / 7) * head, ay - Math.sin(angle - Math.PI / 7) * head);
4977
+ ctx.lineTo(ax - Math.cos(angle + Math.PI / 7) * head, ay - Math.sin(angle + Math.PI / 7) * head);
4978
+ ctx.closePath();
4979
+ ctx.fill();
4980
+ ctx.restore();
4981
+ }
4982
+ ctx.save();
4983
+ ctx.setLineDash([]);
4984
+ ctx.fillStyle = hexToRgba(drawing.color, 0.16);
4985
+ fillRoundedRect(bx, by, blockW, blockH, 6);
4986
+ ctx.strokeStyle = drawing.color;
4987
+ ctx.lineWidth = Math.max(1, drawing.width);
4988
+ strokeRoundedRect(bx, by, blockW, blockH, 6);
4989
+ ctx.fillStyle = drawing.color;
4990
+ ctx.globalAlpha = (draft ? 0.72 : 1) * (isPlaceholder ? 0.55 : 1);
4991
+ ctx.textAlign = "left";
4992
+ ctx.textBaseline = "middle";
4993
+ lines.forEach((line, lineIndex) => {
4994
+ ctx.fillText(line, bx + padX, by + padY + lineIndex * lineH + lineH / 2);
4995
+ });
4996
+ ctx.restore();
4997
+ ctx.font = prevFont;
4998
+ handleAt(ax, ay, drawing.color);
4999
+ handleAt(bx, by, drawing.color);
5000
+ }
4761
5001
  } else if (drawing.type === "price-range") {
4762
5002
  const p0 = drawing.points[0];
4763
5003
  const p1 = drawing.points[1];
@@ -4815,7 +5055,7 @@ function createChart(element, options = {}) {
4815
5055
  const t0 = getTimeForIndex(Math.round(p0.index));
4816
5056
  const t1 = getTimeForIndex(Math.round(p1.index));
4817
5057
  const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
4818
- labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatDuration(spanMs)}` : `${barSpan} bars`);
5058
+ labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatTimeSpan(spanMs)}` : `${barSpan} bars`);
4819
5059
  }
4820
5060
  const pointValue = Number(drawing.pointValue);
4821
5061
  if (Number.isFinite(pointValue) && pointValue > 0) {
@@ -5292,6 +5532,96 @@ function createChart(element, options = {}) {
5292
5532
  if (draftDrawing) {
5293
5533
  drawDrawing(draftDrawing, true);
5294
5534
  }
5535
+ if (measureState) {
5536
+ const mStart = measureState.start;
5537
+ const mEnd = measureState.end;
5538
+ const mx0 = xFromDrawingPoint(mStart);
5539
+ const mx1 = xFromDrawingPoint(mEnd);
5540
+ const my0 = yFromPrice(mStart.price);
5541
+ const my1 = yFromPrice(mEnd.price);
5542
+ const mLeft = Math.min(mx0, mx1);
5543
+ const mRight = Math.max(mx0, mx1);
5544
+ const mTop = Math.min(my0, my1);
5545
+ const mBottom = Math.max(my0, my1);
5546
+ const mUp = mEnd.price >= mStart.price;
5547
+ const mColor = mUp ? "#2962ff" : "#f23645";
5548
+ ctx.save();
5549
+ ctx.fillStyle = hexToRgba(mColor, 0.12);
5550
+ ctx.fillRect(mLeft, mTop, Math.max(1, mRight - mLeft), Math.max(1, mBottom - mTop));
5551
+ ctx.strokeStyle = mColor;
5552
+ ctx.lineWidth = 1;
5553
+ ctx.setLineDash([]);
5554
+ const mArrowHead = 5;
5555
+ const mMidX = (mLeft + mRight) / 2;
5556
+ if (Math.abs(my1 - my0) > mArrowHead * 2) {
5557
+ ctx.beginPath();
5558
+ ctx.moveTo(crisp(mMidX), crisp(my0));
5559
+ ctx.lineTo(crisp(mMidX), crisp(my1));
5560
+ ctx.stroke();
5561
+ const dirY = my1 > my0 ? 1 : -1;
5562
+ ctx.beginPath();
5563
+ ctx.moveTo(mMidX, my1);
5564
+ ctx.lineTo(mMidX - mArrowHead, my1 - dirY * mArrowHead);
5565
+ ctx.moveTo(mMidX, my1);
5566
+ ctx.lineTo(mMidX + mArrowHead, my1 - dirY * mArrowHead);
5567
+ ctx.stroke();
5568
+ }
5569
+ const mMidY = (mTop + mBottom) / 2;
5570
+ if (Math.abs(mx1 - mx0) > mArrowHead * 2) {
5571
+ ctx.beginPath();
5572
+ ctx.moveTo(crisp(mx0), crisp(mMidY));
5573
+ ctx.lineTo(crisp(mx1), crisp(mMidY));
5574
+ ctx.stroke();
5575
+ const dirX = mx1 > mx0 ? 1 : -1;
5576
+ ctx.beginPath();
5577
+ ctx.moveTo(mx1, mMidY);
5578
+ ctx.lineTo(mx1 - dirX * mArrowHead, mMidY - mArrowHead);
5579
+ ctx.moveTo(mx1, mMidY);
5580
+ ctx.lineTo(mx1 - dirX * mArrowHead, mMidY + mArrowHead);
5581
+ ctx.stroke();
5582
+ }
5583
+ const mDiff = mEnd.price - mStart.price;
5584
+ const mBase = Math.abs(mStart.price) > 0 ? Math.abs(mStart.price) : 1;
5585
+ const mPct = mDiff / mBase * 100;
5586
+ const mTick = getConfiguredTickSize();
5587
+ const mTicks = mTick > 0 ? Math.round(mDiff / mTick) : 0;
5588
+ const mSigned = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
5589
+ const mLines = [
5590
+ mTick > 0 ? `${mSigned(mDiff, formatPrice(Math.abs(mDiff)))} (${mSigned(mPct, `${Math.abs(mPct).toFixed(2)}%`)}) ${mSigned(mTicks, String(Math.abs(mTicks)))}` : `${mSigned(mDiff, formatPrice(Math.abs(mDiff)))} (${mSigned(mPct, `${Math.abs(mPct).toFixed(2)}%`)})`
5591
+ ];
5592
+ const mBars = Math.round(mEnd.index) - Math.round(mStart.index);
5593
+ const mT0 = getTimeForIndex(Math.round(mStart.index));
5594
+ const mT1 = getTimeForIndex(Math.round(mEnd.index));
5595
+ const mSpanMs = mT0 && mT1 ? Math.abs(mT1.getTime() - mT0.getTime()) : 0;
5596
+ mLines.push(
5597
+ mSpanMs > 0 ? `${mSigned(mBars, String(Math.abs(mBars)))} bars, ${formatTimeSpan(mSpanMs)}` : `${mSigned(mBars, String(Math.abs(mBars)))} bars`
5598
+ );
5599
+ const prevMeasureFont = ctx.font;
5600
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
5601
+ const mPad = 7;
5602
+ const mLineHeight = 15;
5603
+ const mTextW = Math.max(...mLines.map((line) => measureTextWidth(line)));
5604
+ const mPillW = mTextW + mPad * 2;
5605
+ const mPillH = mLines.length * mLineHeight + mPad;
5606
+ const mPillX = clamp(mMidX - mPillW / 2, chartLeft + 2, chartLeft + chartWidth - mPillW - 2);
5607
+ let mPillY = mUp ? mTop - 8 - mPillH : mBottom + 8;
5608
+ if (mPillY < chartTop + 2) {
5609
+ mPillY = mBottom + 8;
5610
+ }
5611
+ if (mPillY + mPillH > fullChartBottom - 2) {
5612
+ mPillY = Math.max(chartTop + 2, mTop - 8 - mPillH);
5613
+ }
5614
+ ctx.fillStyle = mColor;
5615
+ fillRoundedRect(mPillX, mPillY, mPillW, mPillH, 4);
5616
+ ctx.fillStyle = "#ffffff";
5617
+ ctx.textAlign = "center";
5618
+ ctx.textBaseline = "middle";
5619
+ mLines.forEach((line, lineIndex) => {
5620
+ ctx.fillText(line, mPillX + mPillW / 2, mPillY + mPad / 2 + lineIndex * mLineHeight + mLineHeight / 2);
5621
+ });
5622
+ ctx.font = prevMeasureFont;
5623
+ ctx.restore();
5624
+ }
5295
5625
  if (tradeMarkers.length > 0 && data.length > 0) {
5296
5626
  const visibleStart = Math.floor(xStart) - 1;
5297
5627
  const visibleEnd = Math.ceil(xStart + xSpan) + 1;
@@ -6722,6 +7052,126 @@ function createChart(element, options = {}) {
6722
7052
  if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
6723
7053
  return { drawing, target: "line" };
6724
7054
  }
7055
+ } else if (drawing.type === "ellipse") {
7056
+ const p0 = drawing.points[0];
7057
+ const p1 = drawing.points[1];
7058
+ if (!p0 || !p1) continue;
7059
+ const px0 = canvasXFromDrawingPoint(p0);
7060
+ const px1 = canvasXFromDrawingPoint(p1);
7061
+ const y0 = canvasYFromDrawingPrice(p0.price);
7062
+ const y1 = canvasYFromDrawingPrice(p1.price);
7063
+ if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7064
+ if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7065
+ if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7066
+ if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7067
+ const cx = (px0 + px1) / 2;
7068
+ const cy = (y0 + y1) / 2;
7069
+ const rx = Math.max(1, Math.abs(px1 - px0) / 2);
7070
+ const ry = Math.max(1, Math.abs(y1 - y0) / 2);
7071
+ const norm = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
7072
+ if (norm <= 1) {
7073
+ return { drawing, target: "line" };
7074
+ }
7075
+ } else if (drawing.type === "parallel-channel") {
7076
+ const p0 = drawing.points[0];
7077
+ const p1 = drawing.points[1];
7078
+ const p2 = drawing.points[2];
7079
+ if (!p0 || !p1) continue;
7080
+ const x0 = canvasXFromDrawingPoint(p0);
7081
+ const y0 = canvasYFromDrawingPrice(p0.price);
7082
+ const x1 = canvasXFromDrawingPoint(p1);
7083
+ const y1 = canvasYFromDrawingPrice(p1.price);
7084
+ if (Math.hypot(x - x0, y - y0) <= 8) return { drawing, target: "handle", pointIndex: 0 };
7085
+ if (Math.hypot(x - x1, y - y1) <= 8) return { drawing, target: "handle", pointIndex: 1 };
7086
+ if (p2) {
7087
+ const x2 = canvasXFromDrawingPoint(p2);
7088
+ const y2 = canvasYFromDrawingPrice(p2.price);
7089
+ if (Math.hypot(x - x2, y - y2) <= 8) return { drawing, target: "handle", pointIndex: 2 };
7090
+ const indexSpan = p1.index - p0.index;
7091
+ const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
7092
+ const offset = p2.price - (p0.price + slope * (p2.index - p0.index));
7093
+ const q0y = canvasYFromDrawingPrice(p0.price + offset);
7094
+ const q1y = canvasYFromDrawingPrice(p1.price + offset);
7095
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6) {
7096
+ return { drawing, target: "line" };
7097
+ }
7098
+ const minX = Math.min(x0, x1);
7099
+ const maxX = Math.max(x0, x1);
7100
+ if (x >= minX && x <= maxX && maxX > minX) {
7101
+ const t = (x - x0) / (x1 - x0);
7102
+ const baseY = y0 + t * (y1 - y0);
7103
+ const parY = q0y + t * (q1y - q0y);
7104
+ if (y >= Math.min(baseY, parY) && y <= Math.max(baseY, parY)) {
7105
+ return { drawing, target: "line" };
7106
+ }
7107
+ }
7108
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
7109
+ return { drawing, target: "line" };
7110
+ }
7111
+ } else if (drawing.type === "arrow") {
7112
+ const first = drawing.points[0];
7113
+ const second = drawing.points[1];
7114
+ if (!first || !second) continue;
7115
+ const x1 = canvasXFromDrawingPoint(first);
7116
+ const y1 = canvasYFromDrawingPrice(first.price);
7117
+ const x2 = canvasXFromDrawingPoint(second);
7118
+ const y2 = canvasYFromDrawingPrice(second.price);
7119
+ if (Math.hypot(x - x1, y - y1) <= 8) {
7120
+ return { drawing, target: "handle", pointIndex: 0 };
7121
+ }
7122
+ if (Math.hypot(x - x2, y - y2) <= 8) {
7123
+ return { drawing, target: "handle", pointIndex: 1 };
7124
+ }
7125
+ if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7126
+ return { drawing, target: "line" };
7127
+ }
7128
+ } else if (drawing.type === "brush") {
7129
+ const pts = drawing.points;
7130
+ if (pts.length < 2) continue;
7131
+ let hitBody = false;
7132
+ let prevX = canvasXFromDrawingPoint(pts[0]);
7133
+ let prevY = canvasYFromDrawingPrice(pts[0].price);
7134
+ for (let i = 1; i < pts.length; i += 1) {
7135
+ const nextX = canvasXFromDrawingPoint(pts[i]);
7136
+ const nextY = canvasYFromDrawingPrice(pts[i].price);
7137
+ if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7) {
7138
+ hitBody = true;
7139
+ break;
7140
+ }
7141
+ prevX = nextX;
7142
+ prevY = nextY;
7143
+ }
7144
+ if (hitBody) {
7145
+ return { drawing, target: "line" };
7146
+ }
7147
+ } else if (drawing.type === "callout") {
7148
+ const anchor = drawing.points[0];
7149
+ const boxPoint = drawing.points[1];
7150
+ if (!anchor || !boxPoint) continue;
7151
+ const ax = canvasXFromDrawingPoint(anchor);
7152
+ const ay = canvasYFromDrawingPrice(anchor.price);
7153
+ const bx = canvasXFromDrawingPoint(boxPoint);
7154
+ const by = canvasYFromDrawingPrice(boxPoint.price);
7155
+ if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7156
+ if (Math.hypot(x - bx, y - by) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7157
+ const fontSize = Math.max(6, drawing.fontSize);
7158
+ const prevFont = ctx.font;
7159
+ ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
7160
+ const rawLabel = drawing.label ?? "";
7161
+ const lines = (rawLabel.trim().length === 0 ? "Text" : rawLabel).split("\n");
7162
+ const textW = Math.max(1, ...lines.map((line) => measureTextWidth(line)));
7163
+ ctx.font = prevFont;
7164
+ const padX = 10;
7165
+ const padY = 7;
7166
+ const lineH = Math.round(fontSize * 1.35);
7167
+ const blockW = textW + padX * 2;
7168
+ const blockH = lines.length * lineH + padY * 2;
7169
+ if (x >= bx && x <= bx + blockW && y >= by && y <= by + blockH) {
7170
+ return { drawing, target: "line" };
7171
+ }
7172
+ if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5) {
7173
+ return { drawing, target: "line" };
7174
+ }
6725
7175
  } else if (drawing.type === "price-range") {
6726
7176
  const p0 = drawing.points[0];
6727
7177
  const p1 = drawing.points[1];
@@ -6950,6 +7400,128 @@ function createChart(element, options = {}) {
6950
7400
  scheduleDraw();
6951
7401
  return true;
6952
7402
  }
7403
+ if (activeDrawingTool === "arrow") {
7404
+ if (draftDrawing?.type === "arrow") {
7405
+ const anchor = draftDrawing.points[0];
7406
+ const endPoint = shiftKeyActive ? constrainAngleFromAnchor(anchor, x, y) ?? point : point;
7407
+ const completed = normalizeDrawingState({
7408
+ ...serializeDrawing(draftDrawing),
7409
+ points: [anchor, endPoint]
7410
+ });
7411
+ drawings.push(completed);
7412
+ draftDrawing = null;
7413
+ activeDrawingTool = null;
7414
+ emitDrawingsChange();
7415
+ scheduleDraw();
7416
+ return true;
7417
+ }
7418
+ const defaults = getDrawingToolDefaults("arrow");
7419
+ draftDrawing = normalizeDrawingState({
7420
+ type: "arrow",
7421
+ points: [point, point],
7422
+ color: defaults.color ?? "#2563eb",
7423
+ style: defaults.style ?? "solid",
7424
+ width: defaults.width ?? 2
7425
+ });
7426
+ scheduleDraw();
7427
+ return true;
7428
+ }
7429
+ if (activeDrawingTool === "parallel-channel") {
7430
+ if (draftDrawing?.type === "parallel-channel") {
7431
+ if (draftDrawing.points.length < 3) {
7432
+ draftDrawing = normalizeDrawingState({
7433
+ ...serializeDrawing(draftDrawing),
7434
+ points: [...draftDrawing.points.slice(0, -1), point, point]
7435
+ });
7436
+ scheduleDraw();
7437
+ return true;
7438
+ }
7439
+ const completed = normalizeDrawingState({
7440
+ ...serializeDrawing(draftDrawing),
7441
+ points: [draftDrawing.points[0], draftDrawing.points[1], point]
7442
+ });
7443
+ drawings.push(completed);
7444
+ draftDrawing = null;
7445
+ activeDrawingTool = null;
7446
+ emitDrawingsChange();
7447
+ scheduleDraw();
7448
+ return true;
7449
+ }
7450
+ const defaults = getDrawingToolDefaults("parallel-channel");
7451
+ draftDrawing = normalizeDrawingState({
7452
+ type: "parallel-channel",
7453
+ points: [point, point],
7454
+ color: defaults.color ?? "#2563eb",
7455
+ style: defaults.style ?? "solid",
7456
+ width: defaults.width ?? 2
7457
+ });
7458
+ scheduleDraw();
7459
+ return true;
7460
+ }
7461
+ if (activeDrawingTool === "ellipse") {
7462
+ if (draftDrawing?.type === "ellipse") {
7463
+ const completed = normalizeDrawingState({
7464
+ ...serializeDrawing(draftDrawing),
7465
+ points: [draftDrawing.points[0], point]
7466
+ });
7467
+ drawings.push(completed);
7468
+ draftDrawing = null;
7469
+ activeDrawingTool = null;
7470
+ emitDrawingsChange();
7471
+ scheduleDraw();
7472
+ return true;
7473
+ }
7474
+ const defaults = getDrawingToolDefaults("ellipse");
7475
+ draftDrawing = normalizeDrawingState({
7476
+ type: "ellipse",
7477
+ points: [point, point],
7478
+ color: defaults.color ?? "#2962ff",
7479
+ style: defaults.style ?? "solid",
7480
+ width: defaults.width ?? 1
7481
+ });
7482
+ scheduleDraw();
7483
+ return true;
7484
+ }
7485
+ if (activeDrawingTool === "callout") {
7486
+ if (draftDrawing?.type === "callout") {
7487
+ const completed = normalizeDrawingState({
7488
+ ...serializeDrawing(draftDrawing),
7489
+ points: [draftDrawing.points[0], point]
7490
+ });
7491
+ drawings.push(completed);
7492
+ draftDrawing = null;
7493
+ activeDrawingTool = null;
7494
+ selectedDrawingId = completed.id;
7495
+ emitDrawingsChange();
7496
+ scheduleDraw();
7497
+ drawingEditTextHandler?.({ drawing: serializeDrawing(completed), target: "line", x, y });
7498
+ return true;
7499
+ }
7500
+ const defaults = getDrawingToolDefaults("callout");
7501
+ draftDrawing = normalizeDrawingState({
7502
+ type: "callout",
7503
+ points: [point, point],
7504
+ color: defaults.color ?? "#2962ff",
7505
+ style: defaults.style ?? "solid",
7506
+ width: defaults.width ?? 1,
7507
+ ...defaults.fontSize === void 0 ? {} : { fontSize: defaults.fontSize },
7508
+ label: ""
7509
+ });
7510
+ scheduleDraw();
7511
+ return true;
7512
+ }
7513
+ if (activeDrawingTool === "brush") {
7514
+ const defaults = getDrawingToolDefaults("brush");
7515
+ draftDrawing = normalizeDrawingState({
7516
+ type: "brush",
7517
+ points: [point],
7518
+ color: defaults.color ?? "#f59e0b",
7519
+ style: defaults.style ?? "solid",
7520
+ width: defaults.width ?? 2
7521
+ });
7522
+ scheduleDraw();
7523
+ return true;
7524
+ }
6953
7525
  if (activeDrawingTool === "fib-retracement") {
6954
7526
  if (draftDrawing?.type === "fib-retracement") {
6955
7527
  const completed = normalizeDrawingState({
@@ -7153,7 +7725,7 @@ function createChart(element, options = {}) {
7153
7725
  }
7154
7726
  return { ...drawing, points: pts };
7155
7727
  }
7156
- if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
7728
+ if (drawingDragState.target === "handle" && (drawing.type === "rectangle" || drawing.type === "ellipse")) {
7157
7729
  const pts = drawing.points.map((point) => ({ ...point }));
7158
7730
  const p0 = pts[0];
7159
7731
  const p1 = pts[1];
@@ -7172,7 +7744,7 @@ function createChart(element, options = {}) {
7172
7744
  }
7173
7745
  return { ...drawing, points: pts };
7174
7746
  }
7175
- if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
7747
+ if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray" || drawing.type === "arrow")) {
7176
7748
  const pointIndex = drawingDragState.pointIndex ?? 0;
7177
7749
  const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
7178
7750
  const constrained = anchor ? constrainAngleFromAnchor(anchor, x, y) : null;
@@ -7290,6 +7862,19 @@ function createChart(element, options = {}) {
7290
7862
  return;
7291
7863
  }
7292
7864
  }
7865
+ if (measureState) {
7866
+ if (measureState.dragging) {
7867
+ measureState = { ...measureState, dragging: false };
7868
+ if (activeDrawingTool === "measure") {
7869
+ activeDrawingTool = null;
7870
+ canvas.style.cursor = "default";
7871
+ }
7872
+ scheduleDraw();
7873
+ return;
7874
+ }
7875
+ measureState = null;
7876
+ scheduleDraw();
7877
+ }
7293
7878
  const drawingToolCapturesPointer = activeDrawingTool !== null || draftDrawing !== null;
7294
7879
  if (!drawingToolCapturesPointer) {
7295
7880
  const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
@@ -7364,6 +7949,18 @@ function createChart(element, options = {}) {
7364
7949
  if (region === "outside") {
7365
7950
  return;
7366
7951
  }
7952
+ if (region === "plot" && (activeDrawingTool === "measure" || activeDrawingTool === null && draftDrawing === null && event.shiftKey && event.pointerType === "mouse")) {
7953
+ const measurePoint = drawingPointFromCanvas(point.x, point.y);
7954
+ if (measurePoint) {
7955
+ measureState = { start: measurePoint, end: measurePoint, dragging: true };
7956
+ activePointerId = event.pointerId;
7957
+ capturePointer(event.pointerId);
7958
+ setCrosshairPoint(null);
7959
+ canvas.style.cursor = "crosshair";
7960
+ scheduleDraw();
7961
+ return;
7962
+ }
7963
+ }
7367
7964
  if (region === "plot" && !activeDrawingTool) {
7368
7965
  const drawingHit = getDrawingHit(point.x, point.y);
7369
7966
  if (drawingHit) {
@@ -7397,6 +7994,10 @@ function createChart(element, options = {}) {
7397
7994
  if (region === "plot" && handleDrawingToolPointerDown(point.x, point.y)) {
7398
7995
  setCrosshairPoint(null);
7399
7996
  canvas.style.cursor = "crosshair";
7997
+ if (activeDrawingTool === "brush" && draftDrawing?.type === "brush") {
7998
+ activePointerId = event.pointerId;
7999
+ capturePointer(event.pointerId);
8000
+ }
7400
8001
  return;
7401
8002
  }
7402
8003
  isDragging = true;
@@ -7459,6 +8060,19 @@ function createChart(element, options = {}) {
7459
8060
  }
7460
8061
  return;
7461
8062
  }
8063
+ if (measureState?.dragging) {
8064
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
8065
+ return;
8066
+ }
8067
+ const nextMeasurePoint = drawingPointFromCanvas(point.x, point.y);
8068
+ if (nextMeasurePoint) {
8069
+ measureState = { ...measureState, end: nextMeasurePoint };
8070
+ setCrosshairPoint(null);
8071
+ canvas.style.cursor = "crosshair";
8072
+ scheduleDraw();
8073
+ }
8074
+ return;
8075
+ }
7462
8076
  if (drawingDragState) {
7463
8077
  if (activePointerId !== null && event.pointerId !== activePointerId) {
7464
8078
  return;
@@ -7468,9 +8082,30 @@ function createChart(element, options = {}) {
7468
8082
  setCrosshairPoint(null);
7469
8083
  return;
7470
8084
  }
7471
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "rectangle" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
8085
+ if (draftDrawing?.type === "brush" && activeDrawingTool === "brush") {
8086
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
8087
+ return;
8088
+ }
8089
+ const nextPoint = drawingPointFromCanvas(point.x, point.y);
8090
+ if (nextPoint) {
8091
+ const last = draftDrawing.points[draftDrawing.points.length - 1];
8092
+ const lastX = last ? canvasXFromDrawingPoint(last) : Number.NEGATIVE_INFINITY;
8093
+ const lastY = last ? canvasYFromDrawingPrice(last.price) : Number.NEGATIVE_INFINITY;
8094
+ if (Math.hypot(point.x - lastX, point.y - lastY) >= 3) {
8095
+ draftDrawing = {
8096
+ ...draftDrawing,
8097
+ points: [...draftDrawing.points, nextPoint]
8098
+ };
8099
+ }
8100
+ canvas.style.cursor = "crosshair";
8101
+ setCrosshairPoint(null);
8102
+ scheduleDraw();
8103
+ }
8104
+ return;
8105
+ }
8106
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "arrow" || activeDrawingTool === "rectangle" || activeDrawingTool === "ellipse" || activeDrawingTool === "callout" || activeDrawingTool === "parallel-channel" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
7472
8107
  let nextPoint = drawingPointFromCanvas(point.x, point.y);
7473
- if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
8108
+ if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray" || draftDrawing.type === "arrow") && draftDrawing.points[0]) {
7474
8109
  nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
7475
8110
  }
7476
8111
  if (nextPoint) {
@@ -7689,6 +8324,43 @@ function createChart(element, options = {}) {
7689
8324
  if (event && activePointerId !== null && event.pointerId !== activePointerId) {
7690
8325
  return;
7691
8326
  }
8327
+ if (measureState?.dragging) {
8328
+ const movedMeasure = measureState.start.index !== measureState.end.index || measureState.start.price !== measureState.end.price;
8329
+ if (movedMeasure) {
8330
+ measureState = { ...measureState, dragging: false };
8331
+ if (activeDrawingTool === "measure") {
8332
+ activeDrawingTool = null;
8333
+ }
8334
+ canvas.style.cursor = "default";
8335
+ } else {
8336
+ canvas.style.cursor = "crosshair";
8337
+ }
8338
+ isDragging = false;
8339
+ dragMode = null;
8340
+ activePointerId = null;
8341
+ pointerDownInfo = null;
8342
+ scheduleDraw();
8343
+ return;
8344
+ }
8345
+ if (draftDrawing?.type === "brush" && activeDrawingTool === "brush") {
8346
+ if (draftDrawing.points.length >= 2) {
8347
+ const completed = normalizeDrawingState(serializeDrawing(draftDrawing));
8348
+ drawings.push(completed);
8349
+ draftDrawing = null;
8350
+ activeDrawingTool = null;
8351
+ canvas.style.cursor = "default";
8352
+ emitDrawingsChange();
8353
+ } else {
8354
+ draftDrawing = null;
8355
+ canvas.style.cursor = "crosshair";
8356
+ }
8357
+ isDragging = false;
8358
+ dragMode = null;
8359
+ activePointerId = null;
8360
+ pointerDownInfo = null;
8361
+ scheduleDraw();
8362
+ return;
8363
+ }
7692
8364
  if (orderDragState) {
7693
8365
  const moved = orderDragState.lastPrice !== orderDragState.startPrice;
7694
8366
  const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
@@ -7832,7 +8504,7 @@ function createChart(element, options = {}) {
7832
8504
  x: point.x,
7833
8505
  y: point.y
7834
8506
  };
7835
- if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note") {
8507
+ if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note" || drawingHit.drawing.type === "callout") {
7836
8508
  drawingEditTextHandler?.(payload);
7837
8509
  } else {
7838
8510
  drawingDoubleClickHandler?.(payload);
@@ -7869,6 +8541,15 @@ function createChart(element, options = {}) {
7869
8541
  canvas.addEventListener("dblclick", onDoubleClick);
7870
8542
  canvas.addEventListener("contextmenu", onContextMenu);
7871
8543
  const onModifierKeyChange = (event) => {
8544
+ if (event.key === "Escape" && event.type === "keydown" && measureState) {
8545
+ measureState = null;
8546
+ if (activeDrawingTool === "measure") {
8547
+ activeDrawingTool = null;
8548
+ canvas.style.cursor = "default";
8549
+ }
8550
+ scheduleDraw();
8551
+ return;
8552
+ }
7872
8553
  const active = event.metaKey || event.ctrlKey;
7873
8554
  const shift = event.shiftKey;
7874
8555
  const changed = active !== magnetModifierActive || shift !== shiftKeyActive;
@@ -8181,17 +8862,29 @@ function createChart(element, options = {}) {
8181
8862
  const setActiveDrawingTool = (tool) => {
8182
8863
  activeDrawingTool = tool;
8183
8864
  draftDrawing = null;
8865
+ measureState = null;
8184
8866
  canvas.style.cursor = tool ? "crosshair" : "default";
8185
8867
  scheduleDraw();
8186
8868
  };
8187
8869
  const getActiveDrawingTool = () => activeDrawingTool;
8188
8870
  const cancelDrawing = () => {
8871
+ let cancelled = false;
8189
8872
  if (draftDrawing) {
8190
8873
  draftDrawing = null;
8874
+ cancelled = true;
8875
+ }
8876
+ if (measureState) {
8877
+ measureState = null;
8878
+ if (activeDrawingTool === "measure") {
8879
+ activeDrawingTool = null;
8880
+ canvas.style.cursor = "default";
8881
+ }
8882
+ cancelled = true;
8883
+ }
8884
+ if (cancelled) {
8191
8885
  scheduleDraw();
8192
- return true;
8193
8886
  }
8194
- return false;
8887
+ return cancelled;
8195
8888
  };
8196
8889
  const setTradeMarkers = (markers) => {
8197
8890
  tradeMarkers = Array.isArray(markers) ? markers.slice() : [];
@@ -8243,6 +8936,9 @@ function createChart(element, options = {}) {
8243
8936
  const onDrawingsChange = (handler) => {
8244
8937
  drawingsChangeHandler = handler;
8245
8938
  };
8939
+ const onActiveDrawingToolChange = (handler) => {
8940
+ drawingToolChangeHandler = handler;
8941
+ };
8246
8942
  const onDrawingSelect = (handler) => {
8247
8943
  drawingSelectHandler = handler;
8248
8944
  };
@@ -8255,6 +8951,51 @@ function createChart(element, options = {}) {
8255
8951
  const onDrawingHover = (handler) => {
8256
8952
  drawingHoverHandler = handler;
8257
8953
  };
8954
+ const saveState = () => {
8955
+ const drawingDefaults = {};
8956
+ for (const [tool, defaults] of drawingToolDefaults) {
8957
+ drawingDefaults[tool] = { ...defaults };
8958
+ }
8959
+ return {
8960
+ version: 1,
8961
+ chartType: getChartType(),
8962
+ viewport: getViewport(),
8963
+ drawings: getDrawings(),
8964
+ indicators: getIndicators(),
8965
+ magnetMode: getMagnetMode(),
8966
+ drawingDefaults
8967
+ };
8968
+ };
8969
+ const loadState = (state) => {
8970
+ if (!state || typeof state !== "object") {
8971
+ return;
8972
+ }
8973
+ if (typeof state.chartType === "string") {
8974
+ setChartType(state.chartType);
8975
+ }
8976
+ if (Array.isArray(state.indicators)) {
8977
+ setIndicators(state.indicators);
8978
+ }
8979
+ if (Array.isArray(state.drawings)) {
8980
+ setDrawings(state.drawings);
8981
+ }
8982
+ if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
8983
+ setMagnetMode(state.magnetMode);
8984
+ }
8985
+ if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
8986
+ for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
8987
+ if (defaults && typeof defaults === "object") {
8988
+ setDrawingDefaults(tool, defaults);
8989
+ }
8990
+ }
8991
+ }
8992
+ if (state.viewport && typeof state.viewport === "object") {
8993
+ setViewport(state.viewport);
8994
+ }
8995
+ };
8996
+ const takeScreenshot = (options2 = {}) => {
8997
+ return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
8998
+ };
8258
8999
  const destroy = () => {
8259
9000
  cancelKineticPan();
8260
9001
  if (smoothingRafId !== null) {
@@ -8329,6 +9070,7 @@ function createChart(element, options = {}) {
8329
9070
  removeDrawing,
8330
9071
  clearDrawings,
8331
9072
  onDrawingsChange,
9073
+ onActiveDrawingToolChange,
8332
9074
  onDrawingSelect,
8333
9075
  onDrawingDoubleClick,
8334
9076
  onDrawingEditText,
@@ -8349,6 +9091,9 @@ function createChart(element, options = {}) {
8349
9091
  updateIndicator,
8350
9092
  removeIndicator,
8351
9093
  setIndicators,
9094
+ saveState,
9095
+ loadState,
9096
+ takeScreenshot,
8352
9097
  resize,
8353
9098
  destroy
8354
9099
  };