hyperprop-charting-library 0.1.7 → 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.cts CHANGED
@@ -47,6 +47,15 @@ interface CrosshairOptions {
47
47
  style?: "solid" | "dotted" | "dashed";
48
48
  showHorizontal?: boolean;
49
49
  showVertical?: boolean;
50
+ showPriceLabel?: boolean;
51
+ showTimeLabel?: boolean;
52
+ timeLabelFormat?: "auto" | "date" | "time" | "datetime";
53
+ labelBackgroundColor?: string;
54
+ labelTextColor?: string;
55
+ labelBorderRadius?: number;
56
+ labelBorderColor?: string;
57
+ labelBorderWidth?: number;
58
+ labelBorderStyle?: "solid" | "dotted" | "dashed";
50
59
  }
51
60
  interface WatermarkOptions {
52
61
  visible?: boolean;
@@ -143,6 +152,15 @@ interface ChartClickEvent {
143
152
  price?: number;
144
153
  region: "plot" | "x-axis" | "y-axis";
145
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
+ }
146
164
  interface TickerLineOptions {
147
165
  visible?: boolean;
148
166
  style?: "solid" | "dotted" | "dashed";
@@ -163,6 +181,7 @@ interface ChartInstance {
163
181
  removeOrderLine: (id: string) => void;
164
182
  onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
165
183
  onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
184
+ onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
166
185
  setDoubleClickEnabled: (enabled: boolean) => void;
167
186
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
168
187
  resize: (width?: number, height?: number) => void;
@@ -178,4 +197,4 @@ interface OhlcDataPoint {
178
197
  }
179
198
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
180
199
 
181
- 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.d.ts CHANGED
@@ -47,6 +47,15 @@ interface CrosshairOptions {
47
47
  style?: "solid" | "dotted" | "dashed";
48
48
  showHorizontal?: boolean;
49
49
  showVertical?: boolean;
50
+ showPriceLabel?: boolean;
51
+ showTimeLabel?: boolean;
52
+ timeLabelFormat?: "auto" | "date" | "time" | "datetime";
53
+ labelBackgroundColor?: string;
54
+ labelTextColor?: string;
55
+ labelBorderRadius?: number;
56
+ labelBorderColor?: string;
57
+ labelBorderWidth?: number;
58
+ labelBorderStyle?: "solid" | "dotted" | "dashed";
50
59
  }
51
60
  interface WatermarkOptions {
52
61
  visible?: boolean;
@@ -143,6 +152,15 @@ interface ChartClickEvent {
143
152
  price?: number;
144
153
  region: "plot" | "x-axis" | "y-axis";
145
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
+ }
146
164
  interface TickerLineOptions {
147
165
  visible?: boolean;
148
166
  style?: "solid" | "dotted" | "dashed";
@@ -163,6 +181,7 @@ interface ChartInstance {
163
181
  removeOrderLine: (id: string) => void;
164
182
  onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
165
183
  onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
184
+ onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
166
185
  setDoubleClickEnabled: (enabled: boolean) => void;
167
186
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
168
187
  resize: (width?: number, height?: number) => void;
@@ -178,4 +197,4 @@ interface OhlcDataPoint {
178
197
  }
179
198
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
180
199
 
181
- 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
@@ -18,7 +18,16 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
18
18
  width: 1,
19
19
  style: "dotted",
20
20
  showHorizontal: true,
21
- showVertical: true
21
+ showVertical: true,
22
+ showPriceLabel: true,
23
+ showTimeLabel: true,
24
+ timeLabelFormat: "auto",
25
+ labelBackgroundColor: "#0b1220",
26
+ labelTextColor: "#cbd5e1",
27
+ labelBorderRadius: 3,
28
+ labelBorderColor: "#94a3b8",
29
+ labelBorderWidth: 1,
30
+ labelBorderStyle: "solid"
22
31
  };
23
32
  var DEFAULT_WATERMARK_OPTIONS = {
24
33
  visible: false,
@@ -152,6 +161,7 @@ function createChart(element, options = {}) {
152
161
  }));
153
162
  let orderActionHandler = null;
154
163
  let chartClickHandler = null;
164
+ let crosshairMoveHandler = null;
155
165
  let orderActionRegions = [];
156
166
  let orderDragRegions = [];
157
167
  let generatedPriceLineId = 1;
@@ -339,6 +349,42 @@ function createChart(element, options = {}) {
339
349
  const extra = index - (data.length - 1);
340
350
  return new Date(last.time.getTime() + extra * stepMs);
341
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
+ };
342
388
  const drawText = (text, x, y, align = "left", baseline = "alphabetic", color = mergedOptions.axis?.textColor ?? mergedOptions.axisColor) => {
343
389
  ctx.fillStyle = color;
344
390
  ctx.textAlign = align;
@@ -985,6 +1031,61 @@ function createChart(element, options = {}) {
985
1031
  });
986
1032
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
987
1033
  }
1034
+ if (crosshair.visible && crosshairPoint) {
1035
+ const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
1036
+ const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
1037
+ const labelPaddingX = 8;
1038
+ const labelHeight = 20;
1039
+ const labelRadius = Math.max(0, crosshair.labelBorderRadius);
1040
+ const labelBackground = crosshair.labelBackgroundColor;
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
+ };
1062
+ if (crosshair.showPriceLabel) {
1063
+ const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1064
+ const priceText = formatPrice(hoverPrice);
1065
+ const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1066
+ const priceX = chartRight + 4;
1067
+ const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1068
+ ctx.fillStyle = labelBackground;
1069
+ fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1070
+ strokeCrosshairLabel(priceX, priceY, priceWidth);
1071
+ drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1072
+ }
1073
+ if (crosshair.showTimeLabel) {
1074
+ const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
1075
+ const hoverIndex = Math.round(xStart + ratio * xSpan - 0.5);
1076
+ const hoverTime = getTimeForIndex(hoverIndex);
1077
+ if (hoverTime) {
1078
+ const timeText = formatHoverTimeLabel(hoverTime, crosshair.timeLabelFormat);
1079
+ const timeWidth = Math.ceil(ctx.measureText(timeText).width) + labelPaddingX * 2;
1080
+ const timeX = clamp(cx - timeWidth / 2, chartLeft, chartRight - timeWidth);
1081
+ const timeY = chartBottom + 8;
1082
+ ctx.fillStyle = labelBackground;
1083
+ fillRoundedRect(Math.round(timeX), Math.round(timeY), timeWidth, labelHeight, labelRadius);
1084
+ strokeCrosshairLabel(timeX, timeY, timeWidth);
1085
+ drawText(timeText, timeX + labelPaddingX, timeY + labelHeight / 2, "left", "middle", labelTextColor);
1086
+ }
1087
+ }
1088
+ }
988
1089
  };
989
1090
  const zoomX = (factor, anchorX) => {
990
1091
  if (!drawState || data.length === 0) {
@@ -1079,6 +1180,39 @@ function createChart(element, options = {}) {
1079
1180
  const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
1080
1181
  return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
1081
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
+ };
1082
1216
  const getHitRegion = (x, y) => {
1083
1217
  if (!drawState) {
1084
1218
  return "outside";
@@ -1230,14 +1364,17 @@ function createChart(element, options = {}) {
1230
1364
  }
1231
1365
  const hoverRegion = getHitRegion(point.x, point.y);
1232
1366
  if (hoverRegion === "plot") {
1233
- canvas.style.cursor = "grab";
1367
+ canvas.style.cursor = "default";
1234
1368
  setCrosshairPoint(point);
1369
+ emitCrosshairMove(point.x, point.y, "plot");
1235
1370
  } else if (hoverRegion === "x-axis") {
1236
1371
  canvas.style.cursor = "ew-resize";
1237
1372
  setCrosshairPoint(null);
1373
+ emitCrosshairMove(point.x, point.y, "x-axis");
1238
1374
  } else if (hoverRegion === "y-axis") {
1239
1375
  canvas.style.cursor = "ns-resize";
1240
1376
  setCrosshairPoint(null);
1377
+ emitCrosshairMove(point.x, point.y, "y-axis");
1241
1378
  } else {
1242
1379
  canvas.style.cursor = "default";
1243
1380
  setCrosshairPoint(null);
@@ -1247,7 +1384,7 @@ function createChart(element, options = {}) {
1247
1384
  const deltaX = point.x - lastPointerX;
1248
1385
  const deltaY = point.y - lastPointerY;
1249
1386
  if (dragMode === "plot") {
1250
- canvas.style.cursor = "grabbing";
1387
+ canvas.style.cursor = "default";
1251
1388
  pan(deltaX, deltaY, true, true);
1252
1389
  setCrosshairPoint(null);
1253
1390
  } else if (dragMode === "x-axis") {
@@ -1477,6 +1614,9 @@ function createChart(element, options = {}) {
1477
1614
  const onChartClick = (handler) => {
1478
1615
  chartClickHandler = handler;
1479
1616
  };
1617
+ const onCrosshairMove = (handler) => {
1618
+ crosshairMoveHandler = handler;
1619
+ };
1480
1620
  const setDoubleClickEnabled = (enabled) => {
1481
1621
  doubleClickEnabled = enabled;
1482
1622
  };
@@ -1505,6 +1645,7 @@ function createChart(element, options = {}) {
1505
1645
  removeOrderLine,
1506
1646
  onOrderAction,
1507
1647
  onChartClick,
1648
+ onCrosshairMove,
1508
1649
  setDoubleClickEnabled,
1509
1650
  setDoubleClickAction,
1510
1651
  resize,
package/docs/API.md CHANGED
@@ -78,6 +78,15 @@ Top-level options:
78
78
  - `style` (`"solid" | "dotted" | "dashed"`, default `"dotted"`)
79
79
  - `showHorizontal` (default `true`)
80
80
  - `showVertical` (default `true`)
81
+ - `showPriceLabel` (default `true`)
82
+ - `showTimeLabel` (default `true`)
83
+ - `timeLabelFormat` (`"auto" | "date" | "time" | "datetime"`, default `"auto"`)
84
+ - `labelBackgroundColor` (default `#0b1220`)
85
+ - `labelTextColor` (default `#cbd5e1`)
86
+ - `labelBorderRadius` (default `3`)
87
+ - `labelBorderColor` (default `#94a3b8`)
88
+ - `labelBorderWidth` (default `1`)
89
+ - `labelBorderStyle` (`"solid" | "dotted" | "dashed"`, default `"solid"`)
81
90
 
82
91
  ### `WatermarkOptions`
83
92
 
@@ -98,12 +107,12 @@ Top-level options:
98
107
  ### `TickerLineOptions`
99
108
 
100
109
  - `visible` (default `true`)
101
- - `style` (`"solid" | "dotted" | "dashed"`, default `"dashed"`)
110
+ - `style` (`"solid" | "dotted" | "dashed"`, default `"dotted"`)
102
111
  - `thickness` (default `1`)
103
- - `color` (default `#22c55e`)
104
- - `labelBackgroundColor` (default `#22c55e`)
112
+ - `color` (default `#38bdf8`)
113
+ - `labelBackgroundColor` (default `#38bdf8`)
105
114
  - `labelTextColor` (default `#0b1220`)
106
- - `labelBorderRadius` (default `6`)
115
+ - `labelBorderRadius` (default `3`)
107
116
 
108
117
  ### `PriceLineOptions`
109
118
 
@@ -203,6 +212,7 @@ Connector/fill visuals:
203
212
  - `removeOrderLine(id: string): void`
204
213
  - `onOrderAction(handler: ((event: OrderActionEvent) => void) | null): void`
205
214
  - `onChartClick(handler: ((event: ChartClickEvent) => void) | null): void`
215
+ - `onCrosshairMove(handler: ((event: CrosshairMoveEvent) => void) | null): void`
206
216
  - `setDoubleClickEnabled(enabled: boolean): void`
207
217
  - `setDoubleClickAction(action: "reset" | "placeLimitOrder"): void`
208
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.7",
3
+ "version": "0.1.9",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",