D3.js按组分层边绑定着色

cetgtptt  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(124)

我正在尝试在我的层次边缘绑定可视化中基于它们连接到的组来给连接着色。

下面是我当前的mouseover函数:

function mouseover(d) {
        svg.selectAll("path.link.target-" + d.key)
            .classed("target", true)
            .each(updateNodes("source", true));

        svg.selectAll("path.link.source-" + d.key)
            .classed("source", true)
            .each(updateNodes("target", true));
    }

下面是我发布的示例中的mouseover函数:

function mouseovered(d) 
{
        // Handle tooltip
        // Tooltips should avoid crossing into the center circle

        d3.selectAll("#tooltip").remove();
        d3.selectAll("#vis")
            .append("xhtml:div")
            .attr("id", "tooltip")
            .style("opacity", 0)
            .html(d.title);
        var mouseloc = d3.mouse(d3.select("#vis")[0][0]),
            my = ((rotateit(d.x) > 90) && (rotateit(d.x) < 270)) ? mouseloc[1] + 10 : mouseloc[1] - 35,
            mx = (rotateit(d.x) < 180) ? (mouseloc[0] + 10) :  Math.max(130, (mouseloc[0] - 10 - document.getElementById("tooltip").offsetWidth));
        d3.selectAll("#tooltip").style({"top" : my + "px", "left": mx + "px"});
        d3.selectAll("#tooltip")
            .transition()
            .duration(500)
            .style("opacity", 1);
        node.each(function(n) { n.target = n.source = false; });

        currnode = d3.select(this)[0][0].__data__;

        link.classed("link--target", function(l) { 
                if (l.target === d) 
                { 
                    return l.source.source = true; 
                }
                if (l.source === d) 
                { 
                    return l.target.target = true; 
                }
            })
            .filter(function(l) { return l.target === d || l.source === d; })
            .attr("stroke", function(d){
                if (d[0].name == currnode.name)
                {
                    return color(d[2].cat);
                }
                return color(d[0].cat);
            })
            .each(function() { this.parentNode.appendChild(this); });

        d3.selectAll(".link--clicked").each(function() { this.parentNode.appendChild(this); });

        node.classed("node--target", function(n) { 
                return (n.target || n.source); 
            });
}

我对D3有点陌生,但我假设我需要做的是根据密钥检查组,然后将其与该组的颜色匹配。
我的完整代码在这里:

