Last active
August 11, 2019 08:24
-
-
Save beemyfriend/8c2a0c086f1ed6499a5661c4120e0406 to your computer and use it in GitHub Desktop.
Let's try to explore the different layers of a multigraph
This file contains 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
<head> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
</head> | |
<body> | |
<svg></svg> | |
<button>Toggle Pane Position</button> | |
</body> | |
<script> | |
//Data represents different layers of a multigraph | |
//a seperate layer for each node type | |
//a seperate layer for each edge type | |
let layers = [ | |
{ | |
type: 'dislike', | |
nodes: [], | |
edges: [ | |
{x1: 100, x2: 150, y1: 150, y2: 100, clr: 'red'}, | |
{x1: 150, x2: 150, y1: 100, y2: 150, clr: 'red'} | |
] | |
}, | |
{ | |
type: 'like', | |
nodes: [], | |
edges: [ | |
{x1: 100, x2: 150, y1: 150, y2: 150, clr: 'green'} | |
] | |
}, | |
{ | |
type: 'person', | |
nodes: [ | |
{x: 100, y: 150, clr: 'blue'}, | |
{x: 150, y: 100, clr: 'orange'}, | |
{x: 150, y: 150, clr: 'purple'} | |
], | |
edges: [] | |
} | |
] | |
let button = d3.select('button') | |
let t = 600 | |
function layerBackground(obj, clicked){ | |
let settings = {} | |
if(clicked){ | |
settings.fill = 'lightgrey' | |
settings.opacity = .6 | |
} else { | |
settings.fill = 'white' | |
settings.opacity = 0 | |
} | |
obj.transition() | |
.duration(t) | |
.style('fill', settings.fill) | |
.style('fill-opacity', settings.opacity) | |
.on('end', function(){ | |
if(!clicked){ | |
d3.select(this) | |
.style('fill', 'none') //set to none to interact with all layers | |
} | |
}) | |
} | |
let svg = d3.select('svg') | |
.attr('width', 600) | |
.attr('height', 450) | |
//this contains all the visual information for each layer of the multigraph | |
let layerG = svg.selectAll('g') | |
.data(layers) | |
.enter().append('g') | |
.style('pointer-events', 'none') | |
//pane information contains the actual nodes or edges | |
let gPane = layerG | |
.append('g') | |
.attr('class', 'pane') | |
.attr('transform', 'translate(100, 100)') | |
//clicking on the control will allow the viewer to remove a layer | |
let paneControl = layerG | |
.append('g') | |
.append('circle') | |
.attr('cx', 80) | |
.attr('cy', 100) | |
.attr('r', 10) | |
.style('fill', 'grey') | |
.style('stroke', 'black') | |
.style('stroke-width', 4) | |
.attr('cursor', 'pointer') | |
.attr('class', 'show') | |
.on('click', function(){ | |
togglePane(this) | |
}) | |
//make it clear which layer is which when seperated | |
let paneBackground = gPane.append('rect') | |
.attr('width', 300) | |
.attr('height', 300) | |
.style('stroke', 'black') | |
.style('fill', 'none') //set to none to interact with all layers | |
//this function draws out all the graph components in each layer | |
function createLayer(g){ | |
let edges = g.filter(d => d.edges.length) | |
if(edges.data().length){ | |
edges.selectAll('line') | |
.data(d => d.edges) | |
.enter().append('line') | |
.attr('x1', d => d.x1) | |
.attr('x2', d => d.x2) | |
.attr('y1', d => d.y1) | |
.attr('y2', d => d.y2) | |
.attr('class', function(){ return(d3.select(this.parentNode).data()[0].type)}) | |
.style('stroke', d => d.clr) | |
.style('stroke-width', 3) | |
.style('pointer-events', 'auto') | |
.on('mouseover', function(d){ | |
console.log("I'm on an edge layer and you can interact with me!") | |
}) | |
} | |
let nodes = g.filter(d => d.nodes.length) | |
if(nodes.data().length){ | |
nodes.selectAll('circle') | |
.data(d => d.nodes) | |
.enter().append('circle') | |
.attr('cx', d => d.x) | |
.attr('cy', d => d.y) | |
.style('fill', d => d.clr) | |
.attr('r', 10) | |
.style('stroke', 'black') | |
.style('stroke-width', 1) | |
.attr('class', function(d){ return(d3.select(this.parentNode).data()[0].type)}) | |
.style('pointer-events', 'auto') | |
.on('mouseover', function(d){ | |
console.log("I'm on the node layer and you can interact with me!") | |
}) | |
} | |
} | |
//toggle the panes to view the different layers | |
let togglePane = function(self){ | |
let me = d3.select(self) | |
let sibling = d3.select(self.parentNode.parentNode) | |
.select('.pane') | |
if(me.attr('class') == 'show'){ | |
sibling.attr('opacity', 0) | |
me.attr('class', 'hide') | |
.style('fill', 'transparent') | |
} else { | |
sibling.attr('opacity', 1) | |
me.attr('class', 'show') | |
.style('fill', 'grey') | |
} | |
} | |
//put the top layer at the bottom to adjust for skewing effect | |
function layerYLoc(i){ | |
return(200 - (50 * i)) | |
} | |
//adds the skewing effect to each layer when toggled | |
function layerChange(obj, clicked){ | |
let settings = {} | |
if(clicked){ | |
settings.transform = (i) => `translate(100,${layerYLoc(i)}) scale(.5 .5) skewX(60)` | |
} else { | |
settings.transform = (i) => 'translate(100, 100)' | |
} | |
obj.transition() | |
.duration(t) | |
.attr('transform', (d,i) => settings.transform(i)) | |
} | |
//either view a selected pane or hide it | |
function layerView(g, clicked){ | |
if(clicked){ | |
g.transition() | |
.duration(t) | |
.attr('cy', (d,i) => layerYLoc(i)) | |
.style('pointer-events', 'auto') | |
.attr('opacity', 1) | |
} else { | |
g.transition() | |
.duration(t) | |
.attr('cy', 100) | |
.style('pointer-events', 'none') | |
.attr('opacity', 0) | |
} | |
} | |
let clicked = false; | |
layerChange(gPane, clicked) | |
layerBackground(paneBackground, clicked) | |
layerView(paneControl, clicked) | |
createLayer(gPane) | |
button.on('click', function(){ | |
clicked = !clicked | |
layerChange(gPane, clicked) | |
layerBackground(paneBackground, clicked) | |
layerView(paneControl, clicked) | |
}) | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment