Skip to content

Commit 200e249

Browse files
authored
Merge pull request #122 from UTD-CRSS/graph-widgets
Graph widgets
2 parents 046c60c + 0469728 commit 200e249

File tree

14 files changed

+747
-65
lines changed

14 files changed

+747
-65
lines changed

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"chai": "^3.2.0",
3030
"eslint": "^1.3.1",
3131
"eslint-plugin-react": "^3.5.1",
32+
"json-loader": "^0.5.4",
3233
"karma": "^0.13.9",
3334
"karma-chrome-launcher": "^0.2.0",
3435
"karma-mocha": "^0.2.0",
@@ -51,6 +52,7 @@
5152
"classnames": "^2.2.1",
5253
"connect-history-api-fallback": "^1.1.0",
5354
"css-loader": "^0.23.1",
55+
"d3": "^4.5.0",
5456
"deep-equal": "^1.0.1",
5557
"exports-loader": "^0.6.2",
5658
"file-loader": "^0.8.4",
@@ -67,23 +69,25 @@
6769
"react": "^0.14.3",
6870
"react-dimensions": "^1.0.2",
6971
"react-dom": "^0.14.7",
72+
"react-faux-dom": "^3.0.0",
7073
"react-hot-loader": "^1.3.0",
7174
"react-image-gallery": "^0.5.9",
7275
"react-pure-render": "^1.0.2",
7376
"react-redux": "^4.2.0",
7477
"react-router": "^1.0.2",
7578
"react-spinner": "^0.2.3",
79+
"react-tabs": "^0.8.2",
7680
"react-test-tree": "^1.0.0",
7781
"react-wavesurfer": "^0.7.3",
7882
"redux": "^3.0.4",
7983
"redux-devtools": "^3.0.2",
8084
"redux-logger": "^2.2.1",
8185
"redux-router": "^1.0.0-beta5",
8286
"redux-thunk": "^1.0.0",
87+
"rumble-charts": "^1.1.1",
8388
"sass-loader": "^3.0.0",
8489
"style-loader": "^0.13.0",
8590
"url-loader": "^0.5.6",
86-
"victory": "^0.4.0",
8791
"wavesurfer.js": "^1.1.13",
8892
"webpack": "^1.12.9",
8993
"webpack-dev-middleware": "^1.4.0",

resources/readme.json

+222
Large diffs are not rendered by default.

