hyperprop-charting-library 0.1.17 → 0.1.19

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.
@@ -53,15 +53,12 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
53
53
  labelBorderWidth: 1,
54
54
  labelBorderStyle: "solid",
55
55
  showPriceActionButton: false,
56
+ priceActionButtonIcon: "plusThin",
56
57
  priceActionButtonText: "+",
57
- priceActionButtonSize: 20,
58
- priceActionButtonGap: 6,
59
- priceActionButtonBackgroundColor: "#1f2937",
60
- priceActionButtonTextColor: "#e2e8f0",
61
- priceActionButtonBorderColor: "#475569",
62
- priceActionButtonBorderWidth: 1,
58
+ priceActionButtonSize: 16,
59
+ priceActionButtonGap: 4,
63
60
  priceActionButtonRounded: true,
64
- priceActionButtonBorderRadius: 3
61
+ priceActionButtonBorderRadius: 8
65
62
  };
66
63
  var DEFAULT_WATERMARK_OPTIONS = {
67
64
  visible: false,
@@ -107,6 +104,7 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
107
104
  labelBorderRadius: 3,
108
105
  showCloseButton: true,
109
106
  widgetPosition: "left",
107
+ widgetPaddingRight: 10,
110
108
  draggable: false,
111
109
  actionButtonAction: "execute",
112
110
  actionButtonTextColor: "#dbeafe",
@@ -633,7 +631,16 @@ function createChart(element, options = {}) {
633
631
  const closeWidth = showCloseButton ? 24 : 0;
634
632
  const mainWidgetWidth = qtyWidth + centerWidth + closeWidth;
635
633
  const totalWidth = mainWidgetWidth + actionButtonsGap + actionButtonsTotalWidth;
636
- const rightMarginInsideChart = 10;
634
+ const crosshair = { ...DEFAULT_CROSSHAIR_OPTIONS, ...mergedOptions.crosshair ?? {} };
635
+ const crosshairActionInset = crosshair.visible && crosshair.showPriceLabel && crosshair.showPriceActionButton ? Math.max(
636
+ 0,
637
+ Math.round(crosshair.priceActionButtonGap) + (clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4)) + 10) - 4
638
+ ) + 6 : 0;
639
+ const rightMarginInsideChart = Math.max(
640
+ 0,
641
+ mergedLine.widgetPaddingRight,
642
+ crosshairActionInset
643
+ );
637
644
  const maxWidgetX = chartRight - totalWidth - rightMarginInsideChart;
638
645
  const leftWidgetXBase = chartLeft + 8;
639
646
  let leftWidgetX = leftWidgetXBase;
@@ -1151,35 +1158,79 @@ function createChart(element, options = {}) {
1151
1158
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1152
1159
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1153
1160
  if (crosshair.showPriceActionButton) {
1154
- const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1161
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4));
1155
1162
  const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1156
- const buttonX = priceX - buttonGap - buttonSize;
1163
+ const containerPaddingX = 5;
1164
+ const containerWidth = buttonSize + containerPaddingX * 2;
1165
+ const containerX = priceX - buttonGap - containerWidth;
1166
+ const containerY = priceY;
1167
+ const buttonX = containerX + containerPaddingX;
1157
1168
  const buttonY = priceY + (labelHeight - buttonSize) / 2;
1158
1169
  const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1159
- const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1160
- ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1161
- fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1170
+ const buttonBorderWidth = Math.max(1, Math.round(labelBorderWidth || 1));
1171
+ const buttonCenterX = buttonX + buttonSize / 2;
1172
+ const buttonCenterY = buttonY + buttonSize / 2;
1173
+ const containerRadius = labelRadius;
1174
+ ctx.fillStyle = labelBackground;
1175
+ fillRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1176
+ if (labelBorderWidth > 0) {
1177
+ ctx.save();
1178
+ ctx.strokeStyle = labelBorderColor;
1179
+ ctx.lineWidth = labelBorderWidth;
1180
+ ctx.setLineDash([]);
1181
+ strokeRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1182
+ ctx.restore();
1183
+ }
1162
1184
  if (buttonBorderWidth > 0) {
1163
1185
  ctx.save();
1164
- ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1186
+ ctx.strokeStyle = labelBorderColor;
1165
1187
  ctx.lineWidth = buttonBorderWidth;
1166
1188
  ctx.setLineDash([]);
1167
- strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1189
+ if (crosshair.priceActionButtonRounded) {
1190
+ ctx.beginPath();
1191
+ ctx.arc(
1192
+ buttonCenterX,
1193
+ buttonCenterY,
1194
+ Math.max(1, buttonSize / 2 - buttonBorderWidth / 2),
1195
+ 0,
1196
+ Math.PI * 2
1197
+ );
1198
+ ctx.stroke();
1199
+ } else {
1200
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1201
+ }
1202
+ ctx.restore();
1203
+ }
1204
+ if (crosshair.priceActionButtonIcon !== "text" && crosshair.priceActionButtonText === "+") {
1205
+ const plusHalf = Math.max(3, Math.round(buttonSize * 0.22));
1206
+ const plusThickness = crosshair.priceActionButtonIcon === "plusThin" ? Math.max(1, Math.round(buttonSize * 0.07)) : Math.max(1, Math.round(buttonSize * 0.1));
1207
+ ctx.save();
1208
+ ctx.strokeStyle = labelTextColor;
1209
+ ctx.lineWidth = plusThickness;
1210
+ ctx.lineCap = "round";
1211
+ ctx.setLineDash([]);
1212
+ ctx.beginPath();
1213
+ ctx.moveTo(buttonCenterX - plusHalf, buttonCenterY);
1214
+ ctx.lineTo(buttonCenterX + plusHalf, buttonCenterY);
1215
+ ctx.moveTo(buttonCenterX, buttonCenterY - plusHalf);
1216
+ ctx.lineTo(buttonCenterX, buttonCenterY + plusHalf);
1217
+ ctx.stroke();
1168
1218
  ctx.restore();
1219
+ } else {
1220
+ drawText(
1221
+ crosshair.priceActionButtonText,
1222
+ buttonCenterX,
1223
+ buttonCenterY,
1224
+ "center",
1225
+ "middle",
1226
+ labelTextColor
1227
+ );
1169
1228
  }
1170
- drawText(
1171
- crosshair.priceActionButtonText,
1172
- buttonX + buttonSize / 2,
1173
- buttonY + buttonSize / 2,
1174
- "center",
1175
- "middle",
1176
- crosshair.priceActionButtonTextColor
1177
- );
1178
1229
  crosshairPriceActionRegion = {
1179
- x: buttonX,
1180
- y: buttonY,
1181
- width: buttonSize,
1182
- height: buttonSize,
1230
+ x: containerX,
1231
+ y: containerY,
1232
+ width: containerWidth,
1233
+ height: labelHeight,
1183
1234
  price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1184
1235
  };
1185
1236
  }
@@ -72,13 +72,10 @@ interface CrosshairOptions {
72
72
  labelBorderWidth?: number;
73
73
  labelBorderStyle?: "solid" | "dotted" | "dashed";
74
74
  showPriceActionButton?: boolean;
75
+ priceActionButtonIcon?: "plus" | "plusThin" | "text";
75
76
  priceActionButtonText?: string;
76
77
  priceActionButtonSize?: number;
77
78
  priceActionButtonGap?: number;
78
- priceActionButtonBackgroundColor?: string;
79
- priceActionButtonTextColor?: string;
80
- priceActionButtonBorderColor?: string;
81
- priceActionButtonBorderWidth?: number;
82
79
  priceActionButtonRounded?: boolean;
83
80
  priceActionButtonBorderRadius?: number;
84
81
  }
@@ -129,6 +126,7 @@ interface OrderLineOptions {
129
126
  labelBorderRadius?: number;
130
127
  showCloseButton?: boolean;
131
128
  widgetPosition?: "left" | "center" | "right";
129
+ widgetPaddingRight?: number;
132
130
  draggable?: boolean;
133
131
  actionButtonText?: string;
134
132
  actionButtonAction?: "execute" | "cancel" | "close" | string;
@@ -29,15 +29,12 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
29
29
  labelBorderWidth: 1,
30
30
  labelBorderStyle: "solid",
31
31
  showPriceActionButton: false,
32
+ priceActionButtonIcon: "plusThin",
32
33
  priceActionButtonText: "+",
33
- priceActionButtonSize: 20,
34
- priceActionButtonGap: 6,
35
- priceActionButtonBackgroundColor: "#1f2937",
36
- priceActionButtonTextColor: "#e2e8f0",
37
- priceActionButtonBorderColor: "#475569",
38
- priceActionButtonBorderWidth: 1,
34
+ priceActionButtonSize: 16,
35
+ priceActionButtonGap: 4,
39
36
  priceActionButtonRounded: true,
40
- priceActionButtonBorderRadius: 3
37
+ priceActionButtonBorderRadius: 8
41
38
  };
