Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create new @visx/delaunay package #1678

Merged
merged 21 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2594329
feat(delaunay): Initial setup for the new visx-delaunay package
SheaJanke Mar 19, 2023
00bdb6c
feat(delaunay): Working Voronoi polygon example
SheaJanke Mar 20, 2023
b27beff
feat(delaunay): Add delaunay triangulation example
SheaJanke Mar 21, 2023
6ca2eb1
feat(delaunay): Add examples to gallery
SheaJanke Mar 23, 2023
7097b12
feat(delaunay): Add unit tests
SheaJanke Mar 25, 2023
88a95e9
feat(delaunay): Fix demo pages
SheaJanke Mar 26, 2023
0970234
feat(delaunay): Add margin to examples and fix lint errors
SheaJanke Mar 26, 2023
893f475
feat(delaunay): Cleanup and renaming
SheaJanke Mar 26, 2023
d5dd209
Merge branch 'master' into delaunay
SheaJanke Mar 26, 2023
bf4b1d8
feat(delaunay); Update TS references
SheaJanke Mar 26, 2023
f7dbabc
feat(delaunay): Add ESM-only dependencies to next.config.ts
SheaJanke Mar 27, 2023
fb06335
feat(delaunay): Add ESM-only dependencies to next.config.ts
SheaJanke Mar 27, 2023
6d38e80
Trigger build 1
SheaJanke May 14, 2023
2b437f5
Merge branch 'master' into delaunay-2
SheaJanke Jul 6, 2023
62f944e
feat(delaunay): Address pull request comments
SheaJanke Jul 6, 2023
260de7a
Update tsconfig references.
SheaJanke Jul 6, 2023
593097e
Fix delaunay triangulation closest point calculation.
SheaJanke Jul 7, 2023
8189266
Update packages sizes.
SheaJanke Jul 7, 2023
592d447
Merge branch 'master' into delaunay
SheaJanke Jul 7, 2023
63d7f5e
Remove Voronoi tile from gallery and update jest config.
SheaJanke Jul 11, 2023
6023a25
Retrigger build
SheaJanke Jul 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ module.exports = {
verbose: false,
testPathIgnorePatterns: ['<rootDir>/packages/visx-demo'],
transformIgnorePatterns: [
'node_modules/(?!(d3-(array|color|format|interpolate|scale|time|time-format)|internmap)/)',
'node_modules/(?!(d3-(array|color|delaunay|format|interpolate|scale|time|time-format)|delaunator|internmap|robust-predicates)/)',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah interesting, I missed this in the esm/cjs work. I might need to revisit this (in a separate PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had another look at this, and I was able to remove d3-delaunay from here. The other two still seem to be necessary.

Copy link
Collaborator

@williaster williaster Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking at this. I did some more digging and it seems like delaunator and robust-predicates may be ESM-only which likely trips up jest.

I pulled and built your branch locally and you can see that this generated/vendored CJS file references an the ESM-only delaunator lib. I'm not sure why that doesn't mess up our next demo app, perhaps it's all resolving to the ESM versions so this path isn't triggered.

all this to say that if someone hits CJS/ESM issues with this we might have to also vendor delaunator + robust-predicates in the future, but since it seems to work with next.js as-is I think we can merge this for now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

],
};
2 changes: 1 addition & 1 deletion packages/sizes.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"visx-annotation":{"esm":31002,"lib":43431},"visx-axis":{"esm":21699,"lib":26304},"visx-bounds":{"esm":2948,"lib":3371},"visx-brush":{"esm":54136,"lib":58297},"visx-chord":{"esm":3478,"lib":4691},"visx-clip-path":{"esm":4524,"lib":6062},"visx-curve":{"esm":323,"lib":1462},"visx-demo":{"esm":0,"lib":36517},"visx-drag":{"esm":12756,"lib":14402},"visx-event":{"esm":3878,"lib":5194},"visx-geo":{"esm":13515,"lib":16741},"visx-glyph":{"esm":15177,"lib":19992},"visx-gradient":{"esm":18202,"lib":22847},"visx-grid":{"esm":18982,"lib":22665},"visx-group":{"esm":1648,"lib":2267},"visx-heatmap":{"esm":7394,"lib":8731},"visx-hierarchy":{"esm":12093,"lib":17910},"visx-legend":{"esm":26944,"lib":34024},"visx-marker":{"esm":9152,"lib":11350},"visx-mock-data":{"esm":326040,"lib":329480},"visx-network":{"esm":4674,"lib":6809},"visx-pattern":{"esm":11689,"lib":15763},"visx-point":{"esm":1003,"lib":1818},"visx-react-spring":{"esm":14000,"lib":17725},"visx-responsive":{"esm":12972,"lib":16175},"visx-scale":{"esm":18870,"lib":30555},"visx-shape":{"esm":86912,"lib":108820},"visx-stats":{"esm":13738,"lib":15320},"visx-text":{"esm":8567,"lib":10114},"visx-threshold":{"esm":2907,"lib":3806},"visx-tooltip":{"esm":15233,"lib":21734},"visx-vendor":{"esm":2002,"lib":2170},"visx-visx":{"esm":1524,"lib":4487},"visx-voronoi":{"esm":2314,"lib":3021},"visx-wordcloud":{"esm":2620,"lib":3455},"visx-xychart":{"esm":178370,"lib":240169},"visx-zoom":{"esm":16239,"lib":19297}}
{"visx-annotation":{"esm":31002,"lib":43431},"visx-axis":{"esm":21699,"lib":26304},"visx-bounds":{"esm":2948,"lib":3371},"visx-brush":{"esm":54136,"lib":58297},"visx-chord":{"esm":3478,"lib":4691},"visx-clip-path":{"esm":4524,"lib":6062},"visx-curve":{"esm":323,"lib":1462},"visx-delaunay":{"esm":2599,"lib":3428},"visx-demo":{"esm":0,"lib":36731},"visx-drag":{"esm":12756,"lib":14402},"visx-event":{"esm":3878,"lib":5194},"visx-geo":{"esm":13515,"lib":16741},"visx-glyph":{"esm":15177,"lib":19992},"visx-gradient":{"esm":18202,"lib":22847},"visx-grid":{"esm":18982,"lib":22665},"visx-group":{"esm":1648,"lib":2267},"visx-heatmap":{"esm":7394,"lib":8731},"visx-hierarchy":{"esm":12093,"lib":17910},"visx-legend":{"esm":26944,"lib":34024},"visx-marker":{"esm":9152,"lib":11350},"visx-mock-data":{"esm":326040,"lib":329480},"visx-network":{"esm":4674,"lib":6809},"visx-pattern":{"esm":11689,"lib":15763},"visx-point":{"esm":1003,"lib":1818},"visx-react-spring":{"esm":14000,"lib":17725},"visx-responsive":{"esm":12972,"lib":16175},"visx-scale":{"esm":18870,"lib":30555},"visx-shape":{"esm":86912,"lib":108820},"visx-stats":{"esm":13738,"lib":15320},"visx-text":{"esm":8567,"lib":10114},"visx-threshold":{"esm":2907,"lib":3806},"visx-tooltip":{"esm":15233,"lib":21734},"visx-vendor":{"esm":2257,"lib":2446},"visx-visx":{"esm":1524,"lib":4487},"visx-voronoi":{"esm":2314,"lib":3021},"visx-wordcloud":{"esm":2620,"lib":3455},"visx-xychart":{"esm":178204,"lib":240002},"visx-zoom":{"esm":16239,"lib":19297}}
SheaJanke marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions packages/visx-delaunay/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
67 changes: 67 additions & 0 deletions packages/visx-delaunay/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# @visx/delaunay

