Skip to content

Commit

Permalink
feat(sankey): add @visx/sankey (#1880)
Browse files Browse the repository at this point in the history
* initialize package

* add `Sankey` component

* add exports

* add default color

* add docs page

* fix package

* fix import and tsconfig

* extract to types

* add example

* change version to 1.0.0

* add to visx-visx

* pin version

* add controls to sankey example

* add sankey tile

* add styles to example

* add example tile to docs page

* add usage to readme

* remove console.log

* add class names

* add tests

* fix readme

* add tooltip to example

* update references

* pin d3-shape version d3-sankey dep

* revert automatically applied demo tsconfig changes

* remove newline

* build sizes

* remove console.log

* format

Co-authored-by: Chris Williams <[email protected]>

---------

Co-authored-by: Chris Williams <[email protected]>
  • Loading branch information
jacksonhardaker and williaster authored Nov 7, 2024
1 parent d920798 commit 07a91d8
Show file tree
Hide file tree
Showing 26 changed files with 856 additions and 5 deletions.
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":21783,"lib":26434},"visx-bounds":{"esm":2948,"lib":3371},"visx-brush":{"esm":56194,"lib":60810},"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":13662,"lib":16903},"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":26975,"lib":34055},"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":14040,"lib":17765},"visx-responsive":{"esm":16350,"lib":18791},"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":2492,"lib":2702},"visx-visx":{"esm":1524,"lib":4487},"visx-voronoi":{"esm":2314,"lib":3021},"visx-wordcloud":{"esm":2620,"lib":3455},"visx-xychart":{"esm":178473,"lib":240315},"visx-zoom":{"esm":16239,"lib":19297}}
{"visx-annotation":{"esm":31002,"lib":43431},"visx-axis":{"esm":21783,"lib":26434},"visx-bounds":{"esm":2948,"lib":3371},"visx-brush":{"esm":56194,"lib":60810},"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":36869},"visx-drag":{"esm":12756,"lib":14402},"visx-event":{"esm":3878,"lib":5194},"visx-geo":{"esm":13662,"lib":16903},"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":26975,"lib":34055},"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":14040,"lib":17765},"visx-responsive":{"esm":16350,"lib":18791},"visx-sankey":{"esm":3606,"lib":4677},"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":2492,"lib":2702},"visx-visx":{"esm":1524,"lib":4487},"visx-voronoi":{"esm":2314,"lib":3021},"visx-wordcloud":{"esm":2620,"lib":3455},"visx-xychart":{"esm":178473,"lib":240315},"visx-zoom":{"esm":16239,"lib":19297}}
1 change: 1 addition & 0 deletions packages/visx-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@visx/point": "3.3.0",
"@visx/react-spring": "3.10.1",
"@visx/responsive": "3.10.2",
"@visx/sankey": "1.0.0",
"@visx/scale": "3.5.0",
"@visx/shape": "3.5.0",
"@visx/stats": "3.5.0",
Expand Down
23 changes: 23 additions & 0 deletions packages/visx-demo/src/components/Gallery/SankeyTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import Sankey, { SankeyDemoProps, background, color } from '../../sandboxes/visx-sankey/Example';
import GalleryTile from '../GalleryTile';

export { default as packageJson } from '../../sandboxes/visx-sankey/package.json';

const tileStyles = { background };
const detailsStyles = { color };
const exampleProps = { showControls: false };

export default function SankeyTile() {
return (
<GalleryTile<SankeyDemoProps>
title="Sankey"
description="<Sankey.Sankey />"
exampleProps={exampleProps}
exampleRenderer={Sankey}
exampleUrl="/sankey"
tileStyles={tileStyles}
detailsStyles={detailsStyles}
/>
);
}
2 changes: 2 additions & 0 deletions packages/visx-demo/src/components/Gallery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import * as PolygonsTile from './PolygonsTile';
import * as RadarTile from './RadarTile';
import * as RadialBarsTile from './RadialBarsTile';
import * as ResponsiveTile from './ResponsiveTile';
import * as SankeyTile from './SankeyTile';
import * as SplitLinePathTile from './SplitLinePathTile';
import * as StackedAreasTile from './StackedAreasTile';
import * as StatsPlotTile from './StatsPlotTile';
Expand Down Expand Up @@ -98,6 +99,7 @@ export const tiles = [
RadarTile,
RadialBarsTile,
ResponsiveTile,
SankeyTile,
SplitLinePathTile,
StatsPlotTile,
TextTile,
Expand Down
6 changes: 6 additions & 0 deletions packages/visx-demo/src/components/PackageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ export default function PackageList({
</Link>
{!compact && <p>Animated visx primitives</p>}
</li>
<li className={cx(emphasizePackage === 'sankey' && 'emphasize')}>
<Link href="/docs/sankey">
<a>sankey</a>
</Link>
{!compact && <p>Components to visualize sankey charts</p>}
</li>
<li className={cx(emphasizePackage === 'stats' && 'emphasize')}>
<Link href="/docs/stats">
<a>stats</a>
Expand Down
21 changes: 21 additions & 0 deletions packages/visx-demo/src/pages/docs/sankey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import SankeyReadme from '!!raw-loader!../../../../visx-sankey/Readme.md';
import Sankey from '../../../../visx-sankey/src/Sankey';
import DocPage from '../../components/DocPage';
import SankeyTile from '../../components/Gallery/SankeyTile';

const components = [Sankey];

const examples = [SankeyTile];

function SankeyDocs() {
return (
<DocPage
components={components}
examples={examples}
readme={SankeyReadme}
visxPackage="sankey"
/>
);
}
export default SankeyDocs;
19 changes: 19 additions & 0 deletions packages/visx-demo/src/pages/sankey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import Sankey from '../sandboxes/visx-sankey/Example';
import packageJson from '../sandboxes/visx-sankey/package.json';
import Show from '../components/Show';
import TreemapSource from '!!raw-loader!../sandboxes/visx-sankey/Example';

function SankeyPage() {
return (
<Show
component={Sankey}
title="Sankey"
codeSandboxDirectoryName="visx-sankey"
packageJson={packageJson}
>
{TreemapSource}
</Show>
);
}
export default SankeyPage;
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import radarPackageJson from './visx-radar/package.json';
import responsivePackageJson from './visx-responsive/package.json';
import lineRadialPackageJson from './visx-shape-line-radial/package.json';
import piePackageJson from './visx-shape-pie/package.json';
import sankeyPackageJson from './visx-sankey/package.json';
import splitLinePathPackageJson from './visx-shape-splitlinepath/package.json';
import stackedAreasPackageJson from './visx-stacked-areas/package.json';
import statsPackageJson from './visx-stats/package.json';
Expand Down Expand Up @@ -78,6 +79,7 @@ const examples = [
polygonsPackageJson,
radarPackageJson,
responsivePackageJson,
sankeyPackageJson,
splitLinePathPackageJson,
stackedAreasPackageJson,
statsPackageJson,
Expand Down
189 changes: 189 additions & 0 deletions packages/visx-demo/src/sandboxes/visx-sankey/Example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import React, { useState } from 'react';
import {
Sankey,
sankeyCenter,
sankeyRight,
sankeyLeft,
sankeyJustify,
SankeyNode,
} from '@visx/sankey';
import { Group } from '@visx/group';
import { BarRounded, LinkHorizontal } from '@visx/shape';
import { useTooltip, TooltipWithBounds } from '@visx/tooltip';
import { localPoint } from '@visx/event';

import energy from './energy.json';

export const background = '#84dccf';
export const color = '#392f5a';

type NodeDatum = { name: string };
type LinkDatum = {};

const nodeAlignments = {
sankeyCenter,
sankeyJustify,
sankeyLeft,
sankeyRight,
} as const;

const defaultMargin = { top: 10, left: 10, right: 10, bottom: 10 };

export type SankeyDemoProps = {
width: number;
height: number;
showControls?: boolean;
margin?: { top: number; right: number; bottom: number; left: number };
};

export default function SankeyDemo({
width,
height,
showControls = true,
margin = defaultMargin,
}: SankeyDemoProps) {
const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } =
useTooltip();
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;

const [nodeAlignment, setTileMethod] = useState<keyof typeof nodeAlignments>('sankeyCenter');
const [nodePadding, setNodePadding] = useState(10);
const [nodeWidth, setNodeWidth] = useState(10);

if (width < 10) return null;

return (
<div>
<style>{`
.visx-sankey-link:hover {
stroke-opacity: 0.7;
}
.visx-sankey-node:hover {
filter: brightness(1.3);
}
.visx-sankey-demo-container {
background: ${background};
padding: ${margin.top}px ${margin.right}px ${margin.bottom}px ${margin.left}px;
border-radius: 5px;
position: relative;
}
.visx-sankey-demo-controls {
font-size: 12px;
}
`}</style>
{showControls && (
<div className="visx-sankey-demo-controls">
<label>
node alignment{' '}
<select
onClick={(e) => e.stopPropagation()}
onChange={(e) => setTileMethod(e.target.value as keyof typeof nodeAlignments)}
value={nodeAlignment}
>
{Object.keys(nodeAlignments).map((alignment) => (
<option key={alignment} value={alignment}>
{alignment}
</option>
))}
</select>
</label>{' '}
<label>
node padding{' '}
<input
type="number"
value={nodePadding}
onChange={(e) => setNodePadding(Number(e.target.value))}
/>
</label>{' '}
<label>
node width{' '}
<input
type="number"
value={nodeWidth}
onChange={(e) => setNodeWidth(Number(e.target.value))}
/>
</label>
</div>
)}
<div className="visx-sankey-demo-container">
<svg width={xMax} height={yMax}>
<Sankey<NodeDatum, LinkDatum>
root={energy}
nodeWidth={nodeWidth}
size={[xMax, yMax]}
nodePadding={nodePadding}
nodeAlign={nodeAlignments[nodeAlignment]}
>
{({ graph, createPath }) => (
<>
<Group>
{graph.links.map((link, i) => (
<LinkHorizontal
key={i}
className="visx-sankey-link"
data={link}
path={createPath}
fill="transparent"
stroke={color}
strokeWidth={link.width}
strokeOpacity={0.5}
onPointerMove={(event) => {
const coords = localPoint(
(event.target as SVGElement).ownerSVGElement,
event,
);
showTooltip({
tooltipData: `${
(link.source as SankeyNode<NodeDatum, LinkDatum>).name
} > ${(link.target as SankeyNode<NodeDatum, LinkDatum>).name} = ${
link.value
}`,
tooltipTop: (coords?.y ?? 0) + 10,
tooltipLeft: (coords?.x ?? 0) + 10,
});
}}
onMouseOut={hideTooltip}
/>
))}
</Group>
<Group>
{graph.nodes.map(({ y0, y1, x0, x1, name }, i) => (
<BarRounded
key={i}
className="visx-sankey-node"
width={x1 - x0}
height={y1 - y0}
x={x0}
y={y0}
radius={3}
all
fill={color}
onPointerMove={(event) => {
const coords = localPoint(
(event.target as SVGElement).ownerSVGElement,
event,
);
showTooltip({
tooltipData: name,
tooltipTop: (coords?.y ?? 0) + 10,
tooltipLeft: (coords?.x ?? 0) + 10,
});
}}
onMouseOut={hideTooltip}
/>
))}
</Group>
</>
)}
</Sankey>
</svg>
{tooltipOpen && (
<TooltipWithBounds key={Math.random()} top={tooltipTop} left={tooltipLeft}>
{tooltipData}
</TooltipWithBounds>
)}
</div>
</div>
);
}
1 change: 1 addition & 0 deletions packages/visx-demo/src/sandboxes/visx-sankey/energy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"nodes":[{"name":"Agricultural 'waste'"},{"name":"Bio-conversion"},{"name":"Liquid"},{"name":"Losses"},{"name":"Solid"},{"name":"Gas"},{"name":"Biofuel imports"},{"name":"Biomass imports"},{"name":"Coal imports"},{"name":"Coal"},{"name":"Coal reserves"},{"name":"District heating"},{"name":"Industry"},{"name":"Heating and cooling - commercial"},{"name":"Heating and cooling - homes"},{"name":"Electricity grid"},{"name":"Over generation / exports"},{"name":"H2 conversion"},{"name":"Road transport"},{"name":"Agriculture"},{"name":"Rail transport"},{"name":"Lighting & appliances - commercial"},{"name":"Lighting & appliances - homes"},{"name":"Gas imports"},{"name":"Ngas"},{"name":"Gas reserves"},{"name":"Thermal generation"},{"name":"Geothermal"},{"name":"H2"},{"name":"Hydro"},{"name":"International shipping"},{"name":"Domestic aviation"},{"name":"International aviation"},{"name":"National navigation"},{"name":"Marine algae"},{"name":"Nuclear"},{"name":"Oil imports"},{"name":"Oil"},{"name":"Oil reserves"},{"name":"Other waste"},{"name":"Pumped heat"},{"name":"Solar PV"},{"name":"Solar Thermal"},{"name":"Solar"},{"name":"Tidal"},{"name":"UK land based bioenergy"},{"name":"Wave"},{"name":"Wind"}],"links":[{"source":0,"target":1,"value":124.729},{"source":1,"target":2,"value":0.597},{"source":1,"target":3,"value":26.862},{"source":1,"target":4,"value":280.322},{"source":1,"target":5,"value":81.144},{"source":6,"target":2,"value":35},{"source":7,"target":4,"value":35},{"source":8,"target":9,"value":11.606},{"source":10,"target":9,"value":63.965},{"source":9,"target":4,"value":75.571},{"source":11,"target":12,"value":10.639},{"source":11,"target":13,"value":22.505},{"source":11,"target":14,"value":46.184},{"source":15,"target":16,"value":104.453},{"source":15,"target":14,"value":113.726},{"source":15,"target":17,"value":27.14},{"source":15,"target":12,"value":342.165},{"source":15,"target":18,"value":37.797},{"source":15,"target":19,"value":4.412},{"source":15,"target":13,"value":40.858},{"source":15,"target":3,"value":56.691},{"source":15,"target":20,"value":7.863},{"source":15,"target":21,"value":90.008},{"source":15,"target":22,"value":93.494},{"source":23,"target":24,"value":40.719},{"source":25,"target":24,"value":82.233},{"source":5,"target":13,"value":0.129},{"source":5,"target":3,"value":1.401},{"source":5,"target":26,"value":151.891},{"source":5,"target":19,"value":2.096},{"source":5,"target":12,"value":48.58},{"source":27,"target":15,"value":7.013},{"source":17,"target":28,"value":20.897},{"source":17,"target":3,"value":6.242},{"source":28,"target":18,"value":20.897},{"source":29,"target":15,"value":6.995},{"source":2,"target":12,"value":121.066},{"source":2,"target":30,"value":128.69},{"source":2,"target":18,"value":135.835},{"source":2,"target":31,"value":14.458},{"source":2,"target":32,"value":206.267},{"source":2,"target":19,"value":3.64},{"source":2,"target":33,"value":33.218},{"source":2,"target":20,"value":4.413},{"source":34,"target":1,"value":4.375},{"source":24,"target":5,"value":122.952},{"source":35,"target":26,"value":839.978},{"source":36,"target":37,"value":504.287},{"source":38,"target":37,"value":107.703},{"source":37,"target":2,"value":611.99},{"source":39,"target":4,"value":56.587},{"source":39,"target":1,"value":77.81},{"source":40,"target":14,"value":193.026},{"source":40,"target":13,"value":70.672},{"source":41,"target":15,"value":59.901},{"source":42,"target":14,"value":19.263},{"source":43,"target":42,"value":19.263},{"source":43,"target":41,"value":59.901},{"source":4,"target":19,"value":0.882},{"source":4,"target":26,"value":400.12},{"source":4,"target":12,"value":46.477},{"source":26,"target":15,"value":525.531},{"source":26,"target":3,"value":787.129},{"source":26,"target":11,"value":79.329},{"source":44,"target":15,"value":9.452},{"source":45,"target":1,"value":182.01},{"source":46,"target":15,"value":19.013},{"source":47,"target":15,"value":289.366}]}
12 changes: 12 additions & 0 deletions packages/visx-demo/src/sandboxes/visx-sankey/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import ParentSize from '@visx/responsive/lib/components/ParentSize';

import Example from './Example';
import './sandbox-styles.css';

const root = createRoot(document.getElementById('root')!);

root.render(
<ParentSize>{({ width, height }) => <Example width={width} height={height} />}</ParentSize>,
);
29 changes: 29 additions & 0 deletions packages/visx-demo/src/sandboxes/visx-sankey/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@visx/demo-sankey",
"description": "Standalone visx sankey demo.",
"main": "index.tsx",
"private": true,
"dependencies": {
"@babel/runtime": "^7.8.4",
"@types/react": "^18",
"@types/react-dom": "^18" ,
"@visx/event": "latest",
"@visx/group": "latest",
"@visx/responsive": "latest",
"@visx/sankey": "latest",
"@visx/shape": "latest",
"@visx/tooltip": "latest",
"react": "^18",
"react-dom": "^18",
"react-scripts-ts": "3.1.0",
"typescript": "^3"
},
"keywords": [
"visualization",
"d3",
"react",
"visx",
"sankey"
]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
html,
body,
#root {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 2em;
}
1 change: 1 addition & 0 deletions packages/visx-demo/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type VisxPackage =
| 'point'
| 'react-spring'
| 'responsive'
| 'sankey'
| 'scale'
| 'shape'
| 'stats'
Expand Down
3 changes: 3 additions & 0 deletions packages/visx-demo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
{
"path": "../visx-responsive"
},
{
"path": "../visx-sankey"
},
{
"path": "../visx-scale"
},
Expand Down
1 change: 1 addition & 0 deletions packages/visx-sankey/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
Loading

0 comments on commit 07a91d8

Please sign in to comment.