<script type="text/javascript">
    color = d3.scale.category10(); 

    var w = 840,
        h = 800,
        rx = w / 2,
        ry = h / 2,
        m0,
        rotate = 0
    pi = Math.PI;

    var splines = [];

    var cluster = d3.layout.cluster()
        .size([360, ry - 180])
        .sort(function(a, b) {
            return d3.ascending(a.key, b.key);
        });

    var bundle = d3.layout.bundle();

    var line = d3.svg.line.radial()
        .interpolate("bundle")
        .tension(.5)
        .radius(function(d) {
            return d.y;
        })
        .angle(function(d) {
            return d.x / 180 * Math.PI;
        });

    // Chrome 15 bug: <http://code.google.com/p/chromium/issues/detail?id=98951>
    var div = d3.select("#bundle")
        .style("width", w + "px")
        .style("height", w + "px")
        .style("position", "absolute");

    var svg = div.append("svg:svg")
        .attr("width", w)
        .attr("height", w)
        .append("svg:g")
        .attr("transform", "translate(" + rx + "," + ry + ")");

    svg.append("svg:path")
        .attr("class", "arc")
        .attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
        .on("mousedown", mousedown);

    d3.json("TASKS AND PHASES.json", function(classes) {

        var nodes = cluster.nodes(packages.root(classes)),
            links = packages.imports(nodes),
            splines = bundle(links);

        var path = svg.selectAll("path.link")
            .data(links)
            .enter().append("svg:path")
            .attr("class", function(d) {
                return "link source-" + d.source.key + " target-" + d.target.key;
            })
            .attr("d", function(d, i) {
                return line(splines[i]);
            });

        var groupData = svg.selectAll("g.group")
            .data(nodes.filter(function(d) {
                return (d.key == 'Department' || d.key == 'Software' || d.key == 'Tasks' || d.key == 'Phases') && d.children;
            }))
            .enter().append("group")
            .attr("class", "group");

        var groupArc = d3.svg.arc()
            .innerRadius(ry - 177)
            .outerRadius(ry - 157)
            .startAngle(function(d) {
                return (findStartAngle(d.__data__.children) - 2) * pi / 180;
            })
            .endAngle(function(d) {
                return (findEndAngle(d.__data__.children) + 2) * pi / 180
            });        

        svg.selectAll("g.arc")
            .data(groupData[0])
            .enter().append("svg:path")
            .attr("d", groupArc)
            .attr("class", "groupArc")
            .attr("id", function(d, i) {console.log(d.__data__.key); return d.__data__.key;})
            .style("fill", function(d, i) {return color(i);})
            .style("fill-opacity", 0.5)
            .each(function(d,i) {

                var firstArcSection = /(^.+?)L/;

                var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];

                newArc = newArc.replace(/,/g , " ");

                svg.append("path")
                    .attr("class", "hiddenArcs")
                    .attr("id", "hidden"+d.__data__.key)
                    .attr("d", newArc)
                    .style("fill", "none");
            });


        svg.selectAll(".arcText")
            .data(groupData[0])
            .enter().append("text")
            .attr("class", "arcText")
            .attr("dy", 15)
            .append("textPath")
            .attr("startOffset","50%")
            .style("text-anchor","middle")
            .attr("xlink:href",function(d,i){return "#hidden" + d.__data__.key;})
            .text(function(d){return d.__data__.key;});    

        svg.selectAll("g.node")
            .data(nodes.filter(function(n) {
                return !n.children;
            }))
            .enter().append("svg:g")
            .attr("class", "node")
            .attr("id", function(d) {
                return "node-" + d.key;
            })
            .attr("transform", function(d) {
                return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
            })
            .append("svg:text")
            .attr("dx", function(d) {
                return d.x < 180 ? 25 : -25;
            })
            .attr("dy", ".31em")
            .attr("text-anchor", function(d) {
                return d.x < 180 ? "start" : "end";
            })
            .attr("transform", function(d) {
                return d.x < 180 ? null : "rotate(180)";
            })
            .text(function(d) {
                return d.key.replace(/_/g, ' ');
            })
            .on("mouseover", mouseover)
            .on("mouseout", mouseout);

        d3.select("input[type=range]").on("change", function() {
            line.tension(this.value / 100);
            path.attr("d", function(d, i) {
                return line(splines[i]);
            });
        });
    });

    d3.select(window)
        .on("mousemove", mousemove)
        .on("mouseup", mouseup);

    function mouse(e) {
        return [e.pageX - rx, e.pageY - ry];
    }

    function mousedown() {
        m0 = mouse(d3.event);
        d3.event.preventDefault();
    }

    function mousemove() {
        if (m0) {
            var m1 = mouse(d3.event),
                dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
            div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
        }
    }

    function mouseup() {
        if (m0) {
            var m1 = mouse(d3.event),
                dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;

            rotate += dm;
            if (rotate > 360) rotate -= 360;
            else if (rotate < 0) rotate += 360;
            m0 = null;

            div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");

            svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
                .selectAll("g.node text")
                .attr("dx", function(d) {
                    return (d.x + rotate) % 360 < 180 ? 25 : -25;
                })
                .attr("text-anchor", function(d) {
                    return (d.x + rotate) % 360 < 180 ? "start" : "end";
                })
                .attr("transform", function(d) {
                    return (d.x + rotate) % 360 < 180 ? null : "rotate(180)";
                });
        }
    }

    function mouseover(d) {
        svg.selectAll("path.link.target-" + d.key)
            .classed("target", true)
            .each(updateNodes("source", true));

        svg.selectAll("path.link.source-" + d.key)
            .classed("source", true)
            .each(updateNodes("target", true));
    }

    function mouseout(d) {
        svg.selectAll("path.link.source-" + d.key)
            .classed("source", false)
            .each(updateNodes("target", false));

        svg.selectAll("path.link.target-" + d.key)
            .classed("target", false)
            .each(updateNodes("source", false));
    }

    function updateNodes(name, value) {
        return function(d) {
            if (value) this.parentNode.appendChild(this);
            svg.select("#node-" + d[name].key).classed(name, value);
        };
    }

    function cross(a, b) {
        return a[0] * b[1] - a[1] * b[0];
    }

    function dot(a, b) {
        return a[0] * b[0] + a[1] * b[1];
    }

    function findStartAngle(children) {
        var min = children[0].x;
        children.forEach(function(d) {
            if (d.x < min)
                min = d.x;
        });
        return min;
    }

    function findEndAngle(children) {
        var max = children[0].x;
        children.forEach(function(d) {
            if (d.x > max)
                max = d.x;
        });
        return max;
    }
