d3学习笔记

Selection

Data

        我的数据结构如下:
在这里插入图片描述
        当我删除其中某个中间结点的时候:
请添加图片描述
        我们可以清楚的看到图片在发生转动,而不是单纯的让我们选择的那个点消失,后来我终于发现了原因,就是因为没有让元素绑定数据中的独一无二的键。

const data = [
  {name: "Locke", number: 4},
  {name: "Reyes", number: 8},
  {name: "Ford", number: 15},
  {name: "Jarrah", number: 16},
  {name: "Shephard", number: 23},
  {name: "Kwon", number: 42}
];

d3.selectAll("div")
  .data(data, function(d) { return d ? d.name : this.id; })
  //或者.data(data, d => d.name) 直接将name当作创建div独一无二的键
    .text(d => d.number);

Transition用法

        其实原理非常简单,就是在.transition()之前的attr赋予的属性,或者初始样式是动画的起点,之后attr赋予的属性就是动画需要变化到的终点。

d3
.selectAll("circle")
.attr("fill","red")         //初始颜色为红色
.transition()               //启动过渡
.duration(1000) 			//过渡动画持续时间 1s
.delay(500)					//表示在0.5s后才开始动画
.ease("bounce")             //指定过度方式
.attr("fill","steelblue")   //终止颜色为铁蓝色

        其中可以通过.ease()指定过渡的方式,常用的有:

  • linear:普通的线性变化
  • circle:慢慢地到达变换的最终状态
  • elastic:带有弹跳的到达最终状态
  • bounce:在最终状态处弹跳几次
    调用时,格式形如: ease(“bounce”)。

一个完整的示例

        可以尝试在join()中的exit中添加动画,让删除结点变得更加平滑

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

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Tree map website</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3"
        crossorigin="anonymous"></script>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <link rel="stylesheet" href="../statick/css/tree_style.css">
</head>

<!-- 页面主体 -->

<body onload="start_index()">
    <div style="height: 900px;">
        <!-- 头部 -->
        <div id="header" class="container-fluid bg-light border rounded-2 ">
            <header class="d-flex justify-content-center py-3">header</header>
        </div>

        <!-- 中间体 -->
        <div id="middle" class="container-fluid bg-light ">
            <div class="row">
                <!-- 中左 -->
                <div id="middle_left" class="col border rounded-2">
                    middle_left
                </div>
                <!-- 中心 -->
                <div id="middle_center" class="col-auto border rounded-2">
                    <!-- 中间的树 -->
                    <svg id="center_tree">
                        <g id="links_g"></g>
                        <g id="nodes_g"></g>
                    </svg>
                </div>
                <!-- 中右 -->
                <div id="middle_right" class="col border rounded-2 ">
                    middle_right
                </div>
            </div>
        </div>

        <!-- 底部 -->
        <div id="footer" class="container-fluid bg-light border rounded-2">
            <footer class="d-flex justify-content-center py-3">footer</footer>
        </div>
    </div>
</body>


<!-- 可以交互的脚本 -->
<script>
    //全局变量
    //---svg画布大小-------------------
    var width = 800;
    var height = 800;
    //---颜色色卡数组------------------
    var color_legend = ["LightPink", "DarkOrchid", "Blue", "DoderBlue", "Cyan", "DarkGreen", "PaleGodenrod", "Orange", "OrangeRed", "Gainsboro"];
    //---结点与线条的数量--------------
    var global_nodes_id = 0;
    var global_links_id = 0;
    //--------------------------------

    //位置确定函数
    function caculate_position(source_children_number, source_cx, source_cy) {
        /*
            source_children_number: 根据当前是第几个子节点来确定子节点的位置
            source_cx: 父节点的x位置
            source_cy: 父节点的y位置
        */
        var new_cx, new_cy;
        var step_length = 100
        switch (source_children_number) {
            case 0:
                new_cx = source_cx;
                new_cy = source_cy + step_length;
                break;
            case 1:
                new_cx = source_cx - step_length;
                new_cy = source_cy;
                break;
            case 2:
                new_cx = source_cx;
                new_cy = source_cy - step_length;
                break;
            case 3:
                new_cx = source_cx + step_length;
                new_cy = source_cy;
                break;
            default:
                new_cx = source_cx;
                new_cy = source_cy;
                break;
        }
        return [new_cx, new_cy];
    }
    //添加的结点的字典操作---------------------------------------------
    function add_tree_node_action(grapgh, source_node) {
        var source_children_number = source_node.nodes_children_number;
        var new_position = caculate_position(source_children_number, source_node.nodes_position.cx, source_node.nodes_position.cy)

        var new_dict = {
            nodes_id: global_nodes_id++,
            nodes_father_id: source_node.nodes_id,
            nodes_position: { cx: new_position[0], cy: new_position[1] },
            nodes_children_number: 0, //新结点的子节点数为0
            robustness_value: Math.random() + 0.000001,
            nodes_class: Math.floor(Math.random() * 10),
            nodes_confidence: Math.random()
        }
        return new_dict
    }
    //添加的链接的字典操作---------------------------------------------
    function add_tree_link_action(grapgh, source_node_id, target_node_id, r_differences) {
        var new_dict = {
            links_id: global_links_id++,
            source: source_node_id,
            target: target_node_id,
            robustness_differences: r_differences
        }
        return new_dict
    }
    //根据结点的编号,找到结点-----------------------------------------
    function from_id_find_nodes(graph, id) {
        var source_node;
        var source_index = 0;
        for (let i = 0; i < graph.nodes.length; i++) {
            if (graph.nodes[i].nodes_id == id) {
                source_node = graph.nodes[i];
                source_index = i;
                break;
            }
        }

        return [source_node, source_index];
    }
    //添加树中的元素--------------------------------------------------
    function add_tree_element(graph, source_node_id = 0) {
        /*
            graph: 通过修改graph数据来控制图片
            source_node: 在哪个结点编号上添加结点
        */
        var return_value = from_id_find_nodes(graph, source_node_id); //根据结点的id找到该结点
        var source_node = return_value[0];
        var source_index = return_value[1]

        var new_nodes_dict = add_tree_node_action(graph, source_node);
        var new_links_dict = add_tree_link_action(graph, source_node_id, new_nodes_dict
            .nodes_id, Math.abs(new_nodes_dict.robustness_value - source_node.robustness_value));
        graph.nodes.push(new_nodes_dict);
        graph.links.push(new_links_dict);

        graph.nodes[source_index].nodes_children_number++; //当前节点的孩子数量加一
        graph_init_tree_node(graph)

    }
    //删除树中元素----------------------------------------------------
    function delete_tree_element(graph, delete_node) {
        //删除结点
        for (let i = 0; i < graph.nodes.length; i++) {
            if (graph.nodes[i].nodes_id == delete_node.nodes_id) {
                console.log("当前删除结点的下标:", i)
                console.log("删除前的graph", graph)
                graph.nodes.splice(i, 1); //第一个参数是删除的位置,第二个参数是从这个位置删除几个元素
                console.log("删除后的graph", graph)
                break;
            }
        }
        //找到连接此结点的线
        for (let i = 0; i < graph.links.length; i++) {
            //先修改起点
            if (graph.links[i].source == delete_node.nodes_id || graph.links[i].target == delete_node.nodes_id) {
                //让孩子的记录数量减一,这样再生成结点时候判断更加合理
                //待实现
                //建议修改孩子的数据结构用[0,1,0,0,0]这样的数组来判断哪个方向还可以生成
                //---------------------------------------------------
                graph.links.splice(i, 1);
                i--; //这里要注意,使用splice后,整个的数组相当于在删除的位置进行了偏移
            }
        }
        //重新画图
        graph_init_tree_node(graph)
    }
    //计算鲁棒性与结点的半径,以及stroke
    function calculate_r(robustness_value) {
        var r = 1 / robustness_value * 5;
        return r;
    }
    //通过graph绘制树-------------------------------------------------!!!
    function graph_init_tree_node(graph) {
        //定义该函数下的全局键盘事件
        var focused = null;
        d3.select("body").on("keydown", key_down_gloabal)
        function key_down_gloabal(event, d) {
            //在有聚焦的前提下
            if (focused != null) {
                //如果用户按键为Delete
                if (event.key == "Delete") {
                    //focused.raise(); //这个一定不能少,目的就是让删除的对象图层上移,然后后续匹配数据事不会出现问题!!!!!
                    focused_node = focused.datum();
                    console.log("当前要删除的结点:", focused_node)
                    delete_tree_element(graph, focused_node);
                }
            }
        }
        //核心svg
        center_tree_svg = d3.select("#center_tree")
            .attr("width", width)
            .attr("height", height);
        //核心svg中的nodes_g
        nodes_g = center_tree_svg.select("#nodes_g")
        //核心svg中的links_g
        links_g = center_tree_svg.select("#links_g")
        //结点
        nodes = nodes_g
            .selectAll("circle")
            .data(graph.nodes, d => d.nodes_id)//!!!!!!!给每个元素绑定一个键,这样d3就知道你是更新数据还是修改数据了。
            .join(
                enter => enter.append("circle")
                    .attr("cx", (d, i) => {
                        var source_node = from_id_find_nodes(graph, d.nodes_father_id)[0];
                        return source_node.nodes_position.cx;
                    })
                    .attr("cy", (d, i) => {
                        var source_node = from_id_find_nodes(graph, d.nodes_father_id)[0];
                        return source_node.nodes_position.cy;
                    })
                    .attr("r", "0px")
                    .transition() //过渡动画, 从之前的状态转移到现在的状态,也就是上面的状态
                    .duration(1000) //过渡动画持续时间 1s,
                    .attr("cx", (d, i) => d.nodes_position.cx)
                    .attr("cy", (d, i) => d.nodes_position.cy)
                    .attr("r", (d, i) => {
                        var r;
                        if (d.robustness_value >= 1) {
                            r = 5;
                        } else {
                            r = calculate_r(d.robustness_value);
                        }
                        return String(r + "px");
                    })
                    .attr("stroke", d => "rgb(211, 211, 211)")
                    .attr("style", d => {
                        var stroke_width = calculate_r(d.robustness_value) * d.nodes_confidence
                        return "stroke-width:" + String(stroke_width) + "px;" + "fill:" + color_legend[d.nodes_class] + ";"
                    }),
                update => update,
                exit => exit.remove())
            .attr("node_id_number", d => d.nodes_id)
            .classed("node", true)
            .on("click", click)
            .on("mouseover", node_mouseover)
            .on("mouseout", node_mouseout);
        //结点间的连线
        links = links_g
            .selectAll("line")
            .data(graph.links, d => d.links_id) //!!!!!!!给每个元素绑定一个键,这样d3就知道你是更新数据还是修改数据了。
            .join(
                enter => enter.append("line")
                    .attr("x1", d => {
                        var source_node = from_id_find_nodes(graph, d.source)[0];
                        return source_node.nodes_position.cx;
                    })
                    .attr("y1", d => {
                        var source_node = from_id_find_nodes(graph, d.source)[0];
                        return source_node.nodes_position.cy;
                    })
                    .attr("x2", d => {
                        var source_node = from_id_find_nodes(graph, d.source)[0];
                        return source_node.nodes_position.cx;
                    })
                    .attr("y2", d => {
                        var source_node = from_id_find_nodes(graph, d.source)[0];
                        return source_node.nodes_position.cy;
                    })
                    .transition() //过渡动画, 从之前的状态转移到现在的状态,也就是上面的状态
                    .duration(1000) //过渡动画持续时间 1s
                    .attr("x2", d => {
                        var source_node = from_id_find_nodes(graph, d.target)[0];
                        return source_node.nodes_position.cx;
                    })
                    .attr("y2", d => {
                        var source_node = from_id_find_nodes(graph, d.target)[0];
                        return source_node.nodes_position.cy;
                    }),
                update => update,
                exit => exit.remove())
            .attr("link_id_number", (d, i) => i)
            .attr("style", (d, i) => {
                return "stroke-width:" + String(d.robustness_differences * 10) + "px; stroke:" + color_legend[d.robustness_differences % color_legend.length] + ";"
            })
            // .attr("stroke-width", d => String(d.robustness_differences + 1) + "px")
            // .attr("stroke", d => color_legend[d.robustness_differences % color_legend.length])
            .classed("link", true) //是否将.link这个class属性赋予到这个变量 
            .order();
        // 拖拽事件
        drag = d3.drag()
            .on("start", dragStart)
            .on("drag", dragging)
            .on("end", dragEnded);
        //缩放事件
        zoom = d3.zoom()
            // .extent([[0, 0], [width, height]])
            // .scaleExtent([1, 8])
            .on("zoom", zoomed)

        //然和兴svg绑定缩放事件
        center_tree_svg.call(zoom)
            .on("dblclick.zoom", null) //取消自带的双击放大事件
        //让node绑定拖拽事件
        nodes.call(drag)

        //平移缩放式事件----------------------
        function zoomed(event) {
            var transform = event.transform;
            nodes_g.attr("transform", transform);
            links_g.attr("transform", transform);
            // graph_init_tree_node(graph);
        }
        //点击触发的事件----------------------
        function click(event, d) {
            var current_node_index = d.nodes_id;
            add_tree_element(graph, current_node_index)
        }
        //结点鼠标悬停事件---------------------
        function node_mouseover(event, d) {
            focused = d3.select(this).attr("stroke", "rgb(43, 255, 0)");
            console.log("当前的焦点focused:", focused.datum())
        }
        //结点鼠标移除事件---------------------
        function node_mouseout(event, d) {
            d3.select(this).attr("stroke", "rgb(211, 211, 211)");
            focused = null; //重置聚焦为空
        }
        //开始拖拽时候的操作-------------------
        function dragStart() {
            d3.select(this).attr("stroke", "rgb(43, 255, 0)");
        }
        //拖拽时候的操作-----------------------
        function dragging(event, d) {
            d3.select(this).attr("cx", d.x = event.x).attr("cy", d.y = event.y);
            var current_node_index = d.nodes_id;
            d.nodes_position.cx = event.x;
            d.nodes_position.cy = event.y;
            change_line(current_node_index, event.x, event.y) //修改链接到此点的线
        }
        //拖拽结束时的操作---------------------
        function dragEnded() {
            // d3.select(this).attr("stroke", null);
        }
        //拖拽结点时候修改线条-----------------
        function change_line(index, cx, cy) {
            /*
                index: 表示当前结点的编号
            */
            for (let i = 0; i < graph.links.length; i++) {
                //起点和终点都要修改
                //先修改起点
                links.filter(d => d.source == index)
                    .attr("x1", cx)
                    .attr("y1", cy)
                //再修改终点
                links.filter(d => d.target == index)
                    .attr("x2", cx)
                    .attr("y2", cy)


            }
        }
    }
    //启动页面--------------------------------------------------------
    function start_index() {

        graph = {
            /*
                图的数据,不同类型的数据是通过下标进行关联的
            */
            //结点
            nodes: [
                {
                    //结点的唯一编号
                    nodes_id: global_nodes_id++,
                    //该结点的父亲结点编号
                    nodes_father_id: 0, //初始结点默认父亲为自己
                    //结点位置
                    nodes_position: { cx: width / 2, cy: height / 2 },
                    //结点的子结点个数,用来帮助确定分支的方向
                    nodes_children_number: 0,
                    //鲁棒性数值
                    robustness_value: 0.3,
                    //当前结点的图像类别
                    nodes_class: 0,
                    //当前结点的置信度
                    nodes_confidence: 0.5
                }
            ],

            //连线
            links: [
                // { links_id:0, source: 0, target: 1, robustness_differences: 0.1 },
            ],

        };
        graph = graph_init_tree_node(graph)
    }
</script>

</html>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值