为 Grapheditor 添加蚂蚁线
20230216更新:这里有添加好蚂蚁线的 GraphEditor。
下载原版
下载 GraphEditor:下载代码后,目录 javascript/examples/grapheditor/www 里即是完整的 GraphEditor。
为原版 GraphEditor 添加蚂蚁线(流水效果)
grapheditor/resources/grapheditor.txt 添加3行高亮部分:
flipH=Flip Horizontal flipV=Flip Vertical flowingLine=Ant Line reverseFlow=Flow Reverse flow=Flow
grapheditor/js/EditorUi.js 添加高亮处的2行:
var connectStyles = [ "flowingLineStyle", "flowingLineBackgroundColor", "shape", "edgeStyle", "curved",
grapheditor/js/Graph.js 添加高亮行处的代码:
// 连线的动画 Graph.flowingLineEnabled = true; /** * Default size for line jumps. */ Graph.defaultJumpSize = 6;
grapheditor/js/Format.js 这个文件需要添加共 4 处。
1/4 添加高亮行:
Format.prototype.initSelectionState = function() { return { flowingLine: true, vertices: [], edges: [],
2/4 添加高亮行:
this.container.appendChild(this.addLineJumps(this.createPanel())); this.container.appendChild(this.addFlowingLine(this.createPanel()));
3/4 在文件末尾添加以下代码:
/** * Adds UI for configuring line animations. */ StyleFormatPanel.prototype.addFlowingLine = function (container) { var ss = this.format.getSelectionState(); if (Graph.flowingLineEnabled && ss.edges.length > 0 && ss.vertices.length == 0 && ss.flowingLine) { container.style.padding = "8px 0px 24px 18px"; var ui = this.editorUi; var editor = ui.editor; var graph = editor.graph; var span = document.createElement("div"); span.style.position = "absolute"; span.style.fontWeight = "bold"; span.style.width = "80px"; mxUtils.write(span, mxResources.get("flowingLine")); container.appendChild(span); var styleSelect = document.createElement("select"); styleSelect.style.position = "absolute"; styleSelect.style.marginTop = "-2px"; styleSelect.style.right = "76px"; styleSelect.style.width = "62px"; var styles = ["none", "flow", "reverseFlow"]; for (var i = 0; i < styles.length; i++) { var styleOption = document.createElement("option"); styleOption.setAttribute("value", styles[i]); mxUtils.write(styleOption, mxResources.get(styles[i])); styleSelect.appendChild(styleOption); } graph.addListener("size", function () { // Adds animation to edge shape and makes "pipe" visible graph.view.states.visit(function (key, state) { if (graph.model.isEdge(state.cell)) { var flowingLineStyle = state.style.flowingLineStyle; if (flowingLineStyle) { // console.log('state.style', state.style); var strokeWidth = state.shape.node.getElementsByTagName("path")[1].getAttribute("stroke-width") || state.style.strokeWidth; // 缩放画布时蚂蚁线背景与线保持同宽 state.shape.node.getElementsByTagName("path")[0].removeAttribute("visibility"); state.shape.node.getElementsByTagName("path")[0].setAttribute("stroke-width", strokeWidth); state.shape.node.getElementsByTagName("path")[0].setAttribute("stroke", state.style.flowingLineBackgroundColor || "transparent"); state.shape.node.getElementsByTagName("path")[1].setAttribute("class", flowingLineStyle); // 缩放视图的同时也缩放虚线间隔 if (flowingLineStyle == 'flow') { // JS 操作 @keyframes: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/animate state.shape.node.getElementsByTagName("path")[1].animate([ { strokeDasharray: strokeWidth * 4, strokeDashoffset: 0, }, { strokeDasharray: strokeWidth * 4, strokeDashoffset: -strokeWidth * 8, // strokeDashoffset = strokeDasharray * 2 } ], { iterations: Infinity, duration: 500, easing: 'linear', }); } } } }); }); mxEvent.addListener(styleSelect, "change", function (evt) { graph.getModel().beginUpdate(); try { graph.setCellStyles("flowingLineStyle", styleSelect.value, graph.getSelectionCells()); ui.fireEvent( new mxEventObject( "styleChanged", "keys", ["flowingLineStyle"], "values", [styleSelect.value], "cells", graph.getSelectionCells() ) ); } finally { graph.getModel().endUpdate(); } mxEvent.consume(evt); }); // Stops events from bubbling to color option event handler mxEvent.addListener(styleSelect, "click", function (evt) { mxEvent.consume(evt); }); container.appendChild(styleSelect); var option = this.createColorPicker( function () { var state = graph.view.getState(graph.getSelectionCell()); if (state != null) { return mxUtils.getValue(state.style, 'flowingLineBackgroundColor', null); } return null; }, function (color) { graph.setCellStyles( 'flowingLineBackgroundColor', color, graph.getSelectionCells() ); }, "transparent", { install: function (apply) { // ignore }, destroy: function () { // ignore }, } ); option.style.position = "absolute"; option.style.marginTop = "-4px"; option.style.right = "20px"; container.appendChild(option); var listener = mxUtils.bind(this, function (sender, evt, force) { ss = this.format.getSelectionState(); styleSelect.value = mxUtils.getValue(ss.style, "flowingLineStyle", "none"); }); graph.getModel().addListener(mxEvent.CHANGE, listener); this.listeners.push({ destroy: function () { graph.getModel().removeListener(listener); }, }); listener(); } else { container.style.display = "none"; } return container; };
4/4 在文件末尾添加以下代码:
BaseFormatPanel.prototype.createColorPicker = function ( getColorFn, setColorFn, defaultColor, listener, callbackFn ) { var div = document.createElement("div"); div.style.display = "inline-block"; var value = getColorFn(); var applying = false; var btn = null; var apply = function (color, disableUpdate, forceUpdate) { if (!applying) { applying = true; color = /(^#?[a-zA-Z0-9]*$)/.test(color) ? color : defaultColor; btn.innerHTML = '<div style="width:' + (mxClient.IS_QUIRKS ? "30" : "36") + "px;height:12px;margin:3px;border:1px solid black;background-color:" + mxUtils.htmlEntities(color != null && color != mxConstants.NONE ? color : defaultColor) + ';"></div>'; // Fine-tuning in Firefox, quirks mode and IE8 standards if (mxClient.IS_QUIRKS || document.documentMode == 8) { btn.firstChild.style.margin = "0px"; } if (callbackFn != null) { callbackFn(color); } if (!disableUpdate) { value = color; // Checks if the color value needs to be updated in the model if (forceUpdate || getColorFn() != value) { setColorFn(value); } } applying = false; } }; btn = mxUtils.button( "", mxUtils.bind(this, function (evt) { this.editorUi.pickColor(value, function (color) { apply(color, null, true); }); mxEvent.consume(evt); }) ); btn.className = "geColorBtn"; div.appendChild(btn); apply(value, true); if (listener != null) { listener.install(apply); this.listeners.push(listener); } return div; };
grapheditor/styles/grapheditor.css 添加到文件末尾:
path.flow { stroke-dasharray: 10; animation: flow 0.5s linear; animation-iteration-count: infinite; } @keyframes flow { to { stroke-dashoffset: -20; } } path.reverseFlow { stroke-dasharray: 10; animation: reverse-flow 0.5s linear; animation-iteration-count: infinite; } @keyframes reverse-flow { to { stroke-dashoffset: 20; } }
— 完 —