</script>
kqhtkvqz

kqhtkvqz1#

下面是D3 v6中的一个示例解决方案,它采用了Observable example以及我对另一个问题的回答。基本要点:

  • 你需要在输入数据中添加“group”--对于你的数据mention in the comments,我已经定义了group作为名称的第二个元素(每个点分隔)。Observable中的hierarchy函数似乎可以去除这个元素。
  • 幸运的是,所有的name值都是root.parent.child--这使得leafGroup非常适合您的数据(但可能不适合不对称的层次结构)。
  • 定义一个颜色范围,例如const colors = d3.scaleOrdinal().domain(leafGroups.map(d => d[0])).range(d3.schemeTableau10);,可用于弧线、标签文本(node s)、路径(link s)
  • 我避免在示例中使用mix-blend-mode样式,因为它看起来不太好。
  • 我在overedouted中应用了样式-请参见下面的逻辑。

有关mouseover上的样式逻辑,请参见overed中的注解:

function overed(event, d) {

  //link.style("mix-blend-mode", null);

  d3.select(this)
    // set dark/ bold on hovered node 
    .style("fill", colordark) 
    .attr("font-weight", "bold"); 

  d3.selectAll(d.incoming.map(d => d.path))
    // each link has data with source and target so you can get group 
    // and therefore group color; 0 for incoming and 1 for outgoing
    .attr("stroke", d => colors(d[0].data.group)) 
    // increase stroke width for emphasis
    .attr("stroke-width", 4)
    .raise();

  d3.selectAll(d.outgoing.map(d => d.path))
    // each link has data with source and target so you can get group 
    // and therefore group color; 0 for incoming and 1 for outgoing
    .attr("stroke", d => colors(d[1].data.group))
    // increase stroke width for emphasis
    .attr("stroke-width", 4)
    .raise()

  d3.selectAll(d.incoming.map(([d]) => d.text))
    // source and target nodes to go dark and bold
    .style("fill", colordark) 
    .attr("font-weight", "bold");    

  d3.selectAll(d.outgoing.map(([, d]) => d.text))
    // source and target nodes to go dark and bold
    .style("fill", colordark) 
    .attr("font-weight", "bold");    
}

有关mouseout上的样式逻辑,请参见outed中的注解:

function outed(event, d) {

  //link.style("mix-blend-mode", "multiply");

  d3.select(this)
    // hovered node to revert to group colour on mouseout
    .style("fill", d => colors(d.data.group))
    .attr("font-weight", null); 

  d3.selectAll(d.incoming.map(d => d.path))
    // incoming links to revert to 'colornone' and width 1 on mouseout
    .attr("stroke", colornone)
    .attr("stroke-width", 1);

  d3.selectAll(d.outgoing.map(d => d.path))
    // incoming links to revert to 'colornone' and width 1 on mouseout
    .attr("stroke", colornone)
    .attr("stroke-width", 1);

  d3.selectAll(d.incoming.map(([d]) => d.text))
    // incoming nodes to revert to group colour on mouseout
    .style("fill", d => colors(d.data.group))
    .attr("font-weight", null);    

  d3.selectAll(d.outgoing.map(([, d]) => d.text))
    // incoming nodes to revert to group colour on mouseout
    .style("fill", d => colors(d.data.group))
    .attr("font-weight", null);    
}

使用您在注解中提到的数据的工作示例:
第一个

相关问题