Skip to content

Commit

Permalink
feat(heatmap): add support for annotations for the canvas implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Jan 12, 2022
1 parent e3e8f00 commit 9aaaad5
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 18 deletions.
4 changes: 4 additions & 0 deletions packages/annotations/src/canvas.ts
Expand Up @@ -115,9 +115,13 @@ export const renderAnnotationsToCanvas = <Datum>(
})
} else {
ctx.font = `${theme.annotations.text.fontSize}px ${theme.annotations.text.fontFamily}`
ctx.textAlign = 'left'
ctx.textBaseline = 'alphabetic'

ctx.fillStyle = theme.annotations.text.fill
ctx.strokeStyle = theme.annotations.text.outlineColor
ctx.lineWidth = theme.annotations.text.outlineWidth * 2

if (theme.annotations.text.outlineWidth > 0) {
ctx.lineJoin = 'round'
ctx.strokeText(
Expand Down
29 changes: 21 additions & 8 deletions packages/heatmap/src/HeatMapCanvas.tsx
Expand Up @@ -3,7 +3,8 @@ import { getRelativeCursor, isCursorInRect, useDimensions, useTheme, Container }
import { renderAxesToCanvas, renderGridLinesToCanvas } from '@nivo/axes'
import { useTooltip } from '@nivo/tooltip'
import { renderContinuousColorLegendToCanvas } from '@nivo/legends'
import { useHeatMap } from './hooks'
import { renderAnnotationsToCanvas, useComputedAnnotations } from '@nivo/annotations'
import { useHeatMap, useCellAnnotations } from './hooks'
import { renderRect, renderCircle } from './canvas'
import { canvasDefaultProps } from './defaults'
import {
Expand Down Expand Up @@ -39,7 +40,7 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
opacity = canvasDefaultProps.opacity,
activeOpacity = canvasDefaultProps.activeOpacity,
inactiveOpacity = canvasDefaultProps.inactiveOpacity,
// borderWidth = canvasDefaultProps.borderWidth,
borderWidth = canvasDefaultProps.borderWidth,
borderColor = canvasDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],
enableGridX = canvasDefaultProps.enableGridX,
enableGridY = canvasDefaultProps.enableGridY,
Expand All @@ -53,7 +54,7 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
colors = canvasDefaultProps.colors as HeatMapCommonProps<Datum>['colors'],
emptyColor = canvasDefaultProps.emptyColor,
legends = canvasDefaultProps.legends,
// annotations = canvasDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],
annotations = canvasDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],
isInteractive = canvasDefaultProps.isInteractive,
// onMouseEnter,
// onMouseMove,
Expand Down Expand Up @@ -98,7 +99,10 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
hoverTarget,
})

const theme = useTheme()
const boundAnnotations = useCellAnnotations(cells, annotations)
const computedAnnotations = useComputedAnnotations({
annotations: boundAnnotations,
})

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

const theme = useTheme()

useEffect(() => {
if (canvasEl.current === null) return

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

cells.forEach(cell => {
renderCell(ctx, { cell, enableLabels, theme })
renderCell(ctx, { cell, borderWidth, enableLabels, theme })
})
} else if (layer === 'legends' && colorScale !== null) {
legends.forEach(legend => {
Expand All @@ -174,17 +180,23 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
theme,
})
})
} else if (layer === 'annotations') {
renderAnnotationsToCanvas(ctx, {
annotations: computedAnnotations,
theme,
})
}
})
}, [
canvasEl,
layers,
cells,
pixelRatio,
outerWidth,
outerHeight,
innerWidth,
innerHeight,
margin,
layers,
cells,
renderCell,
enableGridX,
enableGridY,
Expand All @@ -195,10 +207,11 @@ const InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends objec
xScale,
yScale,
theme,
borderWidth,
enableLabels,
colorScale,
legends,
pixelRatio,
computedAnnotations,
])

