hyperprop-charting-library 0.1.89 → 0.1.90

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.
@@ -1000,6 +1000,9 @@ function createChart(element, options = {}) {
1000
1000
  let drawingSelectHandler = null;
1001
1001
  let drawingDoubleClickHandler = null;
1002
1002
  let drawingEditTextHandler = null;
1003
+ let magnetMode = "none";
1004
+ let magnetModifierActive = false;
1005
+ const MAGNET_WEAK_THRESHOLD_PX = 16;
1003
1006
  let drawingHoverHandler = null;
1004
1007
  let lastHoveredDrawingId = null;
1005
1008
  let selectedDrawingId = null;
@@ -3724,17 +3727,39 @@ function createChart(element, options = {}) {
3724
3727
  const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
3725
3728
  return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
3726
3729
  };
3730
+ const applyMagnet = (y, index, price) => {
3731
+ const effective = magnetModifierActive ? "strong" : magnetMode;
3732
+ if (effective === "none") return { index, price };
3733
+ const barIndex = Math.round(index);
3734
+ const bar = data[barIndex];
3735
+ if (!bar) return { index, price };
3736
+ const candidates = [bar.o, bar.h, bar.l, bar.c];
3737
+ let nearest = bar.c;
3738
+ let bestDiff = Infinity;
3739
+ for (const candidate of candidates) {
3740
+ const diff = Math.abs(candidate - price);
3741
+ if (diff < bestDiff) {
3742
+ bestDiff = diff;
3743
+ nearest = candidate;
3744
+ }
3745
+ }
3746
+ if (effective === "weak" && Math.abs(canvasYFromDrawingPrice(nearest) - y) > MAGNET_WEAK_THRESHOLD_PX) {
3747
+ return { index, price };
3748
+ }
3749
+ return { index: barIndex, price: nearest };
3750
+ };
3727
3751
  const drawingPointFromCanvas = (x, y) => {
3728
3752
  if (!drawState) {
3729
3753
  return null;
3730
3754
  }
3731
3755
  const ratio = clamp((x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3732
- const index = drawState.xStart + ratio * drawState.xSpan - 0.5;
3733
- const nearestIndex = Math.round(index);
3756
+ const rawIndex = drawState.xStart + ratio * drawState.xSpan - 0.5;
3757
+ const snapped = applyMagnet(y, rawIndex, priceFromCanvasY(y));
3758
+ const nearestIndex = Math.round(snapped.index);
3734
3759
  const time = getTimeForIndex(nearestIndex);
3735
3760
  return {
3736
- index,
3737
- price: roundToPricePrecision(priceFromCanvasY(y)),
3761
+ index: snapped.index,
3762
+ price: roundToPricePrecision(snapped.price),
3738
3763
  ...time ? { time: time.toISOString() } : {}
3739
3764
  };
3740
3765
  };
@@ -4351,6 +4376,7 @@ function createChart(element, options = {}) {
4351
4376
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4352
4377
  event.preventDefault();
4353
4378
  }
4379
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4354
4380
  const point = getCanvasPoint(event);
4355
4381
  if (event.pointerType === "touch") {
4356
4382
  touchPointers.set(event.pointerId, point);
@@ -4473,6 +4499,7 @@ function createChart(element, options = {}) {
4473
4499
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4474
4500
  event.preventDefault();
4475
4501
  }
4502
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4476
4503
  const point = getCanvasPoint(event);
4477
4504
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
4478
4505
  touchPointers.set(event.pointerId, point);
@@ -5067,6 +5094,10 @@ function createChart(element, options = {}) {
5067
5094
  draw();
5068
5095
  };
5069
5096
  const getActiveDrawingTool = () => activeDrawingTool;
5097
+ const setMagnetMode = (mode) => {
5098
+ magnetMode = mode;
5099
+ };
5100
+ const getMagnetMode = () => magnetMode;
5070
5101
  const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
5071
5102
  const setDrawings = (nextDrawings) => {
5072
5103
  drawings = nextDrawings.map((drawing) => normalizeDrawingState(drawing));
@@ -5175,6 +5206,8 @@ function createChart(element, options = {}) {
5175
5206
  onDrawingSelect,
5176
5207
  onDrawingDoubleClick,
5177
5208
  onDrawingEditText,
5209
+ setMagnetMode,
5210
+ getMagnetMode,
5178
5211
  setSelectedDrawing,
5179
5212
  onDrawingHover,
5180
5213
  setDrawingDefaults,
@@ -430,6 +430,8 @@ interface ChartInstance {
430
430
  onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
431
431
  onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
432
432
  onDrawingEditText: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
433
+ setMagnetMode: (mode: "none" | "weak" | "strong") => void;
434
+ getMagnetMode: () => "none" | "weak" | "strong";
433
435
  onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
434
436
  setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
435
437
  setSelectedDrawing: (id: string | null) => void;
@@ -974,6 +974,9 @@ function createChart(element, options = {}) {
974
974
  let drawingSelectHandler = null;
975
975
  let drawingDoubleClickHandler = null;
976
976
  let drawingEditTextHandler = null;
977
+ let magnetMode = "none";
978
+ let magnetModifierActive = false;
979
+ const MAGNET_WEAK_THRESHOLD_PX = 16;
977
980
  let drawingHoverHandler = null;
978
981
  let lastHoveredDrawingId = null;
979
982
  let selectedDrawingId = null;
@@ -3698,17 +3701,39 @@ function createChart(element, options = {}) {
3698
3701
  const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
3699
3702
  return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
3700
3703
  };
3704
+ const applyMagnet = (y, index, price) => {
3705
+ const effective = magnetModifierActive ? "strong" : magnetMode;
3706
+ if (effective === "none") return { index, price };
3707
+ const barIndex = Math.round(index);
3708
+ const bar = data[barIndex];
3709
+ if (!bar) return { index, price };
3710
+ const candidates = [bar.o, bar.h, bar.l, bar.c];
3711
+ let nearest = bar.c;
3712
+ let bestDiff = Infinity;
3713
+ for (const candidate of candidates) {
3714
+ const diff = Math.abs(candidate - price);
3715
+ if (diff < bestDiff) {
3716
+ bestDiff = diff;
3717
+ nearest = candidate;
3718
+ }
3719
+ }
3720
+ if (effective === "weak" && Math.abs(canvasYFromDrawingPrice(nearest) - y) > MAGNET_WEAK_THRESHOLD_PX) {
3721
+ return { index, price };
3722
+ }
3723
+ return { index: barIndex, price: nearest };
3724
+ };
3701
3725
  const drawingPointFromCanvas = (x, y) => {
3702
3726
  if (!drawState) {
3703
3727
  return null;
3704
3728
  }
3705
3729
  const ratio = clamp((x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3706
- const index = drawState.xStart + ratio * drawState.xSpan - 0.5;
3707
- const nearestIndex = Math.round(index);
3730
+ const rawIndex = drawState.xStart + ratio * drawState.xSpan - 0.5;
3731
+ const snapped = applyMagnet(y, rawIndex, priceFromCanvasY(y));
3732
+ const nearestIndex = Math.round(snapped.index);
3708
3733
  const time = getTimeForIndex(nearestIndex);
3709
3734
  return {
3710
- index,
3711
- price: roundToPricePrecision(priceFromCanvasY(y)),
3735
+ index: snapped.index,
3736
+ price: roundToPricePrecision(snapped.price),
3712
3737
  ...time ? { time: time.toISOString() } : {}
3713
3738
  };
3714
3739
  };
@@ -4325,6 +4350,7 @@ function createChart(element, options = {}) {
4325
4350
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4326
4351
  event.preventDefault();
4327
4352
  }
4353
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4328
4354
  const point = getCanvasPoint(event);
4329
4355
  if (event.pointerType === "touch") {
4330
4356
  touchPointers.set(event.pointerId, point);
@@ -4447,6 +4473,7 @@ function createChart(element, options = {}) {
4447
4473
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4448
4474
  event.preventDefault();
4449
4475
  }
4476
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4450
4477
  const point = getCanvasPoint(event);
4451
4478
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
4452
4479
  touchPointers.set(event.pointerId, point);
@@ -5041,6 +5068,10 @@ function createChart(element, options = {}) {
5041
5068
  draw();
5042
5069
  };
5043
5070
  const getActiveDrawingTool = () => activeDrawingTool;
5071
+ const setMagnetMode = (mode) => {
5072
+ magnetMode = mode;
5073
+ };
5074
+ const getMagnetMode = () => magnetMode;
5044
5075
  const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
5045
5076
  const setDrawings = (nextDrawings) => {
5046
5077
  drawings = nextDrawings.map((drawing) => normalizeDrawingState(drawing));
@@ -5149,6 +5180,8 @@ function createChart(element, options = {}) {
5149
5180
  onDrawingSelect,
5150
5181
  onDrawingDoubleClick,
5151
5182
  onDrawingEditText,
5183
+ setMagnetMode,
5184
+ getMagnetMode,
5152
5185
  setSelectedDrawing,
5153
5186
  onDrawingHover,
5154
5187
  setDrawingDefaults,
package/dist/index.cjs CHANGED
@@ -1000,6 +1000,9 @@ function createChart(element, options = {}) {
1000
1000
  let drawingSelectHandler = null;
1001
1001
  let drawingDoubleClickHandler = null;
1002
1002
  let drawingEditTextHandler = null;
1003
+ let magnetMode = "none";
1004
+ let magnetModifierActive = false;
1005
+ const MAGNET_WEAK_THRESHOLD_PX = 16;
1003
1006
  let drawingHoverHandler = null;
1004
1007
  let lastHoveredDrawingId = null;
1005
1008
  let selectedDrawingId = null;
@@ -3724,17 +3727,39 @@ function createChart(element, options = {}) {
3724
3727
  const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
3725
3728
  return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
3726
3729
  };
3730
+ const applyMagnet = (y, index, price) => {
3731
+ const effective = magnetModifierActive ? "strong" : magnetMode;
3732
+ if (effective === "none") return { index, price };
3733
+ const barIndex = Math.round(index);
3734
+ const bar = data[barIndex];
3735
+ if (!bar) return { index, price };
3736
+ const candidates = [bar.o, bar.h, bar.l, bar.c];
3737
+ let nearest = bar.c;
3738
+ let bestDiff = Infinity;
3739
+ for (const candidate of candidates) {
3740
+ const diff = Math.abs(candidate - price);
3741
+ if (diff < bestDiff) {
3742
+ bestDiff = diff;
3743
+ nearest = candidate;
3744
+ }
3745
+ }
3746
+ if (effective === "weak" && Math.abs(canvasYFromDrawingPrice(nearest) - y) > MAGNET_WEAK_THRESHOLD_PX) {
3747
+ return { index, price };
3748
+ }
3749
+ return { index: barIndex, price: nearest };
3750
+ };
3727
3751
  const drawingPointFromCanvas = (x, y) => {
3728
3752
  if (!drawState) {
3729
3753
  return null;
3730
3754
  }
3731
3755
  const ratio = clamp((x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3732
- const index = drawState.xStart + ratio * drawState.xSpan - 0.5;
3733
- const nearestIndex = Math.round(index);
3756
+ const rawIndex = drawState.xStart + ratio * drawState.xSpan - 0.5;
3757
+ const snapped = applyMagnet(y, rawIndex, priceFromCanvasY(y));
3758
+ const nearestIndex = Math.round(snapped.index);
3734
3759
  const time = getTimeForIndex(nearestIndex);
3735
3760
  return {
3736
- index,
3737
- price: roundToPricePrecision(priceFromCanvasY(y)),
3761
+ index: snapped.index,
3762
+ price: roundToPricePrecision(snapped.price),
3738
3763
  ...time ? { time: time.toISOString() } : {}
3739
3764
  };
3740
3765
  };
@@ -4351,6 +4376,7 @@ function createChart(element, options = {}) {
4351
4376
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4352
4377
  event.preventDefault();
4353
4378
  }
4379
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4354
4380
  const point = getCanvasPoint(event);
4355
4381
  if (event.pointerType === "touch") {
4356
4382
  touchPointers.set(event.pointerId, point);
@@ -4473,6 +4499,7 @@ function createChart(element, options = {}) {
4473
4499
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4474
4500
  event.preventDefault();
4475
4501
  }
4502
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4476
4503
  const point = getCanvasPoint(event);
4477
4504
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
4478
4505
  touchPointers.set(event.pointerId, point);
@@ -5067,6 +5094,10 @@ function createChart(element, options = {}) {
5067
5094
  draw();
5068
5095
  };
5069
5096
  const getActiveDrawingTool = () => activeDrawingTool;
5097
+ const setMagnetMode = (mode) => {
5098
+ magnetMode = mode;
5099
+ };
5100
+ const getMagnetMode = () => magnetMode;
5070
5101
  const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
5071
5102
  const setDrawings = (nextDrawings) => {
5072
5103
  drawings = nextDrawings.map((drawing) => normalizeDrawingState(drawing));
@@ -5175,6 +5206,8 @@ function createChart(element, options = {}) {
5175
5206
  onDrawingSelect,
5176
5207
  onDrawingDoubleClick,
5177
5208
  onDrawingEditText,
5209
+ setMagnetMode,
5210
+ getMagnetMode,
5178
5211
  setSelectedDrawing,
5179
5212
  onDrawingHover,
5180
5213
  setDrawingDefaults,
package/dist/index.d.cts CHANGED
@@ -430,6 +430,8 @@ interface ChartInstance {
430
430
  onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
431
431
  onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
432
432
  onDrawingEditText: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
433
+ setMagnetMode: (mode: "none" | "weak" | "strong") => void;
434
+ getMagnetMode: () => "none" | "weak" | "strong";
433
435
  onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
434
436
  setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
435
437
  setSelectedDrawing: (id: string | null) => void;
package/dist/index.d.ts CHANGED
@@ -430,6 +430,8 @@ interface ChartInstance {
430
430
  onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
431
431
  onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
432
432
  onDrawingEditText: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
433
+ setMagnetMode: (mode: "none" | "weak" | "strong") => void;
434
+ getMagnetMode: () => "none" | "weak" | "strong";
433
435
  onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
434
436
  setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
435
437
  setSelectedDrawing: (id: string | null) => void;
package/dist/index.js CHANGED
@@ -974,6 +974,9 @@ function createChart(element, options = {}) {
974
974
  let drawingSelectHandler = null;
975
975
  let drawingDoubleClickHandler = null;
976
976
  let drawingEditTextHandler = null;
977
+ let magnetMode = "none";
978
+ let magnetModifierActive = false;
979
+ const MAGNET_WEAK_THRESHOLD_PX = 16;
977
980
  let drawingHoverHandler = null;
978
981
  let lastHoveredDrawingId = null;
979
982
  let selectedDrawingId = null;
@@ -3698,17 +3701,39 @@ function createChart(element, options = {}) {
3698
3701
  const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
3699
3702
  return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
3700
3703
  };
3704
+ const applyMagnet = (y, index, price) => {
3705
+ const effective = magnetModifierActive ? "strong" : magnetMode;
3706
+ if (effective === "none") return { index, price };
3707
+ const barIndex = Math.round(index);
3708
+ const bar = data[barIndex];
3709
+ if (!bar) return { index, price };
3710
+ const candidates = [bar.o, bar.h, bar.l, bar.c];
3711
+ let nearest = bar.c;
3712
+ let bestDiff = Infinity;
3713
+ for (const candidate of candidates) {
3714
+ const diff = Math.abs(candidate - price);
3715
+ if (diff < bestDiff) {
3716
+ bestDiff = diff;
3717
+ nearest = candidate;
3718
+ }
3719
+ }
3720
+ if (effective === "weak" && Math.abs(canvasYFromDrawingPrice(nearest) - y) > MAGNET_WEAK_THRESHOLD_PX) {
3721
+ return { index, price };
3722
+ }
3723
+ return { index: barIndex, price: nearest };
3724
+ };
3701
3725
  const drawingPointFromCanvas = (x, y) => {
3702
3726
  if (!drawState) {
3703
3727
  return null;
3704
3728
  }
3705
3729
  const ratio = clamp((x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3706
- const index = drawState.xStart + ratio * drawState.xSpan - 0.5;
3707
- const nearestIndex = Math.round(index);
3730
+ const rawIndex = drawState.xStart + ratio * drawState.xSpan - 0.5;
3731
+ const snapped = applyMagnet(y, rawIndex, priceFromCanvasY(y));
3732
+ const nearestIndex = Math.round(snapped.index);
3708
3733
  const time = getTimeForIndex(nearestIndex);
3709
3734
  return {
3710
- index,
3711
- price: roundToPricePrecision(priceFromCanvasY(y)),
3735
+ index: snapped.index,
3736
+ price: roundToPricePrecision(snapped.price),
3712
3737
  ...time ? { time: time.toISOString() } : {}
3713
3738
  };
3714
3739
  };
@@ -4325,6 +4350,7 @@ function createChart(element, options = {}) {
4325
4350
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4326
4351
  event.preventDefault();
4327
4352
  }
4353
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4328
4354
  const point = getCanvasPoint(event);
4329
4355
  if (event.pointerType === "touch") {
4330
4356
  touchPointers.set(event.pointerId, point);
@@ -4447,6 +4473,7 @@ function createChart(element, options = {}) {
4447
4473
  if (event.pointerType === "touch" || event.pointerType === "pen") {
4448
4474
  event.preventDefault();
4449
4475
  }
4476
+ magnetModifierActive = event.metaKey || event.ctrlKey;
4450
4477
  const point = getCanvasPoint(event);
4451
4478
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
4452
4479
  touchPointers.set(event.pointerId, point);
@@ -5041,6 +5068,10 @@ function createChart(element, options = {}) {
5041
5068
  draw();
5042
5069
  };
5043
5070
  const getActiveDrawingTool = () => activeDrawingTool;
5071
+ const setMagnetMode = (mode) => {
5072
+ magnetMode = mode;
5073
+ };
5074
+ const getMagnetMode = () => magnetMode;
5044
5075
  const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
5045
5076
  const setDrawings = (nextDrawings) => {
5046
5077
  drawings = nextDrawings.map((drawing) => normalizeDrawingState(drawing));
@@ -5149,6 +5180,8 @@ function createChart(element, options = {}) {
5149
5180
  onDrawingSelect,
5150
5181
  onDrawingDoubleClick,
5151
5182
  onDrawingEditText,
5183
+ setMagnetMode,
5184
+ getMagnetMode,
5152
5185
  setSelectedDrawing,
5153
5186
  onDrawingHover,
5154
5187
  setDrawingDefaults,
package/docs/API.md CHANGED
@@ -484,6 +484,7 @@ Use `getDrawings()` / `setDrawings()` for persistence.
484
484
  - `setSelectedDrawing(id: string | null): void` (marks a drawing selected; only the selected/drafted drawing renders its handles, and position tools render their lines/labels only while selected)
485
485
  - `onDrawingDoubleClick(handler): void` (fires when a drawing is double-clicked; use it to open a settings dialog, e.g. for position tools)
486
486
  - `onDrawingEditText(handler): void` (fires when a `text`/`note` tool is placed or double-clicked; use it to show an inline text editor at `{x, y}` and write the result back via `updateDrawing(id, { label })`). `text`/`note` drawings store their content in `label` and size in `fontSize`.
487
+ - `setMagnetMode(mode): void` / `getMagnetMode()` — magnet/snap for all drawing tools. `"none"` off, `"weak"` snaps to a candle's OHLC only when the cursor is near a value, `"strong"` always snaps to the nearest OHLC of the bar under the cursor. Holding Cmd/Ctrl while drawing/dragging forces strong snapping regardless of the mode.
487
488
  - `setActiveDrawingTool(tool: DrawingToolType | null): void` (`DrawingToolType` = `"horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`)
488
489
  - `getActiveDrawingTool(): DrawingToolType | null`
489
490
  - `setDrawings(drawings: DrawingObjectOptions[]): void`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.89",
3
+ "version": "0.1.90",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",