hyperprop-charting-library 0.1.54 → 0.1.55
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 +294 -2
- package/dist/hyperprop-charting-library.d.ts +35 -1
- package/dist/hyperprop-charting-library.js +294 -2
- package/dist/index.cjs +294 -2
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +294 -2
- package/docs/API.md +43 -0
- package/docs/RECIPES.md +28 -0
- package/package.json +1 -1
|
@@ -190,7 +190,8 @@ var DEFAULT_OPTIONS = {
|
|
|
190
190
|
},
|
|
191
191
|
labels: DEFAULT_LABELS_OPTIONS,
|
|
192
192
|
dashPatterns: DEFAULT_DASH_PATTERNS,
|
|
193
|
-
indicators: []
|
|
193
|
+
indicators: [],
|
|
194
|
+
drawings: []
|
|
194
195
|
};
|
|
195
196
|
var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
196
197
|
...baseOptions,
|
|
@@ -881,6 +882,7 @@ function createChart(element, options = {}) {
|
|
|
881
882
|
let generatedPriceLineId = 1;
|
|
882
883
|
let generatedOrderLineId = 1;
|
|
883
884
|
let generatedIndicatorId = 1;
|
|
885
|
+
let generatedDrawingId = 1;
|
|
884
886
|
const indicatorRegistry = /* @__PURE__ */ new Map();
|
|
885
887
|
for (const indicator of BUILTIN_INDICATORS) {
|
|
886
888
|
indicatorRegistry.set(indicator.id, indicator);
|
|
@@ -903,7 +905,41 @@ function createChart(element, options = {}) {
|
|
|
903
905
|
}
|
|
904
906
|
};
|
|
905
907
|
};
|
|
908
|
+
const normalizeDrawingState = (drawing) => ({
|
|
909
|
+
id: drawing.id ?? `drawing-${generatedDrawingId++}`,
|
|
910
|
+
type: drawing.type,
|
|
911
|
+
points: drawing.points.map((point) => ({
|
|
912
|
+
index: Number(point.index) || 0,
|
|
913
|
+
price: Number(point.price) || 0,
|
|
914
|
+
...point.time ? { time: point.time } : {}
|
|
915
|
+
})),
|
|
916
|
+
visible: drawing.visible ?? true,
|
|
917
|
+
color: drawing.color ?? "#94a3b8",
|
|
918
|
+
style: drawing.style ?? "dotted",
|
|
919
|
+
width: Math.max(1, Number(drawing.width) || 1),
|
|
920
|
+
locked: drawing.locked ?? false,
|
|
921
|
+
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
922
|
+
});
|
|
923
|
+
const serializeDrawing = (drawing) => ({
|
|
924
|
+
id: drawing.id,
|
|
925
|
+
type: drawing.type,
|
|
926
|
+
points: drawing.points.map((point) => ({ ...point })),
|
|
927
|
+
visible: drawing.visible,
|
|
928
|
+
color: drawing.color,
|
|
929
|
+
style: drawing.style,
|
|
930
|
+
width: drawing.width,
|
|
931
|
+
locked: drawing.locked,
|
|
932
|
+
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
933
|
+
});
|
|
906
934
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
935
|
+
let drawings = (options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing));
|
|
936
|
+
let activeDrawingTool = null;
|
|
937
|
+
let draftDrawing = null;
|
|
938
|
+
let drawingsChangeHandler = null;
|
|
939
|
+
let drawingSelectHandler = null;
|
|
940
|
+
const emitDrawingsChange = () => {
|
|
941
|
+
drawingsChangeHandler?.(drawings.map((drawing) => serializeDrawing(drawing)));
|
|
942
|
+
};
|
|
907
943
|
const orderWidgetWidthById = /* @__PURE__ */ new Map();
|
|
908
944
|
const orderPriceTagWidthById = /* @__PURE__ */ new Map();
|
|
909
945
|
let xCenter = 0;
|
|
@@ -1957,6 +1993,55 @@ function createChart(element, options = {}) {
|
|
|
1957
1993
|
const yFromPrice = (price) => {
|
|
1958
1994
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
1959
1995
|
};
|
|
1996
|
+
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
1997
|
+
const drawDrawingHandle = (x, y, color) => {
|
|
1998
|
+
ctx.save();
|
|
1999
|
+
ctx.setLineDash([]);
|
|
2000
|
+
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2001
|
+
ctx.strokeStyle = color;
|
|
2002
|
+
ctx.lineWidth = 2;
|
|
2003
|
+
ctx.beginPath();
|
|
2004
|
+
ctx.arc(x, y, 5, 0, Math.PI * 2);
|
|
2005
|
+
ctx.fill();
|
|
2006
|
+
ctx.stroke();
|
|
2007
|
+
ctx.restore();
|
|
2008
|
+
};
|
|
2009
|
+
const drawDrawing = (drawing, draft = false) => {
|
|
2010
|
+
if (!drawing.visible) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
ctx.save();
|
|
2014
|
+
ctx.strokeStyle = drawing.color;
|
|
2015
|
+
ctx.lineWidth = drawing.width;
|
|
2016
|
+
ctx.globalAlpha = draft ? 0.72 : 1;
|
|
2017
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2018
|
+
if (drawing.type === "horizontal-line") {
|
|
2019
|
+
const point = drawing.points[0];
|
|
2020
|
+
if (point) {
|
|
2021
|
+
const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
|
|
2022
|
+
ctx.beginPath();
|
|
2023
|
+
ctx.moveTo(crisp(chartLeft), crisp(y));
|
|
2024
|
+
ctx.lineTo(crisp(chartRight), crisp(y));
|
|
2025
|
+
ctx.stroke();
|
|
2026
|
+
}
|
|
2027
|
+
} else if (drawing.type === "trendline") {
|
|
2028
|
+
const first = drawing.points[0];
|
|
2029
|
+
const second = drawing.points[1];
|
|
2030
|
+
if (first && second) {
|
|
2031
|
+
const firstX = xFromDrawingPoint(first);
|
|
2032
|
+
const firstY = yFromPrice(first.price);
|
|
2033
|
+
const secondX = xFromDrawingPoint(second);
|
|
2034
|
+
const secondY = yFromPrice(second.price);
|
|
2035
|
+
ctx.beginPath();
|
|
2036
|
+
ctx.moveTo(firstX, firstY);
|
|
2037
|
+
ctx.lineTo(secondX, secondY);
|
|
2038
|
+
ctx.stroke();
|
|
2039
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2040
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
ctx.restore();
|
|
2044
|
+
};
|
|
1960
2045
|
if (watermark.visible && watermark.imageSrc.trim().length > 0) {
|
|
1961
2046
|
ensureWatermarkImage(watermark.imageSrc.trim());
|
|
1962
2047
|
if (watermarkImageReady && watermarkImage) {
|
|
@@ -2128,6 +2213,10 @@ function createChart(element, options = {}) {
|
|
|
2128
2213
|
);
|
|
2129
2214
|
});
|
|
2130
2215
|
}
|
|
2216
|
+
drawings.forEach((drawing) => drawDrawing(drawing));
|
|
2217
|
+
if (draftDrawing) {
|
|
2218
|
+
drawDrawing(draftDrawing, true);
|
|
2219
|
+
}
|
|
2131
2220
|
const crosshair = { ...DEFAULT_CROSSHAIR_OPTIONS, ...mergedOptions.crosshair ?? {} };
|
|
2132
2221
|
if (crosshair.visible && crosshairPoint) {
|
|
2133
2222
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
@@ -2964,6 +3053,66 @@ function createChart(element, options = {}) {
|
|
|
2964
3053
|
const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
|
|
2965
3054
|
return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
|
|
2966
3055
|
};
|
|
3056
|
+
const drawingPointFromCanvas = (x, y) => {
|
|
3057
|
+
if (!drawState) {
|
|
3058
|
+
return null;
|
|
3059
|
+
}
|
|
3060
|
+
const ratio = clamp((x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
3061
|
+
const index = drawState.xStart + ratio * drawState.xSpan - 0.5;
|
|
3062
|
+
const nearestIndex = Math.round(index);
|
|
3063
|
+
const time = getTimeForIndex(nearestIndex);
|
|
3064
|
+
return {
|
|
3065
|
+
index,
|
|
3066
|
+
price: roundToPricePrecision(priceFromCanvasY(y)),
|
|
3067
|
+
...time ? { time: time.toISOString() } : {}
|
|
3068
|
+
};
|
|
3069
|
+
};
|
|
3070
|
+
const canvasXFromDrawingPoint = (point) => {
|
|
3071
|
+
if (!drawState) return 0;
|
|
3072
|
+
return drawState.chartLeft + (point.index + 0.5 - drawState.xStart) / drawState.xSpan * drawState.chartWidth;
|
|
3073
|
+
};
|
|
3074
|
+
const canvasYFromDrawingPrice = (price) => {
|
|
3075
|
+
if (!drawState) return 0;
|
|
3076
|
+
const range = drawState.yMax - drawState.yMin || 1;
|
|
3077
|
+
return drawState.chartBottom - (price - drawState.yMin) / range * drawState.chartHeight;
|
|
3078
|
+
};
|
|
3079
|
+
const distanceToSegment = (x, y, x1, y1, x2, y2) => {
|
|
3080
|
+
const dx = x2 - x1;
|
|
3081
|
+
const dy = y2 - y1;
|
|
3082
|
+
const lengthSq = dx * dx + dy * dy;
|
|
3083
|
+
if (lengthSq === 0) return Math.hypot(x - x1, y - y1);
|
|
3084
|
+
const t = clamp(((x - x1) * dx + (y - y1) * dy) / lengthSq, 0, 1);
|
|
3085
|
+
return Math.hypot(x - (x1 + t * dx), y - (y1 + t * dy));
|
|
3086
|
+
};
|
|
3087
|
+
const getDrawingHit = (x, y) => {
|
|
3088
|
+
for (let index = drawings.length - 1; index >= 0; index -= 1) {
|
|
3089
|
+
const drawing = drawings[index];
|
|
3090
|
+
if (!drawing?.visible) continue;
|
|
3091
|
+
if (drawing.type === "horizontal-line") {
|
|
3092
|
+
const point = drawing.points[0];
|
|
3093
|
+
if (!point) continue;
|
|
3094
|
+
const lineY = canvasYFromDrawingPrice(point.price);
|
|
3095
|
+
if (Math.abs(y - lineY) <= 7) {
|
|
3096
|
+
return { drawing, target: "line" };
|
|
3097
|
+
}
|
|
3098
|
+
} else if (drawing.type === "trendline") {
|
|
3099
|
+
const first = drawing.points[0];
|
|
3100
|
+
const second = drawing.points[1];
|
|
3101
|
+
if (!first || !second) continue;
|
|
3102
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3103
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3104
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3105
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3106
|
+
if (Math.hypot(x - x1, y - y1) <= 8 || Math.hypot(x - x2, y - y2) <= 8) {
|
|
3107
|
+
return { drawing, target: "handle" };
|
|
3108
|
+
}
|
|
3109
|
+
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3110
|
+
return { drawing, target: "line" };
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return null;
|
|
3115
|
+
};
|
|
2967
3116
|
const indexFromCanvasX = (x) => {
|
|
2968
3117
|
if (!drawState) {
|
|
2969
3118
|
return null;
|
|
@@ -3016,6 +3165,53 @@ function createChart(element, options = {}) {
|
|
|
3016
3165
|
}
|
|
3017
3166
|
return "outside";
|
|
3018
3167
|
};
|
|
3168
|
+
const handleDrawingToolPointerDown = (x, y) => {
|
|
3169
|
+
if (!activeDrawingTool || !drawState) {
|
|
3170
|
+
return false;
|
|
3171
|
+
}
|
|
3172
|
+
const point = drawingPointFromCanvas(x, y);
|
|
3173
|
+
if (!point) {
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
if (activeDrawingTool === "horizontal-line") {
|
|
3177
|
+
drawings.push(
|
|
3178
|
+
normalizeDrawingState({
|
|
3179
|
+
type: "horizontal-line",
|
|
3180
|
+
points: [point],
|
|
3181
|
+
color: "#38bdf8",
|
|
3182
|
+
style: "dotted",
|
|
3183
|
+
width: 1
|
|
3184
|
+
})
|
|
3185
|
+
);
|
|
3186
|
+
emitDrawingsChange();
|
|
3187
|
+
draw();
|
|
3188
|
+
return true;
|
|
3189
|
+
}
|
|
3190
|
+
if (activeDrawingTool === "trendline") {
|
|
3191
|
+
if (draftDrawing?.type === "trendline") {
|
|
3192
|
+
const completed = normalizeDrawingState({
|
|
3193
|
+
...serializeDrawing(draftDrawing),
|
|
3194
|
+
points: [draftDrawing.points[0], point]
|
|
3195
|
+
});
|
|
3196
|
+
drawings.push(completed);
|
|
3197
|
+
draftDrawing = null;
|
|
3198
|
+
activeDrawingTool = null;
|
|
3199
|
+
emitDrawingsChange();
|
|
3200
|
+
draw();
|
|
3201
|
+
return true;
|
|
3202
|
+
}
|
|
3203
|
+
draftDrawing = normalizeDrawingState({
|
|
3204
|
+
type: "trendline",
|
|
3205
|
+
points: [point, point],
|
|
3206
|
+
color: "#2563eb",
|
|
3207
|
+
style: "solid",
|
|
3208
|
+
width: 2
|
|
3209
|
+
});
|
|
3210
|
+
draw();
|
|
3211
|
+
return true;
|
|
3212
|
+
}
|
|
3213
|
+
return false;
|
|
3214
|
+
};
|
|
3019
3215
|
let isDragging = false;
|
|
3020
3216
|
let dragMode = null;
|
|
3021
3217
|
let lastPointerX = 0;
|
|
@@ -3141,6 +3337,25 @@ function createChart(element, options = {}) {
|
|
|
3141
3337
|
if (region === "outside") {
|
|
3142
3338
|
return;
|
|
3143
3339
|
}
|
|
3340
|
+
if (region === "plot" && !activeDrawingTool) {
|
|
3341
|
+
const drawingHit = getDrawingHit(point.x, point.y);
|
|
3342
|
+
if (drawingHit) {
|
|
3343
|
+
drawingSelectHandler?.({
|
|
3344
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
3345
|
+
target: drawingHit.target,
|
|
3346
|
+
x: point.x,
|
|
3347
|
+
y: point.y
|
|
3348
|
+
});
|
|
3349
|
+
setCrosshairPoint(null);
|
|
3350
|
+
canvas.style.cursor = "pointer";
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
if (region === "plot" && handleDrawingToolPointerDown(point.x, point.y)) {
|
|
3355
|
+
setCrosshairPoint(null);
|
|
3356
|
+
canvas.style.cursor = "crosshair";
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3144
3359
|
isDragging = true;
|
|
3145
3360
|
dragMode = region;
|
|
3146
3361
|
activePointerId = event.pointerId;
|
|
@@ -3172,6 +3387,18 @@ function createChart(element, options = {}) {
|
|
|
3172
3387
|
pointerDownInfo.moved = true;
|
|
3173
3388
|
}
|
|
3174
3389
|
}
|
|
3390
|
+
if (draftDrawing && activeDrawingTool === "trendline") {
|
|
3391
|
+
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3392
|
+
if (nextPoint) {
|
|
3393
|
+
draftDrawing = {
|
|
3394
|
+
...draftDrawing,
|
|
3395
|
+
points: [draftDrawing.points[0], nextPoint]
|
|
3396
|
+
};
|
|
3397
|
+
canvas.style.cursor = "crosshair";
|
|
3398
|
+
draw();
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3175
3402
|
if (orderDragState) {
|
|
3176
3403
|
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
3177
3404
|
return;
|
|
@@ -3242,9 +3469,14 @@ function createChart(element, options = {}) {
|
|
|
3242
3469
|
setCrosshairPoint(null);
|
|
3243
3470
|
return;
|
|
3244
3471
|
}
|
|
3472
|
+
if (!activeDrawingTool && getDrawingHit(point.x, point.y)) {
|
|
3473
|
+
canvas.style.cursor = "pointer";
|
|
3474
|
+
setCrosshairPoint(null);
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3245
3477
|
const hoverRegion = getHitRegion(point.x, point.y);
|
|
3246
3478
|
if (hoverRegion === "plot") {
|
|
3247
|
-
canvas.style.cursor = doubleClickEnabled ? "default" : "crosshair";
|
|
3479
|
+
canvas.style.cursor = activeDrawingTool ? "crosshair" : doubleClickEnabled ? "default" : "crosshair";
|
|
3248
3480
|
setCrosshairPoint(point);
|
|
3249
3481
|
emitCrosshairMove(point.x, point.y, "plot");
|
|
3250
3482
|
} else if (hoverRegion === "x-axis") {
|
|
@@ -3643,6 +3875,56 @@ function createChart(element, options = {}) {
|
|
|
3643
3875
|
indicators = indicators.filter((indicator) => indicator.id !== id);
|
|
3644
3876
|
draw();
|
|
3645
3877
|
};
|
|
3878
|
+
const setActiveDrawingTool = (tool) => {
|
|
3879
|
+
activeDrawingTool = tool;
|
|
3880
|
+
draftDrawing = null;
|
|
3881
|
+
canvas.style.cursor = tool ? "crosshair" : "default";
|
|
3882
|
+
draw();
|
|
3883
|
+
};
|
|
3884
|
+
const getActiveDrawingTool = () => activeDrawingTool;
|
|
3885
|
+
const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
|
|
3886
|
+
const setDrawings = (nextDrawings) => {
|
|
3887
|
+
drawings = nextDrawings.map((drawing) => normalizeDrawingState(drawing));
|
|
3888
|
+
draftDrawing = null;
|
|
3889
|
+
emitDrawingsChange();
|
|
3890
|
+
draw();
|
|
3891
|
+
};
|
|
3892
|
+
const addDrawing = (drawing) => {
|
|
3893
|
+
const next = normalizeDrawingState(drawing);
|
|
3894
|
+
drawings.push(next);
|
|
3895
|
+
emitDrawingsChange();
|
|
3896
|
+
draw();
|
|
3897
|
+
return next.id;
|
|
3898
|
+
};
|
|
3899
|
+
const updateDrawing = (id, patch) => {
|
|
3900
|
+
drawings = drawings.map(
|
|
3901
|
+
(drawing) => drawing.id === id ? normalizeDrawingState({
|
|
3902
|
+
...serializeDrawing(drawing),
|
|
3903
|
+
...patch,
|
|
3904
|
+
id,
|
|
3905
|
+
points: patch.points ?? drawing.points
|
|
3906
|
+
}) : drawing
|
|
3907
|
+
);
|
|
3908
|
+
emitDrawingsChange();
|
|
3909
|
+
draw();
|
|
3910
|
+
};
|
|
3911
|
+
const removeDrawing = (id) => {
|
|
3912
|
+
drawings = drawings.filter((drawing) => drawing.id !== id);
|
|
3913
|
+
emitDrawingsChange();
|
|
3914
|
+
draw();
|
|
3915
|
+
};
|
|
3916
|
+
const clearDrawings = () => {
|
|
3917
|
+
drawings = [];
|
|
3918
|
+
draftDrawing = null;
|
|
3919
|
+
emitDrawingsChange();
|
|
3920
|
+
draw();
|
|
3921
|
+
};
|
|
3922
|
+
const onDrawingsChange = (handler) => {
|
|
3923
|
+
drawingsChangeHandler = handler;
|
|
3924
|
+
};
|
|
3925
|
+
const onDrawingSelect = (handler) => {
|
|
3926
|
+
drawingSelectHandler = handler;
|
|
3927
|
+
};
|
|
3646
3928
|
const destroy = () => {
|
|
3647
3929
|
if (smoothingRafId !== null) {
|
|
3648
3930
|
cancelAnimationFrame(smoothingRafId);
|
|
@@ -3686,6 +3968,16 @@ function createChart(element, options = {}) {
|
|
|
3686
3968
|
getViewport,
|
|
3687
3969
|
setViewport,
|
|
3688
3970
|
onViewportChange,
|
|
3971
|
+
setActiveDrawingTool,
|
|
3972
|
+
getActiveDrawingTool,
|
|
3973
|
+
setDrawings,
|
|
3974
|
+
getDrawings,
|
|
3975
|
+
addDrawing,
|
|
3976
|
+
updateDrawing,
|
|
3977
|
+
removeDrawing,
|
|
3978
|
+
clearDrawings,
|
|
3979
|
+
onDrawingsChange,
|
|
3980
|
+
onDrawingSelect,
|
|
3689
3981
|
setDoubleClickEnabled,
|
|
3690
3982
|
setDoubleClickAction,
|
|
3691
3983
|
registerIndicator,
|