src/components/BarDiagram/index.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, {Component} from "react";
2+
import shouldPureComponentUpdate from "react-pure-render/function";
3+
import Dimensions from "react-dimensions";
4+
import {Chart, Bars, Ticks, Layer} from "rumble-charts";
5+
6+
class BarDiagram extends Component {
7+
shouldComponentUpdate = shouldPureComponentUpdate;
8+
9+
render() {
10+
const {data, containerWidth} = this.props;
11+
12+
if (data.size < 1) {
13+
return <p className="text-center text-muted">No Data Yet</p>;
14+
}
15+
16+
const options = {
17+
fontFamily: "sans-serif",
18+
fontSize: "0.8em",
19+
colors: ["#33ff66"],
20+
title: "Words over Time"
21+
};
22+
const diagramData = [{
23+
data: data.toArray().map((datum) => {
24+
return Number(datum.getIn(["data", "count"]));
25+
})
26+
}];
27+
const diagramTicks = data.toArray().map((datum) => {
28+
return {name: String(datum.get("met_start"))};
29+
});
30+
31+
return (
32+
<div style={{fontFamily:options.fontFamily,fontSize:options.fontSize}}>
33+
<Chart
34+
width={containerWidth}
35+
height={containerWidth/(16/6)}
36+
series={diagramData}
37+
minY={0}
38+
scaleX={{paddingStart: 0, paddingEnd: 0}}
39+
scaleY={{paddingTop: 10}}>
40+
<Layer width='90%' height='90%'>
41+
<Ticks
42+
axis="y"
43+
ticks={{maxTicks: 5}}
44+
tickVisible={({tick}) => tick.y >= 0}
45+
lineLength="100%"
46+
lineVisible={true}
47+
lineStyle={{stroke:"lightgray"}}
48+
labelStyle={{textAnchor:"end", alignmentBaseline:"middle", fill:options.colors}}
49+
labelAttributes={{x: -5}}/>
50+
<Ticks
51+
axis="x"
52+
ticks={{maxTicks: -1}}
53+
cticks={diagramTicks}
54+
label={({index, props}) => props.cticks[index].name}
55+
labelStyle={{textAnchor:"middle", alignmentBaseline:"before-edge", fill:options.colors}}
56+
labelAttributes={{y: 3}}/>
57+
<Bars
58+
colors={options.colors}
59+
groupPadding="3%"
60+
innerPadding="0.5%"/>
61+
</Layer>
62+
</Chart>
63+
<p className="text-center text-muted">{options.title}</p>
64+
</div>);
65+
}
66+
}
67+
68+
export default Dimensions()(BarDiagram);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import * as d3 from "d3";
2+
import curry from "lodash/curry";
3+
4+
export default curry(function d3ChordDiagram(data, node) {
5+
6+
//FIXME revert to dynamic data after demo!!
7+
var matrix = [
8+
[ 7., 1., 5., 0., 1., 0.],
9+
[ 0., 4., 3., 0., 0., 0.],
10+
[ 6., 2., 17., 1., 1., 1.],
11+
[ 0., 0., 1., 0., 0., 0.],
12+
[ 2., 0., 0., 0., 3., 0.],
13+
[ 0., 0., 1., 0., 0., 4.]];
14+
15+
// Returns the Flare package name for the given class name.
16+
// function name(name) {
17+
// return name.substring(0, name.lastIndexOf(".")).substring(6);
18+
// }
19+
//
20+
// Compute a unique index for each package name.
21+
// data.forEach(function(d) {
22+
// if (!indexByName.has(d = name(d.name))) {
23+
// nameByIndex.set(n, d);
24+
// indexByName.set(d, n++);
25+
// }
26+
// });
27+
//
28+
// // Construct a square matrix counting package imports.
29+
// data.forEach(function(d) {
30+
// var source = indexByName.get(name(d.name)),
31+
// row = matrix[source];
32+
// if (!row) {
33+
// row = matrix[source] = [];
34+
// for (var i = -1; ++i < n;) row[i] = 0;
35+
// }
36+
// d.imports.forEach(function(d) {
37+
// row[indexByName.get(name(d))]++;
38+
// });
39+
// });
40+
41+
//FIXME should be the innerwidth of the tab container, not the entire window
42+
var width = window.innerWidth / 2;
43+
var height = width;
44+
var innerRadius = Math.min(width, height) * 0.35;
45+
var outerRadius = innerRadius * 1.1;
46+
var formatValue = d3.formatPrefix(",.0", 1e3);
47+
var color = d3.scaleOrdinal(d3.schemeCategory20c);
48+
49+
var chord = d3.chord()
50+
.padAngle(0.05)
51+
.sortSubgroups(d3.descending);
52+
53+
var arc = d3.arc()
54+
.innerRadius(innerRadius)
55+
.outerRadius(outerRadius);
56+
57+
var ribbon = d3.ribbon()
58+
.radius(innerRadius);
59+
60+
var svg = d3.select(node)
61+
.attr("id", "visual")
62+
.attr("width", width)
63+
.attr("height", height)
64+
.attr("preserveAspectRatio", "xMinYMid")
65+
.attr("viewBox", "0 0 " + width + " " + height);
66+
67+
//FIXME janky positioning fix with '((width / 2) - 40)'
68+
var g = svg.append("g")
69+
.attr("transform", "translate(" + ((width / 2) - 40) + "," + (height / 2) + ")")
70+
.datum(chord(matrix));
71+
72+
73+
var group = g.append("g")
74+
.attr("class", "groups")
75+
.selectAll("g")
76+
.data(function(chords) {return chords.groups;})
77+
.enter().append("g")
78+
.style("fill", function(d) {return color(d.index);});
79+
80+
group.append("path")
81+
.style("fill", function(d) {return color(d.index);})
82+
.style("stroke", function(d) {return d3.rgb(color(d.index)).darker();})
83+
.attr("d", arc)
84+
.on("mouseover", fade(0.05))
85+
.on("mouseout", fade(1));
86+
87+
var groupTick = group.selectAll(".group-tick")
88+
.data(function(d) {return groupTicks(d, 1e3);})
89+
.enter().append("g")
90+
.attr("class", "group-tick")
91+
.attr("transform", function(d) {return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)";});
92+
93+
groupTick.append("line")
94+
.attr("x2", 6);
95+
96+
//FIXME add text from the matrix to the ticks
97+
groupTick.filter(function(d) {return d.value % 5e3 === 0;})
98+
.append("text")
99+
.attr("x", 8)
100+
.attr("dy", ".35em")
101+
.attr("transform", function(d) {return d.angle > Math.PI ? "rotate(180) translate(-16)" : null;})
102+
.style("text-anchor", function(d) {return d.angle > Math.PI ? "end" : null;})
103+
.text(function(d) {return formatValue(d.value);});
104+
105+
g.append("g")
106+
.attr("class", "ribbons")
107+
.selectAll("path")
108+
.data(function(chords) {return chords;})
109+
.enter().append("path")
110+
.attr("d", ribbon)
111+
.style("fill", function(d) {return color(d.target.index);})
112+
.style("stroke", function(d) {return d3.rgb(color(d.target.index)).darker();})
113+
.style("opacity", 0.9)
114+
.on("mouseover", fadeChord(0.05, 0.05))
115+
.on("mouseout", fadeChord(1, 0.9));
116+
117+
// Returns an array of tick angles and values for a given group and step.
118+
function groupTicks(d, step) {
119+
var k = (d.endAngle - d.startAngle) / d.value;
120+
return d3.range(0, d.value, step).map(function(value) {
121+
return {
122+
value: value,
123+
angle: value * k + d.startAngle + (d.endAngle - d.startAngle) / 2
124+
};
125+
});
126+
}
127+
128+
function fade(opacity) {
129+
return function(g, i) {
130+
svg.selectAll(".chord path")
131+
.filter(function(d) {
132+
return d.source.index != i && d.target.index != i;
133+
})
134+
.transition()
135+
.style("opacity", opacity);
136+
};
137+
}
138+
139+
function fadeChord(opacityArcs, opacityChords) {
140+
return function(g, i) {
141+
svg.selectAll(".chord path")
142+
.filter(function(d,j) { return j!=i; })
143+
.transition()
144+
.style("opacity", opacityChords);
145+
svg.selectAll(".arc path")
146+
.filter(function(d) { return !(d.index == g.source.index || d.index == g.target.index); })
147+
.transition()
148+
.style("opacity", opacityArcs);
149+
};
150+
}
151+
152+
function resize() {
153+
var targetWidth = window.innerWidth / 2;
154+
var svg = d3.select("#visual");
155+
svg.attr("width", targetWidth);
156+
svg.attr("height", targetWidth / (width / height));
157+
}
158+
window.onresize = resize;
159+
});
160+
161+
//d3.select(self.frameElement).style("height", outerRadius * 2 + "px");

src/components/ChordDiagram/index.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, {Component, PropTypes} from "react";
2+
//import fetch from "whatwg-fetch";
3+
import d3ChordDiagram from "./d3ChordDiagram";
4+
import Faux from "react-faux-dom";
5+
6+
export default class ChordDiagram extends Component {
7+
constructor(props) {
8+
super(props);
9+
this.state = {
10+
loading: true
11+
};
12+
}
13+
14+
15+
16+
componentDidMount() {
17+
//FIXME revert to dynamic data after demo!!
18+
const matrix = [
19+
[ 7., 1., 5., 0., 1., 0.],
20+
[ 0., 4., 3., 0., 0., 0.],
21+
[ 6., 2., 17., 1., 1., 1.],
22+
[ 0., 0., 1., 0., 0., 0.],
23+
[ 2., 0., 0., 0., 3., 0.],
24+
[ 0., 0., 1., 0., 0., 4.]];
25+
26+
27+
require.ensure(["../../../resources/readme.json"], () => {
28+
const data = matrix;
29+
this.setState({
30+
data,
31+
loading: false,
32+
d3: d3ChordDiagram(data)
33+
});
34+
});
35+
}
36+
37+
render() {
38+
const {
39+
loading,
40+
//data,
41+
d3 = ""
42+
} = this.state;
43+
44+
if (loading) {
45+
return <div> Loading. </div>;
46+
}
47+
48+
const el = Faux.createElement("svg");
49+
d3(el);
50+
51+
// Render it to React elements.
52+
return el.toReact();
53+
}
54+
}
55+
56+
ChordDiagram.propTypes = {
57+
loading: PropTypes.bool,
58+
data: PropTypes.arrayOf(PropTypes.object)
59+
};

0 commit comments

Comments
 (0)