/// import * as THREE from 'three'; import * as dynamicgraph from 'vistorian-core/src/dynamicgraph'; import * as utils from 'vistorian-core/src/utils'; import * as main from 'vistorian-core/src/main'; import * as messenger from 'vistorian-core/src/messenger'; import * as ordering from 'vistorian-core/src/ordering'; import * as TimeSlider from 'vistorian-widgets/src/timeslider'; import * as glutils from 'vistorian-widgets/src/glutils'; const COLOR_HIGHLIGHT = 0x000000; const COLOR_SELECTION = 0xff0000; class NMargin { left: number; top: number; constructor(v: number) { this.left = v; this.top = v; } setMargin(v: number) { this.left = v; this.top = v; } } interface Box { x0: number; x1: number; y0: number; y1: number; } interface Pos { x: number; y: number; } interface Cell { row: number; col: number; } class MatrixMenu { private elem: JQuery; private matrix: Matrix; constructor(elem: JQuery, matrix: Matrix) { this.elem = elem; this.matrix = matrix; this.init(); } init() { this.elem.append( `Zoom: '); $('#cellSizeBox').change(this.updateCellSize); this.elem.append('
'); this.elem.append(''); let orderingMenu = $("#networkcube-matrix-menu") .append('') // VS: Clicks on Manual $("#networkcube-matrix-menu") .append('Manual'); $('#labelOrdering').change(this.reorderHandler); $('#labelOrdering').append(''); $('#labelOrdering').append(''); $('#labelOrdering').append(''); $('#labelOrdering').append(''); $('#labelOrdering').append(''); this.elem.append(''); $('#reorderBtn').click(this.reorderHandler); } updateCellSize() { let value: any = $('#cellSizeBox').val(); matrix.updateCellSize(value); } reorderHandler() { let orderType: any = $('#labelOrdering').val(); matrix.reorderWorker(orderType); } setScale(val: number) { $('#cellSizeBox').val(val); } } class MatrixTimeSlider { private elem: JQuery; private matrix: Matrix; private width: number; private height: number; private svg: any; private timeSlider: any; // BEFORE TimeSlider.TimeSlider; constructor(elem: JQuery, matrix: Matrix, width: number) { this.elem = elem; this.matrix = matrix; this.width = width - 20; this.height = 50; this.init(); } init() { this.svg = d3.select(this.elem.get(0)) .append('svg') .attr('width', this.width) .attr('height', this.height) this.timeSlider = new TimeSlider.TimeSlider(matrix.dgraph, vizWidth); this.timeSlider.appendTo(this.svg); } set(sT: number, eT: number) { this.timeSlider.set(sT, eT); } } class CellLabel { private cellLabelBackground: any; private cellLabel: any; constructor() { this.cellLabelBackground = glutils.selectAll() .data([{ id: 0 }]) .append('text') .style('opacity', 0) .attr('z', -1) .style('font-size', 12) .style('stroke', '#fff') .style('stroke-width', 2.5) this.cellLabel = glutils.selectAll() .data([{ id: 0 }]) .append('text') .style('opacity', 0) .attr('z', -1) .style('font-size', 12) } hideCellLabel() { this.cellLabelBackground.style('opacity', 0); this.cellLabel.attr('z', -1) .style('opacity', 0); } updateCellLabel(mx: number, my: number, val: number | null, fw: number) { this.cellLabel .attr('x', mx + 40) .attr('y', -my) .style('opacity', 1) .text(val ? val : 0) .attr('z', 2) .style('font-size', fw); this.cellLabelBackground .attr('x', mx + 40) .attr('y', -my) .style('opacity', 1) .text(val ? val : 0) .attr('z', 2) .style('font-size', fw); } } class MatrixOverview { private width: number; private height: number; private matrix: Matrix; private svg: any; // BEFORE d3.Selection; private ratio: number; private canvasRatio: number; private focusColor: string = ''; private focus: any; // BEFORE d3.Selection; private context: any; // BEFORE d3.Selection; private contextImg: any; // BEFORE d3.Selection; private contextPattern: any; // BEFORE d3.Selection; private zoom: any; // BEFORE d3.ZoomBehavior; constructor(svg: any, // BEFORE d3.Selection, width: number, height: number, matrix: Matrix) { this.svg = svg; this.matrix = matrix; this.width = width; this.height = height; this.ratio = 1; this.canvasRatio = 1; this.init(); } init() { this.focusColor = "#ccc"; let g = this.svg.append('g'); this.contextPattern = g.append("defs") .append("pattern") .attr("id", "bg") .attr('patternUnits', 'userSpaceOnUse') .attr("width", this.width) .attr("height", this.height); this.contextImg = this.contextPattern.append("image") .attr("width", this.width) .attr("height", this.height); this.context = g.append("rect") .attr("class", "context") .attr("width", this.width) .attr("height", this.height) .attr("stroke", "#aaa") .attr("fill", "white"); g = this.svg.append('g'); this.focus = g.append("rect") .attr("class", "focus") .attr("fill", this.focusColor) .attr("fill-opacity", .2); this.zoom = d3.behavior.zoom() // .scaleExtent([0.2, 4]) .on('zoom', this.zoomed); this.focus.call(this.zoom);//.on('zoom', this.zoomed)); } private zoomed = () => { let z: number, tr: any[] = []; z = this.zoom.scale(); tr = this.zoom.translate(); this.updateTransform(z, tr); } setCanvasRatio(canvasRatio: number) { this.canvasRatio = 1; let w = this.canvasRatio > 1 ? this.width * this.canvasRatio : this.width; let h = this.canvasRatio < 1 ? this.height * this.canvasRatio : this.height; this.contextPattern.attr("width", w) .attr("height", h); this.contextImg.attr("width", w) .attr("height", h); } updateTransform(z: any, tr: any) { tr[0] = -tr[0] * this.ratio; tr[1] = -tr[1] * this.ratio; this.matrix.updateTransform(z, tr); } updateFocus(matrixX0: number, matrixY0: number, visibleW: number, visibleH: number, r: number, z: number, tr: number[]) { tr[0] = this.ratio !== 0 ? -tr[0] / this.ratio : 0; tr[1] = this.ratio !== 0 ? -tr[1] / this.ratio : 0; this.ratio = this.height !== 0 ? r / this.height : 0; this.zoom.scale(z); this.zoom.translate(tr); let focusX = matrixX0 * this.width; let focusY = matrixY0 * this.height; let focusWidth = Math.min(visibleW * this.width, this.width); let focusHeight = Math.min(visibleH * this.height, this.height); focusWidth = (focusX + focusWidth) > this.width ? this.width - focusX : focusWidth; focusHeight = (focusY + focusHeight) > this.height ? this.height - focusY : focusHeight; this.focus.attr("width", focusWidth) .attr("height", focusHeight) .attr("x", focusX) .attr("y", focusY); } updateOverviewImage(dataImg: any) { this.contextImg.attr("xlink:href", dataImg); this.context.attr("fill", "url(#bg)"); } } class MatrixLabels { private width: number = 0; private height: number = 0; private margin: NMargin; private matrix: Matrix; private cellSize: number; public svg: any; // BEFORE D3.Selection; constructor(svg: any, // BEFORE D3.Selection, margin: NMargin, matrix: Matrix) { this.svg = svg; this.matrix = matrix; this.margin = margin; this.cellSize = 0; } updateData(leftNodes: dynamicgraph.Node[], topNodes: dynamicgraph.Node[], cellSize: number, nodeOrder: number[], leftLabelOffset: number, topLabelOffset: number, bbox: Box) { this.cellSize = cellSize; let labelsLeft = this.svg.selectAll('.labelsLeft') .data(leftNodes); let leftLabelPosition = (nodeId: any) => this.margin.top + leftLabelOffset + cellSize * (nodeOrder[nodeId] - bbox.y0) + cellSize / 2; labelsLeft.enter().append('text') .attr('id', (d: any, i: any) => { return 'nodeLabel_left_' + d.id(); }) .attr('class', 'labelsLeft nodeLabel') .attr('text-anchor', 'end') .attr('alignment-baseline', 'middle') .attr('x', this.margin.left - 10) .attr('y', (d: any, i: any) => { return leftLabelPosition(d.id()) }) .on('mouseover', (d: any, i: any) => { messenger.highlight('set', { nodes: [d] }); }) .on('mouseout', (d: any, i: any) => { messenger.highlight('reset'); }) .on('click', (d: any, i: any) => { this.matrix.nodeClicked(d); }); labelsLeft.exit().remove(); labelsLeft .attr('id', (d: any, i: any) => { return 'nodeLabel_left_' + d.id(); }) .text((d: any, i: any) => { return d.label(); }) .attr('x', this.margin.left - 10) .attr('y', (d: any, i: any) => { return leftLabelPosition(d.id()); }); let labelsTop = this.svg.selectAll('.labelsTop') .data(topNodes); let topLabelPosition = (nodeId: any) => this.margin.left + topLabelOffset + cellSize * (nodeOrder[nodeId] - bbox.x0) + cellSize / 2; labelsTop.enter().append('text') .attr('id', (d: any, i: any) => { return 'nodeLabel_top_' + d.id(); }) .attr('class', 'labelsTop nodeLabel') .text((d: any, i: any) => { return d.label(); }) .attr('x', (d: any, i: any) => { return topLabelPosition(d.id()) }) .attr('y', this.margin.left - 10) .attr('transform', (d: any, i: any) => { return 'rotate(-90, ' + (this.margin.top + cellSize * i + cellSize / 2) + ', ' + (this.margin.left - 10) + ')' }) .on('mouseover', (d: any, i: any) => { this.matrix.highlightNodes([d.id()]); }) .on('mouseout', (d: any, i: any) => { this.matrix.highlightNodes([]); }) .on('click', (d: any, i: any) => { this.matrix.nodeClicked(d); }); labelsTop.exit().remove(); labelsTop .attr('id', (d: any, i: any) => { return 'nodeLabel_top_' + d.id(); }) .text((d: any, i: any) => { return d.label(); }) .attr('alignment-baseline', 'middle') .attr('x', (d: any, i: any) => { return topLabelPosition(d.id()); }) .attr('y', this.margin.top - 10) .attr('transform', (d: any, i: any) => { return 'rotate(-90, ' + (this.margin.top + topLabelOffset + cellSize * (nodeOrder[d.id()] - bbox.x0) + cellSize / 2) + ', ' + (this.margin.left - 10) + ')' }); this.updateHighlightedNodes(); } updateHighlightedNodes(highlightedLinks: number[] = []) { let color: any; this.svg.selectAll('.nodeLabel') .style('fill', function (d: any) { color = undefined; if (d.isSelected()) { color = utils.getPriorityColor(d); } if (!color) color = '#000000'; return color; }) .style('font-weight', function (d: any) { if (d.isHighlighted()) { return 900; } return 100; }) .style('font-size', Math.min(this.cellSize, 20)); for (let i = 0; i < highlightedLinks.length; i++) { d3.selectAll('#nodeLabel_left_' + highlightedLinks[i]) .style('font-weight', 900); d3.selectAll('#nodeLabel_top_' + highlightedLinks[i]) .style('font-weight', 900); } } } class MatrixVisualization { private elem: any; public width: number; public height: number; private matrix: Matrix; private cellSize: number = 0; private scale: number; private tr: number[]; private offset: number[]; private nrows: number; private ncols: number; private linksPos: { [row: number]: { [col: number]: number[] } } = {}; // INIT??? private mouseDown: Boolean = false; private mouseDownPos: any; // BEFORE Pos; private mouseDownCell: Cell; private toHoverLinks: number[] = []; private hoveredLinks: number[] | undefined; private previousHoveredLinks: number[] | undefined; private canvas: any; // HTMLCanvasElement = new HTMLCanvasElement(); private view: any; // BEFORE D3.Selection; private zoom: any; // BEFORE D3.Behavior.Zoom; private scene: any; // BEFORE THREE.Scene = new THREE.Scene(); private camera: any; // BEFORE THREE.OrthographicCamera; private renderer: any; // BEFORE THREE.WebGLRenderer = new THREE.WebGLRenderer(); private geometry: THREE.BufferGeometry = new THREE.BufferGeometry(); private mesh: THREE.Mesh = new THREE.Mesh(); private guideLines: THREE.Object3D[]; private vertexPositions: number[][] = []; private vertexColors: number[][] = []; private shaderMaterial: THREE.ShaderMaterial = new THREE.ShaderMaterial(); private cellHighlightFrames: { [id: number]: THREE.Mesh[] }; private cellSelectionFrames: THREE.Mesh[]; private linkWeightScale: any; // BEFORE d3.ScaleLinear; private bufferTexture: any; // BEFORE THREE.WebGLRenderTarget; private data: { [id: number]: { [id: number]: dynamicgraph.NodePair } } = {}; constructor(elem: any, // BEFORE d3.Selection width: number, height: number, matrix: Matrix) { this.width = width; this.height = height; this.elem = elem; this.matrix = matrix; this.nrows = 0; this.ncols = 0; this.scale = 1; this.tr = [0, 0]; this.offset = [0, 0]; this.guideLines = []; this.hoveredLinks = []; this.previousHoveredLinks = []; this.mouseDownCell = { row: 0, col: 0 }; this.cellHighlightFrames = dynamicgraph.array(undefined, matrix.numberOfLinks()); this.cellSelectionFrames = dynamicgraph.array(undefined, matrix.numberOfLinks()); this.linkWeightScale = d3.scale.linear().range([0.1, 1]) .domain([0, matrix.maxWeight()]); this.init(); } init() { this.initWebGL(); this.elem.node().appendChild(this.canvas); this.view = d3.select(this.canvas); this.zoom = d3.behavior.zoom(); this.view.call(this.zoom);//.on('zoom', this.zoomed)); this.initGeometry(); this.cellSize = this.matrix.cellSize; } webgl: any; //glutils.WebGL = new glutils.WebGL(); initWebGL() { this.webgl = glutils.initWebGL('visCanvasFO', this.width, this.height); this.webgl.enablePanning(false); this.webgl.camera.position.x = this.width / 2; this.webgl.camera.position.y = -this.height / 2; this.webgl.camera.position.z = 1000; this.canvas = this.webgl.canvas; this.scene = this.webgl.scene; this.camera = this.webgl.camera; this.renderer = this.webgl.renderer; this.initTextureFramebuffer(); this.webgl.canvas.addEventListener('mousemove', this.mouseMoveHandler); this.webgl.canvas.addEventListener('mousedown', this.mouseDownHandler); this.webgl.canvas.addEventListener('mouseup', this.mouseUpHandler); this.webgl.canvas.addEventListener('click', this.clickHandler); } initTextureFramebuffer() { this.bufferTexture = new THREE.WebGLRenderTarget(256, 256, { minFilter: THREE.NearestMipMapNearestFilter, magFilter: THREE.LinearFilter }); } initGeometry() { let vertexShaderProgram = ` attribute vec4 customColor; varying vec4 vColor; void main() { vColor = customColor; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1 ); }`; let fragmentShaderProgram = ` varying vec4 vColor; void main() { gl_FragColor = vec4(vColor[0], vColor[1], vColor[2], vColor[3]); }`; let attributes = { customColor: { type: 'c', value: [] } } // SHADERS this.shaderMaterial = new THREE.ShaderMaterial({ //attributes: attributes, // Not Exist vertexShader: vertexShaderProgram, fragmentShader: fragmentShaderProgram, }); this.shaderMaterial.blending = THREE.NormalBlending; this.shaderMaterial.depthTest = true; this.shaderMaterial.transparent = true; this.shaderMaterial.side = THREE.DoubleSide; this.geometry = new THREE.BufferGeometry(); } render() { let d = new Date(); let begin = d.getTime(); this.renderer.render(this.scene, this.camera); d = new Date(); } updateData(data: { [id: number]: { [id: number]: dynamicgraph.NodePair } }, nrows: number, ncols: number, cellSize: number, offset: number[], scale: number, tr: number[], getImageData: boolean ) { this.data = data; this.nrows = nrows; this.ncols = ncols; this.offset = offset; this.cellSize = cellSize; this.zoom.scale(scale); this.zoom.translate(tr); if (this.geometry) { this.scene.remove(this.mesh); } if (this.hoveredLinks) for (let id of this.hoveredLinks) { if (this.cellHighlightFrames[id]) for (let frame of this.cellHighlightFrames[id]) this.scene.remove(frame); } for (let i = 0; i < this.guideLines.length; i++) { this.scene.remove(this.guideLines[i]); } this.vertexPositions = []; this.vertexColors = []; this.cellHighlightFrames = []; this.linksPos = {}; let row: any; for (row in this.data) { let col: any; for (col in data[row]) { this.addCell(row, col, data[row][col]); } } // CREATE + ADD MESH this.geometry.addAttribute('position', new THREE.BufferAttribute(glutils.makeBuffer3f(this.vertexPositions), 3)); this.geometry.addAttribute('customColor', new THREE.BufferAttribute(glutils.makeBuffer4f(this.vertexColors), 4)); this.mesh = new THREE.Mesh(this.geometry, this.shaderMaterial); (this.geometry.attributes['customColor'] as THREE.BufferAttribute).needsUpdate = true; this.scene.add(this.mesh); this.render(); if (getImageData) { let smallDim = Math.min(this.height, this.width); this.resizeCanvas(smallDim, smallDim); this.matrix.hideCellLabel(); this.render(); let imgData = this.canvas.toDataURL(); this.matrix.updateOverviewImage(imgData); this.resizeCanvas(this.width, this.height); } this.updateGuideLines(); this.render(); } resizeCanvas(width: number, height: number) { this.camera.position.x = width / 2; this.camera.position.y = -height / 2; this.camera.left = width / -2; this.camera.right = width / 2; this.camera.top = height / 2; this.camera.bottom = height / -2; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height); } addCell(row: number, col: number, pair: dynamicgraph.NodePair) { let links: dynamicgraph.Link[]; let e: dynamicgraph.Link; let x, y, z: number; let linkNum: number; let seg: number; let meanWeight: number; let alpha: number; let color: THREE.Color; links = pair.links().toArray(); linkNum = links.length; seg = linkNum !== 0 ? this.cellSize / linkNum : 0; z = 1; for (let j = 0; j < links.length; j++) { e = links[j]; let webColor: any = utils.getPriorityColor(e); if (!webColor) webColor = '#000000'; meanWeight = e.weights() ? e.weights(this.matrix.startTime, this.matrix.endTime).mean() : 1; color = new THREE.Color(webColor); alpha = this.linkWeightScale(Math.abs(meanWeight)); if (!e.isVisible()) alpha = 0; x = col * this.cellSize + seg * j + seg / 2 + this.offset[0]; y = row * this.cellSize + this.cellSize / 2 + this.offset[1]; this.paintCell(e.id(), x, y, seg, [color.r, color.g, color.b, alpha], meanWeight > 0); if (!this.linksPos[row]) this.linksPos[row] = {}; if (!this.linksPos[row][col]) this.linksPos[row][col] = []; this.linksPos[row][col].push(e.id()); } } paintCell(id: number, x: number, y: number, w: number, color: number[], positive: Boolean) { let h: number = this.cellSize; let highlightFrames: THREE.Mesh = new THREE.Mesh(); let selectionFrames: THREE.Mesh = new THREE.Mesh(); let frame: any; // BEFORE THREE.Line; if (positive) { glutils.addBufferedRect(this.vertexPositions, x, -y, 0, w - 1, h - 1, this.vertexColors, color); } else { glutils.addBufferedDiamond(this.vertexPositions, x, -y, 0, w - 1, h - 1, this.vertexColors, color); } // highlight frame frame = glutils.createRectFrame(w - 1, h - 1, COLOR_HIGHLIGHT, 1) frame.position.x = x; frame.position.y = -y; frame.position.z = 10; highlightFrames.add(frame); if (!this.cellHighlightFrames[id]) this.cellHighlightFrames[id] = []; this.cellHighlightFrames[id].push(highlightFrames); // selection frame frame = glutils.createRectFrame(w - 1, h - 1, COLOR_SELECTION, 2) frame.position.x = x; frame.position.y = -y; frame.position.z = 9; selectionFrames.add(frame); this.cellSelectionFrames[id] = selectionFrames; } updateGuideLines() { this.guideLines = []; if (!this.data) return; let w = this.ncols * this.cellSize; let h = this.nrows * this.cellSize; let geometry1 = new THREE.Geometry(); geometry1.vertices.push( new THREE.Vector3(this.offset[0], 0, 0), new THREE.Vector3(w + this.offset[0], 0, 0) ) let geometry2 = new THREE.Geometry(); geometry2.vertices.push( new THREE.Vector3(0, -this.offset[1], 0), new THREE.Vector3(0, -h - this.offset[1], 0) ) let m, pos; let mat = new THREE.LineBasicMaterial({ color: 0xeeeeee, linewidth: 1 }); let x, y; let j = 0; for (let i = 0; i <= h; i += this.cellSize) { pos = j * this.cellSize + this.offset[1]; m = new THREE.Line(geometry1, mat) m.position.set(0, -pos, 0); this.scene.add(m); this.guideLines.push(m); j++; } j = 0; for (let i = 0; i <= w; i += this.cellSize) { pos = j * this.cellSize + this.offset[0]; m = new THREE.Line(geometry2, mat); m.position.set(pos, 0, 0); this.scene.add(m); this.guideLines.push(m); j++; } } highlightLink(cell: Cell) { let row = cell.row; let col = cell.col; let id: number; if (this.linksPos[row]) { if (this.linksPos[row][col]) { for (let id of this.linksPos[row][col]) { this.toHoverLinks.push(id); } //this.render(); } } } posToCell(pos: Pos) { let row = this.cellSize !== 0 ? Math.round((pos.y - this.offset[1] - this.cellSize / 2) / this.cellSize) : 0; let col = this.cellSize !== 0 ? Math.round((pos.x - this.offset[0] - this.cellSize / 2) / this.cellSize) : 0; return { row: row, col: col }; } private mouseMoveHandler = (e: MouseEvent) => { let mpos: Pos = glutils.getMousePos(this.canvas, e.clientX, e.clientY); this.toHoverLinks = []; let cell = this.posToCell(mpos); if (!this.mouseDown) { this.highlightLink(cell); } else { let box: Box = { x0: 0, y0: 0, x1: 0, y1: 0 }; box.x0 = Math.min(cell.col, this.mouseDownCell.col); box.x1 = Math.max(cell.col, this.mouseDownCell.col); box.y0 = Math.min(cell.row, this.mouseDownCell.row); box.y1 = Math.max(cell.row, this.mouseDownCell.row); for (let c = box.x0; c <= box.x1; c++) { for (let r = box.y0; r <= box.y1; r++) { let ch = { row: r, col: c }; this.highlightLink(ch); } } } if (this.toHoverLinks.length > 0) { this.matrix.highlightLinks(this.toHoverLinks); this.matrix.updateCellLabel(this.toHoverLinks[0], mpos.x, mpos.y); } else { this.matrix.highlightLinks([]); this.matrix.updateCellLabel(-1, -1000, -1000); } } updateHighlightedLinks(hoveredLinks: number[] | undefined = undefined) { this.previousHoveredLinks = this.hoveredLinks; this.hoveredLinks = hoveredLinks; if (this.previousHoveredLinks) for (let id of this.previousHoveredLinks) { if (this.cellHighlightFrames[id]) for (let frame of this.cellHighlightFrames[id]) this.scene.remove(frame); } if (this.hoveredLinks) for (let id of this.hoveredLinks) { if (this.cellHighlightFrames[id]) for (let frame of this.cellHighlightFrames[id]) this.scene.add(frame); } this.render(); } private mouseDownHandler = (e: MouseEvent) => { if (e.shiftKey) { this.view.on('mousedown.zoom', null); this.mouseDown = true; this.mouseDownPos = glutils.getMousePos(this.canvas, e.clientX, e.clientY); this.mouseDownCell = this.posToCell(this.mouseDownPos); } } private mouseUpHandler = (e: Event) => { this.mouseDown = false; this.view.call(this.zoom);//.on('zoom', this.zoomed)); if (this.hoveredLinks) for (let id of this.hoveredLinks) { if (this.cellHighlightFrames[id]) for (let frame of this.cellHighlightFrames[id]) this.scene.remove(frame); } this.hoveredLinks = []; } clickHandler(e: Event) { console.log("click"); } private zoomed = () => { let z: number, tr: number[] = []; z = this.zoom.scale(); tr = this.zoom.translate(); this.updateTransform(z, tr); } updateTransform(z: any, tr: any) { tr[0] = Math.min(0, tr[0]); tr[1] = Math.min(0, tr[1]); this.zoom.scale(z); this.zoom.translate(tr); this.matrix.updateTransform(z, tr); } } class Matrix { private visualization: MatrixVisualization | undefined = undefined; private labels: MatrixLabels | undefined = undefined; private cellLabel: CellLabel | undefined = undefined; private menu: MatrixMenu | undefined = undefined; private timeSlider: MatrixTimeSlider | undefined = undefined; private overview: MatrixOverview | undefined = undefined; private _dgraph: dynamicgraph.DynamicGraph; private times: dynamicgraph.Time[]; public startTime: dynamicgraph.Time; public endTime: dynamicgraph.Time; private nodeOrder: number[]; private bbox: Box; private createOverviewImage: boolean; private offset: number[]; private _tr: number[]; private _scale: number; private _cellSize: any; private initialCellSize: any; private hoveredLinks: dynamicgraph.Link[]; private labelLength: number = 0; public margin: NMargin; constructor() { this._dgraph = main.getDynamicGraph(); this.startTime = this.dgraph.startTime; this.endTime = this.dgraph.endTime; this.times = this._dgraph.times().toArray() this.nodeOrder = this._dgraph.nodes().ids(); this.bbox = { x0: 0, x1: 0, y0: 0, y1: 0 }; this._tr = [0, 0]; this.offset = [0, 0]; this._scale = 1; this.createOverviewImage = false; this.initialCellSize = 12; this._cellSize = this.initialCellSize; this.hoveredLinks = []; this.longestLabelLength(); this.margin = new NMargin(0); this.calculatePlotMargin(); messenger.setDefaultEventListener(this.updateEvent); messenger.addEventListener('timeRange', this.timeRangeHandler) } get dgraph() { return this._dgraph; } get cellSize() { return this._cellSize; } getOverviewScale() { let totalNodes = this.dgraph.nodes().visible().length; let cs: number; if (this.visualization) cs = totalNodes !== 0 ? Math.min(this.visualization.height, this.visualization.width) / totalNodes : 0; else cs = 0 let scale = this.initialCellSize !== 0 ? cs / this.initialCellSize : 0; return scale; } setVis(matrixVis: MatrixVisualization) { this.visualization = matrixVis; this.resetTransform(); } setLabels(matrixLabels: MatrixLabels) { this.labels = matrixLabels; } setCellLabel(cellLabel: CellLabel) { this.cellLabel = cellLabel; } setOverview(overview: MatrixOverview) { this.overview = overview; } setMenu(menu: MatrixMenu) { this.menu = menu; } setTimeSlider(timeSlider: MatrixTimeSlider) { this.timeSlider = timeSlider; } updateOverviewImage(dataImg: any) { if (this.overview) this.overview.updateOverviewImage(dataImg); } hideCellLabel() { if (this.cellLabel) this.cellLabel.hideCellLabel(); } updateCellSize(value: number) { let scale = this.initialCellSize !== 0 ? value / this.initialCellSize : 0; var tr0: number = this._scale !== 0 ? this._tr[0] * scale / this._scale : 0; var tr1: number = this._scale !== 0 ? this._tr[1] * scale / this._scale : 0; let tr = [tr0, tr1]; if (this.visualization) this.visualization.updateTransform(scale, tr); } resetTransform() { let scale = this.getOverviewScale(); this.createOverviewImage = true; this.updateTransform(scale, [0, 0]); this.createOverviewImage = false; } updateTransform(scale: any, tr: any) { this._scale = scale; this._tr = tr; this._tr[0] = Math.min(this._tr[0], 0); this._tr[1] = Math.min(this._tr[1], 0); this._cellSize = this._scale * this.initialCellSize; if (this.menu) this.menu.setScale(this._cellSize); this.updateVisibleData(); } dgraphName() { return this._dgraph.name; } numberOfLinks() { return this._dgraph.links().length; } maxWeight() { return this._dgraph.links().weights().max(); } reorderWorker(orderType: string) { if (orderType == 'alphanumerical') { let nodes2 = this._dgraph.nodes().visible().sort('label').toArray(); this.nodeOrder = []; for (let i = 0; i < nodes2.length; i++) { this.nodeOrder[nodes2[i].id()] = i; } } else if (orderType == 'reverse-alpha') { let nodes2 = this._dgraph.nodes().visible().sort('label', false).toArray(); this.nodeOrder = []; for (let i = 0; i < nodes2.length; i++) { this.nodeOrder[nodes2[i].id()] = i; } } else if (orderType == 'degree') { let nodes2 = this._dgraph.nodes().visible() .createAttribute('degree', (n: any) => { return n.neighbors().length; }) .sort('degree').toArray(); for (let i = 0; i < nodes2.length; i++) { this.nodeOrder[nodes2[i].id()] = i; } } else if (orderType == 'similarity') { let config: ordering.OrderingConfiguration = new ordering.OrderingConfiguration(this.startTime, this.endTime); config.nodes = this._dgraph.nodes().visible().toArray(); config.links = this._dgraph.links().presentIn(this.startTime, this.endTime).visible().toArray(); this.nodeOrder = ordering.orderNodes(this._dgraph, config); } else { let visibleNodes = this._dgraph.nodes().visible().toArray(); this.nodeOrder = []; for (let i = 0; i < visibleNodes.length; i++) { this.nodeOrder[visibleNodes[i].id()] = i; } } this.resetTransform(); } longestLabelLength() { this.labelLength = 30; } calculatePlotMargin() { this.margin.setMargin((this.labelLength * 0.5) * this.cellSize); } updateVisibleBox() { this.bbox.x0 = this._cellSize !== 0 ? -Math.floor(this._tr[0] / this._cellSize) : 0; this.bbox.y0 = this._cellSize !== 0 ? -Math.floor(this._tr[1] / this._cellSize) : 0; if (this.visualization) { this.bbox.x1 = this._cellSize !== 0 ? this.bbox.x0 + Math.floor(this.visualization.width / this._cellSize) : this.bbox.x0; this.bbox.y1 = this._cellSize !== 0 ? this.bbox.y0 + Math.floor(this.visualization.height / this._cellSize) : this.bbox.y0; } else { this.bbox.x1 = this.bbox.x0; this.bbox.y1 = this.bbox.y0; } this.offset[0] = this._cellSize !== 0 ? (this._tr[0] / this._cellSize + this.bbox.x0) * this._cellSize : 0; this.offset[1] = this._cellSize !== 0 ? (this._tr[1] / this._cellSize + this.bbox.y0) * this._cellSize : 0; } updateVisibleData() { this.updateVisibleBox(); let leftNodes = this.dgraph.nodes().visible().toArray(); leftNodes = leftNodes.filter((d: any) => this.nodeOrder[d.id()] >= this.bbox.y0 && this.nodeOrder[d.id()] <= this.bbox.y1); let topNodes = this.dgraph.nodes().visible().toArray(); topNodes = topNodes.filter((d: any) => this.nodeOrder[d.id()] >= this.bbox.x0 && this.nodeOrder[d.id()] <= this.bbox.x1); let visibleData: { [id: number]: { [id: number]: dynamicgraph.NodePair } } = {}; let row, col: number; let node: dynamicgraph.Node; for (let i = 0; i < leftNodes.length; i++) { node = leftNodes[i]; if (node.isVisible()) { row = this.nodeOrder[node.id()] - this.bbox.y0; for (let link of node.links().toArray()) { let neighbor = link.source.id() == node.id() ? link.target : link.source; if (neighbor.isVisible() && this.nodeOrder[neighbor.id()] >= this.bbox.x0 && this.nodeOrder[neighbor.id()] <= this.bbox.x1) { if (!visibleData[row]) visibleData[row] = {}; col = this.nodeOrder[neighbor.id()] - this.bbox.x0; visibleData[row][col] = link.nodePair(); } } } } if (this.visualization) { this.visualization.updateData(visibleData, leftNodes.length, topNodes.length, this.cellSize, this.offset, this._scale, this._tr, this.createOverviewImage); } if (this.overview) { let totalNodes: number = this.dgraph.nodes().visible().length; let widthRatio: number = totalNodes !== 0 ? (this.bbox.x1 - this.bbox.x0) / totalNodes : 0; let heightRatio: number = totalNodes !== 0 ? (this.bbox.y1 - this.bbox.y0) / totalNodes : 0; let ratio: number = totalNodes * this.cellSize; var matrixX0: number = totalNodes !== 0 ? this.bbox.x0 / totalNodes : 0; var matrixY0: number = totalNodes !== 0 ? this.bbox.y0 / totalNodes : 0; this.overview.updateFocus(matrixX0, matrixY0, widthRatio, heightRatio, ratio, this._scale, this._tr); } if (this.labels) { this.labels.updateData(leftNodes, topNodes, this.cellSize, this.nodeOrder, this.offset[1], this.offset[0], this.bbox); } } highlightLinks(highlightedIds: number[]) { if (highlightedIds.length > 0) { let highlightedLinks: (dynamicgraph.Link | undefined)[] = highlightedIds.map( (d) => this._dgraph.link(d)); messenger.highlight('set', { links: highlightedLinks }); } else messenger.highlight('reset'); } nodeClicked(d: dynamicgraph.Node) { let selections = d.getSelections(); let currentSelection = this._dgraph.getCurrentSelection(); for (let j = 0; j < selections.length; j++) { if (selections[j] == currentSelection) { messenger.selection('remove', { nodes: [d] }); return; } } messenger.selection('add', { nodes: [d] }); if (this.labels) this.labels.updateHighlightedNodes(); } highlightNodes(highlightedIds: number[]) { if (highlightedIds.length > 0) { let highlightedNodes: (dynamicgraph.Node | undefined)[] = highlightedIds.map( (d) => this._dgraph.node(d)); messenger.highlight('set', { nodes: highlightedNodes }); } else messenger.highlight('reset'); } updateCellLabel(linkId: number, mx: number, my: number) { if (linkId < 0) { if (this.cellLabel) this.cellLabel.updateCellLabel(-1000, -1000, null, 0); return; } let link = this._dgraph.link(linkId); if (link) { let val = link.weights(this.startTime, this.endTime).get(0); val = Math.round(val * 1000) / 1000; let z = this._scale; let fw = this.initialCellSize; if (this.cellLabel) this.cellLabel.updateCellLabel(mx, my, val, fw); } } updateEvent = () => { let highlightedNodesIds = []; let highlightedLinksIds = []; let highlightedLinks = this._dgraph.links().highlighted().toArray(); if (highlightedLinks.length > 0) { for (let i = 0; i < highlightedLinks.length; i++) { if (!highlightedLinks[i].isVisible()) continue; highlightedNodesIds.push(highlightedLinks[i].source.id()); highlightedNodesIds.push(highlightedLinks[i].target.id()); highlightedLinksIds.push(highlightedLinks[i].id()); } } else { let highlightedNodes = this._dgraph.nodes().highlighted().toArray(); for (let i = 0; i < highlightedNodes.length; i++) { let node = highlightedNodes[i]; if (node.isVisible()) { for (let link of node.links().toArray()) { let neighbor = link.source.id() == node.id() ? link.target : link.source; if (neighbor.isVisible()) highlightedLinksIds.push(link.id()); } } } } // show/hide visible/filtered links this.updateVisibleData(); if (this.labels) this.labels.updateHighlightedNodes(highlightedNodesIds); if (this.visualization) this.visualization.updateHighlightedLinks(highlightedLinksIds); } timeRangeHandler = (m: messenger.TimeRangeMessage) => { this.startTime = this.times[0]; this.endTime = this.times[this.times.length - 1]; for (var i = 0; i < this.times.length; i++) { if (this.times[i].unixTime() > m.startUnix) { this.startTime = this.times[i - 1]; break; } } for (i; i < this.times.length; i++) { if (this.times[i].unixTime() > m.endUnix) { this.endTime = this.times[i - 1]; break; } } if (this.timeSlider) this.timeSlider.set(m.startUnix, m.endUnix); this.updateVisibleData(); } } let matrix = new Matrix(); let vizWidth: number = window.innerWidth - 10; let vizHeight: number = window.innerHeight - 115; let appendToBody = (domId: any) => { return $('
').appendTo('body') }; let menuJQ = appendToBody("networkcube-matrix-menu"); let tsJQ = appendToBody("networkcube-matrix-timelineDiv'") let labJQ = appendToBody("networkcube-matrix-visDiv"); let svg = d3.select(labJQ.get(0)) .append('svg') .attr('id', 'networkcube-matrix-visSvg') .attr('width', vizWidth) .attr('height', vizHeight); let foreignObject: any = svg.append('foreignObject') // BEFORE d3.Selection .attr('id', 'networkcube-matrix-visCanvasFO') .attr('x', matrix.margin.left) .attr('y', matrix.margin.top) .attr('width', vizWidth - matrix.margin.left) .attr('height', vizHeight - matrix.margin.top); let bbox = foreignObject.node().getBBox(); let matrixMenu = new MatrixMenu(menuJQ, matrix); let matrixTimeSlider = new MatrixTimeSlider(tsJQ, matrix, vizWidth); let matrixLabels = new MatrixLabels(svg, matrix.margin, matrix); let matrixVis = new MatrixVisualization(foreignObject, bbox.width, bbox.height, matrix); let matrixOverview = new MatrixOverview(svg, matrix.margin.left - 2, matrix.margin.top - 2, matrix); let cellLabel = new CellLabel(); matrix.setLabels(matrixLabels); matrix.setMenu(matrixMenu); matrix.setTimeSlider(matrixTimeSlider); matrix.setCellLabel(cellLabel); matrix.setOverview(matrixOverview); matrix.setVis(matrixVis); messenger.addEventListener('timeRange', matrix.timeRangeHandler);