demonstration of d3.history, a plugin for D3.js which adds support for deep-linking and URLs based on the user interface state. (To see this demonstration with the URL bar intact, you'll need to open it without the iframe.)
Last active
October 11, 2016 05:29
-
-
Save vijithassar/3518ea727b10e03d02a6c3fdd97d3b69 to your computer and use it in GitHub Desktop.
d3.history example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(d3) { | |
'use strict' | |
var target, | |
height, | |
width, | |
grid, | |
horizontal, | |
style, | |
drawing, | |
get_active, | |
activate, | |
render_value, | |
items, | |
item, | |
circle, | |
dispatcher, | |
initialize, | |
bound, | |
reset, | |
reset_button | |
d3.json('data.json', function(error, data) { | |
if (error) { | |
return new Error('could not fetch data') | |
} | |
// configuration | |
target = d3.select('div.target') | |
height = 500 | |
width = 960 | |
grid = 80 | |
horizontal = Math.floor(width / grid) - 2 | |
style = { | |
active: function(selection) { | |
selection | |
.style('stroke', 'blue') | |
.style('fill', 'blue') | |
.style('fill-opacity', 0.2) | |
}, | |
inactive: function(selection) { | |
selection | |
.style('stroke', 'grey') | |
.style('fill', 'white') | |
.style('fill-opacity', 0.01) | |
} | |
} | |
// draw grid | |
drawing = target | |
.append('svg') | |
.attr('width', width) | |
.attr('height', height) | |
.append('g') | |
.classed('drawing', true) | |
// retrieve current active items from URL bar | |
get_active = function() { | |
var url, | |
ids, | |
active | |
url = window.location.href | |
ids = data.map(function(item) {return item.id}) | |
if (url.indexOf('?') !== -1) { | |
active = url | |
.split('?active=').pop() | |
.split(',') | |
.map(function(active) {return +active}) | |
.filter(function(active) {return ids.indexOf(active) !== -1}) | |
} else { | |
active = [] | |
} | |
return active | |
} | |
// various transformations to make to an item on mouseover, | |
// stored in a reusable function so they can be easily reused | |
// for initialization | |
activate = function(selection) { | |
var parent | |
selection | |
.classed('active', true) | |
.select('circle') | |
.transition() | |
.attr('r', grid * 1.2) | |
.call(style.active) | |
if (history.state && history.state[0]) { | |
render_value(history.state[0]) | |
} | |
// move active item to the back so it's easier to select other nodes | |
if (selection.node()) { | |
parent = selection.node().parentNode | |
parent.insertBefore(selection.node(), parent.firstChild) | |
} | |
} | |
// render bound data as text | |
render_value = function(data) { | |
bound.select('.bound .value').text(JSON.stringify(data)) | |
} | |
// wrapper group | |
items = drawing.append('g') | |
.attr('transform', 'translate(' + (grid * 1.5) + ',' + (grid * 1.5) + ')') | |
// position each item | |
item = items.selectAll('g.item') | |
.data(data) | |
.enter() | |
.append('g') | |
.classed('item', true) | |
.attr('data-id', function(d) {return '_' + d.id}) | |
.attr('transform', function(d, i) { | |
var x, | |
y | |
x = i % horizontal * grid | |
y = Math.floor(i / horizontal) * grid | |
return 'translate(' + x + ',' + y + ')' | |
}) | |
// render circles | |
circle = item.append('circle') | |
circle | |
.attr('r', grid) | |
.call(style.inactive) | |
// create a d3.history dispatcher object | |
dispatcher = d3.history('activate') | |
// use d3.history to activate nodes on mouseover | |
item.on('mousemove', function(d) { | |
var active, | |
current_fragment, | |
new_fragment | |
// get existing active items | |
active = get_active() | |
current_fragment = '?' + window.location.href.split('?').pop() | |
// append new active item | |
if (active.indexOf(d.id) === -1) { | |
active.push(d.id) | |
active.sort(function(a, b) {return a - b}) | |
} | |
// compile active items into URL fragment | |
new_fragment = '?active=' + active.join(',') | |
// prevent updates with redundant urls | |
if (new_fragment !== current_fragment) { | |
// activate current item with new URL fragment | |
dispatcher.call('activate', this, new_fragment, d) | |
} | |
}) | |
// set item state on mouseover | |
dispatcher.on('activate', function(d) { | |
var selector, | |
current_item | |
selector = '.item[data-id=_' + d.id + ']' | |
current_item = d3.select(selector) | |
current_item.classed('active', true) | |
current_item.call(activate) | |
}) | |
// display the datum associated with the most recent URL action | |
bound = target.append('div') | |
.classed('bound', true) | |
bound.append('div') | |
.classed('features', true) | |
.html('Permalinks and forward/back buttons work thanks to <a href="http://github.com/vijithassar/d3-history">d3-history</a>.') | |
bound.append('div') | |
.classed('explanation', true) | |
.text('The state data bound to the most recent URL update event in the HTML5 History API is:') | |
bound.append('div') | |
.classed('value', true) | |
// reset function | |
reset = function() { | |
// set circles back to initial state | |
target.selectAll('circle') | |
.attr('r', grid) | |
.call(style.inactive) | |
bound.select('.value').text('') | |
} | |
// reset button | |
reset_button = target.append('div') | |
reset_button | |
.classed('reset', true) | |
.text('reset') | |
.on('click', function() { | |
reset() | |
// restore url without any actives marked | |
history.replaceState(null, null, window.location.href.split('?')[0]) | |
}) | |
// initialization function to activate on load | |
initialize = function() { | |
var active | |
// select only the items specified in the URL bar | |
active = get_active() | |
item | |
.filter(function(d, i) { | |
return active.indexOf(d.id) !== -1 | |
}) | |
// set UI state | |
.call(activate) | |
} | |
// initialize state on page load | |
initialize() | |
// initialize state for manual browsing actions | |
window.addEventListener('popstate', function(event) { | |
reset() | |
initialize() | |
// the popstate event saves the data associated with the URL change | |
// render the popstate value after running the initialization function, | |
// since both will try to set the most recently bound data | |
if (event.state && event.state[0]) { | |
render_value(event.state[0]) | |
} | |
}) | |
}) | |
}).call(this, d3) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("d3-dispatch")):"function"==typeof define&&define.amd?define(["exports","d3-dispatch"],o):o(t.d3=t.d3||{},t.d3)}(this,function(t,o){"use strict";var e;e=function(){var t,e,n,r;return t=Object.create(null),n=Array.prototype.slice.call(arguments),e=o.dispatch.apply(this,n),r=function(t,o,e){window&&window.history&&window.history.pushState(t,o,e)},t.url=function(o){return o&&"function"!=typeof o&&console.error("optional argument to the .url() method of d3.history object must be a function"),"function"==typeof o?(r=o,t):r},t.on=function(o,n){return"string"!=typeof o&&console.error("first argument to .on() method of d3.history object must be an event name"),"function"!=typeof n&&console.error("second argument to .on() method of d3.history object must be a function"),e.on(o,n),t},t.call=function(){var o,e,n,r;return o=arguments[0],e=arguments[1]||null,n=arguments[2],r=Array.prototype.slice.call(arguments,3)||null,t.apply(o,e,n,r),t},t.apply=function(o,n,u,i){var s;return s=null,i=i||null,n=n||null,"string"!=typeof u&&console.error("third argument to history dispatcher must be a string with which to update the url bar"),r(i,s,u),e.apply(o,n,i),t},t};var n=e;t.history=n}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{ | |
"id": 0, | |
"value": 26 | |
}, { | |
"id": 1, | |
"value": 81 | |
}, { | |
"id": 2, | |
"value": 30 | |
}, { | |
"id": 3, | |
"value": 88 | |
}, { | |
"id": 4, | |
"value": 33 | |
}, { | |
"id": 5, | |
"value": 6 | |
}, { | |
"id": 6, | |
"value": 66 | |
}, { | |
"id": 7, | |
"value": 21 | |
}, { | |
"id": 8, | |
"value": 88 | |
}, { | |
"id": 9, | |
"value": 48 | |
}, { | |
"id": 10, | |
"value": 88 | |
}, { | |
"id": 11, | |
"value": 81 | |
}, { | |
"id": 12, | |
"value": 36 | |
}, { | |
"id": 13, | |
"value": 19 | |
}, { | |
"id": 14, | |
"value": 12 | |
}, { | |
"id": 15, | |
"value": 47 | |
}, { | |
"id": 16, | |
"value": 80 | |
}, { | |
"id": 17, | |
"value": 87 | |
}, { | |
"id": 18, | |
"value": 9 | |
}, { | |
"id": 19, | |
"value": 85 | |
}, { | |
"id": 20, | |
"value": 83 | |
}, { | |
"id": 21, | |
"value": 39 | |
}, { | |
"id": 22, | |
"value": 25 | |
}, { | |
"id": 23, | |
"value": 56 | |
}, { | |
"id": 24, | |
"value": 77 | |
}, { | |
"id": 25, | |
"value": 42 | |
}, { | |
"id": 26, | |
"value": 50 | |
}, { | |
"id": 27, | |
"value": 6 | |
}, { | |
"id": 28, | |
"value": 52 | |
}, { | |
"id": 29, | |
"value": 42 | |
}, { | |
"id": 30, | |
"value": 6 | |
}, { | |
"id": 31, | |
"value": 5 | |
}, { | |
"id": 32, | |
"value": 93 | |
}, { | |
"id": 33, | |
"value": 72 | |
}, { | |
"id": 34, | |
"value": 90 | |
}, { | |
"id": 35, | |
"value": 13 | |
}, { | |
"id": 36, | |
"value": 71 | |
}, { | |
"id": 37, | |
"value": 10 | |
}, { | |
"id": 38, | |
"value": 78 | |
}, { | |
"id": 39, | |
"value": 92 | |
}] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>d3.history example</title> | |
<link rel="stylesheet" type="text/css" href="style.css"> | |
</head> | |
<body> | |
<div class="target"></div> | |
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> | |
<script src="./d3-history.min.js" charset="utf-8"></script> | |
<script type="text/javascript" charset="utf-8" src="circles.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
svg { | |
display: block; | |
margin: auto; | |
} | |
.item circle { | |
stroke-width: 1; | |
} | |
.bound { | |
width: 15em; | |
display: block; | |
margin: 1em auto 1em auto; | |
} | |
.bound .explanation, | |
.bound .features { | |
color: grey; | |
font-style: italic; | |
line-height: 1.5em; | |
margin-bottom: 1em; | |
} | |
.bound .value { | |
color: blue; | |
text-align: center; | |
height: 2em; | |
margin: 2em auto 2em auto; | |
} | |
.reset { | |
text-align: center; | |
padding: 0.5em; | |
width: 5em; | |
border-radius: 0.2em; | |
border: 1px solid black; | |
display: block; | |
margin: auto; | |
cursor: pointer; | |
} | |
.reset:hover { | |
background-color: grey; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment