Skip to content

Commit 7e9853e

Browse files
committed
A pie chart with some animation
1 parent 044532b commit 7e9853e

File tree

5 files changed

+139
-3
lines changed

5 files changed

+139
-3
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"private": true,
55
"dependencies": {
66
"d3-dsv": "=1.0.7",
7+
"d3-interpolate": "=1.1.5",
8+
"d3-shape": "=1.2.0",
79
"moment": "=2.18.1",
810
"react": "^16.0.0",
911
"react-dom": "^16.0.0",

src/AlignPie.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { Component } from 'react';
2+
import * as d3s from 'd3-shape';
3+
import * as d3i from 'd3-interpolate';
4+
5+
const maxDuration = 500;
6+
const step = 10;
7+
const padRadians = 2 * Math.PI * 0.01;
8+
const minPercentageOfTotal = 0.02; // 2 percent
9+
10+
const pieColors = d3i.interpolateRgbBasis([
11+
'#7C98B3',
12+
'#B3B7EE',
13+
'#AF90A9',
14+
'#A05C7B',
15+
'#944654',
16+
]);
17+
18+
class PieSlice extends Component {
19+
render() {
20+
const { slice, arcGen, progress, color } = this.props;
21+
22+
return <g>
23+
<path
24+
d={arcGen({
25+
startAngle: slice.startAngle,
26+
endAngle: slice.interpolator(progress),
27+
})}
28+
fill={pieColors(color)} />
29+
</g>;
30+
}
31+
}
32+
33+
class AlignPie extends Component {
34+
constructor(props) {
35+
super(props);
36+
this.state = {
37+
intervalId: null,
38+
tick: 0,
39+
};
40+
this.handleInterval = this.handleInterval.bind(this);
41+
}
42+
43+
handleInterval() {
44+
if (this.state.tick >= maxDuration) {
45+
clearInterval(this.state.intervalId);
46+
} else {
47+
this.setState({ tick: this.state.tick + step });
48+
}
49+
}
50+
51+
componentWillMount() {
52+
this.setState({intervalId: setInterval(this.handleInterval, 10)});
53+
}
54+
55+
render() {
56+
const size = 500;
57+
const radius = size * 0.8 / 2;
58+
const total = Array.from(this.props.data.values())
59+
.reduce((acc, val) => acc + val, 0);
60+
const dataArray = Array.from(this.props.data.entries())
61+
.map(([key, value]) => value / total > minPercentageOfTotal ? [key, value] : [key, total * minPercentageOfTotal]);
62+
const pie = d3s.pie()
63+
.value((d) => d[1])(dataArray)
64+
.map((slice) => {
65+
slice.interpolator = d3i.interpolateNumber(slice.startAngle, slice.endAngle - padRadians);
66+
return slice;
67+
});
68+
69+
const path = d3s.arc()
70+
.outerRadius(radius)
71+
.innerRadius(0)
72+
73+
const label = d3s.arc()
74+
.outerRadius(radius * 0.8)
75+
.innerRadius(radius * 0.8);
76+
77+
return <svg width={`${size}px`} height={`${size}px`}>
78+
<g transform={`translate(${size/2},${size/2})`}>
79+
{pie.map((angle) => <PieSlice
80+
key={angle.data[0].replace(/ /g, '')}
81+
progress={this.state.tick/maxDuration}
82+
color={angle.data[1]/total}
83+
slice={angle}
84+
arcGen={path} />)}
85+
{pie.map((angle) => {
86+
const textPosition = label.centroid(angle);
87+
const textAnchor = textPosition[0] <= 0 ? 'start' : 'middle';
88+
return <text
89+
key={`text_${angle.data[0].replace(/ /g, '')}`}
90+
transform={`translate(${textPosition})`}
91+
textAnchor={textAnchor}
92+
dy="0.035em">
93+
{angle.data[0]}
94+
</text>;
95+
})}
96+
</g>
97+
</svg>;
98+
}
99+
}
100+
101+
export default AlignPie;

src/App.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import React, { Component } from 'react';
22
import logo from './logo.svg';
33
import './App.css';
44
import { csvParse } from 'd3-dsv';
5-
import { combineCsvs } from './dataParsers';
5+
import * as parsers from './dataParsers';
6+
import AlignPie from './AlignPie';
67

78
class App extends Component {
89
constructor(props) {
@@ -21,7 +22,7 @@ class App extends Component {
2122
.then((response) => response.ok ? response.text() : Promise.reject(`Failed to fetch ${url}`))
2223
.then((text) => csvParse(text));
2324
}))
24-
.then((parsedCsvs) => this.setState({ data: combineCsvs([
25+
.then((parsedCsvs) => this.setState({ data: parsers.combineCsvs([
2526
{
2627
data: parsedCsvs[0],
2728
dateFormat: 'YYYY, MMMM',
@@ -40,7 +41,8 @@ class App extends Component {
4041
<h1 className="App-title">Welcome to React</h1>
4142
</header>
4243
<p className="App-intro">
43-
{ this.state.data ? 'I loaded all the data!' : 'Loading...' }
44+
{ !this.state.data && 'Loading...' }
45+
{ this.state.data && <AlignPie data={parsers.extractAlignmentCounts(this.state.data)} />}
4446
</p>
4547
</div>
4648
);

src/dataParsers.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@ export const combineCsvs = (csvArray) => {
44
return csvArray.reduce((acc, current) => {
55
const dateFormatted = current.data.map((value) => {
66
value['FIRST APPEARANCE'] = moment(value['FIRST APPEARANCE'], current.dateFormat).format('MM/YYYY');
7+
if (!value['ALIGN']) {
8+
value['ALIGN'] = 'Unknown';
9+
}
710
return value;
811
});
912
return acc.concat(dateFormatted);
1013
}, []);
1114
};
15+
16+
export const extractAlignmentCounts = (data) => {
17+
return data.reduce((acc, item) => {
18+
const count = acc.get(item['ALIGN']) || 0;
19+
acc.set(item['ALIGN'], count + 1);
20+
return acc;
21+
}, new Map());
22+
};

yarn.lock

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,10 @@ currently-unhandled@^0.4.1:
17311731
dependencies:
17321732
array-find-index "^1.0.1"
17331733

1734+
d3-color@1:
1735+
version "1.0.3"
1736+
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
1737+
17341738
d3-dsv@=1.0.7:
17351739
version "1.0.7"
17361740
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.7.tgz#137076663f398428fc3d031ae65370522492b78f"
@@ -1739,6 +1743,22 @@ d3-dsv@=1.0.7:
17391743
iconv-lite "0.4"
17401744
rw "1"
17411745

1746+
d3-interpolate@=1.1.5:
1747+
version "1.1.5"
1748+
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.5.tgz#69e099ff39214716e563c9aec3ea9d1ea4b8a79f"
1749+
dependencies:
1750+
d3-color "1"
1751+
1752+
d3-path@1:
1753+
version "1.0.5"
1754+
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"
1755+
1756+
d3-shape@=1.2.0:
1757+
version "1.2.0"
1758+
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
1759+
dependencies:
1760+
d3-path "1"
1761+
17421762
d@1:
17431763
version "1.0.0"
17441764
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"

0 commit comments

Comments
 (0)