42
39
  var DEFAULT_WATERMARK_OPTIONS = {
43
40
  visible: false,
@@ -83,6 +80,7 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
83
80
  labelBorderRadius: 3,
84
81
  showCloseButton: true,
85
82
  widgetPosition: "left",
83
+ widgetPaddingRight: 10,
86
84
  draggable: false,
87
85
  actionButtonAction: "execute",
88
86
  actionButtonTextColor: "#dbeafe",
@@ -609,7 +607,16 @@ function createChart(element, options = {}) {
609
607
  const closeWidth = showCloseButton ? 24 : 0;
610
608
  const mainWidgetWidth = qtyWidth + centerWidth + closeWidth;
611
609
  const totalWidth = mainWidgetWidth + actionButtonsGap + actionButtonsTotalWidth;
612
- const rightMarginInsideChart = 10;
610
+ const crosshair = { ...DEFAULT_CROSSHAIR_OPTIONS, ...mergedOptions.crosshair ?? {} };
611
+ const crosshairActionInset = crosshair.visible && crosshair.showPriceLabel && crosshair.showPriceActionButton ? Math.max(
612
+ 0,
613
+ Math.round(crosshair.priceActionButtonGap) + (clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4)) + 10) - 4
614
+ ) + 6 : 0;
615
+ const rightMarginInsideChart = Math.max(
616
+ 0,
617
+ mergedLine.widgetPaddingRight,
618
+ crosshairActionInset
619
+ );
613
620
  const maxWidgetX = chartRight - totalWidth - rightMarginInsideChart;
614
621
  const leftWidgetXBase = chartLeft + 8;
615
622
  let leftWidgetX = leftWidgetXBase;
@@ -1127,35 +1134,79 @@ function createChart(element, options = {}) {
1127
1134
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1128
1135
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1129
1136
  if (crosshair.showPriceActionButton) {
1130
- const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1137
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4));
1131
1138
  const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1132
- const buttonX = priceX - buttonGap - buttonSize;
1139
+ const containerPaddingX = 5;
1140
+ const containerWidth = buttonSize + containerPaddingX * 2;
1141
+ const containerX = priceX - buttonGap - containerWidth;
1142
+ const containerY = priceY;
1143
+ const buttonX = containerX + containerPaddingX;
1133
1144
  const buttonY = priceY + (labelHeight - buttonSize) / 2;
1134
1145
  const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1135
