前端:SVG绘制流程图

效果

代码

html代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>SVG流程图示例</title>
    <style>
        /* CSS 样式 */
    </style>
    <script src="js/index.js"></script> <!-- 引入外部JS文件 -->
</head>

<body>

    <svg id="workflow-svg" width="1000" height="800">
        <!-- SVG图形内容... -->
    </svg>

</body>

</html>

js/index.js

// 定义矩形框
class FlowChartShape {
    // 定义一个类的构造函数。svgElement容纳新创建的矩形框、initialX,initialY分别代表矩形框左上角的初始X轴和Y轴坐标、width和height:这两个参数表示矩形框的宽度和高度
    constructor(svgElement, initialX, initialY, width, height) {
        //是在当前类的实例(FlowChartShape)内部设置一个成员变量。意味着每个FlowChartShape实例都将与特定的SVG元素关联起来
        this.svgElement = svgElement;
        // 创建一个新的SVG < rect > 元素(图形)。
        this.rectElement = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
        // 设定矩形元素在其父SVG容器中的X坐标位置
        this.rectElement.setAttribute('x', initialX);
        // 设定矩形元素在其父SVG容器中的y坐标位置
        this.rectElement.setAttribute('y', initialY);
        // 设定矩形元素在其父SVG容器中的宽度
        this.rectElement.setAttribute('width', width);
        // 设定矩形元素在其父SVG容器中的高度
        this.rectElement.setAttribute('height', height);
        // 设置矩形的圆角半径设置为10个单位
        this.rectElement.setAttribute('rx', 10);
        // 设置当前SVG矩形元素的内部填充颜色为纯白色。
        this.rectElement.setAttribute('fill', '#fff');
        // 设置了矩形元素的描边颜色为黑色
        this.rectElement.setAttribute('stroke', '#1d6ee7');
        // 矩形元素的描边宽度为2个单位
        this.rectElement.setAttribute('stroke-width', 2);

        // 创建了一个新的SVG文本元素。
        let textElement = document.createElementNS("http://www.w3.org/2000/svg", 'text');
        // 'x' 属性设置文本元素在水平方向上的起始位置:为矩形容器的起始x坐标加上宽度的一半
        textElement.setAttribute('x', initialX + width / 2);
        // 'y' 属性设置文本元素在竖直方向上的起始位置:为矩形容器的起始y坐标加上高度的一半
        textElement.setAttribute('y', initialY + height / 2);
        // 设置SVG文本元素的对齐方式:居中
        textElement.setAttribute('text-anchor', 'middle'); // 这会让文本水平居中
        textElement.setAttribute('dominant-baseline', 'middle'); // 竖直居中,在某些浏览器中可能需要其他值,比如 'central'


        // 将先前创建的SVG矩形元素(this.rectElement) 添加为 this.svgElement 的子元素
        this.svgElement.appendChild(this.rectElement);
        // 将先前创建的SVG矩形元素(this.textElement) 添加为 this.svgElement 的子元素
        this.svgElement.appendChild(textElement);
        // 将文本元素引用赋值给了类的成员变量 this.textElement,这样后续可以通过 this.textElement 直接访问和操作这个文本元素,比如设置文本内容、更改样式等。
        this.textElement = textElement;
    }
    // 设置SVG文本元素的内容
    setText(textContent) {
        this.textElement.textContent = textContent;
    }
    // 添加创建和连接直线箭头的方法
    createArrow(x1, y1, x2, y2, color = '#1d6ee7', strokeWidth = 2) {
        // 创建的SVG元素类型——这里是线段元素
        const line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        // 为刚创建的SVG线段元素设置 x1 属性,表示线段起点的X坐标,这里的 x1 是一个变量,存储了所需的数值。
        line.setAttribute('x1', x1);
        // 为线段元素设置 y1 属性,表示线段起点的Y坐标,这里的 y1 是一个变量,存储了所需的数值。
        line.setAttribute('y1', y1);
        // 为线段元素设置 x2 属性,表示线段终点的X坐标,这里的 x2 是一个变量,存储了所需的数值。
        line.setAttribute('x2', x2);
        // 为线段元素设置 y2 属性,表示线段终点的Y坐标,这里的 y2 是一个变量,存储了所需的数值。
        line.setAttribute('y2', y2);
        // 设置线段的颜色
        line.setAttribute('stroke', color);
        //设置线段的粗细
        line.setAttribute('stroke-width', strokeWidth);

        // 创建箭头头部
        // 创建一个名为 arrowHead 的SVG多边形元素,这里是创建一个三角形作为箭头头部
        const arrowHead = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        // 计算向量的差分和长度
        //计算线段从起点 x1 到终点 x2 在X轴上的位移。
        const dx = x2 - x1;
        //计算线段在Y轴上的位移。
        const dy = y2 - y1;
        //计算线段的长度(欧几里得距离)
        const len = Math.sqrt(dx * dx + dy * dy);
        //计算线段的方向角,即从起点指向终点的角度,使用 Math.atan2 函数得到弧度值
        const angle = Math.atan2(dy, dx);
        //定义箭头头部的大小,这是一个常量,用来决定箭头三角形两边的长度
        const arrowHeadSize = 10;
        // 计算箭头三角形的两个顶点坐标
        //根据角度减去π/6(30度),计算箭头左侧顶点相对于线段终点的X坐标。
        const headX1 = x2 - arrowHeadSize * Math.cos(angle - Math.PI / 6);
        // 计算箭头左侧顶点相对于线段终点的Y坐标。
        const headY1 = y2 - arrowHeadSize * Math.sin(angle - Math.PI / 6);
        // 根据角度加上π/6(30度),计算箭头右侧顶点相对于线段终点的X坐标。
        const headX2 = x2 - arrowHeadSize * Math.cos(angle + Math.PI / 6);
        // 计算箭头右侧顶点相对于线段终点的Y坐标。
        const headY2 = y2 - arrowHeadSize * Math.sin(angle + Math.PI / 6);
        // 设置SVG多边形元素的points属性,该属性接受一系列顶点坐标,此处定义了一个等腰三角形作为箭头头部,三个顶点分别为线段终点和刚刚计算出的两侧顶点。
        arrowHead.setAttribute('points', [`${x2},${y2}`, `${headX1},${headY1}`, `${headX2},${headY2}`].join(' '));
        //设置箭头头部多边形的填充颜色,使其与线段颜色一致。
        arrowHead.setAttribute('fill', color);
        //将之前创建的线段元素添加到一个假设存在的SVG容器元素 this.svgElement 中。
        this.svgElement.appendChild(line);
        //将创建好的箭头头部(多边形)元素也添加到相同的SVG容器元素中,这样就形成了一个带有箭头的线段图形。
        this.svgElement.appendChild(arrowHead);
    }

