Skip to content

Commit

Permalink
A pie chart with some animation
Browse files Browse the repository at this point in the history
  • Loading branch information
mladlow committed Oct 6, 2017
1 parent 044532b commit 7e9853e
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 3 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"dependencies": {
"d3-dsv": "=1.0.7",
"d3-interpolate": "=1.1.5",
"d3-shape": "=1.2.0",
"moment": "=2.18.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
Expand Down
101 changes: 101 additions & 0 deletions src/AlignPie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { Component } from 'react';
import * as d3s from 'd3-shape';
import * as d3i from 'd3-interpolate';

const maxDuration = 500;
const step = 10;
const padRadians = 2 * Math.PI * 0.01;
const minPercentageOfTotal = 0.02; // 2 percent

const pieColors = d3i.interpolateRgbBasis([
'#7C98B3',
'#B3B7EE',
'#AF90A9',
'#A05C7B',
'#944654',
]);

class PieSlice extends Component {
render() {
const { slice, arcGen, progress, color } = this.props;

return <g>
<path
d={arcGen({
startAngle: slice.startAngle,
endAngle: slice.interpolator(progress),
})}
fill={pieColors(color)} />
</g>;
}
}

class AlignPie extends Component {
constructor(props) {
super(props);
this.state = {
intervalId: null,
tick: 0,
};
this.handleInterval = this.handleInterval.bind(this);
}

handleInterval() {
if (this.state.tick >= maxDuration) {
clearInterval(this.state.intervalId);
} else {
this.setState({ tick: this.state.tick + step });
}
}

componentWillMount() {
this.setState({intervalId: setInterval(this.handleInterval, 10)});
}

render() {
const size = 500;
const radius = size * 0.8 / 2;
const total = Array.from(this.props.data.values())
.reduce((acc, val) => acc + val, 0);
const dataArray = Array.from(this.props.data.entries())
.map(([key, value]) => value / total > minPercentageOfTotal ? [key, value] : [key, total * minPercentageOfTotal]);
const pie = d3s.pie()
.value((d) => d[1])(dataArray)
.map((slice) => {
slice.interpolator = d3i.interpolateNumber(slice.startAngle, slice.endAngle - padRadians);
return slice;
});

const path = d3s.arc()
.outerRadius(radius)
.innerRadius(0)

const label = d3s.arc()
.outerRadius(radius * 0.8)
.innerRadius(radius * 0.8);

return <svg width={`${size}px`} height={`${size}px`}>
<g transform={`translate(${size/2},${size/2})`}>
{pie.map((angle) => <PieSlice
key={angle.data[0].replace(/ /g, '')}
progress={this.state.tick/maxDuration}
color={angle.data[1]/total}
slice={angle}
arcGen={path} />)}
{pie.map((angle) => {
const textPosition = label.centroid(angle);
const textAnchor = textPosition[0] <= 0 ? 'start' : 'middle';
return <text
key={`text_${angle.data[0].replace(/ /g, '')}`}
transform={`translate(${textPosition})`}
textAnchor={textAnchor}
dy="0.035em">
{angle.data[0]}
</text>;
})}
</g>
</svg>;
}
}

export default AlignPie;
8 changes: 5 additions & 3 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { csvParse } from 'd3-dsv';
import { combineCsvs } from './dataParsers';
import * as parsers from './dataParsers';
import AlignPie from './AlignPie';

class App extends Component {
constructor(props) {
Expand All @@ -21,7 +22,7 @@ class App extends Component {
.then((response) => response.ok ? response.text() : Promise.reject(`Failed to fetch ${url}`))
.then((text) => csvParse(text));
}))
.then((parsedCsvs) => this.setState({ data: combineCsvs([
.then((parsedCsvs) => this.setState({ data: parsers.combineCsvs([
{
data: parsedCsvs[0],
dateFormat: 'YYYY, MMMM',
Expand All @@ -40,7 +41,8 @@ class App extends Component {
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
{ this.state.data ? 'I loaded all the data!' : 'Loading...' }
{ !this.state.data && 'Loading...' }
{ this.state.data && <AlignPie data={parsers.extractAlignmentCounts(this.state.data)} />}
</p>
</div>
);
Expand Down
11 changes: 11 additions & 0 deletions src/dataParsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ export const combineCsvs = (csvArray) => {
return csvArray.reduce((acc, current) => {
const dateFormatted = current.data.map((value) => {
value['FIRST APPEARANCE'] = moment(value['FIRST APPEARANCE'], current.dateFormat).format('MM/YYYY');
if (!value['ALIGN']) {
value['ALIGN'] = 'Unknown';
}
return value;
});
return acc.concat(dateFormatted);
}, []);
};

export const extractAlignmentCounts = (data) => {
return data.reduce((acc, item) => {
const count = acc.get(item['ALIGN']) || 0;
acc.set(item['ALIGN'], count + 1);
return acc;
}, new Map());
};
20 changes: 20 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,10 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"

d3-color@1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"

d3-dsv@=1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.7.tgz#137076663f398428fc3d031ae65370522492b78f"
Expand All @@ -1739,6 +1743,22 @@ d3-dsv@=1.0.7:
iconv-lite "0.4"
rw "1"

d3-interpolate@=1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.5.tgz#69e099ff39214716e563c9aec3ea9d1ea4b8a79f"
dependencies:
d3-color "1"

d3-path@1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"

d3-shape@=1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
dependencies:
d3-path "1"

d@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
Expand Down

0 comments on commit 7e9853e

Please sign in to comment.