From 9796f3f04936a03c78b302060a6067512c7df84e Mon Sep 17 00:00:00 2001 From: plouc Date: Thu, 24 Dec 2020 11:11:11 +0900 Subject: [PATCH] feat(voronoi): migrate package to TypeScript and remove recompose --- packages/voronoi/index.d.ts | 42 ------ packages/voronoi/index.js | 1 - packages/voronoi/src/Mesh.js | 118 --------------- packages/voronoi/src/Mesh.tsx | 139 ++++++++++++++++++ packages/voronoi/src/ResponsiveVoronoi.js | 11 -- packages/voronoi/src/ResponsiveVoronoi.tsx | 15 ++ packages/voronoi/src/Voronoi.js | 110 -------------- packages/voronoi/src/Voronoi.tsx | 128 ++++++++++++++++ packages/voronoi/src/computeMesh.js | 33 ----- packages/voronoi/src/computeMesh.ts | 50 +++++++ packages/voronoi/src/enhance.js | 42 ------ packages/voronoi/src/hooks.js | 13 -- packages/voronoi/src/hooks.ts | 91 ++++++++++++ packages/voronoi/src/index.ts | 6 +- .../src/{meshCanvas.js => meshCanvas.ts} | 13 +- packages/voronoi/src/props.js | 55 ------- packages/voronoi/src/props.ts | 17 +++ packages/voronoi/src/types.ts | 50 +++++++ ...voronoi.stories.js => voronoi.stories.tsx} | 0 ...omputeMesh.test.js => computeMesh.test.ts} | 10 +- website/src/data/components/voronoi/props.js | 34 ++--- website/src/pages/voronoi/index.js | 14 +- 22 files changed, 525 insertions(+), 467 deletions(-) delete mode 100644 packages/voronoi/index.d.ts delete mode 100644 packages/voronoi/index.js delete mode 100644 packages/voronoi/src/Mesh.js create mode 100644 packages/voronoi/src/Mesh.tsx delete mode 100644 packages/voronoi/src/ResponsiveVoronoi.js create mode 100644 packages/voronoi/src/ResponsiveVoronoi.tsx delete mode 100644 packages/voronoi/src/Voronoi.js create mode 100644 packages/voronoi/src/Voronoi.tsx delete mode 100644 packages/voronoi/src/computeMesh.js create mode 100644 packages/voronoi/src/computeMesh.ts delete mode 100644 packages/voronoi/src/enhance.js delete mode 100644 packages/voronoi/src/hooks.js create mode 100644 packages/voronoi/src/hooks.ts rename packages/voronoi/src/{meshCanvas.js => meshCanvas.ts} (52%) delete mode 100644 packages/voronoi/src/props.js create mode 100644 packages/voronoi/src/props.ts create mode 100644 packages/voronoi/src/types.ts rename packages/voronoi/stories/{voronoi.stories.js => voronoi.stories.tsx} (100%) rename packages/voronoi/tests/{computeMesh.test.js => computeMesh.test.ts} (65%) diff --git a/packages/voronoi/index.d.ts b/packages/voronoi/index.d.ts deleted file mode 100644 index 015ec774e8..0000000000 --- a/packages/voronoi/index.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from 'react' -import { Dimensions, Box, Theme } from '@nivo/core' - -declare module '@nivo/voronoi' { - export interface VoronoiDatum { - id: string | number - x: number - y: number - } - - export type VoronoiDomain = [number, number] - - export type VoronoiCustomLayer = (...args: any[]) => React.ReactNode - - export interface VoronoiProps { - data: VoronoiDatum[] - - xDomain?: VoronoiDomain - yDomain?: VoronoiDomain - - layers?: ('links' | 'cells' | 'points' | 'bounds' | VoronoiCustomLayer)[] - - theme?: Theme - - margin?: Box - - enableLinks?: boolean - linkLineWidth?: number - linkLineColor?: string - - enableCells?: boolean - cellLineWidth?: number - cellLineColor?: string - - enablePoints?: boolean - pointSize?: number - pointColor?: string - } - - export class Voronoi extends React.Component {} - export class ResponsiveVoronoi extends React.Component {} -} diff --git a/packages/voronoi/index.js b/packages/voronoi/index.js deleted file mode 100644 index f5e02c63b2..0000000000 --- a/packages/voronoi/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./cjs/nivo-voronoi') diff --git a/packages/voronoi/src/Mesh.js b/packages/voronoi/src/Mesh.js deleted file mode 100644 index 17fec3a416..0000000000 --- a/packages/voronoi/src/Mesh.js +++ /dev/null @@ -1,118 +0,0 @@ -import React, { useRef, useState, useCallback, useMemo } from 'react' -import PropTypes from 'prop-types' -import { getRelativeCursor } from '@nivo/core' -import { useVoronoiMesh } from './hooks' - -const Mesh = ({ - nodes, - width, - height, - x, - y, - debug, - - onMouseEnter, - onMouseMove, - onMouseLeave, - onClick, -}) => { - const elementRef = useRef(null) - const [currentIndex, setCurrentIndex] = useState(null) - - const { delaunay, voronoi } = useVoronoiMesh({ - points: nodes, - x, - y, - width, - height, - debug, - }) - const voronoiPath = useMemo(() => (debug ? voronoi.render() : undefined)) - - const getIndexAndNodeFromEvent = useCallback( - event => { - const [x, y] = getRelativeCursor(elementRef.current, event) - const index = delaunay.find(x, y) - - return [index, index !== undefined ? nodes[index] : null] - }, - [delaunay] - ) - const handleMouseEnter = useCallback( - event => { - const [index, node] = getIndexAndNodeFromEvent(event) - if (currentIndex !== index) setCurrentIndex(index) - node && onMouseEnter && onMouseEnter(node, event) - }, - [getIndexAndNodeFromEvent, setCurrentIndex] - ) - const handleMouseMove = useCallback( - event => { - const [index, node] = getIndexAndNodeFromEvent(event) - if (currentIndex !== index) setCurrentIndex(index) - node && onMouseMove && onMouseMove(node, event) - }, - [getIndexAndNodeFromEvent, setCurrentIndex] - ) - const handleMouseLeave = useCallback( - event => { - setCurrentIndex(null) - if (onMouseLeave) { - let previousNode - if (currentIndex !== undefined && currentIndex !== null) { - previousNode = nodes[currentIndex] - } - previousNode && onMouseLeave(previousNode, event) - } - }, - [setCurrentIndex, currentIndex, nodes] - ) - const handleClick = useCallback( - event => { - const [index, node] = getIndexAndNodeFromEvent(event) - if (currentIndex !== index) setCurrentIndex(index) - onClick && onClick(node, event) - }, - [getIndexAndNodeFromEvent, setCurrentIndex] - ) - - return ( - - {debug && } - {currentIndex !== null && debug && ( - - )} - - - ) -} - -Mesh.propTypes = { - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - nodes: PropTypes.array.isRequired, - x: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired, - y: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired, - onMouseEnter: PropTypes.func, - onMouseMove: PropTypes.func, - onMouseLeave: PropTypes.func, - onClick: PropTypes.func, - debug: PropTypes.bool.isRequired, -} -Mesh.defaultProps = { - x: 'x', - y: 'y', - debug: false, -} - -export default Mesh diff --git a/packages/voronoi/src/Mesh.tsx b/packages/voronoi/src/Mesh.tsx new file mode 100644 index 0000000000..f08cd2afd6 --- /dev/null +++ b/packages/voronoi/src/Mesh.tsx @@ -0,0 +1,139 @@ +import React, { useRef, useState, useCallback, useMemo } from 'react' +import { getRelativeCursor } from '@nivo/core' +import { useVoronoiMesh } from './hooks' +import { XYAccessor } from './computeMesh' + +type MouseHandler = (datum: Datum, event: React.MouseEvent) => void + +interface MeshProps { + nodes: Datum[] + width: number + height: number + x?: XYAccessor + y?: XYAccessor + onMouseEnter?: MouseHandler + onMouseMove?: MouseHandler + onMouseLeave?: MouseHandler + onClick?: MouseHandler + debug?: boolean +} + +export const Mesh = ({ + nodes, + width, + height, + x, + y, + onMouseEnter, + onMouseMove, + onMouseLeave, + onClick, + debug, +}: MeshProps) => { + const elementRef = useRef(null) + const [currentIndex, setCurrentIndex] = useState(null) + + const { delaunay, voronoi } = useVoronoiMesh({ + points: nodes, + x, + y, + width, + height, + debug, + }) + + const voronoiPath = useMemo(() => { + if (debug && voronoi) { + return voronoi.render() + } + + return undefined + }, [debug, voronoi]) + + const getIndexAndNodeFromEvent = useCallback( + event => { + if (!elementRef.current) { + return [null, null] + } + + const [x, y] = getRelativeCursor(elementRef.current, event) + const index = delaunay.find(x, y) + + return [index, index !== undefined ? nodes[index] : null] as [number, Datum | null] + }, + [elementRef, delaunay] + ) + + const handleMouseEnter = useCallback( + (event: React.MouseEvent) => { + const [index, node] = getIndexAndNodeFromEvent(event) + setCurrentIndex(index) + if (node) { + onMouseEnter?.(node, event) + } + }, + [getIndexAndNodeFromEvent, setCurrentIndex, onMouseEnter] + ) + + const handleMouseMove = useCallback( + (event: React.MouseEvent) => { + const [index, node] = getIndexAndNodeFromEvent(event) + setCurrentIndex(index) + if (node) { + onMouseMove?.(node, event) + } + }, + [getIndexAndNodeFromEvent, setCurrentIndex, onMouseMove] + ) + + const handleMouseLeave = useCallback( + (event: React.MouseEvent) => { + setCurrentIndex(null) + if (onMouseLeave) { + let previousNode: Datum | undefined = undefined + if (currentIndex !== null) { + previousNode = nodes[currentIndex] + } + previousNode && onMouseLeave(previousNode, event) + } + }, + [setCurrentIndex, currentIndex, onMouseLeave, nodes] + ) + + const handleClick = useCallback( + (event: React.MouseEvent) => { + const [index, node] = getIndexAndNodeFromEvent(event) + setCurrentIndex(index) + if (node) { + onClick?.(node, event) + } + }, + [getIndexAndNodeFromEvent, setCurrentIndex, onClick] + ) + + return ( + + {debug && voronoi && ( + <> + + {/* highlight current cell */} + {currentIndex !== null && ( + + )} + + )} + {/* transparent rect to intercept mouse events */} + + + ) +} diff --git a/packages/voronoi/src/ResponsiveVoronoi.js b/packages/voronoi/src/ResponsiveVoronoi.js deleted file mode 100644 index 6f6ff87bcb..0000000000 --- a/packages/voronoi/src/ResponsiveVoronoi.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { ResponsiveWrapper } from '@nivo/core' -import Voronoi from './Voronoi' - -const ResponsiveVoronoi = props => ( - - {({ width, height }) => } - -) - -export default ResponsiveVoronoi diff --git a/packages/voronoi/src/ResponsiveVoronoi.tsx b/packages/voronoi/src/ResponsiveVoronoi.tsx new file mode 100644 index 0000000000..0ccc4d91b0 --- /dev/null +++ b/packages/voronoi/src/ResponsiveVoronoi.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import { ResponsiveWrapper } from '@nivo/core' +import { VoronoiSvgProps } from './types' +import { Voronoi } from './Voronoi' + +type ResponsiveVoronoiProps = Partial> & + Pick + +export const ResponsiveVoronoi = (props: ResponsiveVoronoiProps) => ( + + {({ width, height }: { width: number; height: number }) => ( + + )} + +) diff --git a/packages/voronoi/src/Voronoi.js b/packages/voronoi/src/Voronoi.js deleted file mode 100644 index 06a985d2b4..0000000000 --- a/packages/voronoi/src/Voronoi.js +++ /dev/null @@ -1,110 +0,0 @@ -import React, { Fragment } from 'react' -import { LegacyContainer, SvgWrapper } from '@nivo/core' -import enhance from './enhance' -import { VoronoiPropTypes } from './props' - -const Voronoi = ({ - delaunay, - voronoi, - - data, - layers, - - margin, - width, - height, - outerWidth, - outerHeight, - - enableLinks, - linkLineWidth, - linkLineColor, - - enableCells, - cellLineWidth, - cellLineColor, - - enablePoints, - pointSize, - pointColor, - - theme, -}) => { - const context = { - width, - height, - data, - delaunay, - voronoi, - } - - const layerById = { - bounds: ( - - ), - } - if (enableLinks === true) { - layerById.links = ( - - ) - } - if (enableCells === true) { - layerById.cells = ( - - ) - } - if (enablePoints === true) { - layerById.points = ( - - ) - } - - return ( - - { - (/*{ showTooltip, hideTooltip }*/) => ( - - {layers.map((layer, i) => { - if (typeof layer === 'function') { - return {layer(context)} - } - return layerById[layer] - })} - - ) - } - - ) -} - -Voronoi.propTypes = VoronoiPropTypes - -export default enhance(Voronoi) diff --git a/packages/voronoi/src/Voronoi.tsx b/packages/voronoi/src/Voronoi.tsx new file mode 100644 index 0000000000..942930e4cc --- /dev/null +++ b/packages/voronoi/src/Voronoi.tsx @@ -0,0 +1,128 @@ +import React, { createElement, Fragment, ReactNode } from 'react' +import { Container, SvgWrapper, useDimensions } from '@nivo/core' +import { VoronoiSvgProps, VoronoiLayerId } from './types' +import { defaultVoronoiProps } from './props' +import { useVoronoi, useVoronoiLayerContext } from './hooks' + +type InnerVoronoiProps = Partial> & + Pick + +const InnerVoronoi = ({ + data, + width, + height, + margin: partialMargin, + layers = defaultVoronoiProps.layers, + xDomain = defaultVoronoiProps.xDomain, + yDomain = defaultVoronoiProps.yDomain, + enableLinks = defaultVoronoiProps.enableLinks, + linkLineWidth = defaultVoronoiProps.linkLineWidth, + linkLineColor = defaultVoronoiProps.linkLineColor, + enableCells = defaultVoronoiProps.enableCells, + cellLineWidth = defaultVoronoiProps.cellLineWidth, + cellLineColor = defaultVoronoiProps.cellLineColor, + enablePoints = defaultVoronoiProps.enableCells, + pointSize = defaultVoronoiProps.pointSize, + pointColor = defaultVoronoiProps.pointColor, + role = defaultVoronoiProps.role, +}: InnerVoronoiProps) => { + const { outerWidth, outerHeight, margin, innerWidth, innerHeight } = useDimensions( + width, + height, + partialMargin + ) + + const { points, delaunay, voronoi } = useVoronoi({ + data, + width: innerWidth, + height: innerHeight, + xDomain, + yDomain, + }) + + const layerById: Record = { + links: null, + cells: null, + points: null, + bounds: null, + } + + if (enableLinks && layers.includes('links')) { + layerById.links = ( + + ) + } + + if (enableCells && layers.includes('cells')) { + layerById.cells = ( + + ) + } + + if (enablePoints && layers.includes('points')) { + layerById.points = ( + + ) + } + + if (layers.includes('bounds')) { + layerById.bounds = ( + + ) + } + + const layerContext = useVoronoiLayerContext({ + points, + delaunay, + voronoi, + }) + + return ( + + {layers.map((layer, i) => { + if (layerById[layer as VoronoiLayerId] !== undefined) { + return layerById[layer as VoronoiLayerId] + } + + if (typeof layer === 'function') { + return {createElement(layer, layerContext)} + } + + return null + })} + + ) +} + +export const Voronoi = ({ + theme, + ...otherProps +}: Partial> & + Pick) => ( + + + +) diff --git a/packages/voronoi/src/computeMesh.js b/packages/voronoi/src/computeMesh.js deleted file mode 100644 index 631bfaf498..0000000000 --- a/packages/voronoi/src/computeMesh.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { Delaunay } from 'd3-delaunay' - -const getAccessor = directive => (typeof directive === 'function' ? directive : d => d[directive]) - -/** - * The delaunay generator requires an array - * where each point is defined as an array - * of 2 elements: [x: number, y: number]. - * - * Points represent the raw input data - * and x/y represent accessors to x & y. - */ -export const computeMeshPoints = ({ points, x = 'x', y = 'y' }) => { - const getX = getAccessor(x) - const getY = getAccessor(y) - - return points.map(p => [getX(p), getY(p)]) -} - -export const computeMesh = ({ points, width, height, debug }) => { - const delaunay = Delaunay.from(points) - const voronoi = debug === true ? delaunay.voronoi([0, 0, width, height]) : undefined - - return { delaunay, voronoi } -} diff --git a/packages/voronoi/src/computeMesh.ts b/packages/voronoi/src/computeMesh.ts new file mode 100644 index 0000000000..78fe0ec721 --- /dev/null +++ b/packages/voronoi/src/computeMesh.ts @@ -0,0 +1,50 @@ +import { Delaunay } from 'd3-delaunay' + +type NumberPropertyNames = { + [K in keyof T]: T[K] extends number ? K : never +}[keyof T] + +export type XYAccessor = NumberPropertyNames | ((datum: Datum) => number) + +const getAccessor = (directive: XYAccessor) => + typeof directive === 'function' ? directive : (datum: Datum) => datum[directive] + +/** + * The delaunay generator requires an array + * where each point is defined as an array + * of 2 elements: [x: number, y: number]. + * + * Points represent the raw input data + * and x/y represent accessors to x & y. + */ +export const computeMeshPoints = ({ + points, + x = 'x' as NumberPropertyNames, + y = 'y' as NumberPropertyNames, +}: { + points: Datum[] + x?: XYAccessor + y?: XYAccessor +}): [number, number][] => { + const getX = getAccessor(x) + const getY = getAccessor(y) + + return points.map(point => [getX(point) as number, getY(point) as number]) +} + +export const computeMesh = ({ + points, + width, + height, + debug, +}: { + points: [number, number][] + width: number + height: number + debug?: boolean +}) => { + const delaunay = Delaunay.from(points) + const voronoi = debug ? delaunay.voronoi([0, 0, width, height]) : undefined + + return { delaunay, voronoi } +} diff --git a/packages/voronoi/src/enhance.js b/packages/voronoi/src/enhance.js deleted file mode 100644 index bc4296df69..0000000000 --- a/packages/voronoi/src/enhance.js +++ /dev/null @@ -1,42 +0,0 @@ -import { scaleLinear } from 'd3-scale' -import { Delaunay } from 'd3-delaunay' -import compose from 'recompose/compose' -import defaultProps from 'recompose/defaultProps' -import withPropsOnChange from 'recompose/withPropsOnChange' -import pure from 'recompose/pure' -import { withTheme, withDimensions } from '@nivo/core' -import { VoronoiDefaultProps } from './props' - -export default Component => - compose( - defaultProps(VoronoiDefaultProps), - withTheme(), - withDimensions(), - withPropsOnChange( - ['xDomain', 'yDomain', 'width', 'height'], - ({ xDomain, yDomain, width, height }) => ({ - xScale: scaleLinear().domain(xDomain).range([0, width]), - yScale: scaleLinear().domain(yDomain).range([0, height]), - }) - ), - withPropsOnChange(['data', 'xScale', 'yScale'], ({ data, xScale, yScale }) => ({ - scaledPoints: data.map(d => ({ - data: d, - x: xScale(d.x), - y: yScale(d.y), - })), - })), - withPropsOnChange( - ['scaledPoints', 'width', 'height'], - ({ scaledPoints, width, height }) => { - const delaunay = Delaunay.from(scaledPoints.map(p => [p.x, p.y])) - const voronoi = delaunay.voronoi([0, 0, width, height]) - - return { - delaunay, - voronoi, - } - } - ), - pure - )(Component) diff --git a/packages/voronoi/src/hooks.js b/packages/voronoi/src/hooks.js deleted file mode 100644 index 5ee0db9f69..0000000000 --- a/packages/voronoi/src/hooks.js +++ /dev/null @@ -1,13 +0,0 @@ -import { useMemo } from 'react' -import { computeMeshPoints, computeMesh } from './computeMesh' - -export const useVoronoiMesh = ({ points, x, y, width, height, debug }) => { - const points2d = useMemo(() => computeMeshPoints({ points, x, y }), [points, x, y]) - - return useMemo(() => computeMesh({ points: points2d, width, height, debug }), [ - points2d, - width, - height, - debug, - ]) -} diff --git a/packages/voronoi/src/hooks.ts b/packages/voronoi/src/hooks.ts new file mode 100644 index 0000000000..59aa663868 --- /dev/null +++ b/packages/voronoi/src/hooks.ts @@ -0,0 +1,91 @@ +import { useMemo } from 'react' +import { scaleLinear } from 'd3-scale' +import { Delaunay } from 'd3-delaunay' +import { computeMeshPoints, computeMesh, XYAccessor } from './computeMesh' +import { VoronoiCommonProps, VoronoiDatum, VoronoiCustomLayerProps } from './types' + +export const useVoronoiMesh = ({ + points, + x, + y, + width, + height, + debug, +}: { + points: Datum[] + x?: XYAccessor + y?: XYAccessor + width: number + height: number + debug?: boolean +}) => { + const points2d = useMemo( + () => computeMeshPoints({ points, x, y }), + [points, x, y] + ) + + return useMemo(() => computeMesh({ points: points2d, width, height, debug }), [ + points2d, + width, + height, + debug, + ]) +} + +export const useVoronoi = ({ + data, + width, + height, + xDomain, + yDomain, +}: { + data: VoronoiDatum[] + width: number + height: number + xDomain: VoronoiCommonProps['xDomain'] + yDomain: VoronoiCommonProps['yDomain'] +}) => { + const xScale = useMemo(() => scaleLinear().domain(xDomain).range([0, width]), [xDomain, width]) + const yScale = useMemo(() => scaleLinear().domain(yDomain).range([0, height]), [ + yDomain, + height, + ]) + + const points = useMemo( + () => + data.map(d => ({ + x: xScale(d.x), + y: yScale(d.y), + data: d, + })), + [data, xScale, yScale] + ) + + return useMemo(() => { + const delaunay = Delaunay.from(points.map(p => [p.x, p.y])) + const voronoi = delaunay.voronoi([0, 0, width, height]) + + return { + points, + delaunay, + voronoi, + } + }, [points, width, height]) +} + +/** + * Memoize the context to pass to custom layers. + */ +export const useVoronoiLayerContext = ({ + points, + delaunay, + voronoi, +}: VoronoiCustomLayerProps): VoronoiCustomLayerProps => + useMemo( + () => ({ + points, + delaunay, + voronoi, + }), + [points, delaunay, voronoi] + ) diff --git a/packages/voronoi/src/index.ts b/packages/voronoi/src/index.ts index a6bde99dac..cf1a42ba4b 100644 --- a/packages/voronoi/src/index.ts +++ b/packages/voronoi/src/index.ts @@ -1,6 +1,6 @@ -export { default as Voronoi } from './Voronoi' -export { default as ResponsiveVoronoi } from './ResponsiveVoronoi' -export { default as Mesh } from './Mesh' +export * from './Voronoi' +export * from './ResponsiveVoronoi' +export * from './Mesh' export * from './computeMesh' export * from './meshCanvas' export * from './props' diff --git a/packages/voronoi/src/meshCanvas.js b/packages/voronoi/src/meshCanvas.ts similarity index 52% rename from packages/voronoi/src/meshCanvas.js rename to packages/voronoi/src/meshCanvas.ts index 72329d3fc5..3c06109220 100644 --- a/packages/voronoi/src/meshCanvas.js +++ b/packages/voronoi/src/meshCanvas.ts @@ -1,4 +1,9 @@ -export const renderVoronoiToCanvas = (ctx, voronoi) => { +import { Delaunay, Voronoi } from 'd3-delaunay' + +export const renderVoronoiToCanvas = ( + ctx: CanvasRenderingContext2D, + voronoi: Voronoi +) => { ctx.save() ctx.globalAlpha = 0.75 @@ -11,7 +16,11 @@ export const renderVoronoiToCanvas = (ctx, voronoi) => { ctx.restore() } -export const renderVoronoiCellToCanvas = (ctx, voronoi, index) => { +export const renderVoronoiCellToCanvas = ( + ctx: CanvasRenderingContext2D, + voronoi: Voronoi, + index: number +) => { ctx.save() ctx.globalAlpha = 0.35 diff --git a/packages/voronoi/src/props.js b/packages/voronoi/src/props.js deleted file mode 100644 index cae87cdef9..0000000000 --- a/packages/voronoi/src/props.js +++ /dev/null @@ -1,55 +0,0 @@ -import PropTypes from 'prop-types' - -export const VoronoiPropTypes = { - data: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }) - ).isRequired, - - xDomain: PropTypes.arrayOf(PropTypes.number).isRequired, - yDomain: PropTypes.arrayOf(PropTypes.number).isRequired, - - layers: PropTypes.arrayOf( - PropTypes.oneOfType([ - PropTypes.oneOf(['links', 'cells', 'points', 'bounds']), - PropTypes.func, - ]) - ).isRequired, - - enableLinks: PropTypes.bool.isRequired, - linkLineWidth: PropTypes.number.isRequired, - linkLineColor: PropTypes.string.isRequired, - - enableCells: PropTypes.bool.isRequired, - cellLineWidth: PropTypes.number.isRequired, - cellLineColor: PropTypes.string.isRequired, - - enablePoints: PropTypes.bool.isRequired, - pointSize: PropTypes.number.isRequired, - pointColor: PropTypes.string.isRequired, - - delaunay: PropTypes.object.isRequired, - voronoi: PropTypes.object.isRequired, -} - -export const VoronoiDefaultProps = { - xDomain: [0, 1], - yDomain: [0, 1], - - layers: ['links', 'cells', 'points', 'bounds'], - - enableLinks: false, - linkLineWidth: 1, - linkLineColor: '#bbb', - - enableCells: true, - cellLineWidth: 2, - cellLineColor: '#000', - - enablePoints: true, - pointSize: 4, - pointColor: '#666', -} diff --git a/packages/voronoi/src/props.ts b/packages/voronoi/src/props.ts new file mode 100644 index 0000000000..33467eeb3f --- /dev/null +++ b/packages/voronoi/src/props.ts @@ -0,0 +1,17 @@ +import { VoronoiDomain, VoronoiLayer } from './types' + +export const defaultVoronoiProps = { + xDomain: [0, 1] as VoronoiDomain, + yDomain: [0, 1] as VoronoiDomain, + layers: ['links', 'cells', 'points', 'bounds'] as VoronoiLayer[], + enableLinks: false, + linkLineWidth: 1, + linkLineColor: '#bbbbbb', + enableCells: true, + cellLineWidth: 2, + cellLineColor: '#000000', + enablePoints: true, + pointSize: 4, + pointColor: '#666666', + role: 'img', +} diff --git a/packages/voronoi/src/types.ts b/packages/voronoi/src/types.ts new file mode 100644 index 0000000000..8f2f181ae0 --- /dev/null +++ b/packages/voronoi/src/types.ts @@ -0,0 +1,50 @@ +import * as React from 'react' +import { Theme, Box } from '@nivo/core' +import { Delaunay, Voronoi } from 'd3-delaunay' + +export type VoronoiDatum = { + id: string | number + x: number + y: number +} + +export type VoronoiDomain = [number, number] + +export type VoronoiLayerId = 'links' | 'cells' | 'points' | 'bounds' + +export interface VoronoiCustomLayerProps { + points: { + x: number + y: number + data: VoronoiDatum + }[] + delaunay: Delaunay + voronoi: Voronoi +} + +export type VoronoiCustomLayer = React.FC + +export type VoronoiLayer = VoronoiLayerId | VoronoiCustomLayer + +export type VoronoiCommonProps = { + data: VoronoiDatum[] + width: number + height: number + margin?: Box + xDomain: VoronoiDomain + yDomain: VoronoiDomain + layers: VoronoiLayer[] + theme?: Theme + enableLinks: boolean + linkLineWidth: number + linkLineColor: string + enableCells: boolean + cellLineWidth: number + cellLineColor: string + enablePoints: boolean + pointSize: number + pointColor: string + role: string +} + +export type VoronoiSvgProps = VoronoiCommonProps diff --git a/packages/voronoi/stories/voronoi.stories.js b/packages/voronoi/stories/voronoi.stories.tsx similarity index 100% rename from packages/voronoi/stories/voronoi.stories.js rename to packages/voronoi/stories/voronoi.stories.tsx diff --git a/packages/voronoi/tests/computeMesh.test.js b/packages/voronoi/tests/computeMesh.test.ts similarity index 65% rename from packages/voronoi/tests/computeMesh.test.js rename to packages/voronoi/tests/computeMesh.test.ts index a8f2c6c413..aa5a9f4776 100644 --- a/packages/voronoi/tests/computeMesh.test.js +++ b/packages/voronoi/tests/computeMesh.test.ts @@ -1,7 +1,7 @@ -import { computeMesh } from '../src/computeMesh' +import { computeMesh } from '../src' it(`should be able to compute mesh for collinear points`, () => { - const points = [ + const points: [number, number][] = [ [0, 0], [50, 50], [100, 100], @@ -9,15 +9,15 @@ it(`should be able to compute mesh for collinear points`, () => { const { voronoi } = computeMesh({ points, width: 500, height: 500, debug: true }) - const cells = [...voronoi.cellPolygons()] + const cells = Array.from(voronoi!.cellPolygons()) expect(cells).toHaveLength(3) }) it(`should be able to compute mesh for a single point`, () => { - const points = [[50, 50]] + const points: [number, number][] = [[50, 50]] const { voronoi } = computeMesh({ points, width: 500, height: 500, debug: true }) - const cells = [...voronoi.cellPolygons()] + const cells = Array.from(voronoi!.cellPolygons()) expect(cells).toHaveLength(1) }) diff --git a/website/src/data/components/voronoi/props.js b/website/src/data/components/voronoi/props.js index ad36eea7d8..ca1e273e64 100644 --- a/website/src/data/components/voronoi/props.js +++ b/website/src/data/components/voronoi/props.js @@ -1,12 +1,4 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { VoronoiDefaultProps as defaults } from '@nivo/voronoi' +import { defaultVoronoiProps } from '@nivo/voronoi' import { groupProperties } from '../../../lib/componentProperties' const props = [ @@ -33,7 +25,7 @@ const props = [ help: 'Defines the x values domain.', type: '[number, number]', required: false, - defaultValue: defaults.xDomain, + defaultValue: defaultVoronoiProps.xDomain, }, { key: 'yDomain', @@ -41,7 +33,7 @@ const props = [ help: 'Defines the y values domain.', type: '[number, number]', required: false, - defaultValue: defaults.yDomain, + defaultValue: defaultVoronoiProps.yDomain, }, { key: 'width', @@ -104,14 +96,14 @@ const props = [ data and must return a valid SVG element. `, required: false, - defaultValue: defaults.layers, + defaultValue: defaultVoronoiProps.layers, }, { key: 'enableLinks', help: 'Enable/disable links.', type: 'boolean', required: false, - defaultValue: defaults.enableLinks, + defaultValue: defaultVoronoiProps.enableLinks, controlType: 'switch', group: 'Links', }, @@ -120,7 +112,7 @@ const props = [ help: 'Links line width.', type: 'number', required: false, - defaultValue: defaults.linkLineWidth, + defaultValue: defaultVoronoiProps.linkLineWidth, controlType: 'lineWidth', group: 'Links', }, @@ -129,7 +121,7 @@ const props = [ help: 'Links color.', type: 'string', required: false, - defaultValue: defaults.linkLineColor, + defaultValue: defaultVoronoiProps.linkLineColor, controlType: 'colorPicker', group: 'Links', }, @@ -138,7 +130,7 @@ const props = [ help: 'Enable/disable cells.', type: 'boolean', required: false, - defaultValue: defaults.enableCells, + defaultValue: defaultVoronoiProps.enableCells, controlType: 'switch', group: 'Cells', }, @@ -147,7 +139,7 @@ const props = [ help: 'Border width for cells.', type: 'number', required: false, - defaultValue: defaults.cellLineWidth, + defaultValue: defaultVoronoiProps.cellLineWidth, controlType: 'lineWidth', group: 'Cells', }, @@ -156,7 +148,7 @@ const props = [ help: 'Border color for cells.', type: 'string', required: false, - defaultValue: defaults.cellLineColor, + defaultValue: defaultVoronoiProps.cellLineColor, controlType: 'colorPicker', group: 'Cells', }, @@ -165,7 +157,7 @@ const props = [ help: 'Enable/disable points.', type: 'boolean', required: false, - defaultValue: defaults.enablePoints, + defaultValue: defaultVoronoiProps.enablePoints, controlType: 'switch', group: 'Points', }, @@ -174,7 +166,7 @@ const props = [ help: 'Size of points.', type: 'number', required: false, - defaultValue: defaults.siteSize, + defaultValue: defaultVoronoiProps.pointSize, controlType: 'range', group: 'Points', controlOptions: { @@ -189,7 +181,7 @@ const props = [ help: 'Points color.', type: 'string', required: false, - defaultValue: defaults.pointColor, + defaultValue: defaultVoronoiProps.pointColor, controlType: 'colorPicker', group: 'Points', }, diff --git a/website/src/pages/voronoi/index.js b/website/src/pages/voronoi/index.js index 07a444d3c3..b63fd8c781 100644 --- a/website/src/pages/voronoi/index.js +++ b/website/src/pages/voronoi/index.js @@ -1,14 +1,6 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import React from 'react' import range from 'lodash/range' -import { ResponsiveVoronoi, VoronoiDefaultProps } from '@nivo/voronoi' +import { ResponsiveVoronoi, defaultVoronoiProps } from '@nivo/voronoi' import ComponentTemplate from '../../components/components/ComponentTemplate' import meta from '../../data/components/voronoi/meta.yml' import { groups } from '../../data/components/voronoi/props' @@ -20,7 +12,7 @@ const generateData = () => range(100).map(id => ({ id, x: Math.random() * xDomain[1], y: Math.random() * yDomain[1] })) const initialProperties = { - ...ResponsiveVoronoi.defaultProps, + ...defaultVoronoiProps, xDomain, yDomain, @@ -55,7 +47,7 @@ const Voronoi = () => { currentFlavor="svg" properties={groups} initialProperties={initialProperties} - defaultProperties={VoronoiDefaultProps} + defaultProperties={defaultVoronoiProps} generateData={generateData} > {(properties, data, theme) => {