    //定义了一个名为connectTo的方法,用于连接两个SVG图形
    connectTo(otherShape, startAnchor = { side: 'right', verticalAlign: 'center' }, endAnchor = { side: 'left', verticalAlign: 'center' }) {
        const myBBox = this.rectElement.getBBox();
        const otherBBox = otherShape.rectElement.getBBox();

        let startX, startY, endX, endY;

        switch (startAnchor.side) {
            case 'left':
                startX = myBBox.x;
                startY = startAnchor.verticalAlign === 'top' ? myBBox.y : myBBox.y + myBBox.height / 2;
                break;
            case 'right':
                startX = myBBox.x + myBBox.width;
                startY = startAnchor.verticalAlign === 'top' ? myBBox.y : myBBox.y + myBBox.height / 2;
                break;
            case 'top':
                startX = startAnchor.horizontalAlign === 'left' ? myBBox.x : myBBox.x + myBBox.width / 2;
                startY = myBBox.y;
                break;
            case 'bottom':
                startX = startAnchor.horizontalAlign === 'left' ? myBBox.x : myBBox.x + myBBox.width / 2;
                startY = myBBox.y + myBBox.height;
                break;
            default: // 默认为中心点
                startX = myBBox.x + myBBox.width / 2;
                startY = myBBox.y + myBBox.height / 2;
                break;
        }

        switch (endAnchor.side) {
            case 'left':
                endX = otherBBox.x;
                endY = endAnchor.verticalAlign === 'top' ? otherBBox.y : otherBBox.y + otherBBox.height / 2;
                break;
            case 'right':
                endX = otherBBox.x + otherBBox.width;
                endY = endAnchor.verticalAlign === 'top' ? otherBBox.y : otherBBox.y + otherBBox.height / 2;
                break;
            case 'top':
                endX = endAnchor.horizontalAlign === 'left' ? otherBBox.x : otherBBox.x + otherBBox.width / 2;
                endY = otherBBox.y;
                break;
            case 'bottom':
                endX = endAnchor.horizontalAlign === 'left' ? otherBBox.x : otherBBox.x + otherBBox.width / 2;
                endY = otherBBox.y + otherBBox.height;
                break;
            default: // 默认为中心点
                endX = otherBBox.x + otherBBox.width / 2;
                endY = otherBBox.y + otherBBox.height / 2;
                break;
        }

        this.createArrow(startX, startY, endX, endY);
    }

