Skip to content

Commit 9aaaad5

Browse files
committedJan 12, 2022
feat(heatmap): add support for annotations for the canvas implementation
1 parent e3e8f00 commit 9aaaad5

File tree

7 files changed

+74
-18
lines changed

7 files changed

+74
-18
lines changed
 

‎packages/annotations/src/canvas.ts

+4
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,13 @@ export const renderAnnotationsToCanvas = <Datum>(
115115
})
116116
} else {
117117
ctx.font = `${theme.annotations.text.fontSize}px ${theme.annotations.text.fontFamily}`
118+
ctx.textAlign = 'left'
119+
ctx.textBaseline = 'alphabetic'
120+
118121
ctx.fillStyle = theme.annotations.text.fill
119122
ctx.strokeStyle = theme.annotations.text.outlineColor
120123
ctx.lineWidth = theme.annotations.text.outlineWidth * 2
124+
121125
if (theme.annotations.text.outlineWidth > 0) {
122126
ctx.lineJoin = 'round'
123127
ctx.strokeText(

‎packages/heatmap/src/HeatMapCanvas.tsx

+21-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { getRelativeCursor, isCursorInRect, useDimensions, useTheme, Container }
33
import { renderAxesToCanvas, renderGridLinesToCanvas } from '@nivo/axes'
44
import { useTooltip } from '@nivo/tooltip'
55
import { renderContinuousColorLegendToCanvas } from '@nivo/legends'
6-
import { useHeatMap } from './hooks'
6+
import { renderAnnotationsToCanvas, useComputedAnnotations } from '@nivo/annotations'
7+
import { useHeatMap, useCellAnnotations } from './hooks'
78
import { renderRect, renderCircle } from './canvas'
89
import { canvasDefaultProps } from './defaults'
910
import {
@@ -39,7 +40,7 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
3940
opacity = canvasDefaultProps.opacity,
4041
activeOpacity = canvasDefaultProps.activeOpacity,
4142
inactiveOpacity = canvasDefaultProps.inactiveOpacity,
42-
// borderWidth = canvasDefaultProps.borderWidth,
43+
borderWidth = canvasDefaultProps.borderWidth,
4344
borderColor = canvasDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],
4445
enableGridX = canvasDefaultProps.enableGridX,
4546
enableGridY = canvasDefaultProps.enableGridY,
@@ -53,7 +54,7 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
5354
colors = canvasDefaultProps.colors as HeatMapCommonProps<Datum>['colors'],
5455
emptyColor = canvasDefaultProps.emptyColor,
5556
legends = canvasDefaultProps.legends,
56-
// annotations = canvasDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],
57+
annotations = canvasDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],
5758
isInteractive = canvasDefaultProps.isInteractive,
5859
// onMouseEnter,
5960
// onMouseMove,
@@ -98,7 +99,10 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
9899
hoverTarget,
99100
})
100101

101-
const theme = useTheme()
102+
const boundAnnotations = useCellAnnotations(cells, annotations)
103+
const computedAnnotations = useComputedAnnotations({
104+
annotations: boundAnnotations,
105+
})
102106

103107
let renderCell: CellCanvasRenderer<Datum>
104108
if (typeof _renderCell === 'function') {
@@ -109,6 +113,8 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
109113
renderCell = renderRect
110114
}
111115

116+
const theme = useTheme()
117+
112118
useEffect(() => {
113119
if (canvasEl.current === null) return
114120

@@ -162,7 +168,7 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
162168
ctx.textBaseline = 'middle'
163169

164170
cells.forEach(cell => {
165-
renderCell(ctx, { cell, enableLabels, theme })
171+
renderCell(ctx, { cell, borderWidth, enableLabels, theme })
166172
})
167173
} else if (layer === 'legends' && colorScale !== null) {
168174
legends.forEach(legend => {
@@ -174,17 +180,23 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
174180
theme,
175181
})
176182
})
183+
} else if (layer === 'annotations') {
184+
renderAnnotationsToCanvas(ctx, {
185+
annotations: computedAnnotations,
186+
theme,
187+
})
177188
}
178189
})
179190
}, [
180191
canvasEl,
181-
layers,
182-
cells,
192+
pixelRatio,
183193
outerWidth,
184194
outerHeight,
185195
innerWidth,
186196
innerHeight,
187197
margin,
198+
layers,
199+
cells,
188200
renderCell,
189201
enableGridX,
190202
enableGridY,
@@ -195,10 +207,11 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
195207
xScale,
196208
yScale,
197209
theme,
210+
borderWidth,
198211
enableLabels,
199212
colorScale,
200213
legends,
201-
pixelRatio,
214+
computedAnnotations,
202215
])
203216

204217
const { showTooltipFromEvent, hideTooltip } = useTooltip()

‎packages/heatmap/src/canvas.tsx