- const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1136
- ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1137
- fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1146
+ const buttonBorderWidth = Math.max(1, Math.round(labelBorderWidth || 1));
1147
+ const buttonCenterX = buttonX + buttonSize / 2;
1148
+ const buttonCenterY = buttonY + buttonSize / 2;
1149
+ const containerRadius = labelRadius;
1150
+ ctx.fillStyle = labelBackground;
1151
+ fillRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1152
+ if (labelBorderWidth > 0) {
1153
+ ctx.save();
1154
+ ctx.strokeStyle = labelBorderColor;
1155
+ ctx.lineWidth = labelBorderWidth;
1156
+ ctx.setLineDash([]);
1157
+ strokeRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1158
+ ctx.restore();
1159
+ }
1138
1160
  if (buttonBorderWidth > 0) {
1139
1161
  ctx.save();
1140
- ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1162
+ ctx.strokeStyle = labelBorderColor;
1141
1163
  ctx.lineWidth = buttonBorderWidth;
1142
1164
  ctx.setLineDash([]);
1143
- strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1165
+ if (crosshair.priceActionButtonRounded) {
1166
+ ctx.beginPath();
1167
+ ctx.arc(
1168
+ buttonCenterX,
1169
+ buttonCenterY,
1170
+ Math.max(1, buttonSize / 2 - buttonBorderWidth / 2),
1171
+ 0,
1172
+ Math.PI * 2
1173
+ );
1174
+ ctx.stroke();
1175
+ } else {
1176
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1177
+ }
1178
+ ctx.restore();
1179
+ }
1180
+ if (crosshair.priceActionButtonIcon !== "text" && crosshair.priceActionButtonText === "+") {
1181
+ const plusHalf = Math.max(3, Math.round(buttonSize * 0.22));
1182
+ const plusThickness = crosshair.priceActionButtonIcon === "plusThin" ? Math.max(1, Math.round(buttonSize * 0.07)) : Math.max(1, Math.round(buttonSize * 0.1));
1183
+ ctx.save();
1184
+ ctx.strokeStyle = labelTextColor;
1185
+ ctx.lineWidth = plusThickness;
1186
+ ctx.lineCap = "round";
1187
+ ctx.setLineDash([]);
1188
+ ctx.beginPath();
1189
+ ctx.moveTo(buttonCenterX - plusHalf, buttonCenterY);
1190
+ ctx.lineTo(buttonCenterX + plusHalf, buttonCenterY);
1191
+ ctx.moveTo(buttonCenterX, buttonCenterY - plusHalf);
1192
+ ctx.lineTo(buttonCenterX, buttonCenterY + plusHalf);
1193
+ ctx.stroke();
1144
1194
  ctx.restore();
1195
+ } else {
1196
+ drawText(
1197
+ crosshair.priceActionButtonText,
1198
+ buttonCenterX,
1199
+ buttonCenterY,
1200
+ "center",
1201
+ "middle",
1202
+ labelTextColor
1203
+ );
1145
1204
  }
1146
- drawText(
1147
- crosshair.priceActionButtonText,
1148
- buttonX + buttonSize / 2,
1149
- buttonY + buttonSize / 2,
1150
- "center",
1151
- "middle",
1152
- crosshair.priceActionButtonTextColor
1153
- );
1154
1205
  crosshairPriceActionRegion = {
1155
- x: buttonX,
1156
- y: buttonY,
1157
- width: buttonSize,
1158
- height: buttonSize,
1206
+ x: containerX,
1207
+ y: containerY,
1208
+ width: containerWidth,
1209
+ height: labelHeight,
1159
1210
  price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1160
1211
  };
1161
1212
  }
package/dist/index.cjs CHANGED
@@ -53,15 +53,12 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
53
53
  labelBorderWidth: 1,
54
54
  labelBorderStyle: "solid",
55
55
  showPriceActionButton: false,
56
+ priceActionButtonIcon: "plusThin",
56
57
  priceActionButtonText: "+",
57
- priceActionButtonSize: 20,
58
- priceActionButtonGap: 6,
59
- priceActionButtonBackgroundColor: "#1f2937",
60
- priceActionButtonTextColor: "#e2e8f0",
61
- priceActionButtonBorderColor: "#475569",
62
- priceActionButtonBorderWidth: 1,
58
+ priceActionButtonSize: 16,
59
+ priceActionButtonGap: 4,
63
60
  priceActionButtonRounded: true,
64
- priceActionButtonBorderRadius: 3
61
+ priceActionButtonBorderRadius: 8
65
62
  };
66
63
  var DEFAULT_WATERMARK_OPTIONS = {
67
64
  visible: false,
@@ -107,6 +104,7 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
107
104
  labelBorderRadius: 3,
108
105
  showCloseButton: true,
109
106
  widgetPosition: "left",
107
+ widgetPaddingRight: 10,
110
108
  draggable: false,
111
109
  actionButtonAction: "execute",
112
110
  actionButtonTextColor: "#dbeafe",
@@ -633,7 +631,16 @@ function createChart(element, options = {}) {
633
631
  const closeWidth = showCloseButton ? 24 : 0;
634
632
  const mainWidgetWidth = qtyWidth + centerWidth + closeWidth;
635
633
  const totalWidth = mainWidgetWidth + actionButtonsGap + actionButtonsTotalWidth;
636
- const rightMarginInsideChart = 10;
634
+ const crosshair = { ...DEFAULT_CROSSHAIR_OPTIONS, ...mergedOptions.crosshair ?? {} };
635
+ const crosshairActionInset = crosshair.visible && crosshair.showPriceLabel && crosshair.showPriceActionButton ? Math.max(
636
+ 0,
637
+ Math.round(crosshair.priceActionButtonGap) + (clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4)) + 10) - 4
638
+ ) + 6 : 0;
639
+ const rightMarginInsideChart = Math.max(
640
+ 0,
641
+ mergedLine.widgetPaddingRight,
642
+ crosshairActionInset
643
+ );
637
644
  const maxWidgetX = chartRight - totalWidth - rightMarginInsideChart;
638
645
  const leftWidgetXBase = chartLeft + 8;
639
646
  let leftWidgetX = leftWidgetXBase;
@@ -1151,35 +1158,79 @@ function createChart(element, options = {}) {
1151
1158
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1152
1159
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1153
1160
  if (crosshair.showPriceActionButton) {
1154
- const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1161
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4));
1155
1162
  const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1156
- const buttonX = priceX - buttonGap - buttonSize;
1163
+ const containerPaddingX = 5;
1164
+ const containerWidth = buttonSize + containerPaddingX * 2;
1165
+ const containerX = priceX - buttonGap - containerWidth;
1166
+ const containerY = priceY;
1167
+ const buttonX = containerX + containerPaddingX;
1157
1168
  const buttonY = priceY + (labelHeight - buttonSize) / 2;
1158
1169
  const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1159
- const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1160
- ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1161
- fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1170
+ const buttonBorderWidth = Math.max(1, Math.round(labelBorderWidth || 1));
1171
+ const buttonCenterX = buttonX + buttonSize / 2;
1172
+ const buttonCenterY = buttonY + buttonSize / 2;
1173
+ const containerRadius = labelRadius;
1174
+ ctx.fillStyle = labelBackground;
1175
+ fillRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1176
+ if (labelBorderWidth > 0) {
1177
+ ctx.save();
1178
+ ctx.strokeStyle = labelBorderColor;
1179
+ ctx.lineWidth = labelBorderWidth;
1180
+ ctx.setLineDash([]);
1181
+ strokeRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1182
+ ctx.restore();
1183
+ }
1162
1184
  if (buttonBorderWidth > 0) {
1163
1185
  ctx.save();
1164
- ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1186
+ ctx.strokeStyle = labelBorderColor;
1165
1187
  ctx.lineWidth = buttonBorderWidth;
1166
1188
  ctx.setLineDash([]);
1167
- strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1189
+ if (crosshair.priceActionButtonRounded) {
1190
+ ctx.beginPath();
1191
+ ctx.arc(
1192
+ buttonCenterX,
1193
+ buttonCenterY,
1194
+ Math.max(1, buttonSize / 2 - buttonBorderWidth / 2),
1195
+ 0,
1196
+ Math.PI * 2
1197
+ );
1198
+ ctx.stroke();
1199
+ } else {
1200
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1201
+ }
1202
+ ctx.restore();
1203
+ }
1204
+ if (crosshair.priceActionButtonIcon !== "text" && crosshair.priceActionButtonText === "+") {
1205
+ const plusHalf = Math.max(3, Math.round(buttonSize * 0.22));
1206
+ const plusThickness = crosshair.priceActionButtonIcon === "plusThin" ? Math.max(1, Math.round(buttonSize * 0.07)) : Math.max(1, Math.round(buttonSize * 0.1));
1207
+ ctx.save();
1208
+ ctx.strokeStyle = labelTextColor;
1209
+ ctx.lineWidth = plusThickness;
1210
+ ctx.lineCap = "round";
1211
+ ctx.setLineDash([]);
1212
+ ctx.beginPath();
1213
+ ctx.moveTo(buttonCenterX - plusHalf, buttonCenterY);
1214
+ ctx.lineTo(buttonCenterX + plusHalf, buttonCenterY);
1215
+ ctx.moveTo(buttonCenterX, buttonCenterY - plusHalf);
1216
+ ctx.lineTo(buttonCenterX, buttonCenterY + plusHalf);
1217
+ ctx.stroke();
1168
1218
  ctx.restore();
1219
+ } else {
1220
+ drawText(
1221
+ crosshair.priceActionButtonText,
1222
+ buttonCenterX,
1223
+ buttonCenterY,
1224
+ "center",
1225
+ "middle",
1226
+ labelTextColor
1227
+ );
1169
1228
  }
1170
- drawText(
1171
- crosshair.priceActionButtonText,
1172
- buttonX + buttonSize / 2,
1173
- buttonY + buttonSize / 2,
1174
- "center",
1175
- "middle",
1176
- crosshair.priceActionButtonTextColor
1177
- );
1178
1229
  crosshairPriceActionRegion = {
1179
- x: buttonX,
1180
- y: buttonY,
1181
- width: buttonSize,
1182
- height: buttonSize,
1230
+ x: containerX,
1231
+ y: containerY,
1232
+ width: containerWidth,
1233
+ height: labelHeight,
1183
1234
  price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1184
1235
  };
1185
1236
  }
package/dist/index.d.cts CHANGED
@@ -72,13 +72,10 @@ interface CrosshairOptions {
72
72
  labelBorderWidth?: number;
73
73
  labelBorderStyle?: "solid" | "dotted" | "dashed";
74
74
  showPriceActionButton?: boolean;
75
+ priceActionButtonIcon?: "plus" | "plusThin" | "text";
75
76
  priceActionButtonText?: string;
76
77
  priceActionButtonSize?: number;
77
78
  priceActionButtonGap?: number;
78
- priceActionButtonBackgroundColor?: string;
79
- priceActionButtonTextColor?: string;
80
- priceActionButtonBorderColor?: string;
81
- priceActionButtonBorderWidth?: number;
82
79
  priceActionButtonRounded?: boolean;
83
80
  priceActionButtonBorderRadius?: number;
84
81
  }
