From a50ad3901377b30c5188ff3ebd519f8b0457c5eb Mon Sep 17 00:00:00 2001 From: stuebinm Date: Thu, 10 Feb 2022 18:12:06 +0100 Subject: server: present lint results in pretty html (this is mostly a rewrite / translation of the django templates built into rc3's hub) --- static/d3-graphviz.js | 2198 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2198 insertions(+) create mode 100644 static/d3-graphviz.js (limited to 'static/d3-graphviz.js') diff --git a/static/d3-graphviz.js b/static/d3-graphviz.js new file mode 100644 index 0000000..6f6082b --- /dev/null +++ b/static/d3-graphviz.js @@ -0,0 +1,2198 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-dispatch'), require('d3-transition'), require('d3-timer'), require('d3-interpolate'), require('d3-zoom'), require('@hpcc-js/wasm'), require('d3-format'), require('d3-path')) : + typeof define === 'function' && define.amd ? define(['exports', 'd3-selection', 'd3-dispatch', 'd3-transition', 'd3-timer', 'd3-interpolate', 'd3-zoom', '@hpcc-js/wasm', 'd3-format', 'd3-path'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['d3-graphviz'] = {}, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global['@hpcc-js/wasm'], global.d3, global.d3)); +}(this, (function (exports, d3, d3Dispatch, d3Transition, d3Timer, d3Interpolate, d3Zoom, wasm, d3Format, d3Path) { 'use strict'; + + function _interopNamespace(e) { + if (e && e.__esModule) return e; + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { + return e[k]; + } + }); + } + }); + } + n['default'] = e; + return Object.freeze(n); + } + + var d3__namespace = /*#__PURE__*/_interopNamespace(d3); + + function extractElementData(element) { + + var datum = {}; + var tag = element.node().nodeName; + datum.tag = tag; + if (tag == '#text') { + datum.text = element.text(); + } else if (tag == '#comment') { + datum.comment = element.text(); + } + datum.attributes = {}; + var attributes = element.node().attributes; + if (attributes) { + for (var i = 0; i < attributes.length; i++) { + var attribute = attributes[i]; + var name = attribute.name; + var value = attribute.value; + datum.attributes[name] = value; + } + } + var transform = element.node().transform; + if (transform && transform.baseVal.numberOfItems != 0) { + var matrix = transform.baseVal.consolidate().matrix; + datum.translation = {x: matrix.e, y: matrix.f}; + datum.scale = matrix.a; + } + if (tag == 'ellipse') { + datum.center = { + x: datum.attributes.cx, + y: datum.attributes.cy, + }; + } + if (tag == 'polygon') { + var points = element.attr('points').split(' '); + var x = points.map(function(p) {return p.split(',')[0]}); + var y = points.map(function(p) {return p.split(',')[1]}); + var xmin = Math.min.apply(null, x); + var xmax = Math.max.apply(null, x); + var ymin = Math.min.apply(null, y); + var ymax = Math.max.apply(null, y); + var bbox = { + x: xmin, + y: ymin, + width: xmax - xmin, + height: ymax - ymin, + }; + datum.bbox = bbox; + datum.center = { + x: (xmin + xmax) / 2, + y: (ymin + ymax) / 2, + }; + } + if (tag == 'path') { + var d = element.attr('d'); + var points = d.split(/[A-Z ]/); + points.shift(); + var x = points.map(function(p) {return +p.split(',')[0]}); + var y = points.map(function(p) {return +p.split(',')[1]}); + var xmin = Math.min.apply(null, x); + var xmax = Math.max.apply(null, x); + var ymin = Math.min.apply(null, y); + var ymax = Math.max.apply(null, y); + var bbox = { + x: xmin, + y: ymin, + width: xmax - xmin, + height: ymax - ymin, + }; + datum.bbox = bbox; + datum.center = { + x: (xmin + xmax) / 2, + y: (ymin + ymax) / 2, + }; + datum.totalLength = element.node().getTotalLength(); + } + if (tag == 'text') { + datum.center = { + x: element.attr('x'), + y: element.attr('y'), + }; + } + if (tag == '#text') { + datum.text = element.text(); + } else if (tag == '#comment') { + datum.comment = element.text(); + } + return datum + } + + function extractAllElementsData(element) { + + var datum = extractElementData(element); + datum.children = []; + var children = d3__namespace.selectAll(element.node().childNodes); + children.each(function () { + var childData = extractAllElementsData(d3__namespace.select(this)); + childData.parent = datum; + datum.children.push(childData); + }); + return datum; + } + + function createElement(data) { + + if (data.tag == '#text') { + return document.createTextNode(""); + } else if (data.tag == '#comment') { + return document.createComment(data.comment); + } else { + return document.createElementNS('http://www.w3.org/2000/svg', data.tag); + } + } + + function createElementWithAttributes(data) { + + var elementNode = createElement(data); + var element = d3__namespace.select(elementNode); + var attributes = data.attributes; + for (var attributeName of Object.keys(attributes)) { + var attributeValue = attributes[attributeName]; + element.attr(attributeName, attributeValue); + } + return elementNode; + } + + function replaceElement(element, data) { + var parent = d3__namespace.select(element.node().parentNode); + var newElementNode = createElementWithAttributes(data); + var newElement = parent.insert(function () { + return newElementNode; + }, function () { + return element.node(); + }); + element.remove(); + return newElement; + } + + function insertElementData(element, datum) { + element.datum(datum); + element.data([datum], function (d) { + return d.key; + }); + } + + function insertAllElementsData(element, datum) { + insertElementData(element, datum); + var children = d3__namespace.selectAll(element.node().childNodes); + children.each(function (d, i) { + insertAllElementsData(d3__namespace.select(this), datum.children[i]); + }); + } + + function insertChildren(element, index) { + var children = element.selectAll(function () { + return element.node().childNodes; + }); + + children = children + .data(function (d) { + return d.children; + }, function (d) { + return d.tag + '-' + index; + }); + var childrenEnter = children + .enter() + .append(function(d) { + return createElement(d); + }); + + var childrenExit = children + .exit(); + childrenExit = childrenExit + .remove(); + children = childrenEnter + .merge(children); + var childTagIndexes = {}; + children.each(function(childData) { + var childTag = childData.tag; + if (childTagIndexes[childTag] == null) { + childTagIndexes[childTag] = 0; + } + var childIndex = childTagIndexes[childTag]++; + attributeElement.call(this, childData, childIndex); + }); + } + + function attributeElement(data, index=0) { + var element = d3__namespace.select(this); + data.tag; + var attributes = data.attributes; + var currentAttributes = element.node().attributes; + if (currentAttributes) { + for (var i = 0; i < currentAttributes.length; i++) { + var currentAttribute = currentAttributes[i]; + var name = currentAttribute.name; + if (name.split(':')[0] != 'xmlns' && currentAttribute.namespaceURI) { + var namespaceURIParts = currentAttribute.namespaceURI.split('/'); + var namespace = namespaceURIParts[namespaceURIParts.length - 1]; + name = namespace + ':' + name; + } + if (!(name in attributes)) { + attributes[name] = null; + } + } + } + for (var attributeName of Object.keys(attributes)) { + element + .attr(attributeName, attributes[attributeName]); + } + if (data.text) { + element + .text(data.text); + } + insertChildren(element, index); + } + + function shallowCopyObject(obj) { + return Object.assign({}, obj); + } + + function roundTo2Decimals(x) { + return Math.round(x * 100.0) / 100.0 + } + + function zoom(enable) { + + this._options.zoom = enable; + + if (this._options.zoom && !this._zoomBehavior) { + createZoomBehavior.call(this); + } else if (!this._options.zoom && this._zoomBehavior) { + this._zoomSelection.on(".zoom", null); + this._zoomBehavior = null; + } + + return this; + } + + function createZoomBehavior() { + + function zoomed(event) { + var g = d3__namespace.select(svg.node().querySelector("g")); + g.attr('transform', event.transform); + } + + var root = this._selection; + var svg = d3__namespace.select(root.node().querySelector("svg")); + if (svg.size() == 0) { + return this; + } + this._zoomSelection = svg; + var zoomBehavior = d3Zoom.zoom() + .scaleExtent(this._options.zoomScaleExtent) + .translateExtent(this._options.zoomTranslateExtent) + .interpolate(d3Interpolate.interpolate) + .on("zoom", zoomed); + this._zoomBehavior = zoomBehavior; + var g = d3__namespace.select(svg.node().querySelector("g")); + svg.call(zoomBehavior); + if (!this._active) { + translateZoomBehaviorTransform.call(this, g); + } + this._originalTransform = d3Zoom.zoomTransform(svg.node()); + + return this; + } + function getTranslatedZoomTransform(selection) { + + // Get the current zoom transform for the top level svg and + // translate it uniformly with the given selection, using the + // difference between the translation specified in the selection's + // data and it's saved previous translation. The selection is + // normally the top level g element of the graph. + var oldTranslation = this._translation; + var oldScale = this._scale; + var newTranslation = selection.datum().translation; + var newScale = selection.datum().scale; + var t = d3Zoom.zoomTransform(this._zoomSelection.node()); + if (oldTranslation) { + t = t.scale(1 / oldScale); + t = t.translate(-oldTranslation.x, -oldTranslation.y); + } + t = t.translate(newTranslation.x, newTranslation.y); + t = t.scale(newScale); + return t; + } + + function translateZoomBehaviorTransform(selection) { + + // Translate the current zoom transform for the top level svg + // uniformly with the given selection, using the difference + // between the translation specified in the selection's data and + // it's saved previous translation. The selection is normally the + // top level g element of the graph. + this._zoomBehavior.transform(this._zoomSelection, getTranslatedZoomTransform.call(this, selection)); + + // Save the selections's new translation and scale. + this._translation = selection.datum().translation; + this._scale = selection.datum().scale; + + // Set the original zoom transform to the translation and scale specified in + // the selection's data. + this._originalTransform = d3Zoom.zoomIdentity.translate(selection.datum().translation.x, selection.datum().translation.y).scale(selection.datum().scale); + } + + function resetZoom(transition) { + + // Reset the zoom transform to the original zoom transform. + var selection = this._zoomSelection; + if (transition) { + selection = selection + .transition(transition); + } + selection + .call(this._zoomBehavior.transform, this._originalTransform); + + return this; + } + + function zoomScaleExtent(extent) { + + this._options.zoomScaleExtent = extent; + + return this; + } + + function zoomTranslateExtent(extent) { + + this._options.zoomTranslateExtent = extent; + + return this; + } + + function zoomBehavior() { + return this._zoomBehavior || null; + } + + function zoomSelection() { + return this._zoomSelection || null; + } + + function pathTween(points, d1) { + return function() { + const pointInterpolators = points.map(function(p) { + return d3Interpolate.interpolate([p[0][0], p[0][1]], [p[1][0], p[1][1]]); + }); + return function(t) { + return t < 1 ? "M" + pointInterpolators.map(function(p) { return p(t); }).join("L") : d1; + }; + }; + } + + function pathTweenPoints(node, d1, precision, precisionIsRelative) { + const path0 = node; + const path1 = path0.cloneNode(); + const n0 = path0.getTotalLength(); + const n1 = (path1.setAttribute("d", d1), path1).getTotalLength(); + + // Uniform sampling of distance based on specified precision. + const distances = [0]; + let i = 0; + const dt = precisionIsRelative ? precision : precision / Math.max(n0, n1); + while ((i += dt) < 1) { + distances.push(i); + } + distances.push(1); + + // Compute point-interpolators at each distance. + const points = distances.map(function(t) { + const p0 = path0.getPointAtLength(t * n0); + const p1 = path1.getPointAtLength(t * n1); + return ([[p0.x, p0.y], [p1.x, p1.y]]); + }); + return points; + } + + function data() { + return this._data || null; + } + + function isEdgeElementParent(datum) { + return (datum.attributes.class == 'edge' || ( + datum.tag == 'a' && + datum.parent.tag == 'g' && + datum.parent.parent.attributes.class == 'edge' + )); + } + + function isEdgeElement(datum) { + return datum.parent && isEdgeElementParent(datum.parent); + } + + function getEdgeGroup(datum) { + if (datum.parent.attributes.class == 'edge') { + return datum.parent; + } else { // datum.parent.tag == 'g' && datum.parent.parent.tag == 'g' && datum.parent.parent.parent.attributes.class == 'edge' + return datum.parent.parent.parent; + } + } + + function getEdgeTitle(datum) { + return getEdgeGroup(datum).children.find(function (e) { + return e.tag == 'title'; + }); + } + + function render(callback) { + + if (this._busy) { + this._queue.push(this.render.bind(this, callback)); + return this; + } + this._dispatch.call('renderStart', this); + + if (this._transitionFactory) { + d3Timer.timeout(function () { // Decouple from time spent. See https://github.com/d3/d3-timer/issues/27 + this._transition = d3Transition.transition(this._transitionFactory()); + _render.call(this, callback); + }.bind(this), 0); + } else { + _render.call(this, callback); + } + return this; + } + + function _render(callback) { + + var transitionInstance = this._transition; + var fade = this._options.fade && transitionInstance != null; + var tweenPaths = this._options.tweenPaths; + var tweenShapes = this._options.tweenShapes; + var convertEqualSidedPolygons = this._options.convertEqualSidedPolygons; + var growEnteringEdges = this._options.growEnteringEdges && transitionInstance != null; + var attributer = this._attributer; + var graphvizInstance = this; + + function insertChildren(element) { + var children = element.selectAll(function () { + return element.node().childNodes; + }); + + children = children + .data(function (d) { + return d.children; + }, function (d) { + return d.key; + }); + var childrenEnter = children + .enter() + .append(function(d) { + var element = createElement(d); + if (d.tag == '#text' && fade) { + element.nodeValue = d.text; + } + return element; + }); + + if (fade || (growEnteringEdges && isEdgeElementParent(element.datum()))) { + var childElementsEnter = childrenEnter + .filter(function(d) { + return d.tag[0] == '#' ? null : this; + }) + .each(function (d) { + var childEnter = d3__namespace.select(this); + for (var attributeName of Object.keys(d.attributes)) { + var attributeValue = d.attributes[attributeName]; + childEnter + .attr(attributeName, attributeValue); + } + }); + childElementsEnter + .filter(function(d) { + return d.tag == 'svg' || d.tag == 'g' ? null : this; + }) + .style("opacity", 0.0); + } + var childrenExit = children + .exit(); + if (attributer) { + childrenExit.each(attributer); + } + if (transitionInstance) { + childrenExit = childrenExit + .transition(transitionInstance); + if (fade) { + childrenExit + .filter(function(d) { + return d.tag[0] == '#' ? null : this; + }) + .style("opacity", 0.0); + } + } + childrenExit = childrenExit + .remove(); + children = childrenEnter + .merge(children); + children.each(attributeElement); + } + + function attributeElement(data) { + var element = d3__namespace.select(this); + if (data.tag == "svg") { + var options = graphvizInstance._options; + if (options.width != null || options.height != null) { + var width = options.width; + var height = options.height; + if (width == null) { + width = data.attributes.width.replace('pt', '') * 4 / 3; + } else { + element + .attr("width", width); + data.attributes.width = width; + } + if (height == null) { + height = data.attributes.height.replace('pt', '') * 4 / 3; + } else { + element + .attr("height", height); + data.attributes.height = height; + } + if (!options.fit) { + element + .attr("viewBox", `0 0 ${width * 3 / 4 / options.scale} ${height * 3 / 4 / options.scale}`); + data.attributes.viewBox = `0 0 ${width * 3 / 4 / options.scale} ${height * 3 / 4 / options.scale}`; + } + } + if (options.scale != 1 && (options.fit || (options.width == null && options.height == null))) { + width = data.attributes.viewBox.split(' ')[2]; + height = data.attributes.viewBox.split(' ')[3]; + element + .attr("viewBox", `0 0 ${width / options.scale} ${height / options.scale}`); + data.attributes.viewBox = `0 0 ${width / options.scale} ${height / options.scale}`; + } + } + if (attributer) { + element.each(attributer); + } + var tag = data.tag; + var attributes = data.attributes; + var currentAttributes = element.node().attributes; + if (currentAttributes) { + for (var i = 0; i < currentAttributes.length; i++) { + var currentAttribute = currentAttributes[i]; + var name = currentAttribute.name; + if (name.split(':')[0] != 'xmlns' && currentAttribute.namespaceURI) { + var namespaceURIParts = currentAttribute.namespaceURI.split('/'); + var namespace = namespaceURIParts[namespaceURIParts.length - 1]; + name = namespace + ':' + name; + } + if (!(name in attributes)) { + attributes[name] = null; + } + } + } + var convertShape = false; + var convertPrevShape = false; + if (tweenShapes && transitionInstance) { + if ((this.nodeName == 'polygon' || this.nodeName == 'ellipse') && data.alternativeOld) { + convertPrevShape = true; + } + if ((tag == 'polygon' || tag == 'ellipse') && data.alternativeNew) { + convertShape = true; + } + if (this.nodeName == 'polygon' && tag == 'polygon' && data.alternativeOld) { + var prevData = extractElementData(element); + var prevPoints = prevData.attributes.points; + if (!convertEqualSidedPolygons) { + var nPrevPoints = prevPoints.split(' ').length; + var points = data.attributes.points; + var nPoints = points.split(' ').length; + if (nPoints == nPrevPoints) { + convertShape = false; + convertPrevShape = false; + } + } + } + if (convertPrevShape) { + var prevPathData = data.alternativeOld; + var pathElement = replaceElement(element, prevPathData); + pathElement.data([data], function () { + return data.key; + }); + element = pathElement; + } + if (convertShape) { + var newPathData = data.alternativeNew; + tag = 'path'; + attributes = newPathData.attributes; + } + } + var elementTransition = element; + if (transitionInstance) { + elementTransition = elementTransition + .transition(transitionInstance); + if (fade) { + elementTransition + .filter(function(d) { + return d.tag[0] == '#' ? null : this; + }) + .style("opacity", 1.0); + } + elementTransition + .filter(function(d) { + return d.tag[0] == '#' ? null : this; + }) + .on("end", function(d) { + d3__namespace.select(this) + .attr('style', (d && d.attributes && d.attributes.style) || null); + }); + } + var growThisPath = growEnteringEdges && tag == 'path' && data.offset; + if (growThisPath) { + var totalLength = data.totalLength; + element + .attr("stroke-dasharray", totalLength + " " + totalLength) + .attr("stroke-dashoffset", totalLength) + .attr('transform', 'translate(' + data.offset.x + ',' + data.offset.y + ')'); + attributes["stroke-dashoffset"] = 0; + attributes['transform'] = 'translate(0,0)'; + elementTransition + .attr("stroke-dashoffset", attributes["stroke-dashoffset"]) + .attr('transform', attributes['transform']) + .on("start", function() { + d3__namespace.select(this) + .style('opacity', null); + }) + .on("end", function() { + d3__namespace.select(this) + .attr('stroke-dashoffset', null) + .attr('stroke-dasharray', null) + .attr('transform', null); + }); + } + var moveThisPolygon = growEnteringEdges && tag == 'polygon' && isEdgeElement(data) && data.offset && data.parent.children[3].tag == 'path'; + if (moveThisPolygon) { + var edgePath = d3__namespace.select(element.node().parentNode.querySelector("path")); + var p0 = edgePath.node().getPointAtLength(0); + var p1 = edgePath.node().getPointAtLength(data.totalLength); + var p2 = edgePath.node().getPointAtLength(data.totalLength - 1); + var angle1 = Math.atan2(p1.y - p2.y, p1.x - p2.x) * 180 / Math.PI; + var x = p0.x - p1.x + data.offset.x; + var y = p0.y - p1.y + data.offset.y; + element + .attr('transform', 'translate(' + x + ',' + y + ')'); + elementTransition + .attrTween("transform", function () { + return function (t) { + var p = edgePath.node().getPointAtLength(data.totalLength * t); + var p2 = edgePath.node().getPointAtLength(data.totalLength * t + 1); + var angle = Math.atan2(p2.y - p.y, p2.x - p.x) * 180 / Math.PI - angle1; + x = p.x - p1.x + data.offset.x * (1 - t); + y = p.y - p1.y + data.offset.y * (1 - t); + return 'translate(' + x + ',' + y + ') rotate(' + angle + ' ' + p1.x + ' ' + p1.y + ')'; + } + }) + .on("start", function() { + d3__namespace.select(this) + .style('opacity', null); + }) + .on("end", function() { + d3__namespace.select(this).attr('transform', null); + }); + } + var tweenThisPath = tweenPaths && transitionInstance && tag == 'path' && element.attr('d') != null; + for (var attributeName of Object.keys(attributes)) { + var attributeValue = attributes[attributeName]; + if (tweenThisPath && attributeName == 'd') { + var points = (data.alternativeOld || data).points; + if (points) { + elementTransition + .attrTween("d", pathTween(points, attributeValue)); + } + } else { + if (attributeName == 'transform' && data.translation) { + if (transitionInstance) { + var onEnd = elementTransition.on("end"); + elementTransition + .on("start", function () { + if (graphvizInstance._zoomBehavior) { + // Update the transform to transition to, just before the transition starts + // in order to catch changes between the transition scheduling to its start. + elementTransition + .tween("attr.transform", function() { + var node = this; + return function(t) { + node.setAttribute("transform", d3Interpolate.interpolateTransformSvg(d3Zoom.zoomTransform(graphvizInstance._zoomSelection.node()).toString(), getTranslatedZoomTransform.call(graphvizInstance, element).toString())(t)); + }; + }); + } + }) + .on("end", function () { + onEnd.call(this); + // Update the zoom transform to the new translated transform + if (graphvizInstance._zoomBehavior) { + translateZoomBehaviorTransform.call(graphvizInstance, element); + } + }); + } else { + if (graphvizInstance._zoomBehavior) { + // Update the transform attribute to set with the current pan translation + translateZoomBehaviorTransform.call(graphvizInstance, element); + attributeValue = getTranslatedZoomTransform.call(graphvizInstance, element).toString(); + } + } + } + elementTransition + .attr(attributeName, attributeValue); + } + } + if (convertShape) { + elementTransition + .on("end", function (d, i, nodes) { + pathElement = d3__namespace.select(this); + var newElement = replaceElement(pathElement, d); + newElement.data([d], function () { + return d.key; + }); + }); + } + if (data.text) { + elementTransition + .text(data.text); + } + insertChildren(element); + } + + var root = this._selection; + + if (transitionInstance != null) { + // Ensure original SVG shape elements are restored after transition before rendering new graph + var jobs = this._jobs; + if (graphvizInstance._active) { + jobs.push(null); + return this; + } else { + root + .transition(transitionInstance) + .transition() + .duration(0) + .on("end" , function () { + graphvizInstance._active = false; + if (jobs.length != 0) { + jobs.shift(); + graphvizInstance.render(); + } + }); + this._active = true; + } + } + + if (transitionInstance != null) { + root + .transition(transitionInstance) + .on("start" , function () { + graphvizInstance._dispatch.call('transitionStart', graphvizInstance); + }) + .on("end" , function () { + graphvizInstance._dispatch.call('transitionEnd', graphvizInstance); + }) + .transition() + .duration(0) + .on("start" , function () { + graphvizInstance._dispatch.call('restoreEnd', graphvizInstance); + graphvizInstance._dispatch.call('end', graphvizInstance); + if (callback) { + callback.call(graphvizInstance); + } + }); + } + + var data = this._data; + + var svg = root + .selectAll("svg") + .data([data], function (d) {return d.key}); + svg = svg + .enter() + .append("svg") + .merge(svg); + + attributeElement.call(svg.node(), data); + + + if (this._options.zoom && !this._zoomBehavior) { + createZoomBehavior.call(this); + } + + graphvizInstance._dispatch.call('renderEnd', graphvizInstance); + + if (transitionInstance == null) { + this._dispatch.call('end', this); + if (callback) { + callback.call(this); + } + } + + return this; + } + + function convertToPathData(originalData, guideData) { + if (originalData.tag == 'polygon') { + var newData = shallowCopyObject(originalData); + newData.tag = 'path'; + var originalAttributes = originalData.attributes; + var newAttributes = shallowCopyObject(originalAttributes); + var newPointsString = originalAttributes.points; + if (guideData.tag == 'polygon') { + var bbox = originalData.bbox; + bbox.cx = bbox.x + bbox.width / 2; + bbox.cy = bbox.y + bbox.height / 2; + var pointsString = originalAttributes.points; + var pointStrings = pointsString.split(' '); + var normPoints = pointStrings.map(function(p) {var xy = p.split(','); return [xy[0] - bbox.cx, xy[1] - bbox.cy]}); + var x0 = normPoints[normPoints.length - 1][0]; + var y0 = normPoints[normPoints.length - 1][1]; + for (var i = 0; i < normPoints.length; i++, x0 = x1, y0 = y1) { + var x1 = normPoints[i][0]; + var y1 = normPoints[i][1]; + var dx = x1 - x0; + var dy = y1 - y0; + if (dy == 0) { + continue; + } else { + var x2 = x0 - y0 * dx / dy; + } + if (0 <= x2 && x2 < Infinity && ((x0 <= x2 && x2 <= x1) || (x1 <= x2 && x2 <= x0))) { + break; + } + } + var newPointStrings = [[bbox.cx + x2, bbox.cy + 0].join(',')]; + newPointStrings = newPointStrings.concat(pointStrings.slice(i)); + newPointStrings = newPointStrings.concat(pointStrings.slice(0, i)); + newPointsString = newPointStrings.join(' '); + } + newAttributes['d'] = 'M' + newPointsString + 'z'; + delete newAttributes.points; + newData.attributes = newAttributes; + } else /* if (originalData.tag == 'ellipse') */ { + var newData = shallowCopyObject(originalData); + newData.tag = 'path'; + var originalAttributes = originalData.attributes; + var newAttributes = shallowCopyObject(originalAttributes); + var cx = originalAttributes.cx; + var cy = originalAttributes.cy; + var rx = originalAttributes.rx; + var ry = originalAttributes.ry; + if (guideData.tag == 'polygon') { + var bbox = guideData.bbox; + bbox.cx = bbox.x + bbox.width / 2; + bbox.cy = bbox.y + bbox.height / 2; + var p = guideData.attributes.points.split(' ')[0].split(','); + var sx = p[0]; + var sy = p[1]; + var dx = sx - bbox.cx; + var dy = sy - bbox.cy; + var l = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); + var cosA = dx / l; + var sinA = -dy / l; + } else { // if (guideData.tag == 'path') { + // FIXME: add support for getting start position from path + var cosA = 1; + var sinA = 0; + } + var x1 = rx * cosA; + var y1 = -ry * sinA; + var x2 = rx * (-cosA); + var y2 = -ry * (-sinA); + var dx = x2 - x1; + var dy = y2 - y1; + newAttributes['d'] = 'M ' + cx + ' ' + cy + ' m ' + x1 + ',' + y1 + ' a ' + rx + ',' + ry + ' 0 1,0 ' + dx + ',' + dy + ' a ' + rx + ',' + ry + ' 0 1,0 ' + -dx + ',' + -dy + 'z'; + delete newAttributes.cx; + delete newAttributes.cy; + delete newAttributes.rx; + delete newAttributes.ry; + newData.attributes = newAttributes; + } + return newData; + } + + function translatePointsAttribute(pointsString, x, y) { + var pointStrings = pointsString.split(' '); + var points = pointStrings.map(function(p) {return p.split(',')}); + var points = pointStrings.map(function(p) {return [roundTo2Decimals(+x + +p.split(',')[0]), roundTo2Decimals(+y + +p.split(',')[1])]}); + var pointStrings = points.map(function(p) {return p.join(',')}); + var pointsString = pointStrings.join(' '); + return pointsString; + } + + function translateDAttribute(d, x, y) { + var pointStrings = d.split(/[A-Z ]/); + pointStrings.shift(); + var commands = d.split(/[^[A-Z ]+/); + var points = pointStrings.map(function(p) {return p.split(',')}); + var points = pointStrings.map(function(p) {return [roundTo2Decimals(+x + +p.split(',')[0]), roundTo2Decimals(+y + +p.split(',')[1])]}); + var pointStrings = points.map(function(p) {return p.join(',')}); + d = commands.reduce(function(arr, v, i) { + return arr.concat(v, pointStrings[i]); + }, []).join(''); + return d; + } + + function initViz() { + + // force JIT compilation of Viz.js + try { + wasm.graphviz.layout("", "svg", "dot").then(() => { + wasm.graphvizSync().then((graphviz1) => { + this.layoutSync = graphviz1.layout.bind(graphviz1); + if (this._worker == null) { + this._dispatch.call("initEnd", this); + } + if (this._afterInit) { + this._afterInit(); + } + }); + }); + } catch(error) { + } + if (this._worker != null) { + var vizURL = this._vizURL; + var graphvizInstance = this; + this._workerPort.onmessage = function(event) { + var callback = graphvizInstance._workerCallbacks.shift(); + callback.call(graphvizInstance, event); + }; + if (!vizURL.match(/^https?:\/\/|^\/\//i)) { + // Local URL. Prepend with local domain to be usable in web worker + vizURL = (new window.URL(vizURL, document.location.href)).href; + } + postMessage.call(this, {dot: "", engine: 'dot', vizURL: vizURL}, function(event) { + switch (event.data.type) { + case "init": + graphvizInstance._dispatch.call("initEnd", this); + break; + } + }); + } + } + + function postMessage(message, callback) { + this._workerCallbacks.push(callback); + this._workerPort.postMessage(message); + } + + function layout(src, engine, vizOptions, callback) { + this._worker; + if (this._worker) { + postMessage.call(this, { + dot: src, + engine: engine, + options: vizOptions, + }, function (event) { + callback.call(this, event.data); + }); + } else { + try { + var svgDoc = this.layoutSync(src, "svg", engine, vizOptions); + callback.call(this, {type: 'done', svg: svgDoc}); + } + catch(error) { + callback.call(this, {type: 'error', error: error.message}); + } + } + } + + function dot(src, callback) { + + var graphvizInstance = this; + this._worker; + var engine = this._options.engine; + var images = this._images; + + this._dispatch.call("start", this); + this._busy = true; + this._dispatch.call("layoutStart", this); + var vizOptions = { + images: images, + }; + if (!this._worker && this.layoutSync == null) { + this._afterInit = this.dot.bind(this, src, callback); + return this; + } + this.layout(src, engine, vizOptions, function (data) { + switch (data.type) { + case "error": + if (graphvizInstance._onerror) { + graphvizInstance._onerror(data.error); + } else { + throw data.error.message + } + break; + case "done": + var svgDoc = data.svg; + layoutDone.call(this, svgDoc, callback); + break; + } + }); + + return this; + } + function layoutDone(svgDoc, callback) { + var keyMode = this._options.keyMode; + var tweenPaths = this._options.tweenPaths; + var tweenShapes = this._options.tweenShapes; + if (typeof this._options.tweenPrecision == 'string' && this._options.tweenPrecision.includes('%')) { + var tweenPrecision = +this._options.tweenPrecision.split('%')[0] / 100; + var tweenPrecisionIsRelative = this._options.tweenPrecision.includes('%'); + } else { + var tweenPrecision = this._options.tweenPrecision; + var tweenPrecisionIsRelative = false; + } + var growEnteringEdges = this._options.growEnteringEdges; + var dictionary = {}; + var prevDictionary = this._dictionary || {}; + var nodeDictionary = {}; + var prevNodeDictionary = this._nodeDictionary || {}; + + function setKey(datum, index) { + var tag = datum.tag; + if (keyMode == 'index') { + datum.key = index; + } else if (tag[0] != '#') { + if (keyMode == 'id') { + datum.key = datum.attributes.id; + } else if (keyMode == 'title') { + var title = datum.children.find(function (childData) { + return childData.tag == 'title'; + }); + if (title) { + if (title.children.length > 0) { + datum.key = title.children[0].text; + } else { + datum.key = ''; + } + } + } + } + if (datum.key == null) { + if (tweenShapes) { + if (tag == 'ellipse' || tag == 'polygon') { + tag = 'path'; + } + } + datum.key = tag + '-' + index; + } + } + + function setId(datum, parentData) { + var id = (parentData ? parentData.id + '.' : '') + datum.key; + datum.id = id; + } + + function addToDictionary(datum) { + dictionary[datum.id] = datum; + } + + function calculateAlternativeShapeData(datum, prevDatum) { + if (tweenShapes && datum.id in prevDictionary) { + if ((prevDatum.tag == 'polygon' || prevDatum.tag == 'ellipse' || prevDatum.tag == 'path') && (prevDatum.tag != datum.tag || datum.tag == 'polygon')) { + if (prevDatum.tag != 'path') { + datum.alternativeOld = convertToPathData(prevDatum, datum); + } + if (datum.tag != 'path') { + datum.alternativeNew = convertToPathData(datum, prevDatum); + } + } + } + } + + function calculatePathTweenPoints(datum, prevDatum) { + if (tweenPaths && prevDatum && (prevDatum.tag == 'path' || (datum.alternativeOld && datum.alternativeOld.tag == 'path'))) { + var attribute_d = (datum.alternativeNew || datum).attributes.d; + if (datum.alternativeOld) { + var oldNode = createElementWithAttributes(datum.alternativeOld); + } else { + var oldNode = createElementWithAttributes(prevDatum); + } + (datum.alternativeOld || (datum.alternativeOld = {})).points = pathTweenPoints(oldNode, attribute_d, tweenPrecision, tweenPrecisionIsRelative); + } + } + + function postProcessDataPass1Local(datum, index=0, parentData) { + setKey(datum, index); + setId(datum, parentData); + var id = datum.id; + var prevDatum = prevDictionary[id]; + addToDictionary(datum); + calculateAlternativeShapeData(datum, prevDatum); + calculatePathTweenPoints(datum, prevDatum); + var childTagIndexes = {}; + datum.children.forEach(function (childData) { + var childTag = childData.tag; + if (childTag == 'ellipse' || childTag == 'polygon') { + childTag = 'path'; + } + if (childTagIndexes[childTag] == null) { + childTagIndexes[childTag] = 0; + } + var childIndex = childTagIndexes[childTag]++; + postProcessDataPass1Local(childData, childIndex, datum); + }); + } + + function addToNodeDictionary(datum) { + var tag = datum.tag; + if (growEnteringEdges && datum.parent) { + if (datum.parent.attributes.class == 'node') { + if (tag == 'title') { + if (datum.children.length > 0) { + var child = datum.children[0]; + var nodeId = child.text; + } else { + var nodeId = ''; + } + nodeDictionary[nodeId] = datum.parent; + } + } + } + } + + function extractGrowingEdgesData(datum) { + var id = datum.id; + var tag = datum.tag; + var prevDatum = prevDictionary[id]; + if (growEnteringEdges && !prevDatum && datum.parent) { + if (isEdgeElement(datum)) { + if (tag == 'path' || tag == 'polygon') { + if (tag == 'polygon') { + var path = datum.parent.children.find(function (e) { + return e.tag == 'path'; + }); + if (path) { + datum.totalLength = path.totalLength; + } + } + var title = getEdgeTitle(datum); + var child = title.children[0]; + var nodeIds = child.text.split('->'); + if (nodeIds.length != 2) { + nodeIds = child.text.split('--'); + } + var startNodeId = nodeIds[0]; + var startNode = nodeDictionary[startNodeId]; + var prevStartNode = prevNodeDictionary[startNodeId]; + if (prevStartNode) { + var i = startNode.children.findIndex(function (element, index) { + return element.tag == 'g'; + }); + if (i >= 0) { + var j = startNode.children[i].children.findIndex(function (element, index) { + return element.tag == 'a'; + }); + startNode = startNode.children[i].children[j]; + } + var i = prevStartNode.children.findIndex(function (element, index) { + return element.tag == 'g'; + }); + if (i >= 0) { + var j = prevStartNode.children[i].children.findIndex(function (element, index) { + return element.tag == 'a'; + }); + prevStartNode = prevStartNode.children[i].children[j]; + } + var startShapes = startNode.children; + for (var i = 0; i < startShapes.length; i++) { + if (startShapes[i].tag == 'polygon' || startShapes[i].tag == 'ellipse' || startShapes[i].tag == 'path' || startShapes[i].tag == 'text') { + var startShape = startShapes[i]; + break; + } + } + var prevStartShapes = prevStartNode.children; + for (var i = 0; i < prevStartShapes.length; i++) { + if (prevStartShapes[i].tag == 'polygon' || prevStartShapes[i].tag == 'ellipse' || prevStartShapes[i].tag == 'path' || prevStartShapes[i].tag == 'text') { + var prevStartShape = prevStartShapes[i]; + break; + } + } + if (prevStartShape && startShape) { + datum.offset = { + x: prevStartShape.center.x - startShape.center.x, + y: prevStartShape.center.y - startShape.center.y, + }; + } else { + datum.offset = {x: 0, y: 0}; + } + } + } + } + } + } + + function postProcessDataPass2Global(datum) { + addToNodeDictionary(datum); + extractGrowingEdgesData(datum); + datum.children.forEach(function (childData) { + postProcessDataPass2Global(childData); + }); + } + + this._dispatch.call("layoutEnd", this); + + var newDoc = d3__namespace.select(document.createDocumentFragment()) + .append('div'); + + var parser = new window.DOMParser(); + var doc = parser.parseFromString(svgDoc, "image/svg+xml"); + + newDoc + .append(function() { + return doc.documentElement; + }); + + var newSvg = newDoc + .select('svg'); + + var data = extractAllElementsData(newSvg); + this._dispatch.call('dataExtractEnd', this); + postProcessDataPass1Local(data); + this._dispatch.call('dataProcessPass1End', this); + postProcessDataPass2Global(data); + this._dispatch.call('dataProcessPass2End', this); + this._data = data; + this._dictionary = dictionary; + this._nodeDictionary = nodeDictionary; + + this._extractData = function (element, childIndex, parentData) { + var data = extractAllElementsData(element); + postProcessDataPass1Local(data, childIndex, parentData); + postProcessDataPass2Global(data); + return data; + }; + this._busy = false; + this._dispatch.call('dataProcessEnd', this); + if (callback) { + callback.call(this); + } + if (this._queue.length > 0) { + var job = this._queue.shift(); + job.call(this); + } + } + + function renderDot(src, callback) { + + var graphvizInstance = this; + + this + .dot(src, render); + + function render() { + graphvizInstance + .render(callback); + } + + return this; + } + + function transition(name) { + + if (name instanceof Function) { + this._transitionFactory = name; + } else { + this._transition = d3Transition.transition(name); + } + + return this; + } + function active(name) { + + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + if (svg.size() != 0) { + return d3Transition.active(svg.node(), name); + } else { + return null; + } + } + + function options(options) { + + if (typeof options == 'undefined') { + return Object.assign({}, this._options); + } else { + for (var option of Object.keys(options)) { + this._options[option] = options[option]; + } + return this; + } + } + + function width(width) { + + this._options.width = width; + + return this; + } + + function height(height) { + + this._options.height = height; + + return this; + } + + function scale(scale) { + + this._options.scale = scale; + + return this; + } + + function fit(fit) { + + this._options.fit = fit; + + return this; + } + + function attributer(callback) { + + this._attributer = callback; + + return this; + } + + function engine(engine) { + + this._options.engine = engine; + + return this; + } + + function images(path, width, height) { + + this._images.push({path:path, width: width, height:height}); + + return this; + } + + function keyMode(keyMode) { + + if (!this._keyModes.has(keyMode)) { + throw Error('Illegal keyMode: ' + keyMode); + } + if (keyMode != this._options.keyMode && this._data != null) { + throw Error('Too late to change keyMode'); + } + this._options.keyMode = keyMode; + + return this; + } + + function fade(enable) { + + this._options.fade = enable; + + return this; + } + + function tweenPaths(enable) { + + this._options.tweenPaths = enable; + + return this; + } + + function tweenShapes(enable) { + + this._options.tweenShapes = enable; + if (enable) { + this._options.tweenPaths = true; + } + + return this; + } + + function convertEqualSidedPolygons(enable) { + + this._options.convertEqualSidedPolygons = enable; + + return this; + } + + function tweenPrecision(precision) { + + this._options.tweenPrecision = precision; + + return this; + } + + function growEnteringEdges(enable) { + + this._options.growEnteringEdges = enable; + + return this; + } + + function on(typenames, callback) { + + this._dispatch.on(typenames, callback); + + return this; + } + + function onerror(callback) { + + this._onerror = callback; + + return this; + } + + function logEvents(enable) { + + var t0 = Date.now(); + var times = {}; + var eventTypes = this._eventTypes; + var maxEventTypeLength = Math.max(...(eventTypes.map(eventType => eventType.length))); + for (let i = 0; i < eventTypes.length; i++) { + let eventType = eventTypes[i]; + times[eventType] = []; + var graphvizInstance = this; + var expectedDelay; + var expectedDuration; + this + .on(eventType + '.log', enable ? function () { + var t = Date.now(); + var seqNo = times[eventType].length; + times[eventType].push(t); + var string = ''; + string += 'Event '; + string += d3Format.format(' >2')(i) + ' '; + string += eventType + ' '.repeat(maxEventTypeLength - eventType.length); + string += d3Format.format(' >5')(t - t0) + ' '; + if (eventType != 'initEnd') { + string += d3Format.format(' >5')(t - times['start'][seqNo]); + } + if (eventType == 'dataProcessEnd') { + string += ' prepare ' + d3Format.format(' >5')((t - times['layoutEnd'][seqNo])); + } + if (eventType == 'renderEnd' && graphvizInstance._transition) { + string += ' transition start margin ' + d3Format.format(' >5')(graphvizInstance._transition.delay() - (t - times['renderStart'][seqNo])); + expectedDelay = graphvizInstance._transition.delay(); + expectedDuration = graphvizInstance._transition.duration(); + } + if (eventType == 'transitionStart') { + var actualDelay = (t - times['renderStart'][seqNo]); + string += ' transition delay ' + d3Format.format(' >5')(t - times['renderStart'][seqNo]); + string += ' expected ' + d3Format.format(' >5')(expectedDelay); + string += ' diff ' + d3Format.format(' >5')(actualDelay - expectedDelay); + } + if (eventType == 'transitionEnd') { + var actualDuration = t - times['transitionStart'][seqNo]; + string += ' transition duration ' + d3Format.format(' >5')(actualDuration); + string += ' expected ' + d3Format.format(' >5')(expectedDuration); + string += ' diff ' + d3Format.format(' >5')(actualDuration - expectedDuration); + } + console.log(string); + t0 = t; + } : null); + } + return this; + } + + function destroy() { + + delete this._selection.node().__graphviz__; + if (this._worker) { + this._workerPortClose(); + } + return this; + } + + function rotate(x, y, cosA, sinA) { + // (x + j * y) * (cosA + j * sinA) = x * cosA - y * sinA + j * (x * sinA + y * cosA) + y = -y; + sinA = -sinA; + [x, y] = [x * cosA - y * sinA, x * sinA + y * cosA]; + y = -y; + return [x, y]; + } + + function drawEdge(x1, y1, x2, y2, attributes, options={}) { + attributes = Object.assign({}, attributes); + if (attributes.style && attributes.style.includes('invis')) { + var newEdge = d3__namespace.select(null); + } else { + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + var graph0 = svg.selectWithoutDataPropagation("g"); + var newEdge0 = createEdge.call(this, attributes); + var edgeData = extractAllElementsData(newEdge0); + var newEdge = graph0.append('g') + .data([edgeData]); + attributeElement.call(newEdge.node(), edgeData); + _updateEdge.call(this, newEdge, x1, y1, x2, y2, attributes, options); + } + this._drawnEdge = { + g: newEdge, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + attributes: attributes, + }; + + return this; + } + + function updateDrawnEdge(x1, y1, x2, y2, attributes={}, options={}) { + if (!this._drawnEdge) { + throw Error('No edge has been drawn'); + } + var edge = this._drawnEdge.g; + attributes = Object.assign(this._drawnEdge.attributes, attributes); + this._drawnEdge.x1 = x1; + this._drawnEdge.y1 = y1; + this._drawnEdge.x2 = x2; + this._drawnEdge.y2 = y2; + if (edge.empty() && !(attributes.style && attributes.style.includes('invis'))) { + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + var graph0 = svg.selectWithoutDataPropagation("g"); + var edge = graph0.append('g'); + this._drawnEdge.g = edge; + } + if (!edge.empty()) { + _updateEdge.call(this, edge, x1, y1, x2, y2, attributes, options); + } + + return this; + } + + function _updateEdge(edge, x1, y1, x2, y2, attributes, options) { + + var newEdge = createEdge.call(this, attributes); + var edgeData = extractAllElementsData(newEdge); + edge.data([edgeData]); + attributeElement.call(edge.node(), edgeData); + _moveEdge(edge, x1, y1, x2, y2, attributes, options); + } + + function _moveEdge(edge, x1, y1, x2, y2, attributes, options) { + + var shortening = options.shortening || 0; + var arrowHeadLength = 10; + var arrowHeadWidth = 7; + var margin = 0.1; + + var arrowHeadPoints = [ + [0, -arrowHeadWidth / 2], + [arrowHeadLength, 0], + [0, arrowHeadWidth / 2], + [0, -arrowHeadWidth / 2], + ]; + + var dx = x2 - x1; + var dy = y2 - y1; + var length = Math.sqrt(dx * dx + dy * dy); + if (length == 0) { + var cosA = 1; + var sinA = 0; + } else { + var cosA = dx / length; + var sinA = dy / length; + } + x2 = x1 + (length - shortening - arrowHeadLength - margin) * cosA; + y2 = y1 + (length - shortening - arrowHeadLength - margin) * sinA; + + if (attributes.URL || attributes.tooltip) { + var a = edge.selectWithoutDataPropagation("g").selectWithoutDataPropagation("a"); + var line = a.selectWithoutDataPropagation("path"); + var arrowHead = a.selectWithoutDataPropagation("polygon"); + } else { + var line = edge.selectWithoutDataPropagation("path"); + var arrowHead = edge.selectWithoutDataPropagation("polygon"); + } + + var path1 = d3Path.path(); + path1.moveTo(x1, y1); + path1.lineTo(x2, y2); + + line + .attr("d", path1); + + x2 = x1 + (length - shortening - arrowHeadLength) * cosA; + y2 = y1 + (length - shortening - arrowHeadLength) * sinA; + for (var i = 0; i < arrowHeadPoints.length; i++) { + var point = arrowHeadPoints[i]; + arrowHeadPoints[i] = rotate(point[0], point[1], cosA, sinA); + } + for (var i = 0; i < arrowHeadPoints.length; i++) { + var point = arrowHeadPoints[i]; + arrowHeadPoints[i] = [x2 + point[0], y2 + point[1]]; + } + var allPoints = []; + for (var i = 0; i < arrowHeadPoints.length; i++) { + var point = arrowHeadPoints[i]; + allPoints.push(point.join(',')); + } + var pointsAttr = allPoints.join(' '); + + arrowHead + .attr("points", pointsAttr); + + return this; + } + + function moveDrawnEdgeEndPoint(x2, y2, options={}) { + + if (!this._drawnEdge) { + throw Error('No edge has been drawn'); + } + var edge = this._drawnEdge.g; + var x1 = this._drawnEdge.x1; + var y1 = this._drawnEdge.y1; + var attributes = this._drawnEdge.attributes; + + this._drawnEdge.x2 = x2; + this._drawnEdge.y2 = y2; + _moveEdge(edge, x1, y1, x2, y2, attributes, options); + + return this + } + + function removeDrawnEdge() { + + if (!this._drawnEdge) { + return this; + } + + var edge = this._drawnEdge.g; + + edge.remove(); + + this._drawnEdge = null; + + return this + } + + function insertDrawnEdge(name) { + + if (!this._drawnEdge) { + throw Error('No edge has been drawn'); + } + + var edge = this._drawnEdge.g; + if (edge.empty()) { + return this; + } + this._drawnEdge.attributes; + + var title = edge.selectWithoutDataPropagation("title"); + title + .text(name); + + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + var graph0 = svg.selectWithoutDataPropagation("g"); + var graph0Datum = graph0.datum(); + var edgeData = this._extractData(edge, graph0Datum.children.length, graph0.datum()); + graph0Datum.children.push(edgeData); + + insertAllElementsData(edge, edgeData); + + this._drawnEdge = null; + + return this + + } + + function drawnEdgeSelection() { + + if (this._drawnEdge) { + return this._drawnEdge.g; + } else { + return d3__namespace.select(null); + } + + } + + + function createEdge(attributes) { + var attributesString = ''; + for (var name of Object.keys(attributes)) { + if (attributes[name] != null) { + attributesString += ' "' + name + '"="' + attributes[name] + '"'; + } + } + var dotSrc = 'digraph {a -> b [' + attributesString + ']}'; + var svgDoc = this.layoutSync(dotSrc, 'svg', 'dot'); + var parser = new window.DOMParser(); + var doc = parser.parseFromString(svgDoc, "image/svg+xml"); + var newDoc = d3__namespace.select(document.createDocumentFragment()) + .append(function() { + return doc.documentElement; + }); + var edge = newDoc.select('.edge'); + + return edge; + } + + function drawNode(x, y, nodeId, attributes={}, options={}) { + attributes = Object.assign({}, attributes); + if (attributes.style && attributes.style.includes('invis')) { + var newNode = d3__namespace.select(null); + } else { + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + var graph0 = svg.selectWithoutDataPropagation("g"); + var newNode0 = createNode.call(this, nodeId, attributes); + var nodeData = extractAllElementsData(newNode0); + var newNode = graph0.append('g') + .data([nodeData]); + attributeElement.call(newNode.node(), nodeData); + _updateNode.call(this, newNode, x, y, nodeId, attributes, options); + } + this._drawnNode = { + g: newNode, + nodeId: nodeId, + x: x, + y: y, + attributes: attributes, + }; + + return this; + } + + function updateDrawnNode(x, y, nodeId, attributes={}, options={}) { + if (!this._drawnNode) { + throw Error('No node has been drawn'); + } + + var node = this._drawnNode.g; + if (nodeId == null) { + nodeId = this._drawnNode.nodeId; + } + attributes = Object.assign(this._drawnNode.attributes, attributes); + this._drawnNode.nodeId = nodeId; + this._drawnNode.x = x; + this._drawnNode.y = y; + if (node.empty() && !(attributes.style && attributes.style.includes('invis'))) { + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + var graph0 = svg.selectWithoutDataPropagation("g"); + var node = graph0.append('g'); + this._drawnNode.g = node; + } + if (!node.empty()) { + _updateNode.call(this, node, x, y, nodeId, attributes, options); + } + + return this; + } + + function _updateNode(node, x, y, nodeId, attributes, options) { + + var newNode = createNode.call(this, nodeId, attributes); + var nodeData = extractAllElementsData(newNode); + node.data([nodeData]); + attributeElement.call(node.node(), nodeData); + _moveNode(node, x, y, attributes); + + return this; + } + + function _moveNode(node, x, y, attributes, options) { + if (attributes.URL || attributes.tooltip) { + var subParent = node.selectWithoutDataPropagation("g").selectWithoutDataPropagation("a"); + } else { + var subParent = node; + } + var svgElements = subParent.selectAll('ellipse,polygon,path,polyline'); + var text = node.selectWithoutDataPropagation("text"); + + if (svgElements.size() != 0) { + var bbox = svgElements.node().getBBox(); + bbox.cx = bbox.x + bbox.width / 2; + bbox.cy = bbox.y + bbox.height / 2; + } else if (text.size() != 0) { + bbox = { + x: +text.attr('x'), + y: +text.attr('y'), + width: 0, + height: 0, + cx: +text.attr('x'), + cy: +text.attr('y'), + }; + } + svgElements.each(function(data, index) { + var svgElement = d3__namespace.select(this); + if (svgElement.attr("cx")) { + svgElement + .attr("cx", roundTo2Decimals(x)) + .attr("cy", roundTo2Decimals(y)); + } else if (svgElement.attr("points")) { + var pointsString = svgElement.attr('points').trim(); + svgElement + .attr("points", translatePointsAttribute(pointsString, x - bbox.cx, y - bbox.cy)); + } else { + var d = svgElement.attr('d'); + svgElement + .attr("d", translateDAttribute(d, x - bbox.cx, y - bbox.cy)); + } + }); + + if (text.size() != 0) { + text + .attr("x", roundTo2Decimals(+text.attr("x") + x - bbox.cx)) + .attr("y", roundTo2Decimals(+text.attr("y") + y - bbox.cy)); + } + return this; + } + + function moveDrawnNode(x, y, options={}) { + + if (!this._drawnNode) { + throw Error('No node has been drawn'); + } + var node = this._drawnNode.g; + var attributes = this._drawnNode.attributes; + + this._drawnNode.x = x; + this._drawnNode.y = y; + + if (!node.empty()) { + _moveNode(node, x, y, attributes); + } + + return this + } + + function removeDrawnNode() { + + if (!this._drawnNode) { + return this; + } + + var node = this._drawnNode.g; + + if (!node.empty()) { + node.remove(); + } + + this._drawnNode = null; + + return this + } + + function insertDrawnNode(nodeId) { + + if (!this._drawnNode) { + throw Error('No node has been drawn'); + } + + if (nodeId == null) { + nodeId = this._drawnNode.nodeId; + } + var node = this._drawnNode.g; + if (node.empty()) { + return this; + } + var attributes = this._drawnNode.attributes; + + var title = node.selectWithoutDataPropagation("title"); + title + .text(nodeId); + if (attributes.URL || attributes.tooltip) { + var ga = node.selectWithoutDataPropagation("g"); + var a = ga.selectWithoutDataPropagation("a"); + a.selectWithoutDataPropagation('ellipse,polygon,path,polyline'); + var text = a.selectWithoutDataPropagation('text'); + } else { + node.selectWithoutDataPropagation('ellipse,polygon,path,polyline'); + var text = node.selectWithoutDataPropagation('text'); + } + text + .text(attributes.label || nodeId); + + var root = this._selection; + var svg = root.selectWithoutDataPropagation("svg"); + var graph0 = svg.selectWithoutDataPropagation("g"); + var graph0Datum = graph0.datum(); + var nodeData = this._extractData(node, graph0Datum.children.length, graph0.datum()); + graph0Datum.children.push(nodeData); + + insertAllElementsData(node, nodeData); + + this._drawnNode = null; + + return this + + } + + function drawnNodeSelection() { + + if (this._drawnNode) { + return this._drawnNode.g; + } else { + return d3__namespace.select(null); + } + + } + + function createNode(nodeId, attributes) { + var attributesString = ''; + for (var name of Object.keys(attributes)) { + if (attributes[name] != null) { + attributesString += ' "' + name + '"="' + attributes[name] + '"'; + } + } + var dotSrc = 'graph {"' + nodeId + '" [' + attributesString + ']}'; + var svgDoc = this.layoutSync(dotSrc, 'svg', 'dot'); + var parser = new window.DOMParser(); + var doc = parser.parseFromString(svgDoc, "image/svg+xml"); + var newDoc = d3__namespace.select(document.createDocumentFragment()) + .append(function() { + return doc.documentElement; + }); + var node = newDoc.select('.node'); + + return node; + } + + /* This file is excluded from coverage because the intrumented code + * translates "self" which gives a reference error. + */ + + /* istanbul ignore next */ + + function workerCodeBody(port) { + + self.document = {}; // Workaround for "ReferenceError: document is not defined" in hpccWasm + + port.addEventListener('message', function(event) { + let hpccWasm = self["@hpcc-js/wasm"]; + if (hpccWasm == undefined && event.data.vizURL) { + importScripts(event.data.vizURL); + hpccWasm = self["@hpcc-js/wasm"]; + hpccWasm.wasmFolder(event.data.vizURL.match(/.*\//)[0]); + // This is an alternative workaround where wasmFolder() is not needed + // document = {currentScript: {src: event.data.vizURL}}; + } + hpccWasm.graphviz.layout(event.data.dot, "svg", event.data.engine, event.data.options).then((svg) => { + if (svg) { + port.postMessage({ + type: "done", + svg: svg, + }); + } else if (event.data.vizURL) { + port.postMessage({ + type: "init", + }); + } else { + port.postMessage({ + type: "skip", + }); + } + }).catch(error => { + port.postMessage({ + type: "error", + error: error.message, + }); + }); + }); + } + + /* istanbul ignore next */ + + function workerCode() { + + const port = self; + workerCodeBody(port); + } + + /* istanbul ignore next */ + + function sharedWorkerCode() { + self.onconnect = function(e) { + const port = e.ports[0]; + workerCodeBody(port); + port.start(); + }; + } + + function Graphviz(selection, options) { + this._options = { + useWorker: true, + useSharedWorker: false, + engine: 'dot', + keyMode: 'title', + fade: true, + tweenPaths: true, + tweenShapes: true, + convertEqualSidedPolygons: true, + tweenPrecision: 1, + growEnteringEdges: true, + zoom: true, + zoomScaleExtent: [0.1, 10], + zoomTranslateExtent: [[-Infinity, -Infinity], [+Infinity, +Infinity]], + width: null, + height: null, + scale: 1, + fit: false, + }; + if (options instanceof Object) { + for (var option of Object.keys(options)) { + this._options[option] = options[option]; + } + } else if (typeof options == 'boolean') { + this._options.useWorker = options; + } + var useWorker = this._options.useWorker; + var useSharedWorker = this._options.useSharedWorker; + if (typeof Worker == 'undefined') { + useWorker = false; + } + if (typeof SharedWorker == 'undefined') { + useSharedWorker = false; + } + if (useWorker || useSharedWorker) { + var scripts = d3__namespace.selectAll('script'); + var vizScript = scripts.filter(function() { + return d3__namespace.select(this).attr('type') == 'javascript/worker' || (d3__namespace.select(this).attr('src') && d3__namespace.select(this).attr('src').match(/.*\/@hpcc-js\/wasm/)); + }); + if (vizScript.size() == 0) { + console.warn('No script tag of type "javascript/worker" was found and "useWorker" is true. Not using web worker.'); + useWorker = false; + useSharedWorker = false; + } else { + this._vizURL = vizScript.attr('src'); + if (!this._vizURL) { + console.warn('No "src" attribute of was found on the "javascript/worker" script tag and "useWorker" is true. Not using web worker.'); + useWorker = false; + useSharedWorker = false; + } + } + } + if (useSharedWorker) { + const url = 'data:application/javascript;base64,' + btoa(workerCodeBody.toString() + '(' + sharedWorkerCode.toString() + ')()'); + this._worker = this._worker = new SharedWorker(url); + this._workerPort = this._worker.port; + this._workerPortClose = this._worker.port.close.bind(this._workerPort); + this._worker.port.start(); + this._workerCallbacks = []; + } + else if (useWorker) { + var blob = new Blob([workerCodeBody.toString() + '(' + workerCode.toString() + ')()']); + var blobURL = window.URL.createObjectURL(blob); + this._worker = new Worker(blobURL); + this._workerPort = this._worker; + this._workerPortClose = this._worker.terminate.bind(this._worker); + this._workerCallbacks = []; + } + this._selection = selection; + this._active = false; + this._busy = false; + this._jobs = []; + this._queue = []; + this._keyModes = new Set([ + 'title', + 'id', + 'tag-index', + 'index' + ]); + this._images = []; + this._translation = undefined; + this._scale = undefined; + this._eventTypes = [ + 'initEnd', + 'start', + 'layoutStart', + 'layoutEnd', + 'dataExtractEnd', + 'dataProcessPass1End', + 'dataProcessPass2End', + 'dataProcessEnd', + 'renderStart', + 'renderEnd', + 'transitionStart', + 'transitionEnd', + 'restoreEnd', + 'end' + ]; + this._dispatch = d3Dispatch.dispatch(...this._eventTypes); + initViz.call(this); + selection.node().__graphviz__ = this; + } + + function graphviz(selector, options) { + var g = d3__namespace.select(selector).graphviz(options); + return g; + } + + Graphviz.prototype = graphviz.prototype = { + constructor: Graphviz, + engine: engine, + addImage: images, + keyMode: keyMode, + fade: fade, + tweenPaths: tweenPaths, + tweenShapes: tweenShapes, + convertEqualSidedPolygons: convertEqualSidedPolygons, + tweenPrecision: tweenPrecision, + growEnteringEdges: growEnteringEdges, + zoom: zoom, + resetZoom: resetZoom, + zoomBehavior: zoomBehavior, + zoomSelection: zoomSelection, + zoomScaleExtent: zoomScaleExtent, + zoomTranslateExtent: zoomTranslateExtent, + render: render, + layout: layout, + dot: dot, + data: data, + renderDot: renderDot, + transition: transition, + active: active, + options: options, + width: width, + height: height, + scale: scale, + fit: fit, + attributer: attributer, + on: on, + onerror: onerror, + logEvents: logEvents, + destroy: destroy, + drawEdge: drawEdge, + updateDrawnEdge: updateDrawnEdge, + moveDrawnEdgeEndPoint, + insertDrawnEdge, + removeDrawnEdge, removeDrawnEdge, + drawnEdgeSelection, drawnEdgeSelection, + drawNode: drawNode, + updateDrawnNode: updateDrawnNode, + moveDrawnNode: moveDrawnNode, + insertDrawnNode, + removeDrawnNode, removeDrawnNode, + drawnNodeSelection, drawnNodeSelection, + }; + + function selection_graphviz(options) { + + var g = this.node().__graphviz__; + if (g) { + g.options(options); + // Ensure a possible new initEnd event handler is attached before calling it + d3Timer.timeout(function () { + g._dispatch.call("initEnd", this); + }.bind(this), 0); + } else { + g = new Graphviz(this, options); + } + return g; + } + + function selection_selectWithoutDataPropagation(name) { + + return d3__namespace.select(this.size() > 0 ? this.node().querySelector(name) : null); + } + + d3.selection.prototype.graphviz = selection_graphviz; + d3.selection.prototype.selectWithoutDataPropagation = selection_selectWithoutDataPropagation; + + exports.graphviz = graphviz; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); +//# sourceMappingURL=d3-graphviz.js.map -- cgit v1.2.3