+32-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { CellCanvasRendererProps, HeatMapDatum } from './types'
33
export const renderRect = <Datum extends HeatMapDatum>(
44
ctx: CanvasRenderingContext2D,
55
{
6-
cell: { x, y, width, height, color, opacity, labelTextColor, label },
6+
cell: { x, y, width, height, color, borderColor, opacity, labelTextColor, label },
7+
borderWidth,
78
enableLabels,
89
theme,
910
}: CellCanvasRendererProps<Datum>
@@ -12,11 +13,24 @@ export const renderRect = <Datum extends HeatMapDatum>(
1213
ctx.globalAlpha = opacity
1314

1415
ctx.fillStyle = color
16+
if (borderWidth > 0) {
17+
console.log(borderWidth)
18+
ctx.strokeStyle = borderColor
19+
ctx.lineWidth = borderWidth
20+
}
21+
1522
ctx.fillRect(x - width / 2, y - height / 2, width, height)
23+
if (borderWidth > 0) {
24+
ctx.strokeRect(x - width / 2, y - height / 2, width, height)
25+
}
1626

1727
if (enableLabels) {
1828
ctx.fillStyle = labelTextColor
19-
ctx.font = `${theme.labels.text.fontSize}px ${theme.labels.text.fontFamily}`
29+
ctx.font = `${theme.labels.text.fontWeight ? `${theme.labels.text.fontWeight} ` : ''}${
30+
theme.labels.text.fontSize
31+
}px ${theme.labels.text.fontFamily}`
32+
ctx.textAlign = 'center'
33+
ctx.textBaseline = 'middle'
2034
ctx.fillText(label, x, y)
2135
}
2236

@@ -26,7 +40,8 @@ export const renderRect = <Datum extends HeatMapDatum>(
2640
export const renderCircle = <Datum extends HeatMapDatum>(
2741
ctx: CanvasRenderingContext2D,
2842
{
29-
cell: { x, y, width, height, color, opacity, labelTextColor, label },
43+
cell: { x, y, width, height, color, borderColor, opacity, labelTextColor, label },
44+
borderWidth,
3045
enableLabels,
3146
theme,
3247
}: CellCanvasRendererProps<Datum>
@@ -37,13 +52,26 @@ export const renderCircle = <Datum extends HeatMapDatum>(
3752
const radius = Math.min(width, height) / 2
3853

3954
ctx.fillStyle = color
55+
if (borderWidth > 0) {
56+
ctx.strokeStyle = borderColor
57+
ctx.lineWidth = borderWidth
58+
}
59+
4060
ctx.beginPath()
4161
ctx.arc(x, y, radius, 0, 2 * Math.PI)
62+
4263
ctx.fill()
64+
if (borderWidth > 0) {
65+
ctx.stroke()
66+
}
4367

4468
if (enableLabels) {
4569
ctx.fillStyle = labelTextColor
46-
ctx.font = `${theme.labels.text.fontSize}px ${theme.labels.text.fontFamily}`
70+
ctx.font = `${theme.labels.text.fontWeight ? `${theme.labels.text.fontWeight} ` : ''}${
71+
theme.labels.text.fontSize
72+
}px ${theme.labels.text.fontFamily}`
73+
ctx.textAlign = 'center'
74+
ctx.textBaseline = 'middle'
4775
ctx.fillText(label, x, y)
4876
}
4977

‎packages/heatmap/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export type CellComponent<Datum extends HeatMapDatum> = FunctionComponent<CellCo
9494

9595
export interface CellCanvasRendererProps<Datum extends HeatMapDatum> {
9696
cell: ComputedCell<Datum>
97+
borderWidth: number
9798
enableLabels: boolean
9899
theme: CompleteTheme
99100
}

‎packages/legends/src/canvas.ts

+10
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,13 @@ export const renderContinuousColorLegendToCanvas = (
169169
height,
170170
})
171171

172+
const initialStyles = {
173+
font: ctx.font,
174+
textAlign: ctx.textAlign,
175+
textBaseline: ctx.textBaseline,
176+
}
172177
ctx.save()
178+
173179
ctx.translate(x, y)
174180

175181
const gradient = ctx.createLinearGradient(
@@ -232,4 +238,8 @@ export const renderContinuousColorLegendToCanvas = (
232238
}
233239

234240
ctx.restore()
241+
242+
ctx.font = initialStyles.font
243+
ctx.textAlign = initialStyles.textAlign
244+
ctx.textBaseline = initialStyles.textBaseline
235245
}

‎packages/legends/src/compute.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,10 @@ export const computeContinuousColorsLegend = ({
262262
let width: number
263263
let height: number
264264

265-
const gradientX1: number = 0
266-
const gradientY1: number = 0
267-
let gradientX2: number = 0
268-
let gradientY2: number = 0
265+
const gradientX1 = 0
266+
const gradientY1 = 0
267+
let gradientX2 = 0
268+
let gradientY2 = 0
269269

270270
let titleX: number
271271
let titleY: number

‎website/src/pages/heatmap/canvas.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ const initialProperties: CanvasUnmappedProps = {
8787
opacity: defaults.opacity,
8888
activeOpacity: defaults.activeOpacity,
8989
inactiveOpacity: defaults.inactiveOpacity,
90-
borderWidth: defaults.borderWidth,
91-
borderColor: defaults.borderColor,
90+
borderWidth: 1,
91+
borderColor: '#000000',
9292

9393
enableLabels: false,
9494
labelTextColor: defaults.labelTextColor,

0 commit comments

Comments
 (0)
Please sign in to comment.