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.
@@ -2970,8 +2970,11 @@ function createChart(element, options = {}) {
2970
2970
  );
2971
2971
  let activeDrawingTool = null;
2972
2972
  let draftDrawing = null;
2973
+ let measureState = null;
2973
2974
  let drawingDragState = null;
2974
2975
  let drawingsChangeHandler = null;
2976
+ let drawingToolChangeHandler = null;
2977
+ let lastNotifiedDrawingTool = null;
2975
2978
  let drawingSelectHandler = null;
2976
2979
  let drawingDoubleClickHandler = null;
2977
2980
  let drawingEditTextHandler = null;
@@ -3564,6 +3567,13 @@ function createChart(element, options = {}) {
3564
3567
  points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
3565
3568
  };
3566
3569
  }
3570
+ if (measureState) {
3571
+ measureState = {
3572
+ ...measureState,
3573
+ start: reanchorDrawingPoint(measureState.start),
3574
+ end: reanchorDrawingPoint(measureState.end)
3575
+ };
3576
+ }
3567
3577
  };
3568
3578
  const getCandleDirectionByIndex = (index) => {
3569
3579
  const point = data[index];
@@ -3665,6 +3675,22 @@ function createChart(element, options = {}) {
3665
3675
  const pad = (value) => String(value).padStart(2, "0");
3666
3676
  return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
3667
3677
  };
3678
+ const formatTimeSpan = (ms) => {
3679
+ const totalMinutes = Math.max(0, Math.round(ms / 6e4));
3680
+ if (totalMinutes < 1) {
3681
+ return `${Math.max(1, Math.round(ms / 1e3))}s`;
3682
+ }
3683
+ const days = Math.floor(totalMinutes / 1440);
3684
+ const hours = Math.floor(totalMinutes % 1440 / 60);
3685
+ const minutes = totalMinutes % 60;
3686
+ if (days > 0) {
3687
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
3688
+ }
3689
+ if (hours > 0) {
3690
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
3691
+ }
3692
+ return `${minutes}m`;
3693
+ };
3668
3694
  const getRightAxisLabelX = (chartRight) => chartRight + 1;
3669
3695
  const getRightAxisLabelWidth = (chartRight, contentWidth) => {
3670
3696
  const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
@@ -4033,6 +4059,10 @@ function createChart(element, options = {}) {
4033
4059
  let pendingDrawUpdateAutoScale = false;
4034
4060
  let pendingDrawOverlayOnly = true;
4035
4061
  const scheduleDraw = (options2 = {}) => {
4062
+ if (activeDrawingTool !== lastNotifiedDrawingTool) {
4063
+ lastNotifiedDrawingTool = activeDrawingTool;
4064
+ drawingToolChangeHandler?.(activeDrawingTool);
4065
+ }
4036
4066
  armCountdownHeartbeat();
4037
4067
  const overlayOnly = options2.overlayOnly ?? false;
4038
4068
  pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
@@ -4788,6 +4818,216 @@ function createChart(element, options = {}) {
4788
4818
  drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
4789
4819
  }
4790
4820
  }
4821
+ } else if (drawing.type === "ellipse") {
4822
+ const p0 = drawing.points[0];
4823
+ const p1 = drawing.points[1];
4824
+ if (p0 && p1) {
4825
+ const px0 = xFromDrawingPoint(p0);
4826
+ const px1 = xFromDrawingPoint(p1);
4827
+ const y0 = yFromPrice(p0.price);
4828
+ const y1 = yFromPrice(p1.price);
4829
+ const cx = (px0 + px1) / 2;
4830
+ const cy = (y0 + y1) / 2;
4831
+ const rx = Math.max(1, Math.abs(px1 - px0) / 2);
4832
+ const ry = Math.max(1, Math.abs(y1 - y0) / 2);
4833
+ ctx.save();
4834
+ ctx.globalAlpha = draft ? 0.6 : 1;
4835
+ ctx.beginPath();
4836
+ ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
4837
+ ctx.fillStyle = hexToRgba(drawing.color, 0.14);
4838
+ ctx.fill();
4839
+ ctx.lineWidth = Math.max(1, drawing.width);
4840
+ ctx.strokeStyle = drawing.color;
4841
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
4842
+ ctx.stroke();
4843
+ ctx.restore();
4844
+ handleAt(px0, y0, drawing.color);
4845
+ handleAt(px1, y1, drawing.color);
4846
+ handleAt(px0, y1, drawing.color);
4847
+ handleAt(px1, y0, drawing.color);
4848
+ if (drawing.label) {
4849
+ drawDrawingLabel(drawing.label, cx, cy - ry - 4, drawing.color);
4850
+ }
4851
+ }
4852
+ } else if (drawing.type === "parallel-channel") {
4853
+ const p0 = drawing.points[0];
4854
+ const p1 = drawing.points[1];
4855
+ const p2 = drawing.points[2];
4856
+ if (p0 && p1) {
4857
+ const x0 = xFromDrawingPoint(p0);
4858
+ const y0 = yFromPrice(p0.price);
4859
+ const x1 = xFromDrawingPoint(p1);
4860
+ const y1 = yFromPrice(p1.price);
4861
+ ctx.beginPath();
4862
+ ctx.moveTo(x0, y0);
4863
+ ctx.lineTo(x1, y1);
4864
+ ctx.stroke();
4865
+ handleAt(x0, y0, drawing.color);
4866
+ handleAt(x1, y1, drawing.color);
4867
+ if (p2) {
4868
+ const indexSpan = p1.index - p0.index;
4869
+ const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
4870
+ const basePriceAtP2 = p0.price + slope * (p2.index - p0.index);
4871
+ const offset = p2.price - basePriceAtP2;
4872
+ const q0x = x0;
4873
+ const q0y = yFromPrice(p0.price + offset);
4874
+ const q1x = x1;
4875
+ const q1y = yFromPrice(p1.price + offset);
4876
+ ctx.save();
4877
+ ctx.globalAlpha = draft ? 0.5 : 1;
4878
+ ctx.fillStyle = hexToRgba(drawing.color, 0.08);
4879
+ ctx.beginPath();
4880
+ ctx.moveTo(x0, y0);
4881
+ ctx.lineTo(x1, y1);
4882
+ ctx.lineTo(q1x, q1y);
4883
+ ctx.lineTo(q0x, q0y);
4884
+ ctx.closePath();
4885
+ ctx.fill();
4886
+ ctx.restore();
4887
+ ctx.beginPath();
4888
+ ctx.moveTo(q0x, q0y);
4889
+ ctx.lineTo(q1x, q1y);
4890
+ ctx.stroke();
4891
+ ctx.save();
4892
+ ctx.setLineDash([4, 4]);
4893
+ ctx.globalAlpha = draft ? 0.5 : 0.7;
4894
+ ctx.beginPath();
4895
+ ctx.moveTo(x0, (y0 + q0y) / 2);
4896
+ ctx.lineTo(x1, (y1 + q1y) / 2);
4897
+ ctx.stroke();
4898
+ ctx.restore();
4899
+ handleAt(xFromDrawingPoint(p2), yFromPrice(p2.price), drawing.color);
4900
+ }
4901
+ if (drawing.label) {
4902
+ drawDrawingLabel(drawing.label, (x0 + x1) / 2, Math.min(y0, y1) - 4, drawing.color);
4903
+ }
4904
+ }
4905
+ } else if (drawing.type === "arrow") {
4906
+ const first = drawing.points[0];
4907
+ const second = drawing.points[1];
4908
+ if (first && second) {
4909
+ const x0 = xFromDrawingPoint(first);
4910
+ const y0 = yFromPrice(first.price);
4911
+ const x1 = xFromDrawingPoint(second);
4912
+ const y1 = yFromPrice(second.price);
4913
+ const angle = Math.atan2(y1 - y0, x1 - x0);
4914
+ const head = 5 + Math.max(1, drawing.width) * 3;
4915
+ const shaftX = x1 - Math.cos(angle) * head * 0.6;
4916
+ const shaftY = y1 - Math.sin(angle) * head * 0.6;
4917
+ ctx.beginPath();
4918
+ ctx.moveTo(x0, y0);
4919
+ ctx.lineTo(shaftX, shaftY);
4920
+ ctx.stroke();
4921
+ ctx.save();
4922
+ ctx.setLineDash([]);
4923
+ ctx.fillStyle = drawing.color;
4924
+ ctx.beginPath();
4925
+ ctx.moveTo(x1, y1);
4926
+ ctx.lineTo(x1 - Math.cos(angle - Math.PI / 7) * head, y1 - Math.sin(angle - Math.PI / 7) * head);
4927
+ ctx.lineTo(x1 - Math.cos(angle + Math.PI / 7) * head, y1 - Math.sin(angle + Math.PI / 7) * head);
4928
+ ctx.closePath();
4929
+ ctx.fill();
4930
+ ctx.restore();
4931
+ handleAt(x0, y0, drawing.color);
4932
+ handleAt(x1, y1, drawing.color);
4933
+ if (drawing.label) {
4934
+ const midX = (x0 + x1) / 2;
4935
+ const midY = (y0 + y1) / 2;
4936
+ drawDrawingLabel(drawing.label, midX, midY, drawing.color);
4937
+ }
4938
+ }
4939
+ } else if (drawing.type === "brush") {
4940
+ const pts = drawing.points;
4941
+ if (pts.length >= 2) {
4942
+ ctx.save();
4943
+ ctx.lineJoin = "round";
4944
+ ctx.lineCap = "round";
4945
+ ctx.beginPath();
4946
+ const startX = xFromDrawingPoint(pts[0]);
4947
+ const startY = yFromPrice(pts[0].price);
4948
+ ctx.moveTo(startX, startY);
4949
+ if (pts.length === 2) {
4950
+ ctx.lineTo(xFromDrawingPoint(pts[1]), yFromPrice(pts[1].price));
4951
+ } else {
4952
+ for (let i = 1; i < pts.length - 1; i += 1) {
4953
+ const cxp = xFromDrawingPoint(pts[i]);
4954
+ const cyp = yFromPrice(pts[i].price);
4955
+ const nx = xFromDrawingPoint(pts[i + 1]);
4956
+ const ny = yFromPrice(pts[i + 1].price);
4957
+ ctx.quadraticCurveTo(cxp, cyp, (cxp + nx) / 2, (cyp + ny) / 2);
4958
+ }
4959
+ const last = pts[pts.length - 1];
4960
+ ctx.lineTo(xFromDrawingPoint(last), yFromPrice(last.price));
4961
+ }
4962
+ ctx.stroke();
4963
+ ctx.restore();
4964
+ if (isSelected) {
4965
+ handleAt(startX, startY, drawing.color);
4966
+ const last = pts[pts.length - 1];
4967
+ handleAt(xFromDrawingPoint(last), yFromPrice(last.price), drawing.color);
4968
+ }
4969
+ }
4970
+ } else if (drawing.type === "callout") {
4971
+ const anchor = drawing.points[0];
4972
+ const boxPoint = drawing.points[1];
4973
+ if (anchor && boxPoint) {
4974
+ const ax = xFromDrawingPoint(anchor);
4975
+ const ay = yFromPrice(anchor.price);
4976
+ const bx = xFromDrawingPoint(boxPoint);
4977
+ const by = yFromPrice(boxPoint.price);
4978
+ const fontSize = Math.max(6, drawing.fontSize);
4979
+ const prevFont = ctx.font;
4980
+ ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
4981
+ const rawLabel = drawing.label ?? "";
4982
+ const isPlaceholder = rawLabel.trim().length === 0;
4983
+ const lines = (isPlaceholder ? "Text" : rawLabel).split("\n");
4984
+ const textW = Math.max(1, ...lines.map((line) => measureTextWidth(line)));
4985
+ const padX = 10;
4986
+ const padY = 7;
4987
+ const lineH = Math.round(fontSize * 1.35);
4988
+ const blockW = textW + padX * 2;
4989
+ const blockH = lines.length * lineH + padY * 2;
4990
+ const edgeX = Math.max(bx, Math.min(bx + blockW, ax));
4991
+ const edgeY = Math.max(by, Math.min(by + blockH, ay));
4992
+ const insideBox = ax >= bx && ax <= bx + blockW && ay >= by && ay <= by + blockH;
4993
+ if (!insideBox) {
4994
+ ctx.save();
4995
+ ctx.setLineDash([]);
4996
+ ctx.lineWidth = Math.max(1, drawing.width);
4997
+ ctx.beginPath();
4998
+ ctx.moveTo(edgeX, edgeY);
4999
+ ctx.lineTo(ax, ay);
5000
+ ctx.stroke();
5001
+ const angle = Math.atan2(ay - edgeY, ax - edgeX);
5002
+ const head = 7;
5003
+ ctx.fillStyle = drawing.color;
5004
+ ctx.beginPath();
5005
+ ctx.moveTo(ax, ay);
5006
+ ctx.lineTo(ax - Math.cos(angle - Math.PI / 7) * head, ay - Math.sin(angle - Math.PI / 7) * head);
5007
+ ctx.lineTo(ax - Math.cos(angle + Math.PI / 7) * head, ay - Math.sin(angle + Math.PI / 7) * head);
5008
+ ctx.closePath();
5009
+ ctx.fill();
5010
+ ctx.restore();
5011
+ }
5012
+ ctx.save();
5013
+ ctx.setLineDash([]);
5014
+ ctx.fillStyle = hexToRgba(drawing.color, 0.16);
5015
+ fillRoundedRect(bx, by, blockW, blockH, 6);
5016
+ ctx.strokeStyle = drawing.color;
5017
+ ctx.lineWidth = Math.max(1, drawing.width);
5018
+ strokeRoundedRect(bx, by, blockW, blockH, 6);
5019
+ ctx.fillStyle = drawing.color;
5020
+ ctx.globalAlpha = (draft ? 0.72 : 1) * (isPlaceholder ? 0.55 : 1);
5021
+ ctx.textAlign = "left";
5022
+ ctx.textBaseline = "middle";
5023
+ lines.forEach((line, lineIndex) => {
5024
+ ctx.fillText(line, bx + padX, by + padY + lineIndex * lineH + lineH / 2);
5025
+ });
5026
+ ctx.restore();
5027
+ ctx.font = prevFont;
5028
+ handleAt(ax, ay, drawing.color);
5029
+ handleAt(bx, by, drawing.color);
5030
+ }
4791
5031
  } else if (drawing.type === "price-range") {
4792
5032
  const p0 = drawing.points[0];
4793
5033
  const p1 = drawing.points[1];
@@ -4845,7 +5085,7 @@ function createChart(element, options = {}) {
4845
5085
  const t0 = getTimeForIndex(Math.round(p0.index));
4846
5086
  const t1 = getTimeForIndex(Math.round(p1.index));
4847
5087
  const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
4848
- labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatDuration(spanMs)}` : `${barSpan} bars`);
5088
+ labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatTimeSpan(spanMs)}` : `${barSpan} bars`);
4849
5089
  }
4850
5090
  const pointValue = Number(drawing.pointValue);
4851
5091
  if (Number.isFinite(pointValue) && pointValue > 0) {
@@ -5322,6 +5562,96 @@ function createChart(element, options = {}) {
5322
5562
  if (draftDrawing) {
5323
5563
  drawDrawing(draftDrawing, true);
5324
5564
  }
5565
+ if (measureState) {
5566
+ const mStart = measureState.start;
5567
+ const mEnd = measureState.end;
5568
+ const mx0 = xFromDrawingPoint(mStart);
5569
+ const mx1 = xFromDrawingPoint(mEnd);
5570
+ const my0 = yFromPrice(mStart.price);
5571
+ const my1 = yFromPrice(mEnd.price);
5572
+ const mLeft = Math.min(mx0, mx1);
5573
+ const mRight = Math.max(mx0, mx1);
5574
+ const mTop = Math.min(my0, my1);
5575
+ const mBottom = Math.max(my0, my1);
5576
+ const mUp = mEnd.price >= mStart.price;
5577
+ const mColor = mUp ? "#2962ff" : "#f23645";
5578
+ ctx.save();
5579
+ ctx.fillStyle = hexToRgba(mColor, 0.12);
5580
+ ctx.fillRect(mLeft, mTop, Math.max(1, mRight - mLeft), Math.max(1, mBottom - mTop));
5581
+ ctx.strokeStyle = mColor;
5582
+ ctx.lineWidth = 1;
5583
+ ctx.setLineDash([]);
5584
+ const mArrowHead = 5;
5585
+ const mMidX = (mLeft + mRight) / 2;
5586
+ if (Math.abs(my1 - my0) > mArrowHead * 2) {
5587
+ ctx.beginPath();
5588
+ ctx.moveTo(crisp(mMidX), crisp(my0));
5589
+ ctx.lineTo(crisp(mMidX), crisp(my1));
5590
+ ctx.stroke();
5591
+ const dirY = my1 > my0 ? 1 : -1;
5592
+ ctx.beginPath();
5593
+ ctx.moveTo(mMidX, my1);
5594
+ ctx.lineTo(mMidX - mArrowHead, my1 - dirY * mArrowHead);
5595
+ ctx.moveTo(mMidX, my1);
5596
+ ctx.lineTo(mMidX + mArrowHead, my1 - dirY * mArrowHead);
5597
+ ctx.stroke();
5598
+ }
5599
+ const mMidY = (mTop + mBottom) / 2;
5600
+ if (Math.abs(mx1 - mx0) > mArrowHead * 2) {
5601
+ ctx.beginPath();
5602
+ ctx.moveTo(crisp(mx0), crisp(mMidY));
5603
+ ctx.lineTo(crisp(mx1), crisp(mMidY));
5604
+ ctx.stroke();
5605
+ const dirX = mx1 > mx0 ? 1 : -1;
5606
+ ctx.beginPath();
5607
+ ctx.moveTo(mx1, mMidY);
5608
+ ctx.lineTo(mx1 - dirX * mArrowHead, mMidY - mArrowHead);
5609
+ ctx.moveTo(mx1, mMidY);
5610
+ ctx.lineTo(mx1 - dirX * mArrowHead, mMidY + mArrowHead);
5611
+ ctx.stroke();
5612
+ }
5613
+ const mDiff = mEnd.price - mStart.price;
5614
+ const mBase = Math.abs(mStart.price) > 0 ? Math.abs(mStart.price) : 1;
5615
+ const mPct = mDiff / mBase * 100;
5616
+ const mTick = getConfiguredTickSize();
5617
+ const mTicks = mTick > 0 ? Math.round(mDiff / mTick) : 0;
5618
+ const mSigned = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
5619
+ const mLines = [
5620
+ 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)}%`)})`
5621
+ ];
5622
+ const mBars = Math.round(mEnd.index) - Math.round(mStart.index);
5623
+ const mT0 = getTimeForIndex(Math.round(mStart.index));
5624
+ const mT1 = getTimeForIndex(Math.round(mEnd.index));
5625
+ const mSpanMs = mT0 && mT1 ? Math.abs(mT1.getTime() - mT0.getTime()) : 0;
5626
+ mLines.push(
5627
+ mSpanMs > 0 ? `${mSigned(mBars, String(Math.abs(mBars)))} bars, ${formatTimeSpan(mSpanMs)}` : `${mSigned(mBars, String(Math.abs(mBars)))} bars`
5628
+ );
5629
+ const prevMeasureFont = ctx.font;
5630
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
5631
+ const mPad = 7;
5632
+ const mLineHeight = 15;
5633
+ const mTextW = Math.max(...mLines.map((line) => measureTextWidth(line)));
5634
+ const mPillW = mTextW + mPad * 2;
5635
+ const mPillH = mLines.length * mLineHeight + mPad;
5636
+ const mPillX = clamp(mMidX - mPillW / 2, chartLeft + 2, chartLeft + chartWidth - mPillW - 2);
5637
+ let mPillY = mUp ? mTop - 8 - mPillH : mBottom + 8;
5638
+ if (mPillY < chartTop + 2) {
5639
+ mPillY = mBottom + 8;
5640
+ }
5641
+ if (mPillY + mPillH > fullChartBottom - 2) {
5642
+ mPillY = Math.max(chartTop + 2, mTop - 8 - mPillH);
5643
+ }
5644
+ ctx.fillStyle = mColor;
5645
+ fillRoundedRect(mPillX, mPillY, mPillW, mPillH, 4);
5646
+ ctx.fillStyle = "#ffffff";
5647
+ ctx.textAlign = "center";
5648
+ ctx.textBaseline = "middle";
5649
+ mLines.forEach((line, lineIndex) => {
5650
+ ctx.fillText(line, mPillX + mPillW / 2, mPillY + mPad / 2 + lineIndex * mLineHeight + mLineHeight / 2);
5651
+ });
5652
+ ctx.font = prevMeasureFont;
5653
+ ctx.restore();
5654
+ }
5325
5655
  if (tradeMarkers.length > 0 && data.length > 0) {
5326
5656
  const visibleStart = Math.floor(xStart) - 1;
5327
5657
  const visibleEnd = Math.ceil(xStart + xSpan) + 1;
@@ -6752,6 +7082,126 @@ function createChart(element, options = {}) {
6752
7082
  if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
6753
7083
  return { drawing, target: "line" };
6754
7084
  }
7085
+ } else if (drawing.type === "ellipse") {
7086
+ const p0 = drawing.points[0];
7087
+ const p1 = drawing.points[1];
7088
+ if (!p0 || !p1) continue;
7089
+ const px0 = canvasXFromDrawingPoint(p0);
7090
+ const px1 = canvasXFromDrawingPoint(p1);
7091
+ const y0 = canvasYFromDrawingPrice(p0.price);
7092
+ const y1 = canvasYFromDrawingPrice(p1.price);
7093
+ if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7094
+ if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7095
+ if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7096
+ if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7097
+ const cx = (px0 + px1) / 2;
7098
+ const cy = (y0 + y1) / 2;
7099
+ const rx = Math.max(1, Math.abs(px1 - px0) / 2);
7100
+ const ry = Math.max(1, Math.abs(y1 - y0) / 2);
7101
+ const norm = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
7102
+ if (norm <= 1) {
7103
+ return { drawing, target: "line" };
7104
+ }
7105
+ } else if (drawing.type === "parallel-channel") {
7106
+ const p0 = drawing.points[0];
7107
+ const p1 = drawing.points[1];
7108
+ const p2 = drawing.points[2];
7109
+ if (!p0 || !p1) continue;
7110
+ const x0 = canvasXFromDrawingPoint(p0);
7111
+ const y0 = canvasYFromDrawingPrice(p0.price);
7112
+ const x1 = canvasXFromDrawingPoint(p1);
7113
+ const y1 = canvasYFromDrawingPrice(p1.price);
7114
+ if (Math.hypot(x - x0, y - y0) <= 8) return { drawing, target: "handle", pointIndex: 0 };
7115
+ if (Math.hypot(x - x1, y - y1) <= 8) return { drawing, target: "handle", pointIndex: 1 };
7116
+ if (p2) {
7117
+ const x2 = canvasXFromDrawingPoint(p2);
7118
+ const y2 = canvasYFromDrawingPrice(p2.price);
7119
+ if (Math.hypot(x - x2, y - y2) <= 8) return { drawing, target: "handle", pointIndex: 2 };
7120
+ const indexSpan = p1.index - p0.index;
7121
+ const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
7122
+ const offset = p2.price - (p0.price + slope * (p2.index - p0.index));
7123
+ const q0y = canvasYFromDrawingPrice(p0.price + offset);
7124
+ const q1y = canvasYFromDrawingPrice(p1.price + offset);
7125
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6) {
7126
+ return { drawing, target: "line" };
7127
+ }
7128
+ const minX = Math.min(x0, x1);
7129
+ const maxX = Math.max(x0, x1);
7130
+ if (x >= minX && x <= maxX && maxX > minX) {
7131
+ const t = (x - x0) / (x1 - x0);
7132
+ const baseY = y0 + t * (y1 - y0);
7133
+ const parY = q0y + t * (q1y - q0y);
7134
+ if (y >= Math.min(baseY, parY) && y <= Math.max(baseY, parY)) {
7135
+ return { drawing, target: "line" };
7136
+ }
7137
+ }
7138
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
7139
+ return { drawing, target: "line" };
7140
+ }
7141
+ } else if (drawing.type === "arrow") {
7142
+ const first = drawing.points[0];
7143
+ const second = drawing.points[1];
7144
+ if (!first || !second) continue;
7145
+ const x1 = canvasXFromDrawingPoint(first);
7146
+ const y1 = canvasYFromDrawingPrice(first.price);
7147
+ const x2 = canvasXFromDrawingPoint(second);
7148
+ const y2 = canvasYFromDrawingPrice(second.price);
7149
+ if (Math.hypot(x - x1, y - y1) <= 8) {
7150
+ return { drawing, target: "handle", pointIndex: 0 };
7151
+ }
7152
+ if (Math.hypot(x - x2, y - y2) <= 8) {
7153
+ return { drawing, target: "handle", pointIndex: 1 };
7154
+ }
7155
+ if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7156
+ return { drawing, target: "line" };
7157
+ }
7158
+ } else if (drawing.type === "brush") {
7159
+ const pts = drawing.points;
7160
+ if (pts.length < 2) continue;
7161
+ let hitBody = false;
7162
+ let prevX = canvasXFromDrawingPoint(pts[0]);
7163
+ let prevY = canvasYFromDrawingPrice(pts[0].price);
7164
+ for (let i = 1; i < pts.length; i += 1) {
7165
+ const nextX = canvasXFromDrawingPoint(pts[i]);
7166
+ const nextY = canvasYFromDrawingPrice(pts[i].price);
7167
+ if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7) {
7168
+ hitBody = true;
7169
+ break;
7170
+ }
7171
+ prevX = nextX;
7172
+ prevY = nextY;
7173
+ }
7174
+ if (hitBody) {
7175
+ return { drawing, target: "line" };
7176
+ }
7177
+ } else if (drawing.type === "callout") {
7178
+ const anchor = drawing.points[0];
7179
+ const boxPoint = drawing.points[1];
7180
+ if (!anchor || !boxPoint) continue;
7181
+ const ax = canvasXFromDrawingPoint(anchor);
7182
+ const ay = canvasYFromDrawingPrice(anchor.price);
7183
+ const bx = canvasXFromDrawingPoint(boxPoint);
7184
+ const by = canvasYFromDrawingPrice(boxPoint.price);
7185
+ if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7186
+ if (Math.hypot(x - bx, y - by) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7187
+ const fontSize = Math.max(6, drawing.fontSize);
7188
+ const prevFont = ctx.font;
7189
+ ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
7190
+ const rawLabel = drawing.label ?? "";
7191
+ const lines = (rawLabel.trim().length === 0 ? "Text" : rawLabel).split("\n");
7192
+ const textW = Math.max(1, ...lines.map((line) => measureTextWidth(line)));
7193
+ ctx.font = prevFont;
7194
+ const padX = 10;
7195
+ const padY = 7;
7196
+ const lineH = Math.round(fontSize * 1.35);
7197
+ const blockW = textW + padX * 2;
7198
+ const blockH = lines.length * lineH + padY * 2;
7199
+ if (x >= bx && x <= bx + blockW && y >= by && y <= by + blockH) {
7200
+ return { drawing, target: "line" };
7201
+ }
7202
+ if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5) {
7203
+ return { drawing, target: "line" };
7204
+ }
6755
7205
  } else if (drawing.type === "price-range") {
6756
7206
  const p0 = drawing.points[0];
6757
7207
  const p1 = drawing.points[1];
@@ -6980,6 +7430,128 @@ function createChart(element, options = {}) {
6980
7430
  scheduleDraw();
6981
7431
  return true;
6982
7432
  }
7433
+ if (activeDrawingTool === "arrow") {
7434
+ if (draftDrawing?.type === "arrow") {
7435
+ const anchor = draftDrawing.points[0];
7436
+ const endPoint = shiftKeyActive ? constrainAngleFromAnchor(anchor, x, y) ?? point : point;
7437
+ const completed = normalizeDrawingState({
7438
+ ...serializeDrawing(draftDrawing),
7439
+ points: [anchor, endPoint]
7440
+ });
7441
+ drawings.push(completed);
7442
+ draftDrawing = null;
7443
+ activeDrawingTool = null;
7444
+ emitDrawingsChange();
7445
+ scheduleDraw();
7446
+ return true;
7447
+ }
7448
+ const defaults = getDrawingToolDefaults("arrow");
7449
+ draftDrawing = normalizeDrawingState({
7450
+ type: "arrow",
7451
+ points: [point, point],
7452
+ color: defaults.color ?? "#2563eb",
7453
+ style: defaults.style ?? "solid",
7454
+ width: defaults.width ?? 2
7455
+ });
7456
+ scheduleDraw();
7457
+ return true;
7458
+ }
7459
+ if (activeDrawingTool === "parallel-channel") {
7460
+ if (draftDrawing?.type === "parallel-channel") {
7461
+ if (draftDrawing.points.length < 3) {
7462
+ draftDrawing = normalizeDrawingState({
7463
+ ...serializeDrawing(draftDrawing),
7464
+ points: [...draftDrawing.points.slice(0, -1), point, point]
7465
+ });
7466
+ scheduleDraw();
7467
+ return true;
7468
+ }
7469
+ const completed = normalizeDrawingState({
7470
+ ...serializeDrawing(draftDrawing),
7471
+ points: [draftDrawing.points[0], draftDrawing.points[1], point]
7472
+ });
7473
+ drawings.push(completed);
7474
+ draftDrawing = null;
7475
+ activeDrawingTool = null;
7476
+ emitDrawingsChange();
7477
+ scheduleDraw();
7478
+ return true;
7479
+ }
7480
+ const defaults = getDrawingToolDefaults("parallel-channel");
7481
+ draftDrawing = normalizeDrawingState({
7482
+ type: "parallel-channel",
7483
+ points: [point, point],
7484
+ color: defaults.color ?? "#2563eb",
7485
+ style: defaults.style ?? "solid",
7486
+ width: defaults.width ?? 2
7487
+ });
7488
+ scheduleDraw();
7489
+ return true;
7490
+ }
7491
+ if (activeDrawingTool === "ellipse") {
7492
+ if (draftDrawing?.type === "ellipse") {
7493
+ const completed = normalizeDrawingState({
7494
+ ...serializeDrawing(draftDrawing),
7495
+ points: [draftDrawing.points[0], point]
7496
+ });
7497
+ drawings.push(completed);
7498
+ draftDrawing = null;
7499
+ activeDrawingTool = null;
7500
+ emitDrawingsChange();
7501
+ scheduleDraw();
7502
+ return true;
7503
+ }
7504
+ const defaults = getDrawingToolDefaults("ellipse");
7505
+ draftDrawing = normalizeDrawingState({
7506
+ type: "ellipse",
7507
+ points: [point, point],
7508
+ color: defaults.color ?? "#2962ff",
7509
+ style: defaults.style ?? "solid",
7510
+ width: defaults.width ?? 1
7511
+ });
7512
+ scheduleDraw();
7513
+ return true;
7514
+ }
7515
+ if (activeDrawingTool === "callout") {
7516
+ if (draftDrawing?.type === "callout") {
7517
+ const completed = normalizeDrawingState({
7518
+ ...serializeDrawing(draftDrawing),
7519
+ points: [draftDrawing.points[0], point]
7520
+ });
7521
+ drawings.push(completed);
7522
+ draftDrawing = null;
7523
+ activeDrawingTool = null;
7524
+ selectedDrawingId = completed.id;
7525
+ emitDrawingsChange();
7526
+ scheduleDraw();
7527
+ drawingEditTextHandler?.({ drawing: serializeDrawing(completed), target: "line", x, y });
7528
+ return true;
7529
+ }
7530
+ const defaults = getDrawingToolDefaults("callout");
7531
+ draftDrawing = normalizeDrawingState({
7532
+ type: "callout",
7533
+ points: [point, point],
7534
+ color: defaults.color ?? "#2962ff",
7535
+ style: defaults.style ?? "solid",
7536
+ width: defaults.width ?? 1,
7537
+ ...defaults.fontSize === void 0 ? {} : { fontSize: defaults.fontSize },
7538
+ label: ""
7539
+ });
7540
+ scheduleDraw();
7541
+ return true;
7542
+ }
7543
+ if (activeDrawingTool === "brush") {
7544
+ const defaults = getDrawingToolDefaults("brush");
7545
+ draftDrawing = normalizeDrawingState({
7546
+ type: "brush",
7547
+ points: [point],
7548
+ color: defaults.color ?? "#f59e0b",
7549
+ style: defaults.style ?? "solid",
7550
+ width: defaults.width ?? 2
7551
+ });
7552
+ scheduleDraw();
7553
+ return true;
7554
+ }
6983
7555
  if (activeDrawingTool === "fib-retracement") {
6984
7556
  if (draftDrawing?.type === "fib-retracement") {
6985
7557
  const completed = normalizeDrawingState({
@@ -7183,7 +7755,7 @@ function createChart(element, options = {}) {
7183
7755
  }
7184
7756
  return { ...drawing, points: pts };
7185
7757
  }
7186
- if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
7758
+ if (drawingDragState.target === "handle" && (drawing.type === "rectangle" || drawing.type === "ellipse")) {
7187
7759
  const pts = drawing.points.map((point) => ({ ...point }));
7188
7760
  const p0 = pts[0];
7189
7761
  const p1 = pts[1];
@@ -7202,7 +7774,7 @@ function createChart(element, options = {}) {
7202
7774
  }
7203
7775
  return { ...drawing, points: pts };
7204
7776
  }
7205
- if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
7777
+ if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray" || drawing.type === "arrow")) {
7206
7778
  const pointIndex = drawingDragState.pointIndex ?? 0;
7207
7779
  const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
7208
7780
  const constrained = anchor ? constrainAngleFromAnchor(anchor, x, y) : null;
@@ -7320,6 +7892,19 @@ function createChart(element, options = {}) {
7320
7892
  return;
7321
7893
  }
7322
7894
  }
7895
+ if (measureState) {
7896
+ if (measureState.dragging) {
7897
+ measureState = { ...measureState, dragging: false };
7898
+ if (activeDrawingTool === "measure") {
7899
+ activeDrawingTool = null;
7900
+ canvas.style.cursor = "default";
7901
+ }
7902
+ scheduleDraw();
7903
+ return;
7904
+ }
7905
+ measureState = null;
7906
+ scheduleDraw();
7907
+ }
7323
7908
  const drawingToolCapturesPointer = activeDrawingTool !== null || draftDrawing !== null;
7324
7909
  if (!drawingToolCapturesPointer) {
7325
7910
  const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
@@ -7394,6 +7979,18 @@ function createChart(element, options = {}) {
7394
7979
  if (region === "outside") {
7395
7980
  return;
7396
7981
  }
7982
+ if (region === "plot" && (activeDrawingTool === "measure" || activeDrawingTool === null && draftDrawing === null && event.shiftKey && event.pointerType === "mouse")) {
7983
+ const measurePoint = drawingPointFromCanvas(point.x, point.y);
7984
+ if (measurePoint) {
7985
+ measureState = { start: measurePoint, end: measurePoint, dragging: true };
7986
+ activePointerId = event.pointerId;
7987
+ capturePointer(event.pointerId);
7988
+ setCrosshairPoint(null);
7989
+ canvas.style.cursor = "crosshair";
7990
+ scheduleDraw();
7991
+ return;
7992
+ }
7993
+ }
7397
7994
  if (region === "plot" && !activeDrawingTool) {
7398
7995
  const drawingHit = getDrawingHit(point.x, point.y);
7399
7996
  if (drawingHit) {
@@ -7427,6 +8024,10 @@ function createChart(element, options = {}) {
7427
8024
  if (region === "plot" && handleDrawingToolPointerDown(point.x, point.y)) {
7428
8025
  setCrosshairPoint(null);
7429
8026
  canvas.style.cursor = "crosshair";
8027
+ if (activeDrawingTool === "brush" && draftDrawing?.type === "brush") {
8028
+ activePointerId = event.pointerId;
8029
+ capturePointer(event.pointerId);
8030
+ }
7430
8031
  return;
7431
8032
  }
7432
8033
  isDragging = true;
@@ -7489,6 +8090,19 @@ function createChart(element, options = {}) {
7489
8090
  }
7490
8091
  return;
7491
8092
  }
8093
+ if (measureState?.dragging) {
8094
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
8095
+ return;
8096
+ }
8097
+ const nextMeasurePoint = drawingPointFromCanvas(point.x, point.y);
8098
+ if (nextMeasurePoint) {
8099
+ measureState = { ...measureState, end: nextMeasurePoint };
8100
+ setCrosshairPoint(null);
8101
+ canvas.style.cursor = "crosshair";
8102
+ scheduleDraw();
8103
+ }
8104
+ return;
8105
+ }
7492
8106
  if (drawingDragState) {
7493
8107
  if (activePointerId !== null && event.pointerId !== activePointerId) {
7494
8108
  return;
@@ -7498,9 +8112,30 @@ function createChart(element, options = {}) {
7498
8112
  setCrosshairPoint(null);
7499
8113
  return;
7500
8114
  }
7501
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "rectangle" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
8115
+ if (draftDrawing?.type === "brush" && activeDrawingTool === "brush") {
8116
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
8117
+ return;
8118
+ }
8119
+ const nextPoint = drawingPointFromCanvas(point.x, point.y);
8120
+ if (nextPoint) {
8121
+ const last = draftDrawing.points[draftDrawing.points.length - 1];
8122
+ const lastX = last ? canvasXFromDrawingPoint(last) : Number.NEGATIVE_INFINITY;
8123
+ const lastY = last ? canvasYFromDrawingPrice(last.price) : Number.NEGATIVE_INFINITY;
8124
+ if (Math.hypot(point.x - lastX, point.y - lastY) >= 3) {
8125
+ draftDrawing = {
8126
+ ...draftDrawing,
8127
+ points: [...draftDrawing.points, nextPoint]
8128
+ };
8129
+ }
8130
+ canvas.style.cursor = "crosshair";
8131
+ setCrosshairPoint(null);
8132
+ scheduleDraw();
8133
+ }
8134
+ return;
8135
+ }
8136
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "arrow" || activeDrawingTool === "rectangle" || activeDrawingTool === "ellipse" || activeDrawingTool === "callout" || activeDrawingTool === "parallel-channel" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
7502
8137
  let nextPoint = drawingPointFromCanvas(point.x, point.y);
7503
- if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
8138
+ if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray" || draftDrawing.type === "arrow") && draftDrawing.points[0]) {
7504
8139
  nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
7505
8140
  }
7506
8141
  if (nextPoint) {
@@ -7719,6 +8354,43 @@ function createChart(element, options = {}) {
7719
8354
  if (event && activePointerId !== null && event.pointerId !== activePointerId) {
7720
8355
  return;
7721
8356
  }
8357
+ if (measureState?.dragging) {
8358
+ const movedMeasure = measureState.start.index !== measureState.end.index || measureState.start.price !== measureState.end.price;
8359
+ if (movedMeasure) {
8360
+ measureState = { ...measureState, dragging: false };
8361
+ if (activeDrawingTool === "measure") {
8362
+ activeDrawingTool = null;
8363
+ }
8364
+ canvas.style.cursor = "default";
8365
+ } else {
8366
+ canvas.style.cursor = "crosshair";
8367
+ }
8368
+ isDragging = false;
8369
+ dragMode = null;
8370
+ activePointerId = null;
8371
+ pointerDownInfo = null;
8372
+ scheduleDraw();
8373
+ return;
8374
+ }
8375
+ if (draftDrawing?.type === "brush" && activeDrawingTool === "brush") {
8376
+ if (draftDrawing.points.length >= 2) {
8377
+ const completed = normalizeDrawingState(serializeDrawing(draftDrawing));
8378
+ drawings.push(completed);
8379
+ draftDrawing = null;
8380
+ activeDrawingTool = null;
8381
+ canvas.style.cursor = "default";
8382
+ emitDrawingsChange();
8383
+ } else {
8384
+ draftDrawing = null;
8385
+ canvas.style.cursor = "crosshair";
8386
+ }
8387
+ isDragging = false;
8388
+ dragMode = null;
8389
+ activePointerId = null;
8390
+ pointerDownInfo = null;
8391
+ scheduleDraw();
8392
+ return;
8393
+ }
7722
8394
  if (orderDragState) {
7723
8395
  const moved = orderDragState.lastPrice !== orderDragState.startPrice;
7724
8396
  const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
@@ -7862,7 +8534,7 @@ function createChart(element, options = {}) {
7862
8534
  x: point.x,
7863
8535
  y: point.y
7864
8536
  };
7865
- if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note") {
8537
+ if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note" || drawingHit.drawing.type === "callout") {
7866
8538
  drawingEditTextHandler?.(payload);
7867
8539
  } else {
7868
8540
  drawingDoubleClickHandler?.(payload);
@@ -7899,6 +8571,15 @@ function createChart(element, options = {}) {
7899
8571
  canvas.addEventListener("dblclick", onDoubleClick);
7900
8572
  canvas.addEventListener("contextmenu", onContextMenu);
7901
8573
  const onModifierKeyChange = (event) => {
8574
+ if (event.key === "Escape" && event.type === "keydown" && measureState) {
8575
+ measureState = null;
8576
+ if (activeDrawingTool === "measure") {
8577
+ activeDrawingTool = null;
8578
+ canvas.style.cursor = "default";
8579
+ }
8580
+ scheduleDraw();
8581
+ return;
8582
+ }
7902
8583
  const active = event.metaKey || event.ctrlKey;
7903
8584
  const shift = event.shiftKey;
7904
8585
  const changed = active !== magnetModifierActive || shift !== shiftKeyActive;
@@ -8211,17 +8892,29 @@ function createChart(element, options = {}) {
8211
8892
  const setActiveDrawingTool = (tool) => {
8212
8893
  activeDrawingTool = tool;
8213
8894
  draftDrawing = null;
8895
+ measureState = null;
8214
8896
  canvas.style.cursor = tool ? "crosshair" : "default";
8215
8897
  scheduleDraw();
8216
8898
  };
8217
8899
  const getActiveDrawingTool = () => activeDrawingTool;
8218
8900
  const cancelDrawing = () => {
8901
+ let cancelled = false;
8219
8902
  if (draftDrawing) {
8220
8903
  draftDrawing = null;
8904
+ cancelled = true;
8905
+ }
8906
+ if (measureState) {
8907
+ measureState = null;
8908
+ if (activeDrawingTool === "measure") {
8909
+ activeDrawingTool = null;
8910
+ canvas.style.cursor = "default";
8911
+ }
8912
+ cancelled = true;
8913
+ }
8914
+ if (cancelled) {
8221
8915
  scheduleDraw();
8222
- return true;
8223
8916
  }
8224
- return false;
8917
+ return cancelled;
8225
8918
  };
8226
8919
  const setTradeMarkers = (markers) => {
8227
8920
  tradeMarkers = Array.isArray(markers) ? markers.slice() : [];
@@ -8273,6 +8966,9 @@ function createChart(element, options = {}) {
8273
8966
  const onDrawingsChange = (handler) => {
8274
8967
  drawingsChangeHandler = handler;
8275
8968
  };
8969
+ const onActiveDrawingToolChange = (handler) => {
8970
+ drawingToolChangeHandler = handler;
8971
+ };
8276
8972
  const onDrawingSelect = (handler) => {
8277
8973
  drawingSelectHandler = handler;
8278
8974
  };
@@ -8285,6 +8981,51 @@ function createChart(element, options = {}) {
8285
8981
  const onDrawingHover = (handler) => {
8286
8982
  drawingHoverHandler = handler;
8287
8983
  };
8984
+ const saveState = () => {
8985
+ const drawingDefaults = {};
8986
+ for (const [tool, defaults] of drawingToolDefaults) {
8987
+ drawingDefaults[tool] = { ...defaults };
8988
+ }
8989
+ return {
8990
+ version: 1,
8991
+ chartType: getChartType(),
8992
+ viewport: getViewport(),
8993
+ drawings: getDrawings(),
8994
+ indicators: getIndicators(),
8995
+ magnetMode: getMagnetMode(),
8996
+ drawingDefaults
8997
+ };
8998
+ };
8999
+ const loadState = (state) => {
9000
+ if (!state || typeof state !== "object") {
9001
+ return;
9002
+ }
9003
+ if (typeof state.chartType === "string") {
9004
+ setChartType(state.chartType);
9005
+ }
9006
+ if (Array.isArray(state.indicators)) {
9007
+ setIndicators(state.indicators);
9008
+ }
9009
+ if (Array.isArray(state.drawings)) {
9010
+ setDrawings(state.drawings);
9011
+ }
9012
+ if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
9013
+ setMagnetMode(state.magnetMode);
9014
+ }
9015
+ if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
9016
+ for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
9017
+ if (defaults && typeof defaults === "object") {
9018
+ setDrawingDefaults(tool, defaults);
9019
+ }
9020
+ }
9021
+ }
9022
+ if (state.viewport && typeof state.viewport === "object") {
9023
+ setViewport(state.viewport);
9024
+ }
9025
+ };
9026
+ const takeScreenshot = (options2 = {}) => {
9027
+ return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
9028
+ };
8288
9029
  const destroy = () => {
8289
9030
  cancelKineticPan();
8290
9031
  if (smoothingRafId !== null) {
@@ -8359,6 +9100,7 @@ function createChart(element, options = {}) {
8359
9100
  removeDrawing,
8360
9101
  clearDrawings,
8361
9102
  onDrawingsChange,
9103
+ onActiveDrawingToolChange,
8362
9104
  onDrawingSelect,
8363
9105
  onDrawingDoubleClick,
8364
9106
  onDrawingEditText,
@@ -8379,6 +9121,9 @@ function createChart(element, options = {}) {
8379
9121
  updateIndicator,
8380
9122
  removeIndicator,
8381
9123
  setIndicators,
9124
+ saveState,
9125
+ loadState,
9126
+ takeScreenshot,
8382
9127
  resize,
8383
9128
  destroy
8384
9129
  };