const { showTooltipFromEvent, hideTooltip } = useTooltip()
Expand Down
36 changes: 32 additions & 4 deletions packages/heatmap/src/canvas.tsx
Expand Up @@ -3,7 +3,8 @@ import { CellCanvasRendererProps, HeatMapDatum } from './types'
export const renderRect = <Datum extends HeatMapDatum>(
ctx: CanvasRenderingContext2D,
{
cell: { x, y, width, height, color, opacity, labelTextColor, label },
cell: { x, y, width, height, color, borderColor, opacity, labelTextColor, label },
borderWidth,
enableLabels,
theme,
}: CellCanvasRendererProps<Datum>
Expand All @@ -12,11 +13,24 @@ export const renderRect = <Datum extends HeatMapDatum>(
ctx.globalAlpha = opacity

ctx.fillStyle = color
if (borderWidth > 0) {
console.log(borderWidth)
ctx.strokeStyle = borderColor
ctx.lineWidth = borderWidth
}

ctx.fillRect(x - width / 2, y - height / 2, width, height)
if (borderWidth > 0) {
ctx.strokeRect(x - width / 2, y - height / 2, width, height)
}

if (enableLabels) {
ctx.fillStyle = labelTextColor
ctx.font = `${theme.labels.text.fontSize}px ${theme.labels.text.fontFamily}`
ctx.font = `${theme.labels.text.fontWeight ? `${theme.labels.text.fontWeight} ` : ''}${
theme.labels.text.fontSize
}px ${theme.labels.text.fontFamily}`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(label, x, y)
}

Expand All @@ -26,7 +40,8 @@ export const renderRect = <Datum extends HeatMapDatum>(
export const renderCircle = <Datum extends HeatMapDatum>(
ctx: CanvasRenderingContext2D,
{
cell: { x, y, width, height, color, opacity, labelTextColor, label },
cell: { x, y, width, height, color, borderColor, opacity, labelTextColor, label },
borderWidth,
enableLabels,
theme,
}: CellCanvasRendererProps<Datum>
Expand All @@ -37,13 +52,26 @@ export const renderCircle = <Datum extends HeatMapDatum>(
const radius = Math.min(width, height) / 2

ctx.fillStyle = color
if (borderWidth > 0) {
ctx.strokeStyle = borderColor
ctx.lineWidth = borderWidth
}

ctx.beginPath()
ctx.arc(x, y, radius, 0, 2 * Math.PI)

ctx.fill()
if (borderWidth > 0) {
ctx.stroke()
}

if (enableLabels) {
ctx.fillStyle = labelTextColor
ctx.font = `${theme.labels.text.fontSize}px ${theme.labels.text.fontFamily}`
ctx.font = `${theme.labels.text.fontWeight ? `${theme.labels.text.fontWeight} ` : ''}${
theme.labels.text.fontSize
}px ${theme.labels.text.fontFamily}`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(label, x, y)
}

Expand Down
1 change: 1 addition & 0 deletions packages/heatmap/src/types.ts
Expand Up @@ -94,6 +94,7 @@ export type CellComponent<Datum extends HeatMapDatum> = FunctionComponent<CellCo

export interface CellCanvasRendererProps<Datum extends HeatMapDatum> {
cell: ComputedCell<Datum>
borderWidth: number
enableLabels: boolean
theme: CompleteTheme
}
Expand Down
10 changes: 10 additions & 0 deletions packages/legends/src/canvas.ts
Expand Up @@ -169,7 +169,13 @@ export const renderContinuousColorLegendToCanvas = (
height,
})

const initialStyles = {
font: ctx.font,
textAlign: ctx.textAlign,
textBaseline: ctx.textBaseline,
}
ctx.save()

ctx.translate(x, y)

const gradient = ctx.createLinearGradient(
Expand Down Expand Up @@ -232,4 +238,8 @@ export const renderContinuousColorLegendToCanvas = (
}

ctx.restore()

ctx.font = initialStyles.font
ctx.textAlign = initialStyles.textAlign
ctx.textBaseline = initialStyles.textBaseline
}
8 changes: 4 additions & 4 deletions packages/legends/src/compute.ts
Expand Up @@ -262,10 +262,10 @@ export const computeContinuousColorsLegend = ({
let width: number
let height: number

const gradientX1: number = 0
const gradientY1: number = 0
let gradientX2: number = 0
let gradientY2: number = 0
const gradientX1 = 0
const gradientY1 = 0
let gradientX2 = 0
let gradientY2 = 0

let titleX: number
let titleY: number
Expand Down
4 changes: 2 additions & 2 deletions website/src/pages/heatmap/canvas.tsx
Expand Up @@ -87,8 +87,8 @@ const initialProperties: CanvasUnmappedProps = {
opacity: defaults.opacity,
activeOpacity: defaults.activeOpacity,
inactiveOpacity: defaults.inactiveOpacity,
borderWidth: defaults.borderWidth,
borderColor: defaults.borderColor,
borderWidth: 1,
borderColor: '#000000',

enableLabels: false,
labelTextColor: defaults.labelTextColor,
Expand Down

0 comments on commit 9aaaad5

Please sign in to comment.