Skip to content

Commit

Permalink
feat(parallel-coords): add support for custom layers to the canvas im…
Browse files Browse the repository at this point in the history
…plementation
  • Loading branch information
plouc committed May 8, 2023
1 parent 62df8cf commit 96b2c67
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 65 deletions.
104 changes: 60 additions & 44 deletions packages/parallel-coordinates/src/canvas/ParallelCoordinatesCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const InnerParallelCoordinatesCanvas = <D extends BaseDatum>({
lineWidth = canvasDefaultProps.lineWidth,
axesTicksPosition = canvasDefaultProps.axesTicksPosition,
legends = canvasDefaultProps.legends,
layers = canvasDefaultProps.layers,
role = canvasDefaultProps.role,
ariaLabel,
ariaLabelledBy,
Expand All @@ -38,16 +39,22 @@ export const InnerParallelCoordinatesCanvas = <D extends BaseDatum>({
partialMargin
)

const { variablesScale, variablesWithScale, computedData, lineGenerator, legendData } =
useParallelCoordinates<D>({
width: innerWidth,
height: innerHeight,
data,
variables,
layout,
colors,
curve,
})
const {
variablesScale,
variablesWithScale,
computedData,
lineGenerator,
legendData,
customLayerContext,
} = useParallelCoordinates<D>({
width: innerWidth,
height: innerHeight,
data,
variables,
layout,
colors,
curve,
})

const theme = useTheme()

Expand All @@ -66,40 +73,47 @@ export const InnerParallelCoordinatesCanvas = <D extends BaseDatum>({
ctx.fillRect(0, 0, outerWidth, outerHeight)
ctx.translate(margin.left, margin.top)

lineGenerator.context(ctx)
computedData.forEach(datum => {
ctx.save()
ctx.globalAlpha = lineOpacity

ctx.beginPath()
lineGenerator(datum.points)
ctx.strokeStyle = datum.color
ctx.lineWidth = lineWidth
ctx.stroke()

ctx.restore()
})

variablesWithScale.forEach(variable => {
renderAxisToCanvas(ctx, {
axis: layout === 'horizontal' ? 'y' : 'x',
scale: variable.scale,
x: layout === 'horizontal' ? variablesScale(variable.id) : 0,
y: layout === 'horizontal' ? 0 : variablesScale(variable.id),
length: layout === 'horizontal' ? innerHeight : innerWidth,
ticksPosition: axesTicksPosition,
theme,
})
})

legends.forEach(legend => {
renderLegendToCanvas(ctx, {
...legend,
data: legendData,
containerWidth: innerWidth,
containerHeight: innerHeight,
theme,
})
layers.forEach(layer => {
if (layer === 'axes') {
variablesWithScale.forEach(variable => {
renderAxisToCanvas(ctx, {
axis: layout === 'horizontal' ? 'y' : 'x',
scale: variable.scale,
x: layout === 'horizontal' ? variablesScale(variable.id) : 0,
y: layout === 'horizontal' ? 0 : variablesScale(variable.id),
length: layout === 'horizontal' ? innerHeight : innerWidth,
ticksPosition: axesTicksPosition,
theme,
})
})
} else if (layer === 'lines') {
lineGenerator.context(ctx)

computedData.forEach(datum => {
ctx.save()
ctx.globalAlpha = lineOpacity

ctx.beginPath()
lineGenerator(datum.points)
ctx.strokeStyle = datum.color
ctx.lineWidth = lineWidth
ctx.stroke()

ctx.restore()
})
} else if (layer === 'legends') {
legends.forEach(legend => {
renderLegendToCanvas(ctx, {
...legend,
data: legendData,
containerWidth: innerWidth,
containerHeight: innerHeight,
theme,
})
})
} else if (typeof layer === 'function') {
layer(ctx, customLayerContext)
}
})
}, [
canvasEl,
Expand All @@ -108,6 +122,8 @@ export const InnerParallelCoordinatesCanvas = <D extends BaseDatum>({
innerWidth,
innerHeight,
margin,
layers,
customLayerContext,
lineGenerator,
lineOpacity,
lineWidth,
Expand Down
2 changes: 1 addition & 1 deletion packages/parallel-coordinates/src/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CommonProps, BaseDatum, LayerId} from './types'
import { CommonProps, BaseDatum, LayerId } from './types'

export const commonDefaultProps: Omit<
CommonProps<BaseDatum>,
Expand Down
19 changes: 18 additions & 1 deletion packages/parallel-coordinates/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { scaleLinear, scalePoint } from 'd3-scale'
import { curveFromProp } from '@nivo/core'
import { OrdinalColorScaleConfig, useOrdinalColorScale } from '@nivo/colors'
import { castPointScale, castLinearScale } from '@nivo/scales'
import { VariableSpec, CommonProps, ComputedDatum, BaseDatum, LegendDatum } from './types'
import {
VariableSpec,
CommonProps,
ComputedDatum,
BaseDatum,
LegendDatum,
CustomLayerProps,
} from './types'
import { commonDefaultProps } from './defaults'

const computeParallelCoordinatesLayout = <D extends BaseDatum>({
Expand Down Expand Up @@ -126,11 +133,21 @@ export const useParallelCoordinates = <D extends BaseDatum>({
[computedData]
)

const customLayerContext: CustomLayerProps<D> = useMemo(
() => ({
computedData,
variables,
lineGenerator,
}),
[computedData, variables, lineGenerator]
)

return {
variablesScale,
variablesWithScale,
computedData,
lineGenerator,
legendData,
customLayerContext,
}
}
25 changes: 12 additions & 13 deletions packages/parallel-coordinates/src/svg/ParallelCoordinates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,21 @@ const InnerParallelCoordinates = <D extends BaseDatum>({
partialMargin
)

const { variablesScale, variablesWithScale, computedData, lineGenerator, legendData } =
useParallelCoordinates<D>({
width: innerWidth,
height: innerHeight,
data,
variables,
layout,
colors,
curve,
})

console.log({
const {
variablesScale,
variablesWithScale,
computedData,
lineGenerator,
legendData,
customLayerContext,
} = useParallelCoordinates<D>({
width: innerWidth,
height: innerHeight,
data,
variables,
layout,
colors,
curve,
})

const layerById: Record<LayerId, ReactNode> = {
Expand Down Expand Up @@ -132,7 +131,7 @@ const InnerParallelCoordinates = <D extends BaseDatum>({
>
{layers.map((layer, i) => {
if (typeof layer === 'function') {
return <Fragment key={i}>{createElement(layer, {})}</Fragment>
return <Fragment key={i}>{createElement(layer, customLayerContext)}</Fragment>
}

return layerById?.[layer] ?? null
Expand Down
28 changes: 23 additions & 5 deletions packages/parallel-coordinates/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AriaAttributes } from 'react'
import { AriaAttributes, FunctionComponent } from 'react'
import { Line } from 'd3-shape'
import { Box, Dimensions, MotionProps, LineCurveFactoryId, Theme, ValueFormat } from '@nivo/core'
import { OrdinalColorScaleConfig } from '@nivo/colors'
import { AxisProps } from '@nivo/axes'
Expand Down Expand Up @@ -82,21 +83,38 @@ export interface CommonProps<D extends BaseDatum> extends MotionProps {
ariaDescribedBy: AriaAttributes['aria-describedby']
}

type ParallelCoordinatesLayer = LayerId
export interface CustomLayerProps<D extends BaseDatum> {
computedData: readonly ComputedDatum<D>[]
variables: readonly VariableSpec<D>[]
lineGenerator: Line<[number, number]>
}

export type ParallelCoordinatesCustomLayer<D extends BaseDatum> = FunctionComponent<
CustomLayerProps<D>
>

type ParallelCoordinatesLayer<D extends BaseDatum> = LayerId | ParallelCoordinatesCustomLayer<D>

export type ParallelCoordinatesProps<D extends BaseDatum> = DataProps<D> &
Dimensions &
Partial<CommonProps<D>> & {
layers?: ParallelCoordinatesLayer[]
layers?: ParallelCoordinatesLayer<D>[]
motionStagger?: number
testIdPrefix?: string
}

type ParallelCoordinatesCanvasLayer = LayerId
export type ParallelCoordinatesCanvasCustomLayer<D extends BaseDatum> = (
ctx: CanvasRenderingContext2D,
props: CustomLayerProps<D>
) => void

type ParallelCoordinatesCanvasLayer<D extends BaseDatum> =
| LayerId
| ParallelCoordinatesCanvasCustomLayer<D>

export type ParallelCoordinatesCanvasProps<D extends BaseDatum> = DataProps<D> &
Dimensions &
Partial<CommonProps<D>> & {
layers: ParallelCoordinatesCanvasLayer[]
layers: ParallelCoordinatesCanvasLayer<D>[]
pixelRatio?: number
}
10 changes: 9 additions & 1 deletion website/src/data/components/parallel-coordinates/props.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-ignore
import { lineCurvePropKeys } from '@nivo/core'
import { commonDefaultProps as defaults } from '@nivo/parallel-coordinates'
import { commonDefaultProps as defaults, svgDefaultProps } from '@nivo/parallel-coordinates'
import {
themeProperty,
motionProperties,
Expand Down Expand Up @@ -186,6 +186,14 @@ const props: ChartProperty[] = [
control: { type: 'opacity' },
group: 'Style',
},
{
key: 'layers',
type: `ParallelCoordinatesLayer<D>[] | ParallelCoordinatesCanvasLayer<D>[]`,
group: 'Customization',
help: 'Define layers, please use the appropriate variant for custom layers.',
defaultValue: svgDefaultProps.layers,
flavors: allFlavors,
},
{
key: 'legends',
group: 'Legends',
Expand Down

0 comments on commit 96b2c67

Please sign in to comment.