<a title="@visx/delaunay npm downloads" href="https://www.npmjs.com/package/@visx/delaunay">
<img src="https://img.shields.io/npm/dm/@visx/delaunay.svg?style=flat-square" />
</a>

## Overview

A Voronoi diagram partitions a two-dimensional plane into regions based on a set of input points.
Each unique input point maps to a corresponding region, where each region represents _all points
that are closer to the input point than to any other input point_.

Not only are Voronoi diagrams 😍, but they can be used to
[improve the interactive experience of a visualization](https://www.visualcinnamon.com/2015/07/voronoi.html).
This is most often accomplished by overlaying an invisible voronoi grid on top of the visualization
to increase the target area of interaction sites such as points on a scatter plot.

The `@visx/delaunay` package provides a wrapper around the existing
[d3-delaunay](https://github.com/d3/d3-delaunay) package with some `react`-specific utilities.

## Installation

```
npm install --save @visx/delaunay
```

## Usage

The `@visx/delaunay` package exports a wrapped version of the d3 `voronoi` and `delaunay` layouts for flexible usage,
as well as a `<Polygon />` component for rendering Voronoi and Delaunay regions.

```js
import { voronoi, Polygon } from '@visx/delaunay';

const points = Array(n).fill(null).map(() => ({
x: Math.random() * innerWidth,
y: Math.random() * innerHeight,
}));

// width + height set an extent on the voronoi
// x + y set relevant accessors depending on the shape of your data
const voronoiDiagram = voronoi({
data: points,
x: d => d.x,
y: d => d.y,
width,
height,
});

const polygons = Array.from(voronoiDiagram.cellPolygons());

return (
<svg>
<Group>
{polygons.map((polygon) => (
<Polygon key={...} polygon={polygon} />
))}
{points.map(({ x, y }) => (
<circle key={...} cx={x} cy={y} />
)}
</Group>
</svg>
)
```

Additional information about the voronoi diagram API can be found in the
[d3-delaunay documentation](https://github.com/d3/d3-delaunay#voronoi).
42 changes: 42 additions & 0 deletions packages/visx-delaunay/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@visx/delaunay",
"version": "1.0.0",
"description": "visx delaunay",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"esm"
],
"repository": {
"type": "git",
"url": "git+https://github.com/airbnb/visx.git"
},
"keywords": [
"visx",
"react",
"d3",
"visualizations",
"charts"
],
"author": "@SheaJanke",
"license": "MIT",
"bugs": {
"url": "https://github.com/airbnb/visx/issues"
},
"homepage": "https://github.com/airbnb/visx#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@types/react": "*",
"@visx/vendor": "3.2.0",
"classnames": "^2.3.1",
"prop-types": "^15.6.1"
},
"peerDependencies": {
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
}
24 changes: 24 additions & 0 deletions packages/visx-delaunay/src/components/Polygon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import cx from 'classnames';

export type PolygonProps = {
/** Override render function which is provided polygon and generated path. */
children?: ({ path, polygon }: { path: string; polygon: [number, number][] }) => React.ReactNode;
/** className to apply to path element. */
className?: string;
/** Array of coordinate arrays for the polygon (e.g., [[x,y], [x1,y1], ...]), used to generate polygon path. */
polygon?: [number, number][];
};

export default function Polygon({
polygon,
className,
children,
...restProps
}: PolygonProps & Omit<React.SVGProps<SVGPathElement>, keyof PolygonProps>) {
if (!polygon) return null;
const path = `M${polygon.join('L')}Z`;
if (children) return <>{children({ path, polygon })}</>;

return <path className={cx('visx-delaunay-polygon', className)} d={path} {...restProps} />;
}
17 changes: 17 additions & 0 deletions packages/visx-delaunay/src/delaunay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Delaunay } from '@visx/vendor/d3-delaunay';