@@ -129,6 +126,7 @@ interface OrderLineOptions {
129
126
  labelBorderRadius?: number;
130
127
  showCloseButton?: boolean;
131
128
  widgetPosition?: "left" | "center" | "right";
129
+ widgetPaddingRight?: number;
132
130
  draggable?: boolean;
133
131
  actionButtonText?: string;
134
132
  actionButtonAction?: "execute" | "cancel" | "close" | string;
package/dist/index.d.ts CHANGED
@@ -72,13 +72,10 @@ interface CrosshairOptions {
72
72
  labelBorderWidth?: number;
73
73
  labelBorderStyle?: "solid" | "dotted" | "dashed";
74
74
  showPriceActionButton?: boolean;
75
+ priceActionButtonIcon?: "plus" | "plusThin" | "text";
75
76
  priceActionButtonText?: string;
76
77
  priceActionButtonSize?: number;
77
78
  priceActionButtonGap?: number;
78
- priceActionButtonBackgroundColor?: string;
79
- priceActionButtonTextColor?: string;
80
- priceActionButtonBorderColor?: string;
81
- priceActionButtonBorderWidth?: number;
82
79
  priceActionButtonRounded?: boolean;
83
80
  priceActionButtonBorderRadius?: number;
84
81
  }
@@ -129,6 +126,7 @@ interface OrderLineOptions {
129
126
  labelBorderRadius?: number;
130
127
  showCloseButton?: boolean;
131
128
  widgetPosition?: "left" | "center" | "right";
129
+ widgetPaddingRight?: number;
132
130
  draggable?: boolean;
133
131
  actionButtonText?: string;
134
132
  actionButtonAction?: "execute" | "cancel" | "close" | string;
package/dist/index.js CHANGED
@@ -29,15 +29,12 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
29
29
  labelBorderWidth: 1,
30
30
  labelBorderStyle: "solid",
31
31
  showPriceActionButton: false,
32
+ priceActionButtonIcon: "plusThin",
32
33
  priceActionButtonText: "+",
33
- priceActionButtonSize: 20,
34
- priceActionButtonGap: 6,
35
- priceActionButtonBackgroundColor: "#1f2937",
36
- priceActionButtonTextColor: "#e2e8f0",
37
- priceActionButtonBorderColor: "#475569",
38
- priceActionButtonBorderWidth: 1,
34
+ priceActionButtonSize: 16,
35
+ priceActionButtonGap: 4,
39
36
  priceActionButtonRounded: true,
40
- priceActionButtonBorderRadius: 3
37
+ priceActionButtonBorderRadius: 8
41
38
  };
42
39
  var DEFAULT_WATERMARK_OPTIONS = {
43
40
  visible: false,
@@ -83,6 +80,7 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
83
80
  labelBorderRadius: 3,
84
81
  showCloseButton: true,
85
82
  widgetPosition: "left",
83
+ widgetPaddingRight: 10,
86
84
  draggable: false,
87
85
  actionButtonAction: "execute",
88
86
  actionButtonTextColor: "#dbeafe",
@@ -609,7 +607,16 @@ function createChart(element, options = {}) {
609
607
  const closeWidth = showCloseButton ? 24 : 0;
610
608
  const mainWidgetWidth = qtyWidth + centerWidth + closeWidth;
611
609
  const totalWidth = mainWidgetWidth + actionButtonsGap + actionButtonsTotalWidth;
612
- const rightMarginInsideChart = 10;
610
+ const crosshair = { ...DEFAULT_CROSSHAIR_OPTIONS, ...mergedOptions.crosshair ?? {} };
611
+ const crosshairActionInset = crosshair.visible && crosshair.showPriceLabel && crosshair.showPriceActionButton ? Math.max(
612
+ 0,
613
+ Math.round(crosshair.priceActionButtonGap) + (clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4)) + 10) - 4
614
+ ) + 6 : 0;
615
+ const rightMarginInsideChart = Math.max(
616
+ 0,
617
+ mergedLine.widgetPaddingRight,
618
+ crosshairActionInset
619
+ );
613
620
  const maxWidgetX = chartRight - totalWidth - rightMarginInsideChart;
614
621
  const leftWidgetXBase = chartLeft + 8;
615
622
  let leftWidgetX = leftWidgetXBase;
@@ -1127,35 +1134,79 @@ function createChart(element, options = {}) {
1127
1134
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1128
1135
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1129
1136
  if (crosshair.showPriceActionButton) {
1130
- const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1137
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, Math.max(12, labelHeight - 4));
1131
1138
  const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1132
- const buttonX = priceX - buttonGap - buttonSize;
1139
+ const containerPaddingX = 5;
1140
+ const containerWidth = buttonSize + containerPaddingX * 2;
1141
+ const containerX = priceX - buttonGap - containerWidth;
1142
+ const containerY = priceY;
1143
+ const buttonX = containerX + containerPaddingX;
1133
1144
  const buttonY = priceY + (labelHeight - buttonSize) / 2;
1134
1145
  const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1135
- const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1136
- ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1137
- fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1146
+ const buttonBorderWidth = Math.max(1, Math.round(labelBorderWidth || 1));
1147
+ const buttonCenterX = buttonX + buttonSize / 2;
1148
+ const buttonCenterY = buttonY + buttonSize / 2;
1149
+ const containerRadius = labelRadius;
1150
+ ctx.fillStyle = labelBackground;
1151
+ fillRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1152
+ if (labelBorderWidth > 0) {
1153
+ ctx.save();
1154
+ ctx.strokeStyle = labelBorderColor;
1155
+ ctx.lineWidth = labelBorderWidth;
1156
+ ctx.setLineDash([]);
1157
+ strokeRoundedRect(Math.round(containerX), Math.round(containerY), containerWidth, labelHeight, containerRadius);
1158
+ ctx.restore();
1159
+ }
1138
1160
  if (buttonBorderWidth > 0) {
1139
1161
  ctx.save();
1140
- ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1162
+ ctx.strokeStyle = labelBorderColor;
1141
1163
  ctx.lineWidth = buttonBorderWidth;
1142
1164
  ctx.setLineDash([]);
1143
- strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1165
+ if (crosshair.priceActionButtonRounded) {
1166
+ ctx.beginPath();
1167
+ ctx.arc(
1168
+ buttonCenterX,
1169
+ buttonCenterY,
1170
+ Math.max(1, buttonSize / 2 - buttonBorderWidth / 2),
1171
+ 0,
1172
+ Math.PI * 2
1173
+ );
1174
+ ctx.stroke();
1175
+ } else {
1176
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1177
+ }
1178
+ ctx.restore();
1179
+ }
1180
+ if (crosshair.priceActionButtonIcon !== "text" && crosshair.priceActionButtonText === "+") {
1181
+ const plusHalf = Math.max(3, Math.round(buttonSize * 0.22));
1182
+ const plusThickness = crosshair.priceActionButtonIcon === "plusThin" ? Math.max(1, Math.round(buttonSize * 0.07)) : Math.max(1, Math.round(buttonSize * 0.1));
1183
+ ctx.save();
1184
+ ctx.strokeStyle = labelTextColor;
1185
+ ctx.lineWidth = plusThickness;
1186
+ ctx.lineCap = "round";
1187
+ ctx.setLineDash([]);
1188
+ ctx.beginPath();
1189
+ ctx.moveTo(buttonCenterX - plusHalf, buttonCenterY);
1190
+ ctx.lineTo(buttonCenterX + plusHalf, buttonCenterY);
1191
+ ctx.moveTo(buttonCenterX, buttonCenterY - plusHalf);
1192
+ ctx.lineTo(buttonCenterX, buttonCenterY + plusHalf);
1193
+ ctx.stroke();
1144
1194
  ctx.restore();
1195
+ } else {
1196
+ drawText(
1197
+ crosshair.priceActionButtonText,
1198
+ buttonCenterX,
1199
+ buttonCenterY,
1200
+ "center",
1201
+ "middle",
1202
+ labelTextColor
1203
+ );
1145
1204
  }
1146
- drawText(
1147
- crosshair.priceActionButtonText,
1148
- buttonX + buttonSize / 2,
1149
- buttonY + buttonSize / 2,
1150
- "center",
1151
- "middle",
1152
- crosshair.priceActionButtonTextColor
1153
- );
1154
1205
  crosshairPriceActionRegion = {
1155
- x: buttonX,
1156
- y: buttonY,
1157
- width: buttonSize,
1158
- height: buttonSize,
1206
+ x: containerX,
1207
+ y: containerY,
1208
+ width: containerWidth,
1209
+ height: labelHeight,
1159
1210
  price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1160
1211
  };
1161
1212
  }
package/docs/API.md CHANGED
@@ -95,15 +95,14 @@ Top-level options:
95
95
  - `labelBorderWidth` (default `1`)
96
96
  - `labelBorderStyle` (`"solid" | "dotted" | "dashed"`, default `"solid"`)
97
97
  - `showPriceActionButton` (default `false`)
98
+ - `priceActionButtonIcon` (`"plus" | "plusThin" | "text"`, default `"plusThin"`)
98
99
  - `priceActionButtonText` (default `"+"`)
99
- - `priceActionButtonSize` (default `20`)
100
- - `priceActionButtonGap` (default `6`)
101
- - `priceActionButtonBackgroundColor` (default `#1f2937`)
102
- - `priceActionButtonTextColor` (default `#e2e8f0`)
103
- - `priceActionButtonBorderColor` (default `#475569`)
104
- - `priceActionButtonBorderWidth` (default `1`)
100
+ - `priceActionButtonSize` (default `16`)
101
+ - `priceActionButtonGap` (default `4`)
105
102
  - `priceActionButtonRounded` (default `true`; set `false` for square corners)
106
- - `priceActionButtonBorderRadius` (default `3`)
103
+ - `priceActionButtonBorderRadius` (default `8`)
104
+
105
+ Note: the button and its container automatically inherit crosshair label colors/border for a consistent `[button-box][label]` look.
107
106
 
108
107
  ### `WatermarkOptions`
109
108
 
@@ -206,6 +205,7 @@ Common optional fields:
206
205
  - `labelBorderRadius?: number` (default `3`)
207
206
  - `showCloseButton?: boolean` (default `true`)
208
207
  - `widgetPosition?: "left" | "center" | "right"` (default `"left"`)
208
+ - `widgetPaddingRight?: number` (default `10`, extra right margin when `widgetPosition` is `"right"`)
209
209
  - `draggable?: boolean` (default `false`)
210
210
 
211
211
  Legacy single action button:
package/docs/RECIPES.md CHANGED
@@ -70,7 +70,7 @@ const chart = createChart(root, {
70
70
  crosshair: {
71
71
  showPriceActionButton: true,
72
72
  priceActionButtonText: "+",
73
- priceActionButtonGap: 6
73
+ priceActionButtonGap: 4
74
74
  }
75
75
  });
76
76
 
@@ -86,8 +86,7 @@ Square style example:
86
86
  const chart = createChart(root, {
87
87
  crosshair: {
88
88
  showPriceActionButton: true,
89
- priceActionButtonRounded: false,
90
- priceActionButtonBorderRadius: 0
89
+ priceActionButtonRounded: false
91
90
  }
92
91
  });
93
92
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",