hyperprop-charting-library 0.1.6 → 0.1.8

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.cjs CHANGED
@@ -42,7 +42,12 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
42
42
  width: 1,
43
43
  style: "dotted",
44
44
  showHorizontal: true,
45
- showVertical: true
45
+ showVertical: true,
46
+ showPriceLabel: true,
47
+ showTimeLabel: true,
48
+ labelBackgroundColor: "#0b1220",
49
+ labelTextColor: "#cbd5e1",
50
+ labelBorderRadius: 3
46
51
  };
47
52
  var DEFAULT_WATERMARK_OPTIONS = {
48
53
  visible: false,
@@ -51,7 +56,13 @@ var DEFAULT_WATERMARK_OPTIONS = {
51
56
  opacity: 0.14,
52
57
  fontSize: 92,
53
58
  fontWeight: 700,
54
- thickness: 0
59
+ thickness: 0,
60
+ imageSrc: "",
61
+ imageScale: 1,
62
+ imageMaxWidthRatio: 0.42,
63
+ imageMaxHeightRatio: 0.3,
64
+ imageTintColor: "",
65
+ imageTintOpacity: 1
55
66
  };
56
67
  var DEFAULT_PRICE_LINE_OPTIONS = {
57
68
  visible: true,
@@ -182,6 +193,9 @@ function createChart(element, options = {}) {
182
193
  let yMaxOverride = null;
183
194
  let autoYMin = null;
184
195
  let autoYMax = null;
196
+ let watermarkImageSrc = null;
197
+ let watermarkImage = null;
198
+ let watermarkImageReady = false;
185
199
  let drawState = null;
186
200
  let orderDragState = null;
187
201
  let actionDragState = null;
@@ -207,6 +221,29 @@ function createChart(element, options = {}) {
207
221
  }
208
222
  return Math.max(1, window.devicePixelRatio || 1);
209
223
  };
224
+ const ensureWatermarkImage = (src) => {
225
+ if (src === watermarkImageSrc) {
226
+ return;
227
+ }
228
+ watermarkImageSrc = src;
229
+ watermarkImageReady = false;
230
+ watermarkImage = null;
231
+ if (!src) {
232
+ return;
233
+ }
234
+ const image = new Image();
235
+ image.crossOrigin = "anonymous";
236
+ image.onload = () => {
237
+ watermarkImage = image;
238
+ watermarkImageReady = true;
239
+ draw();
240
+ };
241
+ image.onerror = () => {
242
+ watermarkImage = null;
243
+ watermarkImageReady = false;
244
+ };
245
+ image.src = src;
246
+ };
210
247
  const crisp = (value) => Math.round(value) + 0.5;
211
248
  const clamp = (value, min, max) => {
212
249
  return Math.min(max, Math.max(min, value));
@@ -755,6 +792,31 @@ function createChart(element, options = {}) {
755
792
  const yFromPrice = (price) => {
756
793
  return chartBottom - (price - yMin) / yRange * chartHeight;
757
794
  };
795
+ if (watermark.visible && watermark.imageSrc.trim().length > 0) {
796
+ ensureWatermarkImage(watermark.imageSrc.trim());
797
+ if (watermarkImageReady && watermarkImage) {
798
+ const maxW = chartWidth * clamp(watermark.imageMaxWidthRatio, 0.05, 1);
799
+ const maxH = chartHeight * clamp(watermark.imageMaxHeightRatio, 0.05, 1);
800
+ const naturalW = Math.max(1, watermarkImage.naturalWidth || watermarkImage.width);
801
+ const naturalH = Math.max(1, watermarkImage.naturalHeight || watermarkImage.height);
802
+ const containScale = Math.min(maxW / naturalW, maxH / naturalH);
803
+ const scale = Math.max(0.05, containScale * Math.max(0.05, watermark.imageScale));
804
+ const drawW = naturalW * scale;
805
+ const drawH = naturalH * scale;
806
+ const drawX = chartLeft + (chartWidth - drawW) / 2;
807
+ const drawY = chartTop + (chartHeight - drawH) / 2;
808
+ ctx.save();
809
+ ctx.globalAlpha = clamp(watermark.opacity, 0, 1);
810
+ ctx.drawImage(watermarkImage, drawX, drawY, drawW, drawH);
811
+ if (watermark.imageTintColor.trim().length > 0) {
812
+ ctx.globalCompositeOperation = "source-atop";
813
+ ctx.globalAlpha = clamp(watermark.opacity, 0, 1) * clamp(watermark.imageTintOpacity, 0, 1);
814
+ ctx.fillStyle = watermark.imageTintColor;
815
+ ctx.fillRect(drawX, drawY, drawW, drawH);
816
+ }
817
+ ctx.restore();
818
+ }
819
+ }
758
820
  if (watermark.visible && watermark.text.trim().length > 0) {
759
821
  ctx.save();
760
822
  ctx.globalAlpha = clamp(watermark.opacity, 0, 1);
@@ -952,6 +1014,42 @@ function createChart(element, options = {}) {
952
1014
  });
953
1015
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
954
1016
  }
1017
+ if (crosshair.visible && crosshairPoint) {
1018
+ const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
1019
+ const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
1020
+ const labelPaddingX = 8;
1021
+ const labelHeight = 20;
1022
+ const labelRadius = Math.max(0, crosshair.labelBorderRadius);
1023
+ const labelBackground = crosshair.labelBackgroundColor;
1024
+ const labelTextColor = crosshair.labelTextColor;
1025
+ if (crosshair.showPriceLabel) {
1026
+ const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1027
+ const priceText = formatPrice(hoverPrice);
1028
+ const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1029
+ const priceX = chartRight + 4;
1030
+ const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1031
+ ctx.fillStyle = labelBackground;
1032
+ fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1033
+ drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1034
+ }
1035
+ if (crosshair.showTimeLabel) {
1036
+ const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
1037
+ const hoverIndex = Math.round(xStart + ratio * xSpan - 0.5);
1038
+ const hoverTime = getTimeForIndex(hoverIndex);
1039
+ if (hoverTime) {
1040
+ const timeText = hoverTime.toLocaleDateString(void 0, {
1041
+ month: "short",
1042
+ day: "numeric"
1043
+ });
1044
+ const timeWidth = Math.ceil(ctx.measureText(timeText).width) + labelPaddingX * 2;
1045
+ const timeX = clamp(cx - timeWidth / 2, chartLeft, chartRight - timeWidth);
1046
+ const timeY = chartBottom + 8;
1047
+ ctx.fillStyle = labelBackground;
1048
+ fillRoundedRect(Math.round(timeX), Math.round(timeY), timeWidth, labelHeight, labelRadius);
1049
+ drawText(timeText, timeX + labelPaddingX, timeY + labelHeight / 2, "left", "middle", labelTextColor);
1050
+ }
1051
+ }
1052
+ }
955
1053
  };
956
1054
  const zoomX = (factor, anchorX) => {
957
1055
  if (!drawState || data.length === 0) {
@@ -1197,7 +1295,7 @@ function createChart(element, options = {}) {
1197
1295
  }
1198
1296
  const hoverRegion = getHitRegion(point.x, point.y);
1199
1297
  if (hoverRegion === "plot") {
1200
- canvas.style.cursor = "grab";
1298
+ canvas.style.cursor = "default";
1201
1299
  setCrosshairPoint(point);
1202
1300
  } else if (hoverRegion === "x-axis") {
1203
1301
  canvas.style.cursor = "ew-resize";
package/dist/index.d.cts CHANGED
@@ -47,6 +47,11 @@ interface CrosshairOptions {
47
47
  style?: "solid" | "dotted" | "dashed";
48
48
  showHorizontal?: boolean;
49
49
  showVertical?: boolean;
50
+ showPriceLabel?: boolean;
51
+ showTimeLabel?: boolean;
52
+ labelBackgroundColor?: string;
53
+ labelTextColor?: string;
54
+ labelBorderRadius?: number;
50
55
  }
51
56
  interface WatermarkOptions {
52
57
  visible?: boolean;
@@ -56,6 +61,12 @@ interface WatermarkOptions {
56
61
  fontSize?: number;
57
62
  fontWeight?: number | string;
58
63
  thickness?: number;
64
+ imageSrc?: string;
65
+ imageScale?: number;
66
+ imageMaxWidthRatio?: number;
67
+ imageMaxHeightRatio?: number;
68
+ imageTintColor?: string;
69
+ imageTintOpacity?: number;
59
70
  }
60
71
  interface PriceLineOptions {
61
72
  id?: string;
package/dist/index.d.ts CHANGED
@@ -47,6 +47,11 @@ interface CrosshairOptions {
47
47
  style?: "solid" | "dotted" | "dashed";
48
48
  showHorizontal?: boolean;
49
49
  showVertical?: boolean;
50
+ showPriceLabel?: boolean;
51
+ showTimeLabel?: boolean;
52
+ labelBackgroundColor?: string;
53
+ labelTextColor?: string;
54
+ labelBorderRadius?: number;
50
55
  }
51
56
  interface WatermarkOptions {
52
57
  visible?: boolean;
@@ -56,6 +61,12 @@ interface WatermarkOptions {
56
61
  fontSize?: number;
57
62
  fontWeight?: number | string;
58
63
  thickness?: number;
64
+ imageSrc?: string;
65
+ imageScale?: number;
66
+ imageMaxWidthRatio?: number;
67
+ imageMaxHeightRatio?: number;
68
+ imageTintColor?: string;
69
+ imageTintOpacity?: number;
59
70
  }
60
71
  interface PriceLineOptions {
61
72
  id?: string;
package/dist/index.js CHANGED
@@ -18,7 +18,12 @@ 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
+ labelBackgroundColor: "#0b1220",
25
+ labelTextColor: "#cbd5e1",
26
+ labelBorderRadius: 3
22
27
  };
23
28
  var DEFAULT_WATERMARK_OPTIONS = {
24
29
  visible: false,
@@ -27,7 +32,13 @@ var DEFAULT_WATERMARK_OPTIONS = {
27
32
  opacity: 0.14,
28
33
  fontSize: 92,
29
34
  fontWeight: 700,
30
- thickness: 0
35
+ thickness: 0,
36
+ imageSrc: "",
37
+ imageScale: 1,
38
+ imageMaxWidthRatio: 0.42,
39
+ imageMaxHeightRatio: 0.3,
40
+ imageTintColor: "",
41
+ imageTintOpacity: 1
31
42
  };
32
43
  var DEFAULT_PRICE_LINE_OPTIONS = {
33
44
  visible: true,
@@ -158,6 +169,9 @@ function createChart(element, options = {}) {
158
169
  let yMaxOverride = null;
159
170
  let autoYMin = null;
160
171
  let autoYMax = null;
172
+ let watermarkImageSrc = null;
173
+ let watermarkImage = null;
174
+ let watermarkImageReady = false;
161
175
  let drawState = null;
162
176
  let orderDragState = null;
163
177
  let actionDragState = null;
@@ -183,6 +197,29 @@ function createChart(element, options = {}) {
183
197
  }
184
198
  return Math.max(1, window.devicePixelRatio || 1);
185
199
  };
200
+ const ensureWatermarkImage = (src) => {
201
+ if (src === watermarkImageSrc) {
202
+ return;
203
+ }
204
+ watermarkImageSrc = src;
205
+ watermarkImageReady = false;
206
+ watermarkImage = null;
207
+ if (!src) {
208
+ return;
209
+ }
210
+ const image = new Image();
211
+ image.crossOrigin = "anonymous";
212
+ image.onload = () => {
213
+ watermarkImage = image;
214
+ watermarkImageReady = true;
215
+ draw();
216
+ };
217
+ image.onerror = () => {
218
+ watermarkImage = null;
219
+ watermarkImageReady = false;
220
+ };
221
+ image.src = src;
222
+ };
186
223
  const crisp = (value) => Math.round(value) + 0.5;
187
224
  const clamp = (value, min, max) => {
188
225
  return Math.min(max, Math.max(min, value));
@@ -731,6 +768,31 @@ function createChart(element, options = {}) {
731
768
  const yFromPrice = (price) => {
732
769
  return chartBottom - (price - yMin) / yRange * chartHeight;
733
770
  };
771
+ if (watermark.visible && watermark.imageSrc.trim().length > 0) {
772
+ ensureWatermarkImage(watermark.imageSrc.trim());
773
+ if (watermarkImageReady && watermarkImage) {
774
+ const maxW = chartWidth * clamp(watermark.imageMaxWidthRatio, 0.05, 1);
775
+ const maxH = chartHeight * clamp(watermark.imageMaxHeightRatio, 0.05, 1);
776
+ const naturalW = Math.max(1, watermarkImage.naturalWidth || watermarkImage.width);
777
+ const naturalH = Math.max(1, watermarkImage.naturalHeight || watermarkImage.height);
778
+ const containScale = Math.min(maxW / naturalW, maxH / naturalH);
779
+ const scale = Math.max(0.05, containScale * Math.max(0.05, watermark.imageScale));
780
+ const drawW = naturalW * scale;
781
+ const drawH = naturalH * scale;
782
+ const drawX = chartLeft + (chartWidth - drawW) / 2;
783
+ const drawY = chartTop + (chartHeight - drawH) / 2;
784
+ ctx.save();
785
+ ctx.globalAlpha = clamp(watermark.opacity, 0, 1);
786
+ ctx.drawImage(watermarkImage, drawX, drawY, drawW, drawH);
787
+ if (watermark.imageTintColor.trim().length > 0) {
788
+ ctx.globalCompositeOperation = "source-atop";
789
+ ctx.globalAlpha = clamp(watermark.opacity, 0, 1) * clamp(watermark.imageTintOpacity, 0, 1);
790
+ ctx.fillStyle = watermark.imageTintColor;
791
+ ctx.fillRect(drawX, drawY, drawW, drawH);
792
+ }
793
+ ctx.restore();
794
+ }
795
+ }
734
796
  if (watermark.visible && watermark.text.trim().length > 0) {
735
797
  ctx.save();
736
798
  ctx.globalAlpha = clamp(watermark.opacity, 0, 1);
@@ -928,6 +990,42 @@ function createChart(element, options = {}) {
928
990
  });
929
991
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
930
992
  }
993
+ if (crosshair.visible && crosshairPoint) {
994
+ const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
995
+ const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
996
+ const labelPaddingX = 8;
997
+ const labelHeight = 20;
998
+ const labelRadius = Math.max(0, crosshair.labelBorderRadius);
999
+ const labelBackground = crosshair.labelBackgroundColor;
1000
+ const labelTextColor = crosshair.labelTextColor;
1001
+ if (crosshair.showPriceLabel) {
1002
+ const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1003
+ const priceText = formatPrice(hoverPrice);
1004
+ const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1005
+ const priceX = chartRight + 4;
1006
+ const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1007
+ ctx.fillStyle = labelBackground;
1008
+ fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1009
+ drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1010
+ }
1011
+ if (crosshair.showTimeLabel) {
1012
+ const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
1013
+ const hoverIndex = Math.round(xStart + ratio * xSpan - 0.5);
1014
+ const hoverTime = getTimeForIndex(hoverIndex);
1015
+ if (hoverTime) {
1016
+ const timeText = hoverTime.toLocaleDateString(void 0, {
1017
+ month: "short",
1018
+ day: "numeric"
1019
+ });
1020
+ const timeWidth = Math.ceil(ctx.measureText(timeText).width) + labelPaddingX * 2;
1021
+ const timeX = clamp(cx - timeWidth / 2, chartLeft, chartRight - timeWidth);
1022
+ const timeY = chartBottom + 8;
1023
+ ctx.fillStyle = labelBackground;
1024
+ fillRoundedRect(Math.round(timeX), Math.round(timeY), timeWidth, labelHeight, labelRadius);
1025
+ drawText(timeText, timeX + labelPaddingX, timeY + labelHeight / 2, "left", "middle", labelTextColor);
1026
+ }
1027
+ }
1028
+ }
931
1029
  };
932
1030
  const zoomX = (factor, anchorX) => {
933
1031
  if (!drawState || data.length === 0) {
@@ -1173,7 +1271,7 @@ function createChart(element, options = {}) {
1173
1271
  }
1174
1272
  const hoverRegion = getHitRegion(point.x, point.y);
1175
1273
  if (hoverRegion === "plot") {
1176
- canvas.style.cursor = "grab";
1274
+ canvas.style.cursor = "default";
1177
1275
  setCrosshairPoint(point);
1178
1276
  } else if (hoverRegion === "x-axis") {
1179
1277
  canvas.style.cursor = "ew-resize";
package/docs/API.md CHANGED
@@ -78,26 +78,37 @@ 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
+ - `labelBackgroundColor` (default `#0b1220`)
84
+ - `labelTextColor` (default `#cbd5e1`)
85
+ - `labelBorderRadius` (default `3`)
81
86
 
82
87
  ### `WatermarkOptions`
83
88
 
84
89
  - `visible` (default `false`)
85
90
  - `text` (default `""`)
86
- - `color` (default `#94a3b8`)
87
- - `opacity` (default `0.2`)
91
+ - `color` (default `#81858d`)
92
+ - `opacity` (default `0.14`)
88
93
  - `fontSize` (default `92`)
89
94
  - `fontWeight` (default `700`)
90
95
  - `thickness` (stroke width, default `0`)
96
+ - `imageSrc` (default `""`, URL/path to watermark logo)
97
+ - `imageScale` (default `1`)
98
+ - `imageMaxWidthRatio` (default `0.42`)
99
+ - `imageMaxHeightRatio` (default `0.3`)
100
+ - `imageTintColor` (default `""`, set `"#ffffff"` for white logo tint)
101
+ - `imageTintOpacity` (default `1`)
91
102
 
92
103
  ### `TickerLineOptions`
93
104
 
94
105
  - `visible` (default `true`)
95
- - `style` (`"solid" | "dotted" | "dashed"`, default `"dashed"`)
106
+ - `style` (`"solid" | "dotted" | "dashed"`, default `"dotted"`)
96
107
  - `thickness` (default `1`)
97
- - `color` (default `#22c55e`)
98
- - `labelBackgroundColor` (default `#22c55e`)
108
+ - `color` (default `#38bdf8`)
109
+ - `labelBackgroundColor` (default `#38bdf8`)
99
110
  - `labelTextColor` (default `#0b1220`)
100
- - `labelBorderRadius` (default `6`)
111
+ - `labelBorderRadius` (default `3`)
101
112
 
102
113
  ### `PriceLineOptions`
103
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",