    // 新增setLink方法,处理超链接逻辑
    setLink(url) {
        const linkElement = document.createElementNS("http://www.w3.org/2000/svg", 'a');
        linkElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', url);
        // 如果矩形框和文本不在同一个父节点下,或者不在linkElement内
        if (this.rectElement.parentNode !== linkElement && this.textElement.parentNode !== linkElement) {
            // 移除它们原先所在的位置
            this.rectElement.parentNode.removeChild(this.rectElement);
            this.textElement.parentNode.removeChild(this.textElement);
            // 将矩形和文本都添加到链接元素内部
            linkElement.appendChild(this.rectElement);
            linkElement.appendChild(this.textElement);
            // 确保链接元素被添加到SVG容器内
            this.svgElement.appendChild(linkElement);
            // 更新类的成员变量引用为链接元素
            this.linkElement = linkElement;
        } else if (this.linkElement) {
            // 如果linkElement已经存在但href需要更新
            this.linkElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', url);
        } else {
            // 如果linkElement不存在但在同一父节点下,可以直接将它们包裹进新的linkElement
            this.rectElement.parentNode.insertBefore(linkElement, this.rectElement);
            linkElement.appendChild(this.rectElement);
            linkElement.appendChild(this.textElement);
            this.linkElement = linkElement;
        }
    }
}

// 示例:创建三个矩形框并连接它们
window.onload = function () {
    var svgElement = document.getElementById('workflow-svg');
    // 初始化三个矩形框并设置链接(x轴,y轴,宽,高)
    let shape1Ref = new FlowChartShape(svgElement, 50, 150, 120, 40);
    let shape2Ref = new FlowChartShape(svgElement, 250, 150, 120, 40);
    let shape3Ref = new FlowChartShape(svgElement, 450, 150, 120, 40);
    let shape4Ref = new FlowChartShape(svgElement, 650, 150, 120, 40);
    let shape5Ref = new FlowChartShape(svgElement, 350, 50, 120, 40);

    let shape6Ref = new FlowChartShape(svgElement, 250, 250, 120, 40);
    let shape7Ref = new FlowChartShape(svgElement, 450, 250, 120, 40);
    let shape8Ref = new FlowChartShape(svgElement, 650, 250, 120, 40);
    let shape9Ref = new FlowChartShape(svgElement, 850, 250, 120, 40);

    let shape10Ref = new FlowChartShape(svgElement, 850, 350, 120, 40);
    let shape11Ref = new FlowChartShape(svgElement, 850, 450, 120, 40);

    let shape12Ref = new FlowChartShape(svgElement, 350, 350, 120, 40);
    let shape13Ref = new FlowChartShape(svgElement, 550, 350, 120, 40);

    setTimeout(() => {
        //设置名称
        shape1Ref.setText('销售订单');
        shape2Ref.setText('报价申请');
        shape3Ref.setText('报价单签核');
        shape4Ref.setText('报价单回复');
        shape5Ref.setText('报价单修改');

        shape6Ref.setText('订单建立');
        shape7Ref.setText('订单审核');
        shape8Ref.setText('订单出货');
        shape9Ref.setText('销售退货');

        shape10Ref.setText('出货对账');
        shape11Ref.setText('出货对账取消');

        shape12Ref.setText('订单修改');
        shape13Ref.setText('订单反审核');

        // 连接矩形框
        shape1Ref.connectTo(shape2Ref);
        shape2Ref.connectTo(shape3Ref);
        shape3Ref.connectTo(shape4Ref);
        //2的顶部中点到5的底部中点
        shape2Ref.connectTo(shape5Ref, {
            side: 'top',
            verticalAlign: 'center'
        }, {
            side: 'left',
            verticalAlign: 'center'
        });
        // 从shape1Ref的左上角到shape2Ref的右下角:
        // shape1Ref.connectTo(shape2Ref, {
        //     side: 'left',
        //     verticalAlign: 'top'
        // }, {
        //     side: 'right',
        //     verticalAlign: 'bottom'
        // });
        shape5Ref.connectTo(shape3Ref, {
            side: 'right',
            verticalAlign: 'center'
        }, {
            side: 'top',
            verticalAlign: 'center'
        });

        shape1Ref.connectTo(shape6Ref);
        shape6Ref.connectTo(shape7Ref);
        shape7Ref.connectTo(shape8Ref);
        shape8Ref.connectTo(shape9Ref);

        shape9Ref.connectTo(shape10Ref, {
            side: 'bottom',
            verticalAlign: 'center'
        }, {
            side: 'top',
            verticalAlign: 'center'
        });
        shape10Ref.connectTo(shape11Ref, {
            side: 'bottom',
            verticalAlign: 'center'
        }, {
            side: 'top',
            verticalAlign: 'center'
        });

        shape7Ref.connectTo(shape13Ref, {
            side: 'bottom',
            verticalAlign: 'center'
        }, {
            side: 'top',
            verticalAlign: 'center'
        });
        shape13Ref.connectTo(shape12Ref, {
            side: 'left',
            verticalAlign: 'center'
        }, {
            side: 'right',
            verticalAlign: 'center'
        });
        shape12Ref.connectTo(shape7Ref, {
            side: 'top',
            verticalAlign: 'center'
        }, {
            side: 'bottom',
            verticalAlign: 'center'
        });

        //添加超链接
        shape1Ref.setLink('page1.html'); // 为第一个矩形框设置链接到“page1.html”
        shape2Ref.setLink('page2.html'); // 为第二个矩形框设置链接到“page2.html”
        shape3Ref.setLink('page3.html'); // 为第三个矩形框设置链接到“page3.html”
    }, 0);
};

