jQuery+d3绘制流程图

jQuery + d3绘制流程图

运行效果

这里写图片描述

代码

HTML代码

<!DOCTYPE html>
<html style="overflow: hidden;">
<head>
  <title>流程设计工具</title>
  <link href="https://cdn.bootcss.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet">
  <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet">
  <link rel="stylesheet" type="text/css" href="index.css">
  <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.bootcss.com/popper.js/1.12.5/umd/popper.min.js"></script>
  <script src="https://cdn.bootcss.com/bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>
  <script src="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.min.js"></script>
  <script src="https://cdn.bootcss.com/d3/4.11.0/d3.min.js"></script>
  <script src="https://cdn.bootcss.com/d3-transform/1.0.4/d3-transform.min.js"></script>
</head>
<body>
    <div class="container-fuild">
      <div id="left-wrapper" class="left-wrapper">
        <ul class="sidebar-nav">
          <li>
            <a class="open">
              <i class="fa fa-folder-o"></i>
              <span>源/目标</span>
            </a>
            <ul>
              <li class="node" data-id="101">
                <a href="">
                  <i class="fa fa-database"></i>
                  <span>读数据</span>
                </a>
              </li>
              <li class="node" data-id="102">
                <a href="">
                  <i class="fa fa-database"></i>
                  <span>写数据</span>
                </a>
              </li>
            </ul>
          </li>
          <li>
            <a>
              <i class="fa fa-folder-o"></i>
              <span>数据预处理</span>
            </a>
            <ul>
              <li class="node" data-id="211">
                <a href="">
                  <i class="fa fa-crosshairs" aria-hidden="true"></i>
                  <span>类型转换</span>
                </a>
              </li>
              <li class="node" data-id="212">
                <a href="">
                  <i class="fa fa-crosshairs" aria-hidden="true"></i>
                  <span>拆分</span>
                </a>
              </li>
              <li class="node" data-id="213">
                <a href="">
                  <i class="fa fa-crosshairs" aria-hidden="true"></i>
                  <span>缺失值填充</span>
                </a>
              </li>
              <li class="node" data-id="214">
                <a href="">
                  <i class="fa fa-crosshairs" aria-hidden="true"></i>
                  <span>归一化</span>
                </a>
              </li>
              <li class="node" data-id="215">
                <a href="">
                  <i class="fa fa-crosshairs" aria-hidden="true"></i>
                  <span>标准化</span>
                </a>
              </li>
            </ul>
          </li>
          <li>
            <a>
              <i class="fa fa-folder-o"></i>
              <span>特征工程</span>
            </a>
          </li>
          <li>
            <a>
              <i class="fa fa-folder-o"></i>
              <span>机器学习</span>
            </a>
            <ul>
              <li>
                <a>
                  <i class="fa fa-folder-o"></i>
                  <span>二分类</span>
                </a>
                <ul>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>GBDT二分类</span>
                    </a>
                  </li>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>PS-SMART</span>
                    </a>
                  </li>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>线性支持向量机</span>
                    </a>
                  </li>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>逻辑回归二分类</span>
                    </a>
                  </li>
                </ul>
              </li>
              <li>
                <a>
                  <i class="fa fa-folder-o"></i>
                  <span>聚类</span>
                </a>
                <ul>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>K均值聚类</span>
                    </a>
                  </li>
                </ul>
              </li>
              <li>
                <a>
                  <i class="fa fa-folder-o"></i>
                  <span>回归</span>
                </a>
                <ul>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>GBDT回归</span>
                    </a>
                  </li>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>线性回归</span>
                    </a>
                  </li>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>PS_SMART回归</span>
                    </a>
                  </li>
                  <li class="node">
                    <a href="">
                      <i class="fa fa-circle-o"></i>
                      <span>PS线性回归</span>
                    </a>
                  </li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </div>
      <div class="middle-wrapper">
        <h4>实验名称</h4>
        <div id="idsw-bpmn" class="bpmn" style="position: relative; width: 100%; height: 100%;">
          <svg width="100%" height="100%">
          <defs>
            <marker id="arrowhead" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">
              <path d="M 0 0 L 10 5 L 0 10 z" stroke-width="0" stroke="#333"></path>
            </marker>
          </defs>
        </svg>
        </div>
        <div style="height: 40px; border-top: solid 1px #e7e7e7; text-align: center; line-height: 40px; position: absolute;bottom: 2px; width: 100%">
          <a class="btn btn-link" href="#"><i class="fa fa-play-circle-o" aria-hidden="true"></i>&nbsp;运行</a>
          <a class="btn btn-link" href="#"><i class="fa fa-cloud-upload" aria-hidden="true"></i>&nbsp;部署</a>
          <a class="btn btn-link" href="#"><i class="fa fa-share-alt" aria-hidden="true"></i>&nbsp;分享</a>
        </div>
      </div>
      <div class="right-wrapper">
        <h4>实验属性</h4>
      </div>
    </div>
</body>
<script type="text/javascript" src="index.js"></script>
</html>

CSS代码

