hyperprop-charting-library 0.1.113 → 0.1.115
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 +135 -1
- package/dist/hyperprop-charting-library.d.ts +8 -1
- package/dist/hyperprop-charting-library.js +135 -1
- package/dist/index.cjs +135 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +135 -1
- package/package.json +1 -1
|
@@ -2666,6 +2666,37 @@ function createChart(element, options = {}) {
|
|
|
2666
2666
|
}
|
|
2667
2667
|
}
|
|
2668
2668
|
}
|
|
2669
|
+
} else if (drawing.type === "rectangle") {
|
|
2670
|
+
const p0 = drawing.points[0];
|
|
2671
|
+
const p1 = drawing.points[1];
|
|
2672
|
+
if (p0 && p1) {
|
|
2673
|
+
const px0 = xFromDrawingPoint(p0);
|
|
2674
|
+
const px1 = xFromDrawingPoint(p1);
|
|
2675
|
+
const leftX = Math.min(px0, px1);
|
|
2676
|
+
const rightX = Math.max(px0, px1);
|
|
2677
|
+
const y0 = yFromPrice(p0.price);
|
|
2678
|
+
const y1 = yFromPrice(p1.price);
|
|
2679
|
+
const topY = Math.min(y0, y1);
|
|
2680
|
+
const botY = Math.max(y0, y1);
|
|
2681
|
+
const boxW = Math.max(1, rightX - leftX);
|
|
2682
|
+
const boxH = Math.max(1, botY - topY);
|
|
2683
|
+
ctx.save();
|
|
2684
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2685
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.14);
|
|
2686
|
+
ctx.fillRect(leftX, topY, boxW, boxH);
|
|
2687
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2688
|
+
ctx.strokeStyle = drawing.color;
|
|
2689
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2690
|
+
ctx.strokeRect(crisp(leftX), crisp(topY), crisp(rightX) - crisp(leftX), crisp(botY) - crisp(topY));
|
|
2691
|
+
ctx.restore();
|
|
2692
|
+
handleAt(px0, y0, drawing.color);
|
|
2693
|
+
handleAt(px1, y1, drawing.color);
|
|
2694
|
+
handleAt(px0, y1, drawing.color);
|
|
2695
|
+
handleAt(px1, y0, drawing.color);
|
|
2696
|
+
if (drawing.label) {
|
|
2697
|
+
drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2669
2700
|
} else if (drawing.type === "price-range") {
|
|
2670
2701
|
const p0 = drawing.points[0];
|
|
2671
2702
|
const p1 = drawing.points[1];
|
|
@@ -4155,6 +4186,21 @@ function createChart(element, options = {}) {
|
|
|
4155
4186
|
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
4156
4187
|
return { drawing, target: "line" };
|
|
4157
4188
|
}
|
|
4189
|
+
} else if (drawing.type === "rectangle") {
|
|
4190
|
+
const p0 = drawing.points[0];
|
|
4191
|
+
const p1 = drawing.points[1];
|
|
4192
|
+
if (!p0 || !p1) continue;
|
|
4193
|
+
const px0 = canvasXFromDrawingPoint(p0);
|
|
4194
|
+
const px1 = canvasXFromDrawingPoint(p1);
|
|
4195
|
+
const y0 = canvasYFromDrawingPrice(p0.price);
|
|
4196
|
+
const y1 = canvasYFromDrawingPrice(p1.price);
|
|
4197
|
+
if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
4198
|
+
if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
4199
|
+
if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
4200
|
+
if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
4201
|
+
if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
|
|
4202
|
+
return { drawing, target: "line" };
|
|
4203
|
+
}
|
|
4158
4204
|
} else if (drawing.type === "price-range") {
|
|
4159
4205
|
const p0 = drawing.points[0];
|
|
4160
4206
|
const p1 = drawing.points[1];
|
|
@@ -4411,6 +4457,30 @@ function createChart(element, options = {}) {
|
|
|
4411
4457
|
drawingEditTextHandler?.({ drawing: serializeDrawing(created), target: "line", x, y });
|
|
4412
4458
|
return true;
|
|
4413
4459
|
}
|
|
4460
|
+
if (activeDrawingTool === "rectangle") {
|
|
4461
|
+
if (draftDrawing?.type === "rectangle") {
|
|
4462
|
+
const completed = normalizeDrawingState({
|
|
4463
|
+
...serializeDrawing(draftDrawing),
|
|
4464
|
+
points: [draftDrawing.points[0], point]
|
|
4465
|
+
});
|
|
4466
|
+
drawings.push(completed);
|
|
4467
|
+
draftDrawing = null;
|
|
4468
|
+
activeDrawingTool = null;
|
|
4469
|
+
emitDrawingsChange();
|
|
4470
|
+
scheduleDraw();
|
|
4471
|
+
return true;
|
|
4472
|
+
}
|
|
4473
|
+
const defaults = getDrawingToolDefaults("rectangle");
|
|
4474
|
+
draftDrawing = normalizeDrawingState({
|
|
4475
|
+
type: "rectangle",
|
|
4476
|
+
points: [point, point],
|
|
4477
|
+
color: defaults.color ?? "#2962ff",
|
|
4478
|
+
style: defaults.style ?? "solid",
|
|
4479
|
+
width: defaults.width ?? 1
|
|
4480
|
+
});
|
|
4481
|
+
scheduleDraw();
|
|
4482
|
+
return true;
|
|
4483
|
+
}
|
|
4414
4484
|
if (activeDrawingTool === "price-range") {
|
|
4415
4485
|
const tick = getConfiguredTickSize();
|
|
4416
4486
|
const visibleRange = drawState.yMax - drawState.yMin;
|
|
@@ -4512,6 +4582,25 @@ function createChart(element, options = {}) {
|
|
|
4512
4582
|
}
|
|
4513
4583
|
return { ...drawing, points: pts };
|
|
4514
4584
|
}
|
|
4585
|
+
if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
|
|
4586
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
4587
|
+
const p0 = pts[0];
|
|
4588
|
+
const p1 = pts[1];
|
|
4589
|
+
if (!p0 || !p1) return drawing;
|
|
4590
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4591
|
+
if (pointIndex === 0) {
|
|
4592
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4593
|
+
} else if (pointIndex === 1) {
|
|
4594
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4595
|
+
} else if (pointIndex === 2) {
|
|
4596
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, p0.price);
|
|
4597
|
+
pts[1] = normalizeDrawingPoint(p1.index, currentPoint.price);
|
|
4598
|
+
} else if (pointIndex === 3) {
|
|
4599
|
+
pts[0] = normalizeDrawingPoint(p0.index, currentPoint.price);
|
|
4600
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, p1.price);
|
|
4601
|
+
}
|
|
4602
|
+
return { ...drawing, points: pts };
|
|
4603
|
+
}
|
|
4515
4604
|
if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
|
|
4516
4605
|
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4517
4606
|
const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
|
|
@@ -4780,7 +4869,7 @@ function createChart(element, options = {}) {
|
|
|
4780
4869
|
setCrosshairPoint(null);
|
|
4781
4870
|
return;
|
|
4782
4871
|
}
|
|
4783
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4872
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "rectangle" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4784
4873
|
let nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
4785
4874
|
if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
|
|
4786
4875
|
nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
|
|
@@ -5198,6 +5287,50 @@ function createChart(element, options = {}) {
|
|
|
5198
5287
|
}
|
|
5199
5288
|
scheduleDraw();
|
|
5200
5289
|
};
|
|
5290
|
+
const upsertBar = (point) => {
|
|
5291
|
+
const time = new Date(point.t);
|
|
5292
|
+
const timeMs = time.getTime();
|
|
5293
|
+
const open = Number(point.o);
|
|
5294
|
+
const close = Number(point.c);
|
|
5295
|
+
const highInput = Number(point.h);
|
|
5296
|
+
const lowInput = Number(point.l);
|
|
5297
|
+
if (!Number.isFinite(timeMs) || !Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(highInput) || !Number.isFinite(lowInput)) {
|
|
5298
|
+
return;
|
|
5299
|
+
}
|
|
5300
|
+
const volumeValue = point.v === void 0 ? void 0 : Number(point.v);
|
|
5301
|
+
const parsed = {
|
|
5302
|
+
time,
|
|
5303
|
+
o: open,
|
|
5304
|
+
h: Math.max(highInput, open, close),
|
|
5305
|
+
l: Math.min(lowInput, open, close),
|
|
5306
|
+
c: close,
|
|
5307
|
+
...volumeValue === void 0 || !Number.isFinite(volumeValue) ? {} : { v: volumeValue }
|
|
5308
|
+
};
|
|
5309
|
+
const last = data[data.length - 1];
|
|
5310
|
+
if (!last) {
|
|
5311
|
+
setData([point]);
|
|
5312
|
+
return;
|
|
5313
|
+
}
|
|
5314
|
+
const lastMs = last.time.getTime();
|
|
5315
|
+
if (timeMs === lastMs) {
|
|
5316
|
+
data[data.length - 1] = parsed;
|
|
5317
|
+
} else if (timeMs > lastMs) {
|
|
5318
|
+
data.push(parsed);
|
|
5319
|
+
if (mergedOptions.preserveViewportOnDataUpdate && followLatest) {
|
|
5320
|
+
xCenter = data.length - xSpan / 2 + rightEdgePaddingBars;
|
|
5321
|
+
clampXViewport();
|
|
5322
|
+
}
|
|
5323
|
+
} else {
|
|
5324
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5325
|
+
for (const existing of data) {
|
|
5326
|
+
merged.set(existing.time.getTime(), existing);
|
|
5327
|
+
}
|
|
5328
|
+
merged.set(timeMs, parsed);
|
|
5329
|
+
data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
|
|
5330
|
+
}
|
|
5331
|
+
pushSmoothedTicker(parsed.c, parsed.v);
|
|
5332
|
+
scheduleDraw();
|
|
5333
|
+
};
|
|
5201
5334
|
const setPriceLines = (lines) => {
|
|
5202
5335
|
priceLines = lines.map((line, index) => ({
|
|
5203
5336
|
...line,
|
|
@@ -5453,6 +5586,7 @@ function createChart(element, options = {}) {
|
|
|
5453
5586
|
return {
|
|
5454
5587
|
updateOptions,
|
|
5455
5588
|
setData,
|
|
5589
|
+
upsertBar,
|
|
5456
5590
|
setPriceLines,
|
|
5457
5591
|
addPriceLine,
|
|
5458
5592
|
removePriceLine,
|
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "text" | "note";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "rectangle" | "text" | "note";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -412,6 +412,13 @@ interface LabelsOptions {
|
|
|
412
412
|
interface ChartInstance {
|
|
413
413
|
updateOptions: (options: ChartOptions) => void;
|
|
414
414
|
setData: (data: OhlcDataPoint[]) => void;
|
|
415
|
+
/**
|
|
416
|
+
* Incrementally upsert a single bar: O(1) update-in-place for the live
|
|
417
|
+
* (forming) bar and O(1) append for a new bar, instead of re-ingesting the
|
|
418
|
+
* full series through `setData`. Out-of-order timestamps fall back to a
|
|
419
|
+
* full merge, so correctness never depends on call ordering.
|
|
420
|
+
*/
|
|
421
|
+
upsertBar: (point: OhlcDataPoint) => void;
|
|
415
422
|
setPriceLines: (lines: PriceLineOptions[]) => void;
|
|
416
423
|
addPriceLine: (line: PriceLineOptions) => string;
|
|
417
424
|
removePriceLine: (id: string) => void;
|
|
@@ -2640,6 +2640,37 @@ function createChart(element, options = {}) {
|
|
|
2640
2640
|
}
|
|
2641
2641
|
}
|
|
2642
2642
|
}
|
|
2643
|
+
} else if (drawing.type === "rectangle") {
|
|
2644
|
+
const p0 = drawing.points[0];
|
|
2645
|
+
const p1 = drawing.points[1];
|
|
2646
|
+
if (p0 && p1) {
|
|
2647
|
+
const px0 = xFromDrawingPoint(p0);
|
|
2648
|
+
const px1 = xFromDrawingPoint(p1);
|
|
2649
|
+
const leftX = Math.min(px0, px1);
|
|
2650
|
+
const rightX = Math.max(px0, px1);
|
|
2651
|
+
const y0 = yFromPrice(p0.price);
|
|
2652
|
+
const y1 = yFromPrice(p1.price);
|
|
2653
|
+
const topY = Math.min(y0, y1);
|
|
2654
|
+
const botY = Math.max(y0, y1);
|
|
2655
|
+
const boxW = Math.max(1, rightX - leftX);
|
|
2656
|
+
const boxH = Math.max(1, botY - topY);
|
|
2657
|
+
ctx.save();
|
|
2658
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2659
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.14);
|
|
2660
|
+
ctx.fillRect(leftX, topY, boxW, boxH);
|
|
2661
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2662
|
+
ctx.strokeStyle = drawing.color;
|
|
2663
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2664
|
+
ctx.strokeRect(crisp(leftX), crisp(topY), crisp(rightX) - crisp(leftX), crisp(botY) - crisp(topY));
|
|
2665
|
+
ctx.restore();
|
|
2666
|
+
handleAt(px0, y0, drawing.color);
|
|
2667
|
+
handleAt(px1, y1, drawing.color);
|
|
2668
|
+
handleAt(px0, y1, drawing.color);
|
|
2669
|
+
handleAt(px1, y0, drawing.color);
|
|
2670
|
+
if (drawing.label) {
|
|
2671
|
+
drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2643
2674
|
} else if (drawing.type === "price-range") {
|
|
2644
2675
|
const p0 = drawing.points[0];
|
|
2645
2676
|
const p1 = drawing.points[1];
|
|
@@ -4129,6 +4160,21 @@ function createChart(element, options = {}) {
|
|
|
4129
4160
|
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
4130
4161
|
return { drawing, target: "line" };
|
|
4131
4162
|
}
|
|
4163
|
+
} else if (drawing.type === "rectangle") {
|
|
4164
|
+
const p0 = drawing.points[0];
|
|
4165
|
+
const p1 = drawing.points[1];
|
|
4166
|
+
if (!p0 || !p1) continue;
|
|
4167
|
+
const px0 = canvasXFromDrawingPoint(p0);
|
|
4168
|
+
const px1 = canvasXFromDrawingPoint(p1);
|
|
4169
|
+
const y0 = canvasYFromDrawingPrice(p0.price);
|
|
4170
|
+
const y1 = canvasYFromDrawingPrice(p1.price);
|
|
4171
|
+
if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
4172
|
+
if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
4173
|
+
if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
4174
|
+
if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
4175
|
+
if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
|
|
4176
|
+
return { drawing, target: "line" };
|
|
4177
|
+
}
|
|
4132
4178
|
} else if (drawing.type === "price-range") {
|
|
4133
4179
|
const p0 = drawing.points[0];
|
|
4134
4180
|
const p1 = drawing.points[1];
|
|
@@ -4385,6 +4431,30 @@ function createChart(element, options = {}) {
|
|
|
4385
4431
|
drawingEditTextHandler?.({ drawing: serializeDrawing(created), target: "line", x, y });
|
|
4386
4432
|
return true;
|
|
4387
4433
|
}
|
|
4434
|
+
if (activeDrawingTool === "rectangle") {
|
|
4435
|
+
if (draftDrawing?.type === "rectangle") {
|
|
4436
|
+
const completed = normalizeDrawingState({
|
|
4437
|
+
...serializeDrawing(draftDrawing),
|
|
4438
|
+
points: [draftDrawing.points[0], point]
|
|
4439
|
+
});
|
|
4440
|
+
drawings.push(completed);
|
|
4441
|
+
draftDrawing = null;
|
|
4442
|
+
activeDrawingTool = null;
|
|
4443
|
+
emitDrawingsChange();
|
|
4444
|
+
scheduleDraw();
|
|
4445
|
+
return true;
|
|
4446
|
+
}
|
|
4447
|
+
const defaults = getDrawingToolDefaults("rectangle");
|
|
4448
|
+
draftDrawing = normalizeDrawingState({
|
|
4449
|
+
type: "rectangle",
|
|
4450
|
+
points: [point, point],
|
|
4451
|
+
color: defaults.color ?? "#2962ff",
|
|
4452
|
+
style: defaults.style ?? "solid",
|
|
4453
|
+
width: defaults.width ?? 1
|
|
4454
|
+
});
|
|
4455
|
+
scheduleDraw();
|
|
4456
|
+
return true;
|
|
4457
|
+
}
|
|
4388
4458
|
if (activeDrawingTool === "price-range") {
|
|
4389
4459
|
const tick = getConfiguredTickSize();
|
|
4390
4460
|
const visibleRange = drawState.yMax - drawState.yMin;
|
|
@@ -4486,6 +4556,25 @@ function createChart(element, options = {}) {
|
|
|
4486
4556
|
}
|
|
4487
4557
|
return { ...drawing, points: pts };
|
|
4488
4558
|
}
|
|
4559
|
+
if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
|
|
4560
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
4561
|
+
const p0 = pts[0];
|
|
4562
|
+
const p1 = pts[1];
|
|
4563
|
+
if (!p0 || !p1) return drawing;
|
|
4564
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4565
|
+
if (pointIndex === 0) {
|
|
4566
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4567
|
+
} else if (pointIndex === 1) {
|
|
4568
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4569
|
+
} else if (pointIndex === 2) {
|
|
4570
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, p0.price);
|
|
4571
|
+
pts[1] = normalizeDrawingPoint(p1.index, currentPoint.price);
|
|
4572
|
+
} else if (pointIndex === 3) {
|
|
4573
|
+
pts[0] = normalizeDrawingPoint(p0.index, currentPoint.price);
|
|
4574
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, p1.price);
|
|
4575
|
+
}
|
|
4576
|
+
return { ...drawing, points: pts };
|
|
4577
|
+
}
|
|
4489
4578
|
if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
|
|
4490
4579
|
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4491
4580
|
const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
|
|
@@ -4754,7 +4843,7 @@ function createChart(element, options = {}) {
|
|
|
4754
4843
|
setCrosshairPoint(null);
|
|
4755
4844
|
return;
|
|
4756
4845
|
}
|
|
4757
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4846
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "rectangle" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4758
4847
|
let nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
4759
4848
|
if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
|
|
4760
4849
|
nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
|
|
@@ -5172,6 +5261,50 @@ function createChart(element, options = {}) {
|
|
|
5172
5261
|
}
|
|
5173
5262
|
scheduleDraw();
|
|
5174
5263
|
};
|
|
5264
|
+
const upsertBar = (point) => {
|
|
5265
|
+
const time = new Date(point.t);
|
|
5266
|
+
const timeMs = time.getTime();
|
|
5267
|
+
const open = Number(point.o);
|
|
5268
|
+
const close = Number(point.c);
|
|
5269
|
+
const highInput = Number(point.h);
|
|
5270
|
+
const lowInput = Number(point.l);
|
|
5271
|
+
if (!Number.isFinite(timeMs) || !Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(highInput) || !Number.isFinite(lowInput)) {
|
|
5272
|
+
return;
|
|
5273
|
+
}
|
|
5274
|
+
const volumeValue = point.v === void 0 ? void 0 : Number(point.v);
|
|
5275
|
+
const parsed = {
|
|
5276
|
+
time,
|
|
5277
|
+
o: open,
|
|
5278
|
+
h: Math.max(highInput, open, close),
|
|
5279
|
+
l: Math.min(lowInput, open, close),
|
|
5280
|
+
c: close,
|
|
5281
|
+
...volumeValue === void 0 || !Number.isFinite(volumeValue) ? {} : { v: volumeValue }
|
|
5282
|
+
};
|
|
5283
|
+
const last = data[data.length - 1];
|
|
5284
|
+
if (!last) {
|
|
5285
|
+
setData([point]);
|
|
5286
|
+
return;
|
|
5287
|
+
}
|
|
5288
|
+
const lastMs = last.time.getTime();
|
|
5289
|
+
if (timeMs === lastMs) {
|
|
5290
|
+
data[data.length - 1] = parsed;
|
|
5291
|
+
} else if (timeMs > lastMs) {
|
|
5292
|
+
data.push(parsed);
|
|
5293
|
+
if (mergedOptions.preserveViewportOnDataUpdate && followLatest) {
|
|
5294
|
+
xCenter = data.length - xSpan / 2 + rightEdgePaddingBars;
|
|
5295
|
+
clampXViewport();
|
|
5296
|
+
}
|
|
5297
|
+
} else {
|
|
5298
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5299
|
+
for (const existing of data) {
|
|
5300
|
+
merged.set(existing.time.getTime(), existing);
|
|
5301
|
+
}
|
|
5302
|
+
merged.set(timeMs, parsed);
|
|
5303
|
+
data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
|
|
5304
|
+
}
|
|
5305
|
+
pushSmoothedTicker(parsed.c, parsed.v);
|
|
5306
|
+
scheduleDraw();
|
|
5307
|
+
};
|
|
5175
5308
|
const setPriceLines = (lines) => {
|
|
5176
5309
|
priceLines = lines.map((line, index) => ({
|
|
5177
5310
|
...line,
|
|
@@ -5427,6 +5560,7 @@ function createChart(element, options = {}) {
|
|
|
5427
5560
|
return {
|
|
5428
5561
|
updateOptions,
|
|
5429
5562
|
setData,
|
|
5563
|
+
upsertBar,
|
|
5430
5564
|
setPriceLines,
|
|
5431
5565
|
addPriceLine,
|
|
5432
5566
|
removePriceLine,
|
package/dist/index.cjs
CHANGED
|
@@ -2666,6 +2666,37 @@ function createChart(element, options = {}) {
|
|
|
2666
2666
|
}
|
|
2667
2667
|
}
|
|
2668
2668
|
}
|
|
2669
|
+
} else if (drawing.type === "rectangle") {
|
|
2670
|
+
const p0 = drawing.points[0];
|
|
2671
|
+
const p1 = drawing.points[1];
|
|
2672
|
+
if (p0 && p1) {
|
|
2673
|
+
const px0 = xFromDrawingPoint(p0);
|
|
2674
|
+
const px1 = xFromDrawingPoint(p1);
|
|
2675
|
+
const leftX = Math.min(px0, px1);
|
|
2676
|
+
const rightX = Math.max(px0, px1);
|
|
2677
|
+
const y0 = yFromPrice(p0.price);
|
|
2678
|
+
const y1 = yFromPrice(p1.price);
|
|
2679
|
+
const topY = Math.min(y0, y1);
|
|
2680
|
+
const botY = Math.max(y0, y1);
|
|
2681
|
+
const boxW = Math.max(1, rightX - leftX);
|
|
2682
|
+
const boxH = Math.max(1, botY - topY);
|
|
2683
|
+
ctx.save();
|
|
2684
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2685
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.14);
|
|
2686
|
+
ctx.fillRect(leftX, topY, boxW, boxH);
|
|
2687
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2688
|
+
ctx.strokeStyle = drawing.color;
|
|
2689
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2690
|
+
ctx.strokeRect(crisp(leftX), crisp(topY), crisp(rightX) - crisp(leftX), crisp(botY) - crisp(topY));
|
|
2691
|
+
ctx.restore();
|
|
2692
|
+
handleAt(px0, y0, drawing.color);
|
|
2693
|
+
handleAt(px1, y1, drawing.color);
|
|
2694
|
+
handleAt(px0, y1, drawing.color);
|
|
2695
|
+
handleAt(px1, y0, drawing.color);
|
|
2696
|
+
if (drawing.label) {
|
|
2697
|
+
drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2669
2700
|
} else if (drawing.type === "price-range") {
|
|
2670
2701
|
const p0 = drawing.points[0];
|
|
2671
2702
|
const p1 = drawing.points[1];
|
|
@@ -4155,6 +4186,21 @@ function createChart(element, options = {}) {
|
|
|
4155
4186
|
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
4156
4187
|
return { drawing, target: "line" };
|
|
4157
4188
|
}
|
|
4189
|
+
} else if (drawing.type === "rectangle") {
|
|
4190
|
+
const p0 = drawing.points[0];
|
|
4191
|
+
const p1 = drawing.points[1];
|
|
4192
|
+
if (!p0 || !p1) continue;
|
|
4193
|
+
const px0 = canvasXFromDrawingPoint(p0);
|
|
4194
|
+
const px1 = canvasXFromDrawingPoint(p1);
|
|
4195
|
+
const y0 = canvasYFromDrawingPrice(p0.price);
|
|
4196
|
+
const y1 = canvasYFromDrawingPrice(p1.price);
|
|
4197
|
+
if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
4198
|
+
if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
4199
|
+
if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
4200
|
+
if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
4201
|
+
if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
|
|
4202
|
+
return { drawing, target: "line" };
|
|
4203
|
+
}
|
|
4158
4204
|
} else if (drawing.type === "price-range") {
|
|
4159
4205
|
const p0 = drawing.points[0];
|
|
4160
4206
|
const p1 = drawing.points[1];
|
|
@@ -4411,6 +4457,30 @@ function createChart(element, options = {}) {
|
|
|
4411
4457
|
drawingEditTextHandler?.({ drawing: serializeDrawing(created), target: "line", x, y });
|
|
4412
4458
|
return true;
|
|
4413
4459
|
}
|
|
4460
|
+
if (activeDrawingTool === "rectangle") {
|
|
4461
|
+
if (draftDrawing?.type === "rectangle") {
|
|
4462
|
+
const completed = normalizeDrawingState({
|
|
4463
|
+
...serializeDrawing(draftDrawing),
|
|
4464
|
+
points: [draftDrawing.points[0], point]
|
|
4465
|
+
});
|
|
4466
|
+
drawings.push(completed);
|
|
4467
|
+
draftDrawing = null;
|
|
4468
|
+
activeDrawingTool = null;
|
|
4469
|
+
emitDrawingsChange();
|
|
4470
|
+
scheduleDraw();
|
|
4471
|
+
return true;
|
|
4472
|
+
}
|
|
4473
|
+
const defaults = getDrawingToolDefaults("rectangle");
|
|
4474
|
+
draftDrawing = normalizeDrawingState({
|
|
4475
|
+
type: "rectangle",
|
|
4476
|
+
points: [point, point],
|
|
4477
|
+
color: defaults.color ?? "#2962ff",
|
|
4478
|
+
style: defaults.style ?? "solid",
|
|
4479
|
+
width: defaults.width ?? 1
|
|
4480
|
+
});
|
|
4481
|
+
scheduleDraw();
|
|
4482
|
+
return true;
|
|
4483
|
+
}
|
|
4414
4484
|
if (activeDrawingTool === "price-range") {
|
|
4415
4485
|
const tick = getConfiguredTickSize();
|
|
4416
4486
|
const visibleRange = drawState.yMax - drawState.yMin;
|
|
@@ -4512,6 +4582,25 @@ function createChart(element, options = {}) {
|
|
|
4512
4582
|
}
|
|
4513
4583
|
return { ...drawing, points: pts };
|
|
4514
4584
|
}
|
|
4585
|
+
if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
|
|
4586
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
4587
|
+
const p0 = pts[0];
|
|
4588
|
+
const p1 = pts[1];
|
|
4589
|
+
if (!p0 || !p1) return drawing;
|
|
4590
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4591
|
+
if (pointIndex === 0) {
|
|
4592
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4593
|
+
} else if (pointIndex === 1) {
|
|
4594
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4595
|
+
} else if (pointIndex === 2) {
|
|
4596
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, p0.price);
|
|
4597
|
+
pts[1] = normalizeDrawingPoint(p1.index, currentPoint.price);
|
|
4598
|
+
} else if (pointIndex === 3) {
|
|
4599
|
+
pts[0] = normalizeDrawingPoint(p0.index, currentPoint.price);
|
|
4600
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, p1.price);
|
|
4601
|
+
}
|
|
4602
|
+
return { ...drawing, points: pts };
|
|
4603
|
+
}
|
|
4515
4604
|
if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
|
|
4516
4605
|
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4517
4606
|
const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
|
|
@@ -4780,7 +4869,7 @@ function createChart(element, options = {}) {
|
|
|
4780
4869
|
setCrosshairPoint(null);
|
|
4781
4870
|
return;
|
|
4782
4871
|
}
|
|
4783
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4872
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "rectangle" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4784
4873
|
let nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
4785
4874
|
if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
|
|
4786
4875
|
nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
|
|
@@ -5198,6 +5287,50 @@ function createChart(element, options = {}) {
|
|
|
5198
5287
|
}
|
|
5199
5288
|
scheduleDraw();
|
|
5200
5289
|
};
|
|
5290
|
+
const upsertBar = (point) => {
|
|
5291
|
+
const time = new Date(point.t);
|
|
5292
|
+
const timeMs = time.getTime();
|
|
5293
|
+
const open = Number(point.o);
|
|
5294
|
+
const close = Number(point.c);
|
|
5295
|
+
const highInput = Number(point.h);
|
|
5296
|
+
const lowInput = Number(point.l);
|
|
5297
|
+
if (!Number.isFinite(timeMs) || !Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(highInput) || !Number.isFinite(lowInput)) {
|
|
5298
|
+
return;
|
|
5299
|
+
}
|
|
5300
|
+
const volumeValue = point.v === void 0 ? void 0 : Number(point.v);
|
|
5301
|
+
const parsed = {
|
|
5302
|
+
time,
|
|
5303
|
+
o: open,
|
|
5304
|
+
h: Math.max(highInput, open, close),
|
|
5305
|
+
l: Math.min(lowInput, open, close),
|
|
5306
|
+
c: close,
|
|
5307
|
+
...volumeValue === void 0 || !Number.isFinite(volumeValue) ? {} : { v: volumeValue }
|
|
5308
|
+
};
|
|
5309
|
+
const last = data[data.length - 1];
|
|
5310
|
+
if (!last) {
|
|
5311
|
+
setData([point]);
|
|
5312
|
+
return;
|
|
5313
|
+
}
|
|
5314
|
+
const lastMs = last.time.getTime();
|
|
5315
|
+
if (timeMs === lastMs) {
|
|
5316
|
+
data[data.length - 1] = parsed;
|
|
5317
|
+
} else if (timeMs > lastMs) {
|
|
5318
|
+
data.push(parsed);
|
|
5319
|
+
if (mergedOptions.preserveViewportOnDataUpdate && followLatest) {
|
|
5320
|
+
xCenter = data.length - xSpan / 2 + rightEdgePaddingBars;
|
|
5321
|
+
clampXViewport();
|
|
5322
|
+
}
|
|
5323
|
+
} else {
|
|
5324
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5325
|
+
for (const existing of data) {
|
|
5326
|
+
merged.set(existing.time.getTime(), existing);
|
|
5327
|
+
}
|
|
5328
|
+
merged.set(timeMs, parsed);
|
|
5329
|
+
data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
|
|
5330
|
+
}
|
|
5331
|
+
pushSmoothedTicker(parsed.c, parsed.v);
|
|
5332
|
+
scheduleDraw();
|
|
5333
|
+
};
|
|
5201
5334
|
const setPriceLines = (lines) => {
|
|
5202
5335
|
priceLines = lines.map((line, index) => ({
|
|
5203
5336
|
...line,
|
|
@@ -5453,6 +5586,7 @@ function createChart(element, options = {}) {
|
|
|
5453
5586
|
return {
|
|
5454
5587
|
updateOptions,
|
|
5455
5588
|
setData,
|
|
5589
|
+
upsertBar,
|
|
5456
5590
|
setPriceLines,
|
|
5457
5591
|
addPriceLine,
|
|
5458
5592
|
removePriceLine,
|
package/dist/index.d.cts
CHANGED
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "text" | "note";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "rectangle" | "text" | "note";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -412,6 +412,13 @@ interface LabelsOptions {
|
|
|
412
412
|
interface ChartInstance {
|
|
413
413
|
updateOptions: (options: ChartOptions) => void;
|
|
414
414
|
setData: (data: OhlcDataPoint[]) => void;
|
|
415
|
+
/**
|
|
416
|
+
* Incrementally upsert a single bar: O(1) update-in-place for the live
|
|
417
|
+
* (forming) bar and O(1) append for a new bar, instead of re-ingesting the
|
|
418
|
+
* full series through `setData`. Out-of-order timestamps fall back to a
|
|
419
|
+
* full merge, so correctness never depends on call ordering.
|
|
420
|
+
*/
|
|
421
|
+
upsertBar: (point: OhlcDataPoint) => void;
|
|
415
422
|
setPriceLines: (lines: PriceLineOptions[]) => void;
|
|
416
423
|
addPriceLine: (line: PriceLineOptions) => string;
|
|
417
424
|
removePriceLine: (id: string) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "text" | "note";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "rectangle" | "text" | "note";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -412,6 +412,13 @@ interface LabelsOptions {
|
|
|
412
412
|
interface ChartInstance {
|
|
413
413
|
updateOptions: (options: ChartOptions) => void;
|
|
414
414
|
setData: (data: OhlcDataPoint[]) => void;
|
|
415
|
+
/**
|
|
416
|
+
* Incrementally upsert a single bar: O(1) update-in-place for the live
|
|
417
|
+
* (forming) bar and O(1) append for a new bar, instead of re-ingesting the
|
|
418
|
+
* full series through `setData`. Out-of-order timestamps fall back to a
|
|
419
|
+
* full merge, so correctness never depends on call ordering.
|
|
420
|
+
*/
|
|
421
|
+
upsertBar: (point: OhlcDataPoint) => void;
|
|
415
422
|
setPriceLines: (lines: PriceLineOptions[]) => void;
|
|
416
423
|
addPriceLine: (line: PriceLineOptions) => string;
|
|
417
424
|
removePriceLine: (id: string) => void;
|
package/dist/index.js
CHANGED
|
@@ -2640,6 +2640,37 @@ function createChart(element, options = {}) {
|
|
|
2640
2640
|
}
|
|
2641
2641
|
}
|
|
2642
2642
|
}
|
|
2643
|
+
} else if (drawing.type === "rectangle") {
|
|
2644
|
+
const p0 = drawing.points[0];
|
|
2645
|
+
const p1 = drawing.points[1];
|
|
2646
|
+
if (p0 && p1) {
|
|
2647
|
+
const px0 = xFromDrawingPoint(p0);
|
|
2648
|
+
const px1 = xFromDrawingPoint(p1);
|
|
2649
|
+
const leftX = Math.min(px0, px1);
|
|
2650
|
+
const rightX = Math.max(px0, px1);
|
|
2651
|
+
const y0 = yFromPrice(p0.price);
|
|
2652
|
+
const y1 = yFromPrice(p1.price);
|
|
2653
|
+
const topY = Math.min(y0, y1);
|
|
2654
|
+
const botY = Math.max(y0, y1);
|
|
2655
|
+
const boxW = Math.max(1, rightX - leftX);
|
|
2656
|
+
const boxH = Math.max(1, botY - topY);
|
|
2657
|
+
ctx.save();
|
|
2658
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2659
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.14);
|
|
2660
|
+
ctx.fillRect(leftX, topY, boxW, boxH);
|
|
2661
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2662
|
+
ctx.strokeStyle = drawing.color;
|
|
2663
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2664
|
+
ctx.strokeRect(crisp(leftX), crisp(topY), crisp(rightX) - crisp(leftX), crisp(botY) - crisp(topY));
|
|
2665
|
+
ctx.restore();
|
|
2666
|
+
handleAt(px0, y0, drawing.color);
|
|
2667
|
+
handleAt(px1, y1, drawing.color);
|
|
2668
|
+
handleAt(px0, y1, drawing.color);
|
|
2669
|
+
handleAt(px1, y0, drawing.color);
|
|
2670
|
+
if (drawing.label) {
|
|
2671
|
+
drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2643
2674
|
} else if (drawing.type === "price-range") {
|
|
2644
2675
|
const p0 = drawing.points[0];
|
|
2645
2676
|
const p1 = drawing.points[1];
|
|
@@ -4129,6 +4160,21 @@ function createChart(element, options = {}) {
|
|
|
4129
4160
|
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
4130
4161
|
return { drawing, target: "line" };
|
|
4131
4162
|
}
|
|
4163
|
+
} else if (drawing.type === "rectangle") {
|
|
4164
|
+
const p0 = drawing.points[0];
|
|
4165
|
+
const p1 = drawing.points[1];
|
|
4166
|
+
if (!p0 || !p1) continue;
|
|
4167
|
+
const px0 = canvasXFromDrawingPoint(p0);
|
|
4168
|
+
const px1 = canvasXFromDrawingPoint(p1);
|
|
4169
|
+
const y0 = canvasYFromDrawingPrice(p0.price);
|
|
4170
|
+
const y1 = canvasYFromDrawingPrice(p1.price);
|
|
4171
|
+
if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
4172
|
+
if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
4173
|
+
if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
4174
|
+
if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
4175
|
+
if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
|
|
4176
|
+
return { drawing, target: "line" };
|
|
4177
|
+
}
|
|
4132
4178
|
} else if (drawing.type === "price-range") {
|
|
4133
4179
|
const p0 = drawing.points[0];
|
|
4134
4180
|
const p1 = drawing.points[1];
|
|
@@ -4385,6 +4431,30 @@ function createChart(element, options = {}) {
|
|
|
4385
4431
|
drawingEditTextHandler?.({ drawing: serializeDrawing(created), target: "line", x, y });
|
|
4386
4432
|
return true;
|
|
4387
4433
|
}
|
|
4434
|
+
if (activeDrawingTool === "rectangle") {
|
|
4435
|
+
if (draftDrawing?.type === "rectangle") {
|
|
4436
|
+
const completed = normalizeDrawingState({
|
|
4437
|
+
...serializeDrawing(draftDrawing),
|
|
4438
|
+
points: [draftDrawing.points[0], point]
|
|
4439
|
+
});
|
|
4440
|
+
drawings.push(completed);
|
|
4441
|
+
draftDrawing = null;
|
|
4442
|
+
activeDrawingTool = null;
|
|
4443
|
+
emitDrawingsChange();
|
|
4444
|
+
scheduleDraw();
|
|
4445
|
+
return true;
|
|
4446
|
+
}
|
|
4447
|
+
const defaults = getDrawingToolDefaults("rectangle");
|
|
4448
|
+
draftDrawing = normalizeDrawingState({
|
|
4449
|
+
type: "rectangle",
|
|
4450
|
+
points: [point, point],
|
|
4451
|
+
color: defaults.color ?? "#2962ff",
|
|
4452
|
+
style: defaults.style ?? "solid",
|
|
4453
|
+
width: defaults.width ?? 1
|
|
4454
|
+
});
|
|
4455
|
+
scheduleDraw();
|
|
4456
|
+
return true;
|
|
4457
|
+
}
|
|
4388
4458
|
if (activeDrawingTool === "price-range") {
|
|
4389
4459
|
const tick = getConfiguredTickSize();
|
|
4390
4460
|
const visibleRange = drawState.yMax - drawState.yMin;
|
|
@@ -4486,6 +4556,25 @@ function createChart(element, options = {}) {
|
|
|
4486
4556
|
}
|
|
4487
4557
|
return { ...drawing, points: pts };
|
|
4488
4558
|
}
|
|
4559
|
+
if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
|
|
4560
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
4561
|
+
const p0 = pts[0];
|
|
4562
|
+
const p1 = pts[1];
|
|
4563
|
+
if (!p0 || !p1) return drawing;
|
|
4564
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4565
|
+
if (pointIndex === 0) {
|
|
4566
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4567
|
+
} else if (pointIndex === 1) {
|
|
4568
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, currentPoint.price);
|
|
4569
|
+
} else if (pointIndex === 2) {
|
|
4570
|
+
pts[0] = normalizeDrawingPoint(currentPoint.index, p0.price);
|
|
4571
|
+
pts[1] = normalizeDrawingPoint(p1.index, currentPoint.price);
|
|
4572
|
+
} else if (pointIndex === 3) {
|
|
4573
|
+
pts[0] = normalizeDrawingPoint(p0.index, currentPoint.price);
|
|
4574
|
+
pts[1] = normalizeDrawingPoint(currentPoint.index, p1.price);
|
|
4575
|
+
}
|
|
4576
|
+
return { ...drawing, points: pts };
|
|
4577
|
+
}
|
|
4489
4578
|
if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
|
|
4490
4579
|
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
4491
4580
|
const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
|
|
@@ -4754,7 +4843,7 @@ function createChart(element, options = {}) {
|
|
|
4754
4843
|
setCrosshairPoint(null);
|
|
4755
4844
|
return;
|
|
4756
4845
|
}
|
|
4757
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4846
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "rectangle" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
4758
4847
|
let nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
4759
4848
|
if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
|
|
4760
4849
|
nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
|
|
@@ -5172,6 +5261,50 @@ function createChart(element, options = {}) {
|
|
|
5172
5261
|
}
|
|
5173
5262
|
scheduleDraw();
|
|
5174
5263
|
};
|
|
5264
|
+
const upsertBar = (point) => {
|
|
5265
|
+
const time = new Date(point.t);
|
|
5266
|
+
const timeMs = time.getTime();
|
|
5267
|
+
const open = Number(point.o);
|
|
5268
|
+
const close = Number(point.c);
|
|
5269
|
+
const highInput = Number(point.h);
|
|
5270
|
+
const lowInput = Number(point.l);
|
|
5271
|
+
if (!Number.isFinite(timeMs) || !Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(highInput) || !Number.isFinite(lowInput)) {
|
|
5272
|
+
return;
|
|
5273
|
+
}
|
|
5274
|
+
const volumeValue = point.v === void 0 ? void 0 : Number(point.v);
|
|
5275
|
+
const parsed = {
|
|
5276
|
+
time,
|
|
5277
|
+
o: open,
|
|
5278
|
+
h: Math.max(highInput, open, close),
|
|
5279
|
+
l: Math.min(lowInput, open, close),
|
|
5280
|
+
c: close,
|
|
5281
|
+
...volumeValue === void 0 || !Number.isFinite(volumeValue) ? {} : { v: volumeValue }
|
|
5282
|
+
};
|
|
5283
|
+
const last = data[data.length - 1];
|
|
5284
|
+
if (!last) {
|
|
5285
|
+
setData([point]);
|
|
5286
|
+
return;
|
|
5287
|
+
}
|
|
5288
|
+
const lastMs = last.time.getTime();
|
|
5289
|
+
if (timeMs === lastMs) {
|
|
5290
|
+
data[data.length - 1] = parsed;
|
|
5291
|
+
} else if (timeMs > lastMs) {
|
|
5292
|
+
data.push(parsed);
|
|
5293
|
+
if (mergedOptions.preserveViewportOnDataUpdate && followLatest) {
|
|
5294
|
+
xCenter = data.length - xSpan / 2 + rightEdgePaddingBars;
|
|
5295
|
+
clampXViewport();
|
|
5296
|
+
}
|
|
5297
|
+
} else {
|
|
5298
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5299
|
+
for (const existing of data) {
|
|
5300
|
+
merged.set(existing.time.getTime(), existing);
|
|
5301
|
+
}
|
|
5302
|
+
merged.set(timeMs, parsed);
|
|
5303
|
+
data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
|
|
5304
|
+
}
|
|
5305
|
+
pushSmoothedTicker(parsed.c, parsed.v);
|
|
5306
|
+
scheduleDraw();
|
|
5307
|
+
};
|
|
5175
5308
|
const setPriceLines = (lines) => {
|
|
5176
5309
|
priceLines = lines.map((line, index) => ({
|
|
5177
5310
|
...line,
|
|
@@ -5427,6 +5560,7 @@ function createChart(element, options = {}) {
|
|
|
5427
5560
|
return {
|
|
5428
5561
|
updateOptions,
|
|
5429
5562
|
setData,
|
|
5563
|
+
upsertBar,
|
|
5430
5564
|
setPriceLines,
|
|
5431
5565
|
addPriceLine,
|
|
5432
5566
|
removePriceLine,
|