Skip to content

Instantly share code, notes, and snippets.

@vijithassar
Last active October 11, 2016 05:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vijithassar/3518ea727b10e03d02a6c3fdd97d3b69 to your computer and use it in GitHub Desktop.
Save vijithassar/3518ea727b10e03d02a6c3fdd97d3b69 to your computer and use it in GitHub Desktop.
d3.history example

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.)

(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)
!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});
[{
"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
}]
<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>
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