.left-wrapper {
    width: 250px;
    position: absolute;
    top:0px;
    bottom: 0px;
    left: 0px;
    background: #f0f0f0;
    border-right: solid 1px #e7e7e7;
}
.middle-wrapper {
    position: absolute;
    top: 0px;
    bottom: 0px;
    left: 250px;
    right: 250px;
}
.right-wrapper {
    position: absolute;
    width: 250px;
    top: 0px;
    bottom: 0px;
    right: 0px;
    background: #f5f5f5;
    border-left: solid 1px #e7e7e7;
}

.sidebar-nav,
.sidebar-nav ul {
    list-style: none;
    padding: 0px;
}

.sidebar-nav > li:nth-child(odd),
.sidebar-nav ul li:nth-child(odd) {
    background: #f0f0f0;
}
.sidebar-nav > li:nth-child(even),
.sidebar-nav ul > li:nth-child(even) {
    background: #fff;
}

.sidebar-nav li {
    text-indent: 4px;
    line-height: 30px;
}

.sidebar-nav li li {
    text-indent: 15px;
}

.sidebar-nav li li li {
    text-indent: 25px;
}

.sidebar-nav li a {
    display: block;
    text-decoration: none;
    color: #333;
}

.sidebar-nav li.active a {
    background-color: #3e99ff;
}

.sidebar-nav li a>i+span {
    margin-left: 3px;
}

.sidebar-nav li a:hover {
    text-decoration: none;
    background: rgba(255, 255, 255, 0.2);
}

.sidebar-nav li a:active, .sidebar-nav li a:focus {
    text-decoration: none;
}

.middle-wrapper h4,
.right-wrapper h4 {
    font-size: 1em;
    height: 40px; 
    border-bottom: solid 1px #e7e7e7; 
    text-align: center; 
    line-height: 40px
}

.bpmn .node rect {
    width:180px; 
    height:36px;
    cursor: pointer;
    stroke: #333;
    stroke-width:2;
    fill: #fff;
}

.bpmn .node.active rect,
.bpmn .node.active circle {
    stroke: lightblue;
}

.bpmn .node circle {
    stroke: #333;
    stroke-width: 2px;
    fill: #fff;
    cursor: crosshair;
}

.bpmn .node circle.end {
    fill: orange;
}

.bpmn .cable {
    stroke: #333;
    stroke-width: 2px;
    fill: none;
}

JavaScript

var workflow = {
    nodes: {}
};
$(function() {
    var svg = d3.select("svg");
    // 绑定拖拽
    $('#left-wrapper .node').draggable({
        helper: "clone",
        addClass: false,
        connectToSortable: "#idsw-bpmn",
        start: function (e, ui) {
            ui.helper.addClass("ui-draggable-helper");
        },
        stop: function (e, ui) {
            var node = {
                id: new Date().getTime(),
                dataId: ui.helper.attr('data-id'),
                x: ui.position.left - 250,
                y: ui.position.top - 40,
                text: ui.helper.text(),
                inputs: 1,
                outputs: 2
            };

            if(node.dataId == 101) {
                node.inputs = 0;
                node.outputs = 1;
            } else if(node.dataId == 102) {
                node.inputs = 1;
                node.outputs = 0;
            } else {
                node.inputs = 1;
                node.outputs = 1;
            }
            // 计算节点编号
            if(workflow.nodes[node.dataId]) {
                workflow.nodes[node.dataId] += 1;
            } else {
                workflow.nodes[node.dataId] = 1;
            }
            var g = addNode(svg, node);

            g.call(
                d3.drag()
                  .on("start", dragstarted)
                  .on("drag", dragged)
                  .on("end", dragended)
            );

            g.selectAll("circle.output").call(
                d3.drag()
                  .on("start", linestarted)
                  .on("drag", linedragged)
                  .on("end", lineended)
            );

            g.selectAll("circle.input")
             .on("mouseover", function() {
                if(drawLine) {
                    d3.selectAll("circle.end").classed("end", false);
                    d3.select(this).classed("end", true);
                }
            });
        }
    });
});

var activeLine = null;
var points = [];
var translate = null;
var drawLine = false;
function linestarted() {
    drawLine = false;
    // 当前选中的circle
    var anchor = d3.select(this);
    // 当前选中的节点
    var node = d3.select(this.parentNode);
    var rect = node.node().getBoundingClientRect();
    var dx = rect.width / (+anchor.attr("output") + 1);
    var dy = rect.height;
    var transform = node.attr("transform");
    translate = getTranslate(transform);
    points.push([dx + translate[0], dy + translate[1]]);
    activeLine = d3.select("svg")
      .append("path")
      .attr("class", "cable")
      .attr("from", node.attr("id"))
      .attr("start", dx + ", " + dy)
      .attr("output", d3.select(this).attr("output"))
      .attr("marker-end", "url(#arrowhead)");
}

function linedragged() {
    drawLine = true;
    points[1] = [d3.event.x + translate[0], d3.event.y + translate[1]];
    activeLine.attr("d", function() {
        return "M" + points[0][0] + "," + points[0][1]
        + "C" + points[0][0] + "," + (points[0][1] + points[1][1]) / 2
        + " " + points[1][0] + "," +  (points[0][1] + points[1][1]) / 2
        + " " + points[1][0] + "," + points[1][1];
    });
}

function lineended(d) {
    drawLine = false;
    var anchor = d3.selectAll("circle.end");
    if(anchor.empty()) {
        activeLine.remove();
    } else {
        var pNode = d3.select(anchor.node().parentNode);
        var input = pNode.node().getBoundingClientRect().width / (+anchor.attr("input") + 1);
        anchor.classed("end", false);
        activeLine.attr("to", pNode.attr("id"));
        activeLine.attr("input", anchor.attr("input"));
        activeLine.attr("end", input + ", 0");
    }
    activeLine = null;
    points.length = 0;
    translate = null;
}

function getTranslate(transform) {
    var arr = transform.substring(transform.indexOf("(")+1, transform.indexOf(")")).split(",");
    return [+arr[0], +arr[1]];
}

var dx = 0;
var dy = 0;
var dragElem = null;
function dragstarted() {
    var transform = d3.select(this).attr("transform");
    var translate = getTranslate(transform);
    dx = d3.event.x - translate[0];
    dy = d3.event.y - translate[1];
    dragElem = d3.select(this);
}

function dragged() {
  dragElem.attr("transform", "translate(" + (d3.event.x - dx) + ", " + (d3.event.y - dy) + ")");
  updateCable(dragElem);
}

function updateCable(elem) {
    var bound = elem.node().getBoundingClientRect();
    var width = bound.width;
    var height = bound.height;
    var id = elem.attr("id");
    var transform = elem.attr("transform");
    var t1 = getTranslate(transform);


    // 更新输出线的位置
    d3.selectAll('path[from="' + id + '"]')
      .each(function() {
        var start = d3.select(this).attr("start").split(",");
        start[0] = +start[0] + t1[0];
        start[1] = +start[1] + t1[1];

        var path = d3.select(this).attr("d");
        var end = path.substring(path.lastIndexOf(" ") + 1).split(",");
        end[0] = +end[0];
        end[1] = +end[1];

        d3.select(this).attr("d", function() {
            return "M" + start[0] + "," + start[1]
            + " C" + start[0] + "," + (start[1] + end[1]) / 2
            + " " + end[0] + "," +  (start[1] + end[1]) / 2
            + " " + end[0] + "," + end[1];
        });
      });

    // 更新输入线的位置
    d3.selectAll('path[to="' + id + '"]')
      .each(function() {
        var path = d3.select(this).attr("d");
        var start = path.substring(1, path.indexOf("C")).split(",");
        start[0] = +start[0];
        start[1] = +start[1];

        var end = d3.select(this).attr("end").split(",");
        end[0] = +end[0] + t1[0];
        end[1] = +end[1] + t1[1];

        d3.select(this).attr("d", function() {
            return "M" + start[0] + "," + start[1]
            + " C" + start[0] + "," + (start[1] + end[1]) / 2
            + " " + end[0] + "," +  (start[1] + end[1]) / 2
            + " " + end[0] + "," + end[1];
        });
      });

}

function dragended() {
    dx = dy = 0;
    dragElem = null;
}

function addNode(svg, node) {
    var g = svg.append("g")
               .attr("class", "node")
               .attr("data-id", node.dataId)
               .attr("id", node.id)
               .attr("transform", 'translate(' + node.x + ', ' + node.y + ')');

    var rect = g.append("rect")
     .attr("rx", 5)
     .attr("ry", 5)
     .attr("stroke-width", 2)
     .attr("stroke", "#333")
     .attr("fill", "#fff");

    var bound = rect.node().getBoundingClientRect();
    var width = bound.width;
    var height = bound.height;

    // text
    g.append("text")
     .text(node.text)
     .attr("x", width / 2)
     .attr("y", height / 2)
     .attr("dominant-baseline", "central") 
     .attr("text-anchor", "middle");

    // left icon
    g.append('text')
     .attr("x", 18)
     .attr("y", height / 2)
     .attr("dominant-baseline", "central") 
     .attr("text-anchor", "middle")
     .attr('font-family', 'FontAwesome')
         .text('\uf1c0');

    // right icon
    g.append('text')
     .attr("x", width - 18)
     .attr("y", height / 2)
     .attr("dominant-baseline", "central") 
     .attr("text-anchor", "middle")
     .attr('font-family', 'FontAwesome')
         .text('\uf00c');

    // input circle
    var inputs = node.inputs || 0;
    g.attr("inputs", inputs);
    for(var i = 0; i < inputs; i++) {
        g.append("circle")
         .attr("class", "input")
         .attr("input", (i + 1))
         .attr("cx", width * (i + 1) / (inputs + 1))
         .attr("cy", 0)
         .attr("r", 5);
    }

    // output circle
    var outputs = node.outputs || 0;
    g.attr("outputs", outputs);
    for(i = 0; i < outputs; i++) {
        g.append("circle")
         .attr("output", (i + 1))
         .attr("class", "output")
         .attr("cx", width * (i + 1) / (outputs + 1))
         .attr("cy", height)
         .attr("r", 5);
    }

    return g;
}
阅读更多
换一批

没有更多推荐了,返回首页