hyperprop-charting-library 0.1.135 → 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.
- package/dist/hyperprop-charting-library.cjs +549 -5
- package/dist/hyperprop-charting-library.d.ts +49 -1
- package/dist/hyperprop-charting-library.js +549 -5
- package/dist/index.cjs +549 -5
- package/dist/index.d.cts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +549 -5
- package/docs/API.md +44 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4788,6 +4788,216 @@ function createChart(element, options = {}) {
|
|
|
4788
4788
|
drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
|
|
4789
4789
|
}
|
|
4790
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
|
+
}
|
|
4791
5001
|
} else if (drawing.type === "price-range") {
|
|
4792
5002
|
const p0 = drawing.points[0];
|
|
4793
5003
|
const p1 = drawing.points[1];
|
|
@@ -6842,6 +7052,126 @@ function createChart(element, options = {}) {
|
|
|
6842
7052
|
if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
|
|
6843
7053
|
return { drawing, target: "line" };
|
|
6844
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
|
+
}
|
|
6845
7175
|
} else if (drawing.type === "price-range") {
|
|
6846
7176
|
const p0 = drawing.points[0];
|
|
6847
7177
|
const p1 = drawing.points[1];
|
|
@@ -7070,6 +7400,128 @@ function createChart(element, options = {}) {
|
|
|
7070
7400
|
scheduleDraw();
|
|
7071
7401
|
return true;
|
|
7072
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
|
+
}
|
|
7073
7525
|
if (activeDrawingTool === "fib-retracement") {
|
|
7074
7526
|
if (draftDrawing?.type === "fib-retracement") {
|
|
7075
7527
|
const completed = normalizeDrawingState({
|
|
@@ -7273,7 +7725,7 @@ function createChart(element, options = {}) {
|
|
|
7273
7725
|
}
|
|
7274
7726
|
return { ...drawing, points: pts };
|
|
7275
7727
|
}
|
|
7276
|
-
if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
|
|
7728
|
+
if (drawingDragState.target === "handle" && (drawing.type === "rectangle" || drawing.type === "ellipse")) {
|
|
7277
7729
|
const pts = drawing.points.map((point) => ({ ...point }));
|
|
7278
7730
|
const p0 = pts[0];
|
|
7279
7731
|
const p1 = pts[1];
|
|
@@ -7292,7 +7744,7 @@ function createChart(element, options = {}) {
|
|
|
7292
7744
|
}
|
|
7293
7745
|
return { ...drawing, points: pts };
|
|
7294
7746
|
}
|
|
7295
|
-
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")) {
|
|
7296
7748
|
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
7297
7749
|
const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
|
|
7298
7750
|
const constrained = anchor ? constrainAngleFromAnchor(anchor, x, y) : null;
|
|
@@ -7542,6 +7994,10 @@ function createChart(element, options = {}) {
|
|
|
7542
7994
|
if (region === "plot" && handleDrawingToolPointerDown(point.x, point.y)) {
|
|
7543
7995
|
setCrosshairPoint(null);
|
|
7544
7996
|
canvas.style.cursor = "crosshair";
|
|
7997
|
+
if (activeDrawingTool === "brush" && draftDrawing?.type === "brush") {
|
|
7998
|
+
activePointerId = event.pointerId;
|
|
7999
|
+
capturePointer(event.pointerId);
|
|
8000
|
+
}
|
|
7545
8001
|
return;
|
|
7546
8002
|
}
|
|
7547
8003
|
isDragging = true;
|
|
@@ -7626,9 +8082,30 @@ function createChart(element, options = {}) {
|
|
|
7626
8082
|
setCrosshairPoint(null);
|
|
7627
8083
|
return;
|
|
7628
8084
|
}
|
|
7629
|
-
if (draftDrawing
|
|
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")) {
|
|
7630
8107
|
let nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
7631
|
-
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]) {
|
|
7632
8109
|
nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
|
|
7633
8110
|
}
|
|
7634
8111
|
if (nextPoint) {
|
|
@@ -7865,6 +8342,25 @@ function createChart(element, options = {}) {
|
|
|
7865
8342
|
scheduleDraw();
|
|
7866
8343
|
return;
|
|
7867
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
|
+
}
|
|
7868
8364
|
if (orderDragState) {
|
|
7869
8365
|
const moved = orderDragState.lastPrice !== orderDragState.startPrice;
|
|
7870
8366
|
const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
|
|
@@ -8008,7 +8504,7 @@ function createChart(element, options = {}) {
|
|
|
8008
8504
|
x: point.x,
|
|
8009
8505
|
y: point.y
|
|
8010
8506
|
};
|
|
8011
|
-
if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note") {
|
|
8507
|
+
if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note" || drawingHit.drawing.type === "callout") {
|
|
8012
8508
|
drawingEditTextHandler?.(payload);
|
|
8013
8509
|
} else {
|
|
8014
8510
|
drawingDoubleClickHandler?.(payload);
|
|
@@ -8455,6 +8951,51 @@ function createChart(element, options = {}) {
|
|
|
8455
8951
|
const onDrawingHover = (handler) => {
|
|
8456
8952
|
drawingHoverHandler = handler;
|
|
8457
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
|
+
};
|
|
8458
8999
|
const destroy = () => {
|
|
8459
9000
|
cancelKineticPan();
|
|
8460
9001
|
if (smoothingRafId !== null) {
|
|
@@ -8550,6 +9091,9 @@ function createChart(element, options = {}) {
|
|
|
8550
9091
|
updateIndicator,
|
|
8551
9092
|
removeIndicator,
|
|
8552
9093
|
setIndicators,
|
|
9094
|
+
saveState,
|
|
9095
|
+
loadState,
|
|
9096
|
+
takeScreenshot,
|
|
8553
9097
|
resize,
|
|
8554
9098
|
destroy
|
|
8555
9099
|
};
|