把实现图形的部分写在html页面

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>SVG流程图示例</title>
    <style>
        /* CSS 样式 */
    </style>
    <script src="js/index.js"></script>
    <!-- 引入外部JS文件 -->
    <script>
        // 示例:创建三个矩形框并连接它们
        window.onload = function () {
            var svgElement = document.getElementById('workflow-svg');
            // 初始化三个矩形框并设置链接(x轴,y轴,宽,高)
            let shape1Ref = new FlowChartShape(svgElement, 50, 150, 120, 40);
            let shape2Ref = new FlowChartShape(svgElement, 250, 150, 120, 40);
            let shape3Ref = new FlowChartShape(svgElement, 450, 150, 120, 40);
            let shape4Ref = new FlowChartShape(svgElement, 650, 150, 120, 40);
            let shape5Ref = new FlowChartShape(svgElement, 350, 50, 120, 40);

            let shape6Ref = new FlowChartShape(svgElement, 250, 250, 120, 40);
            let shape7Ref = new FlowChartShape(svgElement, 450, 250, 120, 40);
            let shape8Ref = new FlowChartShape(svgElement, 650, 250, 120, 40);
            let shape9Ref = new FlowChartShape(svgElement, 850, 250, 120, 40);

            let shape10Ref = new FlowChartShape(svgElement, 850, 350, 120, 40);
            let shape11Ref = new FlowChartShape(svgElement, 850, 450, 120, 40);

            let shape12Ref = new FlowChartShape(svgElement, 350, 350, 120, 40);
            let shape13Ref = new FlowChartShape(svgElement, 550, 350, 120, 40);

            setTimeout(() => {
                //设置名称
                shape1Ref.setText('销售订单');
                shape2Ref.setText('报价申请');
                shape3Ref.setText('报价单签核');
                shape4Ref.setText('报价单回复');
                shape5Ref.setText('报价单修改');

                shape6Ref.setText('订单建立');
                shape7Ref.setText('订单审核');
                shape8Ref.setText('订单出货');
                shape9Ref.setText('销售退货');

                shape10Ref.setText('出货对账');
                shape11Ref.setText('出货对账取消');

                shape12Ref.setText('订单修改');
                shape13Ref.setText('订单反审核');

                // 连接矩形框
                shape1Ref.connectTo(shape2Ref);
                shape2Ref.connectTo(shape3Ref);
                shape3Ref.connectTo(shape4Ref);
                //2的顶部中点到5的底部中点
                shape2Ref.connectTo(shape5Ref, {
                    side: 'top',
                    verticalAlign: 'center'
                }, {
                    side: 'left',
                    verticalAlign: 'center'
                });
                // 从shape1Ref的左上角到shape2Ref的右下角:
                // shape1Ref.connectTo(shape2Ref, {
                //     side: 'left',
                //     verticalAlign: 'top'
                // }, {
                //     side: 'right',
                //     verticalAlign: 'bottom'
                // });
                shape5Ref.connectTo(shape3Ref, {
                    side: 'right',
                    verticalAlign: 'center'
                }, {
                    side: 'top',
                    verticalAlign: 'center'
                });

                shape1Ref.connectTo(shape6Ref);
                shape6Ref.connectTo(shape7Ref);
                shape7Ref.connectTo(shape8Ref);
                shape8Ref.connectTo(shape9Ref);

                shape9Ref.connectTo(shape10Ref, {
                    side: 'bottom',
                    verticalAlign: 'center'
                }, {
                    side: 'top',
                    verticalAlign: 'center'
                });
                shape10Ref.connectTo(shape11Ref, {
                    side: 'bottom',
                    verticalAlign: 'center'
                }, {
                    side: 'top',
                    verticalAlign: 'center'
                });

                shape7Ref.connectTo(shape13Ref, {
                    side: 'bottom',
                    verticalAlign: 'center'
                }, {
                    side: 'top',
                    verticalAlign: 'center'
                });
                shape13Ref.connectTo(shape12Ref, {
                    side: 'left',
                    verticalAlign: 'center'
                }, {
                    side: 'right',
                    verticalAlign: 'center'
                });
                shape12Ref.connectTo(shape7Ref, {
                    side: 'top',
                    verticalAlign: 'center'
                }, {
                    side: 'bottom',
                    verticalAlign: 'center'
                });

                //添加超链接
                shape1Ref.setLink('page1.html'); // 为第一个矩形框设置链接到“page1.html”
                shape2Ref.setLink('page2.html'); // 为第二个矩形框设置链接到“page2.html”
                shape3Ref.setLink('page3.html'); // 为第三个矩形框设置链接到“page3.html”
            }, 0);
        };
    </script>
