Skip to content

Instantly share code, notes, and snippets.

@beemyfriend
Last active August 11, 2019 08:24
Show Gist options
  • Save beemyfriend/8c2a0c086f1ed6499a5661c4120e0406 to your computer and use it in GitHub Desktop.
Save beemyfriend/8c2a0c086f1ed6499a5661c4120e0406 to your computer and use it in GitHub Desktop.
Let's try to explore the different layers of a multigraph
<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