Skip to content

Commit

Permalink
feat(demo): add radial bars demo using Arc (#1785)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamzuch authored Jan 11, 2024
1 parent f4629c8 commit 7b3f28a
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 0 deletions.
23 changes: 23 additions & 0 deletions packages/visx-demo/src/components/Gallery/RadialBarsTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import RadialBars, { RadialBarsProps } from '../../sandboxes/visx-radial-bars/Example';
import GalleryTile from '../GalleryTile';

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

const tileStyles = { background: '#3dbdb1' };
const detailsStyles = { color: '#93F9B9' };
const exampleProps = { showControls: false };

export default function BarsTile() {
return (
<GalleryTile<RadialBarsProps>
title="Radial Bars"
description="<Shape.Arc />"
exampleProps={exampleProps}
exampleRenderer={RadialBars}
exampleUrl="/radial-bars"
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 @@ -36,6 +36,7 @@ import * as PatternsTile from './PatternsTile';
import * as PiesTile from './PiesTile';
import * as PolygonsTile from './PolygonsTile';
import * as RadarTile from './RadarTile';
import * as RadialBarsTile from './RadialBarsTile';
import * as ResponsiveTile from './ResponsiveTile';
import * as SplitLinePathTile from './SplitLinePathTile';
import * as StackedAreasTile from './StackedAreasTile';
Expand Down Expand Up @@ -95,6 +96,7 @@ export const tiles = [
PackTile,
PolygonsTile,
RadarTile,
RadialBarsTile,
ResponsiveTile,
SplitLinePathTile,
StatsPlotTile,
Expand Down
20 changes: 20 additions & 0 deletions packages/visx-demo/src/pages/radial-bars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import RadialBars from '../sandboxes/visx-radial-bars/Example';
import packageJson from '../sandboxes/visx-radial-bars/package.json';
import Show from '../components/Show';
import RadialBarsSource from '!!raw-loader!../sandboxes/visx-radial-bars/Example';

function BarsRadialPage() {
return (
<Show
events
component={RadialBars}
title="Radial Bars"
codeSandboxDirectoryName="visx-radial-bars"
packageJson={packageJson}
>
{RadialBarsSource}
</Show>
);
}
export default BarsRadialPage;
156 changes: 156 additions & 0 deletions packages/visx-demo/src/sandboxes/visx-radial-bars/Example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useMemo, useState } from 'react';
import { Arc } from '@visx/shape';
import { Group } from '@visx/group';
import { GradientLightgreenGreen } from '@visx/gradient';
import { scaleBand, scaleRadial } from '@visx/scale';
import { Text } from '@visx/text';
import letterFrequency, { LetterFrequency } from '@visx/mock-data/src/mocks/letterFrequency';

const data = letterFrequency;

const getLetter = (d: LetterFrequency) => d.letter;
const getLetterFrequency = (d: LetterFrequency) => Number(d.frequency) * 100;

const frequencySort = (a: LetterFrequency, b: LetterFrequency) => b.frequency - a.frequency;
const alphabeticalSort = (a: LetterFrequency, b: LetterFrequency) =>
a.letter.localeCompare(b.letter);

const toRadians = (x: number) => (x * Math.PI) / 180;
const toDegrees = (x: number) => (x * 180) / Math.PI;

const barColor = '#93F9B9';
const margin = { top: 20, bottom: 20 };

export type RadialBarsProps = {
width: number;
height: number;
showControls?: boolean;
};

export default function Example({ width, height, showControls = true }: RadialBarsProps) {
const [rotation, setRotation] = useState(0);
const [sortAlphabetically, setSortAlphabetically] = useState(true);

// bounds
const xMax = width;
const yMax = height - margin.top - margin.bottom;
const radiusMax = Math.min(xMax, yMax) / 2;

const innerRadius = radiusMax / 3;

const xDomain = useMemo(
() => data.sort(sortAlphabetically ? alphabeticalSort : frequencySort).map(getLetter),
[sortAlphabetically],
);

const xScale = useMemo(
() =>
scaleBand<string>({
range: [0 + rotation, 2 * Math.PI + rotation],
domain: xDomain,
padding: 0.2,
}),
[rotation, xDomain],
);

const yScale = useMemo(
() =>
scaleRadial<number>({
range: [innerRadius, radiusMax],
domain: [0, Math.max(...data.map(getLetterFrequency))],
}),
[innerRadius, radiusMax],
);

return width < 10 ? null : (
<>
<svg width={width} height={height}>
<GradientLightgreenGreen id="green" />
<rect width={width} height={height} fill="url(#green)" rx={14} />
<Group top={yMax / 2 + margin.top} left={xMax / 2}>
{data.map((d) => {
const letter = getLetter(d);
const startAngle = xScale(letter);
const midAngle = startAngle + xScale.bandwidth() / 2;
const endAngle = startAngle + xScale.bandwidth();

const outerRadius = yScale(getLetterFrequency(d)) ?? 0;

// convert polar coordinates to cartesian for drawing labels
const textRadius = outerRadius + 4;
const textX = textRadius * Math.cos(midAngle - Math.PI / 2);
const textY = textRadius * Math.sin(midAngle - Math.PI / 2);

return (
<>
<Arc
key={`bar-${letter}`}
cornerRadius={4}
startAngle={startAngle}
endAngle={endAngle}
outerRadius={outerRadius}
innerRadius={innerRadius}
fill={barColor}
/>
<Text
x={textX}
y={textY}
dominantBaseline="end"
textAnchor="middle"
fontSize={16}
fontWeight="bold"
fill={barColor}
angle={toDegrees(midAngle)}
>
{letter}
</Text>
</>
);
})}
</Group>
</svg>
{showControls && (
<div className="controls">
<label>
<strong>Rotate</strong>&nbsp;
<input
type="range"
min="0"
max="360"
value={toDegrees(rotation)}
onChange={(e) => setRotation(toRadians(Number(e.target.value)))}
/>
&nbsp;{toDegrees(rotation).toFixed(0)}°
</label>
<br />
<div>
<strong>Sort bars</strong>&nbsp;&nbsp;&nbsp;
<label>
<input
type="radio"
checked={sortAlphabetically}
onChange={(e) => setSortAlphabetically(true)}
/>
Alphabetically&nbsp;&nbsp;&nbsp;
</label>
<label>
<input
type="radio"
checked={!sortAlphabetically}
onChange={(e) => setSortAlphabetically(false)}
/>
By frequency
</label>
</div>
<br />
</div>
)}
<style jsx>{`
.controls {
font-size: 14px;
line-height: 1.5em;
}
`}</style>
</>
);
}
12 changes: 12 additions & 0 deletions packages/visx-demo/src/sandboxes/visx-radial-bars/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-radial-bars/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@visx/demo-radial-bars",
"description": "Standalone visx radial bars demo.",
"main": "index.tsx",
"private": true,
"dependencies": {
"@babel/runtime": "^7.8.4",
"@types/react": "^18",
"@types/react-dom": "^18",
"@visx/gradient": "latest",
"@visx/group": "latest",
"@visx/mock-data": "latest",
"@visx/responsive": "latest",
"@visx/scale": "latest",
"@visx/shape": "latest",
"@visx/text": "latest",
"react": "^18",
"react-dom": "^18",
"react-scripts-ts": "3.1.0",
"typescript": "^3"
},
"keywords": [
"visualization",
"d3",
"react",
"visx",
"bar"
]
}
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;
}

0 comments on commit 7b3f28a

Please sign in to comment.