Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: draw semi-transparent rectangles for tooltip #601

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/guide/styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion src/common/Styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface PolygonStyle {

export interface RectStyle extends PolygonStyle {
borderRadius: number
globalAlpha?: number
}

export interface TextStyle extends Padding {
Expand Down Expand Up @@ -488,7 +489,8 @@ function getDefaultCandleStyle (): CandleStyle {
borderRadius: 4,
borderSize: 1,
borderColor: '#F2F3F5',
color: '#FEFEFE'
color: '#FEFEFE',
globalAlpha: 0.5,
},
text: {
size: 12,
Expand Down
11 changes: 10 additions & 1 deletion src/extension/figure/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 10 additions & 6 deletions src/view/CandleTooltipView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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 &&
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
123 changes: 119 additions & 4 deletions src/view/IndicatorTooltipView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -57,13 +57,14 @@ export default class IndicatorTooltipView extends View<YAxis> {
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
)
}
}
Expand All @@ -81,7 +82,8 @@ export default class IndicatorTooltipView extends View<YAxis> {
left: number,
top: number,
maxWidth: number,
styles: IndicatorStyle
styles: IndicatorStyle,
defaultTooltipRectStyles: CandleTooltipRectStyle
): number {
const tooltipStyles = styles.tooltip
if (this.isDrawTooltip(crosshair, tooltipStyles)) {
Expand All @@ -94,6 +96,9 @@ export default class IndicatorTooltipView extends View<YAxis> {
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,
Expand Down Expand Up @@ -126,7 +131,7 @@ export default class IndicatorTooltipView extends View<YAxis> {
if (legendValid) {
prevRowHeight = this.drawStandardTooltipLegends(
ctx, legends, coordinate,
left, prevRowHeight, maxWidth, tooltipStyles.text
left, prevRowHeight, maxWidth, tooltipTextStyles
)
}

Expand Down Expand Up @@ -249,6 +254,116 @@ export default class IndicatorTooltipView extends View<YAxis> {
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 ||
Expand Down