-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
259 lines (227 loc) · 8.59 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<!DOCTYPE html>
<meta charset="utf-8">
<style>
html, body {
height: 100%;
margin: 0;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
min-height: 100%;
vertical-align: top;
overflow: hidden;
}
.svg-content {
display: inline-block;
position: absolute;
top: 0;
left: 0;
}
.legend-box {
fill: white;
stroke: black;
}
.hints-div {
position: absolute;
top: 10%;
left: 20%;
width: 60%;
background: white;
outline: black solid;
padding: 0 10px 0 10px;
}
.topcorner {
position: absolute;
top: 0;
right: 0;
padding: 0 10px 0 10px;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.d3-tip p {
text-align: center;
}
p, h3 {
font-family: monospace;
font-size: 14pt;
}
</style>
<link rel="stylesheet" href="https://rawgithub.jparrowsec.cn/Caged/d3-tip/master/examples/example-styles.css">
<div id="container" class="svg-container"></div>
<div class="topcorner">
<p>hints/about</p>
</div>
<div class="hints-div">
<h3>hints/about</h3>
<p>each <b>large point</b> is an album, color coded by who selected it</p>
<p><b>small points</b> are related albums that have not actually been selected</p>
<p><b>scroll and drag</b> in empty space to zoom and pan</p>
<p><b>mouse over</b> an album to see its title, when it was selected, and who selected it</p>
<p><b>click and drag</b> an album to move it around</p>
<p><b>double click</b> on an album to open its YouTube video in a new tab</p>
<p><b>right click</b> on an album or on the legend to toggle highlighting the albums selected by one person</p>
<p>go <b>full-screen</b> to maximize your experience (f11 in chrome)</p>
<p>use <b>esc</b> to toggle this panel</p>
<br>
<p>built with <a href="https://d3js.org/">d3.js</a> by <a href="http://sclabs.gilgi.org/">scoot's canoe labs</a></p>
<p>full source available on <a href="https://github.com/sclabs/aotd-graph-d3">github</a></p>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.24.0/d3-legend.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.8.0-alpha.1/d3-tip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script>
// the root svg element
var svg = d3.select("div#container")
.append("svg")
.attr("preserveAspectRatio", "none") // important for full-screen effect
.attr("viewBox", "0 0 650 650") // ditto
.classed("svg-content", true);
// a group right under the root svg that contains all elements that are affected by the zoom
var zoomable = svg.append("g");
// hint div toggling
var hintDiv = d3.select('div.hints-div');
function toggleHintDiv() {
var oldDisplay = hintDiv.style('display');
hintDiv.style('display', oldDisplay === 'none' ? null : 'none');
}
d3.select('div.topcorner p').on('click', toggleHintDiv);
d3.select('body').on('keydown', function() { if (d3.event.keyCode === 27) toggleHintDiv(); });
// set up color scale
var color = d3.scaleOrdinal(d3.schemeCategory20);
// set up spring force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.url; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(650 / 2, 650 / 2));
// set up d3-tip tooltips for showing album titles on mouseover
var tip = d3.tip().attr('class', 'd3-tip').html(function(d) {
if (d.submitter) {
return '<p>' + d.title + '</p><p>' + moment(d.date).format('MM/DD/YYYY') + ' by ' + d.submitter + '</p>';
}
else {
return d.title;
}
});
svg.call(tip);
// set up zoom
var zoom = d3.zoom()
.scaleExtent([0.01, 40])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
zoomable.attr("transform", d3.event.transform);
}
// state variable for highlighting a single submitter
var selectedSubmitter = null;
// load data and draw links, nodes, and legend
d3.json("graph.json", function(error, graph) {
// error handling: handles 100% of errors
if (error) throw error;
// draw links
var link = zoomable.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.edges)
.enter().append("line")
.attr("stroke-width", 1);
// draw nodes
var node = zoomable.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.vertices)
.enter().append("circle")
.attr("r", function(d) { return d.submitter ? 25 : 10})
.attr("fill", function(d) { return color(d.submitter ? d.submitter : 'no one'); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.on('dblclick', function(d) { return window.open('https://youtube.com/' + d.url, '_blank'); })
.on('contextmenu', highlight)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// hook simulation into nodes and links
simulation
.nodes(graph.vertices)
.on("tick", ticked);
simulation.force("link")
.links(graph.edges);
// set up legend
// step 1: create a group for all legend-related elements
var legendGroup = svg.append("g")
.attr("class", "legendOrdinal")
.attr("transform", "translate(20,20)");
// step 2: first thing (lowest in z-order) to add is a rectangle to outline the legend with
// we will fill in its size later
var legendBox = legendGroup.append("rect")
.attr("class", "legend-box");
// step 3: invoke d3-legend to create the legend
var legendOrdinal = d3.legendColor()
.shape("path", d3.symbol().type(d3.symbolCircle).size(150)())
.shapePadding(10)
.scale(color);
var legend = svg.select(".legendOrdinal")
.call(legendOrdinal);
// step 4: select the legend cells - we will do something with them
var legendCells = legendGroup.selectAll('.cell');
// step 4a: bind the highlight action to right click on the legend cells
legendCells.on('contextmenu', highlight);
// step 4b: figure out what size to make the legendBox by iterating over the legendCells
var maxWidth = 0;
var totalHeight = 0;
legendCells.each(function(d) {
var bbox = d3.select(this).node().getBBox();
maxWidth = bbox.width > maxWidth ? bbox.width : maxWidth;
totalHeight += bbox.height + 10;
});
legendBox.attr("width", maxWidth + 15)
.attr("height", totalHeight)
.attr("transform", "translate(-15,-15)");
// data-dependent actions
// highlight
function highlight(d) {
d3.event.preventDefault();
var submitter = typeof d === 'string' ? d : d.submitter;
if (submitter === 'no one') submitter = undefined;
selectedSubmitter = selectedSubmitter === submitter ? null : submitter;
node.style('opacity', function(d) {
return (!selectedSubmitter || d.submitter === selectedSubmitter) ? 1.0 : 0.25;
});
}
// simulation update tick
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});
// drag actions for nodes
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>