</head>

<body>

    <svg id="workflow-svg" width="1000" height="800">
        <!-- SVG图形内容... -->
    </svg>

</body>

</html>

js

增加linkElement.setAttribute('target', '_blank'); // 使链接在新窗口打开

// 定义矩形框
class FlowChartShape {
    // 定义一个类的构造函数。svgElement容纳新创建的矩形框、initialX,initialY分别代表矩形框左上角的初始X轴和Y轴坐标、width和height:这两个参数表示矩形框的宽度和高度
    constructor(svgElement, initialX, initialY, width, height) {
        //是在当前类的实例(FlowChartShape)内部设置一个成员变量。意味着每个FlowChartShape实例都将与特定的SVG元素关联起来
        this.svgElement = svgElement;
        // 创建一个新的SVG < rect > 元素(图形)。
        this.rectElement = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
        // 设定矩形元素在其父SVG容器中的X坐标位置
        this.rectElement.setAttribute('x', initialX);
        // 设定矩形元素在其父SVG容器中的y坐标位置
        this.rectElement.setAttribute('y', initialY);
        // 设定矩形元素在其父SVG容器中的宽度
        this.rectElement.setAttribute('width', width);
        // 设定矩形元素在其父SVG容器中的高度
        this.rectElement.setAttribute('height', height);
        // 设置矩形的圆角半径设置为10个单位
        this.rectElement.setAttribute('rx', 10);
        // 设置当前SVG矩形元素的内部填充颜色为纯白色。
        this.rectElement.setAttribute('fill', '#fff');
        // 设置了矩形元素的描边颜色为黑色
        this.rectElement.setAttribute('stroke', '#1d6ee7');
        // 矩形元素的描边宽度为2个单位
        this.rectElement.setAttribute('stroke-width', 2);

        // 创建了一个新的SVG文本元素。
        let textElement = document.createElementNS("http://www.w3.org/2000/svg", 'text');
        // 'x' 属性设置文本元素在水平方向上的起始位置:为矩形容器的起始x坐标加上宽度的一半
        textElement.setAttribute('x', initialX + width / 2);
        // 'y' 属性设置文本元素在竖直方向上的起始位置:为矩形容器的起始y坐标加上高度的一半
        textElement.setAttribute('y', initialY + height / 2);
        // 设置SVG文本元素的对齐方式:居中
        textElement.setAttribute('text-anchor', 'middle'); // 这会让文本水平居中
        textElement.setAttribute('dominant-baseline', 'middle'); // 竖直居中,在某些浏览器中可能需要其他值,比如 'central'


        // 将先前创建的SVG矩形元素(this.rectElement) 添加为 this.svgElement 的子元素
        this.svgElement.appendChild(this.rectElement);
        // 将先前创建的SVG矩形元素(this.textElement) 添加为 this.svgElement 的子元素
        this.svgElement.appendChild(textElement);
        // 将文本元素引用赋值给了类的成员变量 this.textElement,这样后续可以通过 this.textElement 直接访问和操作这个文本元素,比如设置文本内容、更改样式等。
        this.textElement = textElement;
    }
    // 设置SVG文本元素的内容
    setText(textContent) {
        this.textElement.textContent = textContent;
    }
    // 添加创建和连接直线箭头的方法
    createArrow(x1, y1, x2, y2, color = '#1d6ee7', strokeWidth = 2) {
        // 创建的SVG元素类型——这里是线段元素
        const line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        // 为刚创建的SVG线段元素设置 x1 属性,表示线段起点的X坐标,这里的 x1 是一个变量,存储了所需的数值。
        line.setAttribute('x1', x1);
        // 为线段元素设置 y1 属性,表示线段起点的Y坐标,这里的 y1 是一个变量,存储了所需的数值。
        line.setAttribute('y1', y1);
        // 为线段元素设置 x2 属性,表示线段终点的X坐标,这里的 x2 是一个变量,存储了所需的数值。
        line.setAttribute('x2', x2);
        // 为线段元素设置 y2 属性,表示线段终点的Y坐标,这里的 y2 是一个变量,存储了所需的数值。
        line.setAttribute('y2', y2);
        // 设置线段的颜色
        line.setAttribute('stroke', color);
        //设置线段的粗细
        line.setAttribute('stroke-width', strokeWidth);

        // 创建箭头头部
        // 创建一个名为 arrowHead 的SVG多边形元素,这里是创建一个三角形作为箭头头部
        const arrowHead = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        // 计算向量的差分和长度
        //计算线段从起点 x1 到终点 x2 在X轴上的位移。
        const dx = x2 - x1;
        //计算线段在Y轴上的位移。
        const dy = y2 - y1;
        //计算线段的长度(欧几里得距离)
        const len = Math.sqrt(dx * dx + dy * dy);
        //计算线段的方向角,即从起点指向终点的角度,使用 Math.atan2 函数得到弧度值
        const angle = Math.atan2(dy, dx);
        //定义箭头头部的大小,这是一个常量,用来决定箭头三角形两边的长度
        const arrowHeadSize = 10;
        // 计算箭头三角形的两个顶点坐标
        //根据角度减去π/6(30度),计算箭头左侧顶点相对于线段终点的X坐标。
        const headX1 = x2 - arrowHeadSize * Math.cos(angle - Math.PI / 6);
        // 计算箭头左侧顶点相对于线段终点的Y坐标。
        const headY1 = y2 - arrowHeadSize * Math.sin(angle - Math.PI / 6);
        // 根据角度加上π/6(30度),计算箭头右侧顶点相对于线段终点的X坐标。
        const headX2 = x2 - arrowHeadSize * Math.cos(angle + Math.PI / 6);
        // 计算箭头右侧顶点相对于线段终点的Y坐标。
        const headY2 = y2 - arrowHeadSize * Math.sin(angle + Math.PI / 6);
        // 设置SVG多边形元素的points属性,该属性接受一系列顶点坐标,此处定义了一个等腰三角形作为箭头头部,三个顶点分别为线段终点和刚刚计算出的两侧顶点。
        arrowHead.setAttribute('points', [`${x2},${y2}`, `${headX1},${headY1}`, `${headX2},${headY2}`].join(' '));
        //设置箭头头部多边形的填充颜色,使其与线段颜色一致。
        arrowHead.setAttribute('fill', color);
        //将之前创建的线段元素添加到一个假设存在的SVG容器元素 this.svgElement 中。
        this.svgElement.appendChild(line);
        //将创建好的箭头头部(多边形)元素也添加到相同的SVG容器元素中,这样就形成了一个带有箭头的线段图形。
        this.svgElement.appendChild(arrowHead);
    }

    //定义了一个名为connectTo的方法,用于连接两个SVG图形
    connectTo(otherShape, startAnchor = { side: 'right', verticalAlign: 'center' }, endAnchor = { side: 'left', verticalAlign: 'center' }) {
        const myBBox = this.rectElement.getBBox();
        const otherBBox = otherShape.rectElement.getBBox();

        let startX, startY, endX, endY;

        switch (startAnchor.side) {
            case 'left':
                startX = myBBox.x;
                startY = startAnchor.verticalAlign === 'top' ? myBBox.y : myBBox.y + myBBox.height / 2;
                break;
            case 'right':
                startX = myBBox.x + myBBox.width;
                startY = startAnchor.verticalAlign === 'top' ? myBBox.y : myBBox.y + myBBox.height / 2;
                break;
            case 'top':
                startX = startAnchor.horizontalAlign === 'left' ? myBBox.x : myBBox.x + myBBox.width / 2;
                startY = myBBox.y;
                break;
            case 'bottom':
                startX = startAnchor.horizontalAlign === 'left' ? myBBox.x : myBBox.x + myBBox.width / 2;
                startY = myBBox.y + myBBox.height;
                break;
            default: // 默认为中心点
                startX = myBBox.x + myBBox.width / 2;
                startY = myBBox.y + myBBox.height / 2;
                break;
        }

        switch (endAnchor.side) {
            case 'left':
                endX = otherBBox.x;
                endY = endAnchor.verticalAlign === 'top' ? otherBBox.y : otherBBox.y + otherBBox.height / 2;
                break;
            case 'right':
                endX = otherBBox.x + otherBBox.width;
                endY = endAnchor.verticalAlign === 'top' ? otherBBox.y : otherBBox.y + otherBBox.height / 2;
                break;
            case 'top':
                endX = endAnchor.horizontalAlign === 'left' ? otherBBox.x : otherBBox.x + otherBBox.width / 2;
                endY = otherBBox.y;
                break;
            case 'bottom':
                endX = endAnchor.horizontalAlign === 'left' ? otherBBox.x : otherBBox.x + otherBBox.width / 2;
                endY = otherBBox.y + otherBBox.height;
                break;
            default: // 默认为中心点
                endX = otherBBox.x + otherBBox.width / 2;
                endY = otherBBox.y + otherBBox.height / 2;
                break;
        }

        this.createArrow(startX, startY, endX, endY);
    }

