class Visualization {
constructor (selector, data) {
this.selector = selector;
this.data = data;
this.data.nodes = this.data.nodes.filter(d => d.nEdge >= n);
this.rootDOM = document.querySelector(this.selector);
this.height = d3.select(this.selector).node().getBoundingClientRect().height;
this.width = 600;
this.margin = {'top': 10, 'bottom': 150, 'left': 75, 'right': 10};
this.hoverNodes = [];
this.yAxisData = [
{'level': 4, 'name': "Activity or Actor"},
{'level': 3, 'name': "Process 3"},
{'level': 2, 'name': "Process 2"},
{'level': 1, 'name': "Process 1"}
];
this.labelStyle = {
align: "center",
fill: 0xffffff,
fontFamily: ["ibmplexsans-regular-webfont", "Plex", "Arial"],
fontSize: 11,
padding: 5,
textBaseline: "bottom",
wordWrap: true,
wordWrapWidth: 90,
leading: -2,
dropShadow: true,
dropShadowAngle: 90,
dropShadowBlur: 5,
dropShadowDistance: 2,
dropShadowColor: 0x21252b
}
this.initApp();
this.initTooltip();
this.initScales();
this.initSimulation();
}
initApp() {
this.app = new PIXI.Application({ resizeTo: this.rootDOM,
resolution: devicePixelRatio,
width: this.width,
height: this.height
});
this.rootDOM.appendChild(this.app.view);
// this.viewport = new Viewport({
// screenWidth: this.width,
// screenHeight: this.height,
// worldWidth: this.width,
// worldHeight: this.height,
// // passiveWheel: false,
// // interaction: this.app.renderer.events, // the interaction module is important for wheel to work properly when renderer.view is placed or scaled
// // events: this.app.renderer.events
// });
// this.app.stage.addChild(this.viewport);
}
initTooltip () {
this.tooltip = d3.select(this.selector)
.append("div")
.attr("class", "tooltip");
}
initScales () {
const stat = d3.extent(this.data.nodes.map(d=>d.nEdge));
this.sizeScale = d3.scaleSqrt()
.domain(stat)
.range([.5, 15]);
this.xScale = d3.scaleLinear()
.domain(stat)
.range([this.margin.left, this.width - this.margin.right]);
this.yScale = d3.scaleBand()
.domain(this.yAxisData.map(d => d.level))
.range([this.height - this.margin.bottom, this.margin.top]);
}
initSimulation () {
this.simulation = d3.forceSimulation(this.data.nodes)
.force('center', d3.forceCenter(this.width / 2, this.height / 2))
// .force('charge', d3.forceManyBody().strength(5))
.force('x', d3.forceX().x(d => this.xScale(d.nEdge)).strength(1.5))
.force('y', d3.forceY().y(d => this.yScale(d.level)).strength(1.5))
.force('collision', d3.forceCollide().radius(d => this.sizeScale(d.nEdge)).strength(1))
}
drawNodes () {
this.containerNodes = new PIXI.Container();
// this.nodes = [];
this.data.nodes.forEach((node) => {
console.log(this.yScale(node.level))
node.gfx = new PIXI.Graphics();
node.gfx.lineStyle(0); // draw a circle, set the lineStyle to zero so the circle doesn't have an outline
node.gfx.beginFill(0xcbcbcb, 1);
node.gfx.drawCircle(0, 0, this.sizeScale(node.nEdge));
node.gfx.endFill();
node.gfx.eventMode = 'static';
node.gfx.cursor = 'pointer';
// this.nodes.push(node);
this.containerNodes.addChild(node.gfx);
})
this.app.stage.addChild(this.containerNodes);
}
drawYAxis() {
this.containerYAxis = new PIXI.Container();
// Line -- Y axis
const lineYAxis = new PIXI.Graphics();
lineYAxis.lineStyle(1, 0x919295, 1)
.moveTo(this.xScale(0), this.margin.top)
.lineTo(this.xScale(0), this.height - this.margin.bottom);
lineYAxis.alpha = 0.5;
this.containerYAxis.addChild(lineYAxis);
// Labels - YAxis
for (let d of this.yAxisData) {
const id = new PIXI.Text(String(d.name), this.labelStyle);
id.anchor.set(0, 0);
id.position.set(10, this.yScale(d.level));
id.zIndex = 100;
this.containerYAxis.addChild(id);
const minorY = new PIXI.Graphics();
minorY.lineStyle(1, 0x919295, 1)
.moveTo(this.xScale(0), this.yScale(d.level))
.lineTo(this.width - this.margin.right, this.yScale(d.level));
minorY.alpha = 0.5;
this.containerYAxis.addChild(minorY);
}
for (let d of [this.margin.top, this.height - this.margin.bottom]) {
const majorY = new PIXI.Graphics();
majorY.lineStyle(1, 0x919295, 1)
.moveTo(this.xScale(0), d)
.lineTo(this.width - this.margin.right, d);
this.containerYAxis.addChild(majorY);
}
this.app.stage.addChild(this.containerYAxis);
}
drawXAxis() {
this.containerXAxis = new PIXI.Container();
// Labels - YAxis
for (let d of [0, 50, 100, 150, 200, 250, 300, 350, 400, 450]) {
const id = new PIXI.Text(String(d), this.labelStyle);
id.anchor.set(.5, 0);
id.position.set(this.xScale(d), this.height - this.margin.bottom + 16);
id.zIndex = 100;
this.containerXAxis.addChild(id);
const tickX = new PIXI.Graphics();
tickX.lineStyle(1, 0x919295, 1)
.moveTo(this.xScale(d), this.height - this.margin.bottom)
.lineTo(this.xScale(d), this.height - this.margin.bottom + 10);
this.containerXAxis.addChild(tickX);
}
this.app.stage.addChild(this.containerXAxis);
}
// Adds glow to nodes when hovered over
hoverNetworkNodes (node) {
this.hoverNodes = this.data.nodes.filter(d => d.id === node.id);
this.hoverNodes
.forEach(node => {
const { gfx } = node;
gfx.filters = [
new GlowFilter.GlowFilter({
distance: 2,
innerStrength: 0,
outerStrength: 2,
color: 0xffffff,
quality: 1
})
];
gfx.zIndex = 1;
});
}
showTooltip (node) {
this.tooltip.style('visibility', 'visible')
.style('top', `${node.y}px`)
.style('left', `${node.x}px`)
.html(`${node.name}, ${node.nEdge} connections`);
}
hideTooltip () {
this.tooltip.style('visibility', 'hidden');
}
pointerOver (node) {
this.hoverNetworkNodes(node);
this.showTooltip(node);
}
pointerOut () {
this.hideTooltip();
this.hoverNodes
.forEach(node => {
const { gfx } = node;
gfx.filters.pop();
gfx.zIndex = 0;
});
}
updateNodePosition () {
this.data.nodes.forEach((node) => {
node.gfx.x = node.x;
node.gfx.y = node.y + this.yScale.bandwidth()/2 - this.margin.top;
node.gfx
.on('pointerover', () => this.pointerOver(node))
.on('pointerout', () => this.pointerOut());
});
}
draw () {
this.drawYAxis();
this.drawXAxis();
this.drawNodes();
// this.simulation.alpha(1).restart();
}
animate () {
this.app.ticker.add(() => {
this.updateNodePosition();
});
}
}