Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
981 views
in Technique[技术] by (71.8m points)

d3.js - D3 bar chart where axis labels are hyperlinks from data

I am trying to get my y-axis links on a horizontal bar chart to be hyperlinks based on a value in the data. and I can't get it to work, and I don't find an example where it has been done.

Any examples or suggestions would be appreciated.

My code is:

function dohorizontalbarchartD3(data, elementid, topic, topicscore, width, height, margin)
{
        var y = d3.scaleBand().range([height, 0]).padding(0.1);
        var x = d3.scaleLinear().range([0, width]);
        var svg = d3.select(elementid).append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("fill", "steelblue")
                .attr("transform",
                            "translate(" + margin.left + "," + margin.top + ")");

            // Scale the range of the data in the domains
            x.domain([0, d3.max(data, function(d){ return d[topicscore]; })])
            y.domain(data.map(function(d) { return d[topic]; }));

            // append the rectangles for the bar chart
            var bars = svg.selectAll(".bar")
                    .data(data)
                .enter().append("rect")
                    .attr("class", "bar")
                    .attr("width", function(d) {return x(d[topicscore]); } )
                    .attr("y", function(d) { return y(d[topic]); })
                    .attr("height", y.bandwidth())

            // add the x Axis
            svg.append("g")
                    .attr("transform", "translate(0," + height + ")")
                    .call(d3.axisBottom(x));

            // add the y Axis
            svg.append("g")
                    .call(d3.axisLeft(y))
                    .style("cursor", "pointer")
                    .on("click", function(d) {window.location.href = "signal.html?id=" + d["id"];}); 
}

var data = [{topic: "disaster management", weight: 5.044282881692003, "id": 10}, {topic: "analytics", weight: 5.111022997620935, "id": 11}, {topic: "systems management", weight: 5.255783212161269, "id": 12}, {topic: "human resources", weight: 5.420123698898777, "id": 13}, {topic: "machine learning", weight: 6.357388316151202, "id": 14}, {topic: "automation", weight: 6.579434311393074, "id": 15}, {topic: "health and safety", weight: 7.607482274852919, "id": 16}, {topic: "cost control", weight: 7.876784769962982, "id": 17}, {topic: "climate change", weight: 8.24345757335448, "id": 18}, {topic: "inventory management", weight: 8.511369645690111, "id": 19}, {topic: "transformation", weight: 8.650363516192993, "id": 20}, {topic: "supply chain", weight: 8.916204070843246, "id": 21}, {topic: "water treatment", weight: 9.996866186148543, "id": 22}];

var margin = {top: 10, right: 20, bottom: 30, left: 200};
dohorizontalbarchartD3(data, "#scoutblock1", "topic", "weight", 300, 500, margin);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">

<body>
<div id="scoutblock1"></div>
</body>
</html>
question from:https://stackoverflow.com/questions/65855742/d3-bar-chart-where-axis-labels-are-hyperlinks-from-data

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You are almost there!

Let's look at the code lines where you try to attach the links:

            // add the y Axis
        svg.append("g")
                .call(d3.axisLeft(y))
                .style("cursor", "pointer")
                .on("click", function(d) {window.location.href = "signal.html?id=" + d["id"];}); 

With selection.call(d3.axisLeft(y)) d3 is adding an axis which is first of all a path element for the axis line and so called ticks for each label with a tiny orthogonal line at the respective position. Like many times, the element inspector is your friend, I encourage you to open it up in your browser and look at the resulting elements in your DOM.

enter image description here

That's all working as expected, however, the very selection you are currently refering to, has no data attached to it. Again, let's look with the inspector into the properties and you see no __data__ property:

enter image description here

That means, your reference to data, in the .on("click", function(d)...) callback function will always have undefined as the value of d.

What d3 does is it has created ticks as the children nodes and there data is attached to each tick, respectively. So one thing you have to do is call the .on("click", function(d)...) for each tick.

Here is one way to do it:

// add the y Axis
        svg.append("g")
            .attr("class", "y axis") // assign a class, so we can reference it below
            .call(d3.axisLeft(y));

        svg.select(".y.axis") // select the g element with the y axis
           .selectAll(".tick") // select all ticks of the axis and do for each:
              .style("cursor", "pointer")
              .on("click", function(d) { 
                 // your callback 
               });

Now we have the logic in place, we want to call the hyperlink. The data which is attached to each tick is based on what we provided to the d3.axisLeft() and that is y. Given our choice of they function, each tick contains the topic string of each data element. Let's look again with the element inspector to confirm:

enter image description here

That means as of now, we cannot access the id property in your callback function because the data of each tick just contains the topic string of the data.

We notice to have 2 separate requirements:

  • On the one hand, the topic string is what we want the axis to display as a label.
  • on the other hand we want to reference the id property to call the hyperlink.

There are many ways to do it, to keep the latter requirement in the callback function, we can do:

            svg.select(".y.axis")
               .selectAll(".tick")
                 .style("cursor", "pointer")
                 .on("click", function(d) {
                    var id = data.find(function(ele) { return ele.topic === d;}).id;
                    window.location.href = "signal.html?id=" + id;
                 });

So recall, that data d in each tick element refer solely to the corresponding topic string. So we can loop through the original data to find the first object which contains the topic d. Of that object, we want to have the id property.

Here is the complete code (I changed your html code a bit to match common practice):

        var data = [{topic: "disaster management", weight: 5.044282881692003, "id": 10}, {topic: "analytics", weight: 5.111022997620935, "id": 11}, {topic: "systems management", weight: 5.255783212161269, "id": 12}, {topic: "human resources", weight: 5.420123698898777, "id": 13}, {topic: "machine learning", weight: 6.357388316151202, "id": 14}, {topic: "automation", weight: 6.579434311393074, "id": 15}, {topic: "health and safety", weight: 7.607482274852919, "id": 16}, {topic: "cost control", weight: 7.876784769962982, "id": 17}, {topic: "climate change", weight: 8.24345757335448, "id": 18}, {topic: "inventory management", weight: 8.511369645690111, "id": 19}, {topic: "transformation", weight: 8.650363516192993, "id": 20}, {topic: "supply chain", weight: 8.916204070843246, "id": 21}, {topic: "water treatment", weight: 9.996866186148543, "id": 22}];

        var margin = {top: 10, right: 20, bottom: 30, left: 200};
        dohorizontalbarchartD3(data, "#scoutblock1", "topic", "weight", 300, 500, margin);

        function dohorizontalbarchartD3(data, elementid, topic, topicscore, width, height, margin) {
            var y = d3.scaleBand().range([height, 0]).padding(0.1);
            var x = d3.scaleLinear().range([0, width]);
            var svg = d3.select(elementid).append("svg")
                    .attr("width", width + margin.left + margin.right)
                    .attr("height", height + margin.top + margin.bottom)
                    .append("g")
                    .attr("fill", "steelblue")
                    .attr("transform",
                                "translate(" + margin.left + "," + margin.top + ")");

            // Scale the range of the data in the domains
            x.domain([0, d3.max(data, function(d){ return d[topicscore]; })])
            y.domain(data.map(function(d) { return d[topic]; }));

            // append the rectangles for the bar chart
            var bars = svg.selectAll(".bar")
                    .data(data)
                .enter().append("rect")
                    .attr("class", "bar")
                    .attr("width", function(d) {return x(d[topicscore]); } )
                    .attr("y", function(d) { return y(d[topic]); })
                    .attr("height", y.bandwidth())

            // add the x Axis
            svg.append("g")
                .attr("transform", "translate(0," + height + ")")
                .call(d3.axisBottom(x));

            // add the y Axis
            svg.append("g")
                .attr("class", "y axis")
                .call(d3.axisLeft(y));

            svg.select(".y.axis")
               .selectAll(".tick")
                 .style("cursor", "pointer")
                 .on("click", function(d) {
                    var id = data.find(function(ele) { return ele.topic === d;}).id;
                    window.location.href = "signal.html?id=" + id;
                  });
        }
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <!--link rel="stylesheet" href="style.css"-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
  </head>
  <body>
    <div id="scoutblock1"></div>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...