    // 新增setLink方法,处理超链接逻辑
    setLink(url) {
        const linkElement = document.createElementNS("http://www.w3.org/2000/svg", 'a');
        linkElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', url);
        linkElement.setAttribute('target', '_blank'); // 使链接在新窗口打开
        // 如果矩形框和文本不在同一个父节点下,或者不在linkElement内
        if (this.rectElement.parentNode !== linkElement && this.textElement.parentNode !== linkElement) {
            // 移除它们原先所在的位置
            this.rectElement.parentNode.removeChild(this.rectElement);
            this.textElement.parentNode.removeChild(this.textElement);
            // 将矩形和文本都添加到链接元素内部
            linkElement.appendChild(this.rectElement);
            linkElement.appendChild(this.textElement);
            // 确保链接元素被添加到SVG容器内
            this.svgElement.appendChild(linkElement);
            // 更新类的成员变量引用为链接元素
            this.linkElement = linkElement;
        } else if (this.linkElement) {
            // 如果linkElement已经存在但href需要更新
            this.linkElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', url);
        } else {
            // 如果linkElement不存在但在同一父节点下,可以直接将它们包裹进新的linkElement
            this.rectElement.parentNode.insertBefore(linkElement, this.rectElement);
            linkElement.appendChild(this.rectElement);
            linkElement.appendChild(this.textElement);
            this.linkElement = linkElement;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/524459.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式浅析(十) ·设计模式之迭代器组合模式

设计模式浅析(十) 设计模式之迭代器&组合模式 日常叨逼叨 java设计模式浅析&#xff0c;如果觉得对你有帮助&#xff0c;记得一键三连&#xff0c;谢谢各位观众老爷&#x1f601;&#x1f601; 案例 有两家门店&#xff0c;门店A呢只提供早餐&#xff0c;门店B呢只提供午…

IntelliJ IDEA 2024.1 更新亮点汇总:全面提升开发体验

IntelliJ IDEA 2024.1 更新亮点汇总&#xff1a;全面提升开发体验 文章目录 IntelliJ IDEA 2024.1 更新亮点汇总&#xff1a;全面提升开发体验摘要引言 IntelliJ IDEA 2024.1 的新增功能主要亮点全行代码完成 最终的支持 Java 22 功能新航站楼 贝塔编辑器中的粘滞线 人工智能助…

2024新版PHP在线客服系统多商户AI智能在线客服系统源码机器人自动回复即时通讯聊天系统源码PC+H5

搭建环境&#xff1a; 服务器 CPU 2核心 ↑ 运存 2G ↑ 宽带 5M ↑ 服务器操作系统 Linux Centos7.6-7.9 ↑ 运行环境&#xff1a; 宝塔面板 Nginx1.18- 1.22 PHP 7.1-7.3 MYSQL 5.6 -5.7 朵米客服系统是一款全功能的客户服务解决方案&#xff0c;提供多渠道支持…

深入浅出 -- 系统架构之负载均衡Nginx实现高可用

一、Nginx的高可用 线上如果采用单个节点的方式部署Nginx&#xff0c;难免会出现天灾人祸&#xff0c;比如系统异常、程序宕机、服务器断电、机房爆炸、地球毁灭....哈哈哈&#xff0c;夸张了。但实际生产环境中确实存在隐患问题&#xff0c;由于Nginx作为整个系统的网关层接入…

图解Java23种设计模式

好代码与烂代码 对代码质量的评判不能依据笼统的感觉&#xff0c;而是根据精准的标准去判断 我们应该从以下角度去判断自己写的代码到底是不是屎山&#xff1a; 可维护性&#xff08;Maintainability&#xff09;&#xff1a;能够以最小的成本和最快的速度修改或优化代码。可维…

git bash上传文件至github仓库

Linux运维工具-ywtool 目录 一.访问github二.新建仓库1.点击自己头像2.选择"your repositories"3.点击"New"4.创建新仓库 三.通过git bash软件上传文件1.提示2.打开git bash软件3.切换到本地仓库目录4.配置github的用户名和邮箱信息5.生成SSH Key6.github添…

麒麟系统ARM安装rabbitmq

简单记录下&#xff0c;信创服务器&#xff1a;麒麟系统&#xff0c;安装rabbitmq的踩坑记录。 本文章参考了很多大佬文章&#xff0c;我整理后提供。 一、安装基础依赖 yum -y install make gcc gcc-c kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel 二、下载…

蓝桥杯刷题day14——盖印章【算法赛】

一、问题描述 小 Z 喜欢盖印章。 有一天,小 Z 得到了一个 nm 的网格图,与此同时,他的手上有两种印章(分别称为 A,B),如下图所示。 他想将这两种印章盖在这个网格图上。 由于小 Z 是一个有原则的人,他将按照以下规则进行操作。 每个印章所形成的图案的边必须和网格图…

性能分析-CPU知识

目录 CPU知识 cpu组成 查看cpu信息&#xff1a; top命令中 cpu相关&#xff1a; top命令看到系统负载&#xff1a; CPU负载 IO负载 上下文&#xff1a; CPU的寄存器和程序计数器----在cpu的控制器中 实战演示分析 top命令分析 arthas工具 进程上下文切换高的问题分析…

零信任安全模型:构建未来数字世界的安全基石

在数字化转型的浪潮中&#xff0c;云原生技术已成为推动企业创新和灵活性的关键力量&#x1f4a1;。然而&#xff0c;随着技术的进步和应用的广泛&#xff0c;网络安全威胁也日益严峻&#x1f513;&#xff0c;传统的网络安全模型已经难以应对复杂多变的网络环境。在这样的背景…

考研||考公||就业||其他?-------愿不再犹豫

大三下了&#xff0c;现在已经开学一个多月了&#xff0c;在上个学期的时候陆陆续续吧周围有的行动早的人已经开始准备考研了&#xff0c;当然这只是下小部分人吧&#xff0c;也有一部分人是寒假可能就开始了&#xff0c;更多的则是开学的时候&#xff0c;我的直观感受是图书馆…

Sundar Pichai 谈巨型公司创新挑战及他今年感到兴奋的事物

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

javaer 为什么称redis、rabbitmq这些东西为中间件?

中间件&#xff08;Middleware&#xff09;是位于客户端和服务器端之间的软件服务层&#xff0c;它提供了一种通用服务的方式&#xff0c;帮助不同的应用程序、系统组件和服务之间进行交互和数据交换。中间件隐藏了底层的复杂性&#xff0c;使得开发者可以专注于业务逻辑的实现…

珠海华发实业股份有限公司副总毛冰清莅临天府锋巢直播产业基地考察调研

3月19日&#xff0c;珠海华发实业股份有限公司副总毛冰清拜访天府锋巢直播产业基地&#xff08;以下简称天府锋巢&#xff09;&#xff0c;由产业招商总负责人姜国东进行接待。 基地建设情况 姜国东负责人介绍到&#xff0c;天府锋巢是由德商产投携手无锋科技于兴隆湖落地的成都…

循环双链表算法库构建

学习贺老师数据结构数据结构之自建算法库——循环双链表_数据结构编写一个程序linklist.cpp-CSDN博客 模仿单链表逻辑,实现双链表, 大差不差 v1.0: 实现基本功能 V1.0 1.主要功能: //(1)头插法建立循环双链表 void Create_Double_CyclicList_Head(DoubleLinkList_Cyclic *&am…

Redis常见的一些问题和注意事项

本文汇总的都是在我们公司出现过的常见问题以及自己曾经记录的注意事项。 我们公司sentinel模式以及RedisCluster集群两种部署方式都有使用&#xff0c;下面问题有些可能是哨兵模式下存在的&#xff0c;比如批量操作&#xff0c;下面可能不会特别说明。 1、注意热点key 之前单位…

YOLOV8 + 双目测距

YOLOV8 双目测距 1. 环境配置2. 测距流程和原理2.1 测距流程2.2 测距原理 3. 代码部分解析3.1 相机参数stereoconfig.py3.2 测距部分3.3 主代码yolov8-stereo.py 4. 实验结果4.1 测距4.2 测距跟踪4.3 测距跟踪分割4.4 视频展示 相关文章 1. YOLOv5双目测距&#xff08;python&…

MySQL高级篇(存储引擎InnoDB、MyISAM、Memory)

目录 1、存储引擎简介 1.1、查询建表语句&#xff0c;默认存储引擎&#xff1a;InnoDB 1.2、查看当前数据库支持的存储引擎 1.3、创建表&#xff0c;并指定存储引擎 2、 存储引擎-InnoDB介绍 2.1、存储引擎特点 3、MyISAM存储引擎 4、Memory存储引擎 5、InnoDB、MyISAM、Memory…

layui在上传多图时,allDone方法只是在第一次全部成功时调用了

问题点&#xff1a;在使用layui框架做多张图片上传时&#xff0c;遇见只有第一次操作上传图片时&#xff0c;触发了allDone全部上传成功的方法&#xff0c;后面再添加图片时&#xff0c;就不会调用这个方法 原因&#xff1a;是因为我删除了 choose 方法&#xff0c;并且也没有将…

算法部署 | 使用TensorRT+DeepSort+YOLOv5在NVIDIA-Jetson平台上部署目标跟踪算法

项目应用场景 面向英伟达 Jetson 边缘计算平台部署目标跟踪算法场景&#xff0c;使用深度学习算法 YOLOv5 DeepSort 来实现&#xff0c;并使用 TensorRT 进行算法加速&#xff0c;项目支持 NVIDIA Jetson Xavier、NVIDIA Jetson Xavier NX、X86 平台的算法部署。 项目效果 项…