Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(heatmap): add support for annotations
  • Loading branch information
plouc committed Jan 12, 2022
1 parent 6462c08 commit 262c34a
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 289 deletions.
2 changes: 1 addition & 1 deletion packages/heatmap/package.json
Expand Up @@ -29,6 +29,7 @@
"!dist/tsconfig.tsbuildinfo"
],
"dependencies": {
"@nivo/annotations": "0.78.0",
"@nivo/axes": "0.78.0",
"@nivo/colors": "0.78.0",
"@nivo/tooltip": "0.78.0",
Expand All @@ -40,7 +41,6 @@
},
"peerDependencies": {
"@nivo/core": "0.78.0",
"prop-types": ">= 15.5.10 < 16.0.0",
"react": ">= 16.14.0 < 18.0.0"
},
"publishConfig": {
Expand Down
13 changes: 13 additions & 0 deletions packages/heatmap/src/HeatMap.tsx
Expand Up @@ -12,6 +12,7 @@ import {
import { useHeatMap } from './hooks'
import { svgDefaultProps } from './defaults'
import { HeatMapCells } from './HeatMapCells'
import { HeatMapCellAnnotations } from './HeatMapCellAnnotations'

type InnerHeatMapProps<Datum extends HeatMapDatum, ExtraProps extends object> = Omit<
HeatMapSvgProps<Datum, ExtraProps>,
Expand Down Expand Up @@ -52,6 +53,7 @@ const InnerHeatMap = <Datum extends HeatMapDatum, ExtraProps extends object>({
colors = svgDefaultProps.colors,
nanColor = svgDefaultProps.nanColor,
legends = svgDefaultProps.legends,
annotations = svgDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],
isInteractive = svgDefaultProps.isInteractive,
onMouseEnter,
onMouseMove,
Expand Down Expand Up @@ -93,6 +95,7 @@ const InnerHeatMap = <Datum extends HeatMapDatum, ExtraProps extends object>({
axes: null,
cells: null,
legends: null,
annotations: null,
}

if (layers.includes('grid')) {
Expand Down Expand Up @@ -158,6 +161,16 @@ const InnerHeatMap = <Datum extends HeatMapDatum, ExtraProps extends object>({
)
}

if (layers.includes('annotations') && annotations.length > 0) {
layerById.annotations = (
<HeatMapCellAnnotations<Datum>
key="annotations"
cells={cells}
annotations={annotations}
/>
)
}

return (
<SvgWrapper
width={outerWidth}
Expand Down
23 changes: 23 additions & 0 deletions packages/heatmap/src/HeatMapCellAnnotations.tsx
@@ -0,0 +1,23 @@
import { Annotation } from '@nivo/annotations'
import { ComputedCell, HeatMapCommonProps, HeatMapDatum } from './types'
import { useCellAnnotations } from './hooks'

interface HeatMapCellAnnotationsProps<Datum extends HeatMapDatum> {
cells: ComputedCell<Datum>[]
annotations: NonNullable<HeatMapCommonProps<Datum>['annotations']>
}

export const HeatMapCellAnnotations = <Datum extends HeatMapDatum>({
cells,
annotations,
}: HeatMapCellAnnotationsProps<Datum>) => {
const boundAnnotations = useCellAnnotations<Datum>(cells, annotations)

return (
<>
{boundAnnotations.map((annotation, i) => (
<Annotation key={i} {...annotation} />
))}
</>
)
}
2 changes: 1 addition & 1 deletion packages/heatmap/src/compute.ts
Expand Up @@ -33,7 +33,7 @@ export const computeCells = <Datum extends HeatMapDatum, ExtraProps extends obje
allValues.push(datum.y)

cells.push({
id: `${datum.x}.${serie.id}`,
id: `${serie.id}.${datum.x}`,
serieId: serie.id,
value: datum.y,
data: datum,
Expand Down
3 changes: 2 additions & 1 deletion packages/heatmap/src/defaults.ts
Expand Up @@ -14,7 +14,7 @@ export const commonDefaultProps: Omit<
> & {
layers: LayerId[]
} = {
layers: ['grid', 'axes', 'cells', 'legends'],
layers: ['grid', 'axes', 'cells', 'legends', 'annotations'],

minValue: 'auto',
maxValue: 'auto',
Expand Down Expand Up @@ -50,6 +50,7 @@ export const commonDefaultProps: Omit<
nanColor: '#000000',

legends: [],
annotations: [],

isInteractive: true,
hoverTarget: 'rowColumn',
Expand Down
24 changes: 24 additions & 0 deletions packages/heatmap/src/hooks.ts
Expand Up @@ -16,6 +16,8 @@ import {
} from './types'
import { commonDefaultProps } from './defaults'
import { computeCells } from './compute'
import { ComputedNode, InputNode } from '@nivo/network'
import { AnnotationMatcher, useAnnotations } from '@nivo/annotations'

export const useComputeCells = <Datum extends HeatMapDatum, ExtraProps extends object>({
data,
Expand Down Expand Up @@ -321,3 +323,25 @@ export const useHeatMap = <
}
*/
}

const getCellAnnotationPosition = <Datum extends HeatMapDatum>(cell: ComputedCell<Datum>) => ({
x: cell.x,
y: cell.y,
})

const getCellAnnotationDimensions = <Datum extends HeatMapDatum>(cell: ComputedCell<Datum>) => ({
size: Math.max(cell.width, cell.height),
width: cell.width,
height: cell.height,
})

export const useCellAnnotations = <Datum extends HeatMapDatum>(
cells: ComputedCell<Datum>[],
annotations: AnnotationMatcher<ComputedCell<Datum>>[]
) =>
useAnnotations<ComputedCell<Datum>>({
data: cells,
annotations,
getPosition: getCellAnnotationPosition,
getDimensions: getCellAnnotationDimensions,
})
9 changes: 6 additions & 3 deletions packages/heatmap/src/types.ts
Expand Up @@ -11,15 +11,16 @@ import {
import { AxisProps } from '@nivo/axes'
import { InheritedColorConfig, ContinuousColorScaleConfig } from '@nivo/colors'
import { AnchoredContinuousColorsLegendProps } from '@nivo/legends'
import { AnnotationMatcher } from '@nivo/annotations'

export interface HeatMapDatum {
x: string | number
y: number
y: number | null | undefined
}

export interface DefaultHeatMapDatum {
x: string
y: number
y: number | null | undefined
}

export type HeatMapSerie<Datum extends HeatMapDatum, ExtraProps extends object> = {
Expand Down Expand Up @@ -64,7 +65,7 @@ export interface HeatMapDataProps<Datum extends HeatMapDatum, ExtraProps extends
data: HeatMapSerie<Datum, ExtraProps>[]
}

export type LayerId = 'grid' | 'axes' | 'cells' | 'legends'
export type LayerId = 'grid' | 'axes' | 'cells' | 'legends' | 'annotations'
export interface CustomLayerProps<Datum extends HeatMapDatum> {
cells: ComputedCell<Datum>[]
}
Expand Down Expand Up @@ -138,6 +139,8 @@ export type HeatMapCommonProps<Datum extends HeatMapDatum> = {

legends: Omit<AnchoredContinuousColorsLegendProps, 'containerWidth' | 'containerHeight'>[]

annotations: AnnotationMatcher<ComputedCell<Datum>>[]

isInteractive: boolean
hoverTarget: 'cell' | 'row' | 'column' | 'rowColumn'
tooltip: TooltipComponent<Datum>
Expand Down
4 changes: 2 additions & 2 deletions website/src/components/controls/generics/RadioControl.tsx
Expand Up @@ -20,7 +20,7 @@ export const RadioControl = memo(
property,
flavors,
currentFlavor,
config,
config: { choices, columns },
value,
onChange,
context,
Expand All @@ -36,7 +36,7 @@ export const RadioControl = memo(
supportedFlavors={property.flavors}
>
<PropertyHeader {...property} context={context} />
<Radio options={config.choices} value={value} onChange={handleUpdate} />
<Radio options={choices} columns={columns} value={value} onChange={handleUpdate} />
<Help>{property.help}</Help>
</Control>
)
Expand Down
1 change: 1 addition & 0 deletions website/src/components/controls/types.ts
Expand Up @@ -32,6 +32,7 @@ export interface ChoicesControlConfig {

export interface RadioControlConfig {
type: 'radio'
columns?: number
choices: {
label: string
value: string
Expand Down
11 changes: 7 additions & 4 deletions website/src/components/controls/ui/Radio.tsx
Expand Up @@ -3,16 +3,17 @@ import styled from 'styled-components'

interface RadioProps {
value: string
columns?: number
options: {
value: string
label: string
}[]
onChange: (e: any) => void
}

export const Radio = memo(({ options, value, onChange }: RadioProps) => {
export const Radio = memo(({ options, columns = 2, value, onChange }: RadioProps) => {
return (
<Container>
<Container columns={columns}>
{options.map(option => (
<Item isSelected={option.value === value} key={option.value}>
<input
Expand All @@ -28,10 +29,12 @@ export const Radio = memo(({ options, value, onChange }: RadioProps) => {
)
})

const Container = styled.div`
const Container = styled.div<{
columns: number
}>`
display: grid;
align-items: center;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(${({ columns }) => columns}, 1fr);
border: 1px solid ${({ theme }) => theme.colors.border};
font-size: 14px;
border-right-width: 0;
Expand Down
8 changes: 7 additions & 1 deletion website/src/data/components/heatmap/mapper.tsx
Expand Up @@ -41,14 +41,20 @@ const CustomCell = ({
export default settingsMapper(
{
valueFormat: mapFormat,
cellShape: value => {
cellShape: (value: string) => {
if (value === `Custom(props) => (…)`) return CustomCell
return value
},
axisTop: mapAxis('top'),
axisRight: mapAxis('right'),
axisBottom: mapAxis('bottom'),
axisLeft: mapAxis('left'),
legends: (legends: any[]) => {
return legends.map(legend => ({
...legend,
tickFormat: mapFormat(legend.tickFormat),
}))
},
},
{
exclude: ['enable axisTop', 'enable axisRight', 'enable axisBottom', 'enable axisLeft'],
Expand Down
21 changes: 21 additions & 0 deletions website/src/data/components/heatmap/props.ts
Expand Up @@ -6,6 +6,7 @@ import {
axes,
isInteractive,
commonAccessibilityProps,
annotations,
} from '../../../lib/chart-properties'
import { ChartProperty, Flavor } from '../../../types'

Expand Down Expand Up @@ -149,6 +150,7 @@ const props: ChartProperty[] = [
key: 'colors',
group: 'Style',
type: 'ContinuousColorScaleConfig | ((datum) => string)',
defaultValue: defaults.colors,
control: {
type: 'continuous_colors',
},
Expand Down Expand Up @@ -345,6 +347,11 @@ const props: ChartProperty[] = [
type: 'boolean',
control: { type: 'switch' },
},
{
key: 'tickFormat',
type: 'string | (value: number) => string | number',
control: { type: 'valueFormat' },
},
{
key: 'title',
type: 'string',
Expand All @@ -355,6 +362,7 @@ const props: ChartProperty[] = [
type: `'start' | 'middle' | 'end'`,
control: {
type: 'radio',
columns: 3,
choices: [
{
label: 'start',
Expand Down Expand Up @@ -473,6 +481,19 @@ const props: ChartProperty[] = [
})),
},
},
annotations({
target: 'nodes',
flavors: allFlavors,
createDefaults: {
type: 'rect',
match: { id: 'Germany.Bus' },
note: 'Bus in Germany',
noteX: 120,
noteY: -130,
offset: 6,
noteTextOffset: 5,
},
}),
...commonAccessibilityProps(allFlavors),
...motionProperties(['svg'], defaults, 'react-spring'),
]
Expand Down

0 comments on commit 262c34a

Please sign in to comment.