hyperprop-charting-library 0.1.8 → 0.1.9

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/index.d.ts CHANGED
@@ -49,9 +49,13 @@ interface CrosshairOptions {
49
49
  showVertical?: boolean;
50
50
  showPriceLabel?: boolean;
51
51
  showTimeLabel?: boolean;
52
+ timeLabelFormat?: "auto" | "date" | "time" | "datetime";
52
53
  labelBackgroundColor?: string;
53
54
  labelTextColor?: string;
54
55
  labelBorderRadius?: number;
56
+ labelBorderColor?: string;
57
+ labelBorderWidth?: number;
58
+ labelBorderStyle?: "solid" | "dotted" | "dashed";
55
59
  }
56
60
  interface WatermarkOptions {
57
61
  visible?: boolean;
@@ -148,6 +152,15 @@ interface ChartClickEvent {
148
152
  price?: number;
149
153
  region: "plot" | "x-axis" | "y-axis";
150
154
  }
155
+ interface CrosshairMoveEvent {
156
+ x: number;
157
+ y: number;
158
+ region: "plot" | "x-axis" | "y-axis";
159
+ price?: number;
160
+ index?: number;
161
+ time?: string;
162
+ point?: OhlcDataPoint;
163
+ }
151
164
  interface TickerLineOptions {
152
165
  visible?: boolean;
153
166
  style?: "solid" | "dotted" | "dashed";
@@ -168,6 +181,7 @@ interface ChartInstance {
168
181
  removeOrderLine: (id: string) => void;
169
182
  onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
170
183
  onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
184
+ onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
171
185
  setDoubleClickEnabled: (enabled: boolean) => void;
172
186
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
173
187
  resize: (width?: number, height?: number) => void;
@@ -183,4 +197,4 @@ interface OhlcDataPoint {
183
197
  }
184
198
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
185
199
 
186
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
200
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
package/dist/index.js CHANGED
@@ -21,9 +21,13 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
21
21
  showVertical: true,
22
22
  showPriceLabel: true,
23
23
  showTimeLabel: true,
24
+ timeLabelFormat: "auto",
24
25
  labelBackgroundColor: "#0b1220",
25
26
  labelTextColor: "#cbd5e1",
26
- labelBorderRadius: 3
27
+ labelBorderRadius: 3,
28
+ labelBorderColor: "#94a3b8",
29
+ labelBorderWidth: 1,
30
+ labelBorderStyle: "solid"
27
31
  };
28
32
  var DEFAULT_WATERMARK_OPTIONS = {
29
33
  visible: false,
@@ -157,6 +161,7 @@ function createChart(element, options = {}) {
157
161
  }));
158
162
  let orderActionHandler = null;
159
163
  let chartClickHandler = null;
164
+ let crosshairMoveHandler = null;
160
165
  let orderActionRegions = [];
161
166
  let orderDragRegions = [];
162
167
  let generatedPriceLineId = 1;
@@ -344,6 +349,42 @@ function createChart(element, options = {}) {
344
349
  const extra = index - (data.length - 1);
345
350
  return new Date(last.time.getTime() + extra * stepMs);
346
351
  };
352
+ const formatHoverTimeLabel = (time, mode) => {
353
+ if (mode === "time") {
354
+ return time.toLocaleTimeString(void 0, {
355
+ hour: "2-digit",
356
+ minute: "2-digit",
357
+ hour12: false
358
+ });
359
+ }
360
+ if (mode === "datetime") {
361
+ return time.toLocaleString(void 0, {
362
+ month: "short",
363
+ day: "numeric",
364
+ hour: "2-digit",
365
+ minute: "2-digit",
366
+ hour12: false
367
+ });
368
+ }
369
+ if (mode === "date") {
370
+ return time.toLocaleDateString(void 0, {
371
+ month: "short",
372
+ day: "numeric"
373
+ });
374
+ }
375
+ const stepMs = getTimeStepMs();
376
+ if (stepMs < 24 * 60 * 60 * 1e3) {
377
+ return time.toLocaleTimeString(void 0, {
378
+ hour: "2-digit",
379
+ minute: "2-digit",
380
+ hour12: false
381
+ });
382
+ }
383
+ return time.toLocaleDateString(void 0, {
384
+ month: "short",
385
+ day: "numeric"
386
+ });
387
+ };
347
388
  const drawText = (text, x, y, align = "left", baseline = "alphabetic", color = mergedOptions.axis?.textColor ?? mergedOptions.axisColor) => {
348
389
  ctx.fillStyle = color;
349
390
  ctx.textAlign = align;
@@ -998,6 +1039,26 @@ function createChart(element, options = {}) {
998
1039
  const labelRadius = Math.max(0, crosshair.labelBorderRadius);
999
1040
  const labelBackground = crosshair.labelBackgroundColor;
1000
1041
  const labelTextColor = crosshair.labelTextColor;
1042
+ const labelBorderColor = crosshair.labelBorderColor;
1043
+ const labelBorderWidth = Math.max(0, crosshair.labelBorderWidth);
1044
+ const labelBorderStyle = crosshair.labelBorderStyle;
1045
+ const strokeCrosshairLabel = (x, y, widthValue) => {
1046
+ if (labelBorderWidth <= 0) {
1047
+ return;
1048
+ }
1049
+ ctx.save();
1050
+ ctx.strokeStyle = labelBorderColor;
1051
+ ctx.lineWidth = labelBorderWidth;
1052
+ if (labelBorderStyle === "dotted") {
1053
+ ctx.setLineDash([2, 3]);
1054
+ } else if (labelBorderStyle === "dashed") {
1055
+ ctx.setLineDash([6, 4]);
1056
+ } else {
1057
+ ctx.setLineDash([]);
1058
+ }
1059
+ strokeRoundedRect(Math.round(x), Math.round(y), widthValue, labelHeight, labelRadius);
1060
+ ctx.restore();
1061
+ };
1001
1062
  if (crosshair.showPriceLabel) {
1002
1063
  const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1003
1064
  const priceText = formatPrice(hoverPrice);
@@ -1006,6 +1067,7 @@ function createChart(element, options = {}) {
1006
1067
  const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1007
1068
  ctx.fillStyle = labelBackground;
1008
1069
  fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1070
+ strokeCrosshairLabel(priceX, priceY, priceWidth);
1009
1071
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1010
1072
  }
1011
1073
  if (crosshair.showTimeLabel) {
@@ -1013,15 +1075,13 @@ function createChart(element, options = {}) {
1013
1075
  const hoverIndex = Math.round(xStart + ratio * xSpan - 0.5);
1014
1076
  const hoverTime = getTimeForIndex(hoverIndex);
1015
1077
  if (hoverTime) {
1016
- const timeText = hoverTime.toLocaleDateString(void 0, {
1017
- month: "short",
1018
- day: "numeric"
1019
- });
1078
+ const timeText = formatHoverTimeLabel(hoverTime, crosshair.timeLabelFormat);
1020
1079
  const timeWidth = Math.ceil(ctx.measureText(timeText).width) + labelPaddingX * 2;
1021
1080
  const timeX = clamp(cx - timeWidth / 2, chartLeft, chartRight - timeWidth);
1022
1081
  const timeY = chartBottom + 8;
1023
1082
  ctx.fillStyle = labelBackground;
1024
1083
  fillRoundedRect(Math.round(timeX), Math.round(timeY), timeWidth, labelHeight, labelRadius);
1084
+ strokeCrosshairLabel(timeX, timeY, timeWidth);
1025
1085
  drawText(timeText, timeX + labelPaddingX, timeY + labelHeight / 2, "left", "middle", labelTextColor);
1026
1086
  }
1027
1087
  }
@@ -1120,6 +1180,39 @@ function createChart(element, options = {}) {
1120
1180
  const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
1121
1181
  return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
1122
1182
  };
1183
+ const indexFromCanvasX = (x) => {
1184
+ if (!drawState) {
1185
+ return null;
1186
+ }
1187
+ const ratio = clamp((x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
1188
+ return Math.round(drawState.xStart + ratio * drawState.xSpan - 0.5);
1189
+ };
1190
+ const emitCrosshairMove = (x, y, region) => {
1191
+ if (!crosshairMoveHandler) {
1192
+ return;
1193
+ }
1194
+ const index = indexFromCanvasX(x);
1195
+ const hoverTime = index === null ? null : getTimeForIndex(index);
1196
+ const parsedPoint = index === null ? void 0 : data[index];
1197
+ const point = parsedPoint === void 0 ? void 0 : {
1198
+ t: parsedPoint.time.toISOString(),
1199
+ o: parsedPoint.o,
1200
+ h: parsedPoint.h,
1201
+ l: parsedPoint.l,
1202
+ c: parsedPoint.c,
1203
+ ...parsedPoint.v === void 0 ? {} : { v: parsedPoint.v }
1204
+ };
1205
+ const payload = {
1206
+ x,
1207
+ y,
1208
+ region,
1209
+ ...region === "plot" ? { price: Number(priceFromCanvasY(y).toFixed(mergedOptions.priceDecimals)) } : {},
1210
+ ...index === null ? {} : { index },
1211
+ ...hoverTime ? { time: hoverTime.toISOString() } : {},
1212
+ ...point ? { point } : {}
1213
+ };
1214
+ crosshairMoveHandler(payload);
1215
+ };
1123
1216
  const getHitRegion = (x, y) => {
1124
1217
  if (!drawState) {
1125
1218
  return "outside";
@@ -1273,12 +1366,15 @@ function createChart(element, options = {}) {
1273
1366
  if (hoverRegion === "plot") {
1274
1367
  canvas.style.cursor = "default";
1275
1368
  setCrosshairPoint(point);
1369
+ emitCrosshairMove(point.x, point.y, "plot");
1276
1370
  } else if (hoverRegion === "x-axis") {
1277
1371
  canvas.style.cursor = "ew-resize";
1278
1372
  setCrosshairPoint(null);
1373
+ emitCrosshairMove(point.x, point.y, "x-axis");
1279
1374
  } else if (hoverRegion === "y-axis") {
1280
1375
  canvas.style.cursor = "ns-resize";
1281
1376
  setCrosshairPoint(null);
1377
+ emitCrosshairMove(point.x, point.y, "y-axis");
1282
1378
  } else {
1283
1379
  canvas.style.cursor = "default";
1284
1380
  setCrosshairPoint(null);
@@ -1288,7 +1384,7 @@ function createChart(element, options = {}) {
1288
1384
  const deltaX = point.x - lastPointerX;
1289
1385
  const deltaY = point.y - lastPointerY;
1290
1386
  if (dragMode === "plot") {
1291
- canvas.style.cursor = "grabbing";
1387
+ canvas.style.cursor = "default";
1292
1388
  pan(deltaX, deltaY, true, true);
1293
1389
  setCrosshairPoint(null);
1294
1390
  } else if (dragMode === "x-axis") {
@@ -1518,6 +1614,9 @@ function createChart(element, options = {}) {
1518
1614
  const onChartClick = (handler) => {
1519
1615
  chartClickHandler = handler;
1520
1616
  };
1617
+ const onCrosshairMove = (handler) => {
1618
+ crosshairMoveHandler = handler;
1619
+ };
1521
1620
  const setDoubleClickEnabled = (enabled) => {
1522
1621
  doubleClickEnabled = enabled;
1523
1622
  };
@@ -1546,6 +1645,7 @@ function createChart(element, options = {}) {
1546
1645
  removeOrderLine,
1547
1646
  onOrderAction,
1548
1647
  onChartClick,
1648
+ onCrosshairMove,
1549
1649
  setDoubleClickEnabled,
1550
1650
  setDoubleClickAction,
1551
1651
  resize,
package/docs/API.md CHANGED
@@ -80,9 +80,13 @@ Top-level options:
80
80
  - `showVertical` (default `true`)
81
81
  - `showPriceLabel` (default `true`)
82
82
  - `showTimeLabel` (default `true`)
83
+ - `timeLabelFormat` (`"auto" | "date" | "time" | "datetime"`, default `"auto"`)
83
84
  - `labelBackgroundColor` (default `#0b1220`)
84
85
  - `labelTextColor` (default `#cbd5e1`)
85
86
  - `labelBorderRadius` (default `3`)
87
+ - `labelBorderColor` (default `#94a3b8`)
88
+ - `labelBorderWidth` (default `1`)
89
+ - `labelBorderStyle` (`"solid" | "dotted" | "dashed"`, default `"solid"`)
86
90
 
87
91
  ### `WatermarkOptions`
88
92
 
@@ -208,6 +212,7 @@ Connector/fill visuals:
208
212
  - `removeOrderLine(id: string): void`
209
213
  - `onOrderAction(handler: ((event: OrderActionEvent) => void) | null): void`
210
214
  - `onChartClick(handler: ((event: ChartClickEvent) => void) | null): void`
215
+ - `onCrosshairMove(handler: ((event: CrosshairMoveEvent) => void) | null): void`
211
216
  - `setDoubleClickEnabled(enabled: boolean): void`
212
217
  - `setDoubleClickAction(action: "reset" | "placeLimitOrder"): void`
213
218
  - `resize(width?: number, height?: number): void`
package/docs/EVENTS.md CHANGED
@@ -76,6 +76,39 @@ type ChartClickEvent = {
76
76
 
77
77
  ---
78
78
 
79
+ ## `onCrosshairMove`
80
+
81
+ Register:
82
+
83
+ ```ts
84
+ chart.onCrosshairMove((event) => {
85
+ // update OHLC header, hover widgets, etc
86
+ });
87
+ ```
88
+
89
+ Payload type:
90
+
91
+ ```ts
92
+ type CrosshairMoveEvent = {
93
+ x: number;
94
+ y: number;
95
+ region: "plot" | "x-axis" | "y-axis";
96
+ price?: number;
97
+ index?: number;
98
+ time?: string;
99
+ point?: OhlcDataPoint;
100
+ };
101
+ ```
102
+
103
+ ### Notes
104
+
105
+ - Fires while hovering in chart regions.
106
+ - For plot hover, `price` is included.
107
+ - `point` is included when hover maps to an existing candle index.
108
+ - Use this to implement TradingView-style dynamic OHLC readout in your app header.
109
+
110
+ ---
111
+
79
112
  ## Double-click integration
80
113
 
81
114
  Set behavior:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",