diff --git a/docs/guide/styles.md b/docs/guide/styles.md index caec1905..cb6e2ddd 100644 --- a/docs/guide/styles.md +++ b/docs/guide/styles.md @@ -162,7 +162,9 @@ import StyleExplain from '../@views/styles/Explain.vue' borderRadius: 4, borderSize: 1, borderColor: '#f2f3f5', - color: '#FEFEFE' + color: '#FEFEFE', + // the alpha (transparency) value applied when draw semi-transparent rectangles for tooltip + globalAlpha: 0.5 }, text: { size: 12, diff --git a/src/common/Styles.ts b/src/common/Styles.ts index e4311854..5be821de 100644 --- a/src/common/Styles.ts +++ b/src/common/Styles.ts @@ -76,6 +76,7 @@ export interface PolygonStyle { export interface RectStyle extends PolygonStyle { borderRadius: number + globalAlpha?: number } export interface TextStyle extends Padding { @@ -488,7 +489,8 @@ function getDefaultCandleStyle (): CandleStyle { borderRadius: 4, borderSize: 1, borderColor: '#F2F3F5', - color: '#FEFEFE' + color: '#FEFEFE', + globalAlpha: 0.5, }, text: { size: 12, diff --git a/src/extension/figure/rect.ts b/src/extension/figure/rect.ts index cdcdefa7..1d24ff75 100644 --- a/src/extension/figure/rect.ts +++ b/src/extension/figure/rect.ts @@ -58,8 +58,13 @@ export function drawRect (ctx: CanvasRenderingContext2D, attrs: RectAttrs | Rect borderColor = 'transparent', borderStyle = LineType.Solid, borderRadius: r = 0, - borderDashedValue = [2, 2] + borderDashedValue = [2, 2], + globalAlpha = 1, } = styles + // modify the alpha value + if (globalAlpha !== 1) { + ctx.globalAlpha = globalAlpha + } // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unnecessary-condition const draw = ctx.roundRect ?? ctx.rect const solid = (style === PolygonType.Fill || styles.style === PolygonType.StrokeFill) && (!isString(color) || !isTransparent(color)) @@ -96,6 +101,10 @@ export function drawRect (ctx: CanvasRenderingContext2D, attrs: RectAttrs | Rect } }) } + // reset the alpha value + if (globalAlpha !== 1) { + ctx.globalAlpha = 1 + } } export interface RectAttrs { diff --git a/src/view/CandleTooltipView.ts b/src/view/CandleTooltipView.ts index bd8a9f0a..2b3cf262 100644 --- a/src/view/CandleTooltipView.ts +++ b/src/view/CandleTooltipView.ts @@ -19,7 +19,7 @@ import type Precision from '../common/Precision' import type Crosshair from '../common/Crosshair' import { type Styles, type CandleStyle, type TooltipLegend, type TooltipLegendChild, TooltipShowType, CandleTooltipRectPosition, - type CandleTooltipCustomCallbackData, PolygonType + type CandleTooltipCustomCallbackData, PolygonType, type CandleTooltipRectStyle } from '../common/Styles' import { formatPrecision, formatThousands, formatFoldDecimal } from '../common/utils/format' import { createFont } from '../common/utils/canvas' @@ -62,6 +62,7 @@ export default class CandleTooltipView extends IndicatorTooltipView { const dateTimeFormat = chartStore.getDateTimeFormat() const candleStyles = styles.candle const indicatorStyles = styles.indicator + const tooltipRectStyles = candleStyles.tooltip.rect if ( candleStyles.tooltip.showType === TooltipShowType.Rect && indicatorStyles.tooltip.showType === TooltipShowType.Rect @@ -85,13 +86,13 @@ export default class CandleTooltipView extends IndicatorTooltipView { const top = this._drawCandleStandardTooltip( ctx, dataList, paneId, crosshair, activeIcon, precision, dateTimeFormat, locale, customApi, thousandsSeparator, decimalFoldThreshold, - offsetLeft, offsetTop, maxWidth, candleStyles + offsetLeft, offsetTop, maxWidth, candleStyles, tooltipRectStyles ) this.drawIndicatorTooltip( ctx, paneId, dataList, crosshair, activeIcon, indicators, customApi, thousandsSeparator, decimalFoldThreshold, - offsetLeft, top, maxWidth, indicatorStyles + offsetLeft, top, maxWidth, indicatorStyles, tooltipRectStyles ) } else if ( candleStyles.tooltip.showType === TooltipShowType.Rect && @@ -103,7 +104,7 @@ export default class CandleTooltipView extends IndicatorTooltipView { ctx, paneId, dataList, crosshair, activeIcon, indicators, customApi, thousandsSeparator, decimalFoldThreshold, - offsetLeft, offsetTop, maxWidth, indicatorStyles + offsetLeft, offsetTop, maxWidth, indicatorStyles, tooltipRectStyles ) const isDrawCandleTooltip = this.isDrawTooltip(crosshair, candleStyles.tooltip) this._drawRectTooltip( @@ -119,7 +120,7 @@ export default class CandleTooltipView extends IndicatorTooltipView { const top = this._drawCandleStandardTooltip( ctx, dataList, paneId, crosshair, activeIcon, precision, dateTimeFormat, locale, customApi, thousandsSeparator, decimalFoldThreshold, - offsetLeft, offsetTop, maxWidth, candleStyles + offsetLeft, offsetTop, maxWidth, candleStyles, tooltipRectStyles ) const isDrawIndicatorTooltip = this.isDrawTooltip(crosshair, indicatorStyles.tooltip) this._drawRectTooltip( @@ -148,7 +149,8 @@ export default class CandleTooltipView extends IndicatorTooltipView { left: number, top: number, maxWidth: number, - styles: CandleStyle + styles: CandleStyle, + rectStyles: CandleTooltipRectStyle ): number { const tooltipStyles = styles.tooltip const tooltipTextStyles = tooltipStyles.text @@ -163,6 +165,8 @@ export default class CandleTooltipView extends IndicatorTooltipView { const [leftIcons, middleIcons, rightIcons] = this.classifyTooltipIcons(tooltipStyles.icons) + this.drawStandardTooltipRect(ctx, leftIcons, '', middleIcons, legends, rightIcons, coordinate, maxWidth, tooltipTextStyles, rectStyles) + prevRowHeight = this.drawStandardTooltipIcons( ctx, activeTooltipIcon, leftIcons, coordinate, paneId, '', left, prevRowHeight, maxWidth diff --git a/src/view/IndicatorTooltipView.ts b/src/view/IndicatorTooltipView.ts index a38b4188..1308a1bd 100644 --- a/src/view/IndicatorTooltipView.ts +++ b/src/view/IndicatorTooltipView.ts @@ -15,7 +15,7 @@ import type Nullable from '../common/Nullable' import type { KLineData } from '../common/Data' import type Crosshair from '../common/Crosshair' -import { type IndicatorStyle, type TooltipStyle, type TooltipIconStyle, type TooltipTextStyle, type TooltipLegend, TooltipShowRule, type TooltipLegendChild, TooltipIconPosition } from '../common/Styles' +import { type IndicatorStyle, type TooltipStyle, type TooltipIconStyle, type TooltipTextStyle, type TooltipLegend, TooltipShowRule, type TooltipLegendChild, TooltipIconPosition, PolygonType, type CandleTooltipRectStyle } from '../common/Styles' import { ActionType } from '../common/Action' import { formatPrecision, formatThousands, formatFoldDecimal } from '../common/utils/format' import { isValid, isObject, isString, isNumber, isFunction } from '../common/utils/typeChecks' @@ -57,13 +57,14 @@ export default class IndicatorTooltipView extends View { const indicators = chartStore.getIndicatorsByPaneId(pane.getId()) const activeIcon = chartStore.getActiveTooltipIcon() const defaultStyles = styles.indicator + const defaultTooltipRectStyles = styles.candle.tooltip.rect; const { offsetLeft, offsetTop, offsetRight } = defaultStyles.tooltip this.drawIndicatorTooltip( ctx, pane.getId(), chartStore.getDataList(), crosshair, activeIcon, indicators, customApi, thousandsSeparator, decimalFoldThreshold, offsetLeft, offsetTop, - bounding.width - offsetRight, defaultStyles + bounding.width - offsetRight, defaultStyles, defaultTooltipRectStyles ) } } @@ -81,7 +82,8 @@ export default class IndicatorTooltipView extends View { left: number, top: number, maxWidth: number, - styles: IndicatorStyle + styles: IndicatorStyle, + defaultTooltipRectStyles: CandleTooltipRectStyle ): number { const tooltipStyles = styles.tooltip if (this.isDrawTooltip(crosshair, tooltipStyles)) { @@ -94,6 +96,9 @@ export default class IndicatorTooltipView extends View { const legendValid = legends.length > 0 if (nameValid || legendValid) { const [leftIcons, middleIcons, rightIcons] = this.classifyTooltipIcons(icons) + + this.drawStandardTooltipRect(ctx, leftIcons, name, middleIcons, legends, rightIcons, coordinate, maxWidth, tooltipTextStyles, defaultTooltipRectStyles) + prevRowHeight = this.drawStandardTooltipIcons( ctx, activeTooltipIcon, leftIcons, coordinate, paneId, indicator.name, @@ -126,7 +131,7 @@ export default class IndicatorTooltipView extends View { if (legendValid) { prevRowHeight = this.drawStandardTooltipLegends( ctx, legends, coordinate, - left, prevRowHeight, maxWidth, tooltipStyles.text + left, prevRowHeight, maxWidth, tooltipTextStyles ) } @@ -249,6 +254,116 @@ export default class IndicatorTooltipView extends View { return prevRowHeight } + protected drawStandardTooltipRect ( + ctx: CanvasRenderingContext2D, + leftIcons: TooltipIconStyle[], + name: string, + middleIcons: TooltipIconStyle[], + legends: TooltipLegend[], + rightIcons: TooltipIconStyle[], + coordinate: Coordinate, maxWidth: number, tooltipTextStyles: TooltipTextStyle, rectStyles: CandleTooltipRectStyle): void { + let rectWidth = 0 + let rectHeight = 0 + let elmWidth = 0 + let elmHeight = 0 + let positionX = 0 + + function updateRectSize (width: number, height: number): void { + rectWidth = Math.max(rectWidth, positionX + width) + rectHeight = Math.max(rectHeight, height) + if (rectWidth > maxWidth) { + rectHeight += height + positionX = width + rectWidth -= width + } else { + positionX += width + } + } + + // measure box left icons + if (leftIcons.length > 0) { + leftIcons.forEach(icon => { + const { + marginLeft = 0, marginTop = 0, marginRight = 0, marginBottom = 0, + paddingLeft = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0, + size, fontFamily, icon: text + } = icon + ctx.font = createFont(size, 'normal', fontFamily) + elmWidth = marginLeft + paddingLeft + ctx.measureText(text).width + paddingRight + marginRight + elmHeight = marginTop + paddingTop + size + paddingBottom + marginBottom + updateRectSize(elmWidth, elmHeight) + }) + } + + // measure box name + if (name.length > 0) { + ctx.font = createFont(tooltipTextStyles.size, tooltipTextStyles.weight, tooltipTextStyles.family) + elmWidth = tooltipTextStyles.marginLeft + ctx.measureText(name).width + tooltipTextStyles.marginRight + elmHeight = tooltipTextStyles.marginTop + tooltipTextStyles.size + tooltipTextStyles.marginBottom + updateRectSize(elmWidth, elmHeight) + } + + // measure box middle icons + if (middleIcons.length > 0) { + middleIcons.forEach(icon => { + const { + marginLeft = 0, marginTop = 0, marginRight = 0, marginBottom = 0, + paddingLeft = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0, + size, fontFamily, icon: text + } = icon + ctx.font = createFont(size, 'normal', fontFamily) + elmWidth = (marginLeft + paddingLeft + ctx.measureText(text).width + paddingRight + marginRight) + elmHeight = marginTop + paddingTop + size + paddingBottom + marginBottom + updateRectSize(elmWidth, elmHeight) + }) + } + + // measure box legends + if (legends.length > 0) { + ctx.font = createFont(tooltipTextStyles.size, tooltipTextStyles.weight, tooltipTextStyles.family) + legends.forEach(legend => { + const title = legend.title as TooltipLegendChild + const value = legend.value as TooltipLegendChild + elmWidth = tooltipTextStyles.marginLeft + ctx.measureText(title.text).width + ctx.measureText(value.text).width + tooltipTextStyles.marginRight + elmHeight = tooltipTextStyles.marginTop + tooltipTextStyles.size + tooltipTextStyles.marginBottom + updateRectSize(elmWidth, elmHeight) + }) + } + + // measure box right icons + if (rightIcons.length > 0) { + rightIcons.forEach(icon => { + const { + marginLeft = 0, marginTop = 0, marginRight = 0, marginBottom = 0, + paddingLeft = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0, + size, fontFamily, icon: text + } = icon + ctx.font = createFont(size, 'normal', fontFamily) + elmWidth = (marginLeft + paddingLeft + ctx.measureText(text).width + paddingRight + marginRight) + elmHeight = marginTop + paddingTop + size + paddingBottom + marginBottom + updateRectSize(elmWidth, elmHeight) + }) + } + + this.createFigure({ + name: 'rect', + attrs: { + x: coordinate.x, + y: coordinate.y, + width: rectWidth, + height: rectHeight + }, + styles: { + style: PolygonType.Fill, + color: rectStyles.color, + borderColor: rectStyles.borderColor, + borderSize: rectStyles.borderSize, + borderRadius: rectStyles.borderRadius, + globalAlpha: rectStyles.globalAlpha + } + })?.draw(ctx) + } + protected isDrawTooltip (crosshair: Crosshair, styles: TooltipStyle): boolean { const showRule = styles.showRule return showRule === TooltipShowRule.Always ||