index.html
<!doctype html>
<head>
<link rel='stylesheet' href='styles.css'>
</head>
<body>
<svg viewBox='0 0 100 100'>
<g class='nodes' transform='translate(50,50)'></g>
</svg>
<button data-handler='create'>create layout</button>
<button data-handler='save'>save layout</button>
<button data-handler='restore'>restore layout</button>
<script src='d3.v3.min.js'></script>
<script src='app.js'></script>
</body>
app.js
var WIDTH = 100;
var HEIGHT = 100;
var SIZE = 2;
var nodes;
var force;
var handlers = {
create () {
nodes = [];
for ( let i = 0; i < 100; i += 1 ) {
nodes.push({
state: 'yellow',
x: ( Math.random() - 0.5 ) * WIDTH / 2,
y: ( Math.random() - 0.5 ) * HEIGHT / 2
});
}
init( nodes );
force.start();
},
save () {
var saved = nodes.map( node => {
return {
state: node.state,
x: node.x,
y: node.y
};
});
console.log( 'saving nodes', saved );
localStorage.setItem( 'nodes', JSON.stringify( saved ) );
},
restore () {
nodes = JSON.parse( localStorage.getItem( 'nodes' ) );
console.log( 'restored nodes', nodes );
init( nodes );
}
};
var buttons = [].slice.call( document.querySelectorAll( 'button' ) );
buttons.forEach( button => {
const handler = button.getAttribute( 'data-handler' );
button.addEventListener( 'click', () => {
handlers[ handler ]();
});
});
function init ( nodes ) {
if ( force ) force.stop();
d3.select( '.nodes' )
.selectAll( 'circle' )
.data( nodes )
.attr( 'cx', d => d.x )
.attr( 'cy', d => d.y )
.enter().append( 'circle' )
.attr( 'cx', d => d.x )
.attr( 'cy', d => d.y )
.attr( 'r', SIZE * 1.5 )
.attr( 'class', d => d.state )
.on( 'click', function ( d ) {
d.state = 'red';
d3.select( this ).attr( 'class', 'red' );
force.start();
});
force = d3.layout.force()
.nodes( nodes )
.size([ SIZE, SIZE ])
.charge( ( n, i ) => {
return -( n.state === 'red' ? SIZE * 3 : SIZE );
})
.on( 'tick', tick );
function tick () {
d3.selectAll( 'circle' )
.data( nodes )
.attr( 'cx', d => d.x )
.attr( 'cy', d => d.y );
}
}