interface Config<Datum> {
/** The data for the delaunay triangulation */
data?: Datum[];
/** Set the x-value accessor function for the delaunay triangulation. */
x: (d: Datum) => number;
/** Set the y-value accessor function for the delaunay triangulation. */
y: (d: Datum) => number;
}

/**
* Returns a configured d3 delaunay triangulation. See d3-delaunay for the complete API reference.
*/
export default function delaunay<Datum>({ data = [], x, y }: Config<Datum>) {
return Delaunay.from(data, x, y);
}
3 changes: 3 additions & 0 deletions packages/visx-delaunay/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as delaunay } from './delaunay';
export { default as voronoi } from './voronoi';
export { default as Polygon } from './components/Polygon';
30 changes: 30 additions & 0 deletions packages/visx-delaunay/src/voronoi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Delaunay } from '@visx/vendor/d3-delaunay';

const CLIP_PADDING = 1;

interface Config<Datum> {
/** The data for the voronoi diagram */
data?: Datum[];
/** The total width of the voronoi diagram. */
width?: number;
/** The total width of the voronoi diagram. */
height?: number;
/** Set the x-value accessor function for the voronoi diagram. */
x: (d: Datum) => number;
/** Set the y-value accessor function for the voronoi diagram. */
y: (d: Datum) => number;
}

/**
* Returns a configured d3 voronoi diagram for the given data. See d3-delaunay
* for the complete API reference.
*/
export default function voronoi<Datum>({ data = [], width = 0, height = 0, x, y }: Config<Datum>) {
const delaunay = Delaunay.from(data, x, y);
return delaunay.voronoi([
-CLIP_PADDING,
-CLIP_PADDING,
width + CLIP_PADDING,
height + CLIP_PADDING,
]);
}
35 changes: 35 additions & 0 deletions packages/visx-delaunay/test/Polygon.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { shallow } from 'enzyme';

import { Polygon } from '../src';

describe('<Polygon />', () => {
const polygon: [number, number][] = new Array(3).fill(null).map((_, i) => [i, i]);

const props = { polygon };

test('it should be defined', () => {
expect(Polygon).toBeDefined();
});

test('it should not render without a polygon', () => {
const wrapper = shallow(<Polygon />);
expect(wrapper.type()).toBeNull();
});

test('it should render a path', () => {
const wrapper = shallow(<Polygon {...props} />);
expect(wrapper.find('path')).toHaveLength(1);
});

test('it should set a d attribute based on the polygon prop', () => {
const wrapper = shallow(<Polygon {...props} />);
const d = 'M0,0L1,1L2,2Z';
expect(wrapper.find('path').props().d).toEqual(d);
});

test('it should add extra (non-func) props to the path element', () => {
const wrapper = shallow(<Polygon {...props} fill="orange" />);
expect(wrapper.find('path').props().fill).toBe('orange');
});
});
26 changes: 26 additions & 0 deletions packages/visx-delaunay/test/delaunay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { delaunay } from '../src';

const data = [
{ x: 10, y: 10 },
{ x: 10, y: 20 },
{ x: 20, y: 20 },
{ x: 20, y: 10 },
];

describe('delaunay', () => {
test('it should be defined', () => {
expect(delaunay).toBeDefined();
});

test('it should find closest point', () => {
const delaunayDiagram = delaunay({ data, x: (d) => d.x, y: (d) => d.y });
expect(delaunayDiagram.find(9, 11)).toBe(0);
expect(delaunayDiagram.find(11, 19)).toBe(1);
expect(delaunayDiagram.find(21, 19)).toBe(2);
});

test('the delaunay triagulation of a square should contain two triangles', () => {
const delaunayDiagram = delaunay({ data, x: (d) => d.x, y: (d) => d.y });
expect(Array.from(delaunayDiagram.trianglePolygons())).toHaveLength(2);
});
});
15 changes: 15 additions & 0 deletions packages/visx-delaunay/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"composite": false,
"emitDeclarationOnly": false,
"noEmit": true,
"rootDir": "."
},
"extends": "../../../tsconfig.options.json",
"include": ["**/*", "../types/**/*", "../../../types/**/*"],
"references": [
{
"path": ".."
}
]
}
29 changes: 29 additions & 0 deletions packages/visx-delaunay/test/voronoi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { voronoi } from '../src';
SheaJanke marked this conversation as resolved.
Show resolved Hide resolved

const x = () => 123;
const y = () => 123;

describe('voronoi', () => {
test('it should be defined', () => {
expect(voronoi).toBeDefined();
});

test('width and height params should define extent', () => {
const width = 17;
const height = 99;
const v = voronoi({ width, height, x, y });
expect(v.xmin).toBe(-1);
expect(v.ymin).toBe(-1);
expect(v.xmax).toEqual(width + 1);
expect(v.ymax).toEqual(height + 1);
});

test('100 random points should give 100 cell polygons', () => {
const data = new Array(100).fill(null).map(() => ({
x: Math.random(),
y: Math.random(),
}));
const v = voronoi({ data, x: (d) => d.x, y: (d) => d.y });
expect(Array.from(v.cellPolygons())).toHaveLength(100);
});
});
22 changes: 22 additions & 0 deletions packages/visx-delaunay/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"declarationDir": "lib",
"outDir": "lib",
"rootDir": "src"
},
"exclude": [
"lib",
"test"
],
"extends": "../../tsconfig.options.json",
"include": [
"src/**/*",
"types/**/*",
"../../types/**/*"
],
"references": [
{
"path": "../visx-vendor"
}
]
}
1 change: 1 addition & 0 deletions packages/visx-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@visx/chord": "3.0.0",
"@visx/clip-path": "3.0.0",
"@visx/curve": "3.0.0",
"@visx/delaunay": "1.0.0",
"@visx/drag": "3.0.1",
"@visx/event": "3.0.1",
"@visx/geo": "3.0.0",
Expand Down
45 changes: 0 additions & 45 deletions packages/visx-demo/public/static/docs/visx-demo.html

This file was deleted.

Loading