D3 Circular Heat Chart increases the height of the segment on hover

I created a pie chart of acoustic data, each layer of the circle is on the same day, and the 24 parts of the circle represent 24 hours a day. I want to implement something to increase the segment height of all segments of the same date as the segment that is being shaded and decreases the height of all other segments accordingly to keep the arc radius the same. Currently I can only select all segments from the same date, but I am having a hard time figuring out how to manipulate the heights. Can anyone point me in the right direction?

Here's the image that you will hover over the segment right now: enter image description here

Here is my code:

    var radial_labels = ['2016-10-22', '2016-10-23', '2016-10-24', '2016-10-25', '2016-10-26', '2016-10-27', '2016-10-28', '2016-10-29', '2016-10-30'];

    var segment_labels = ['0:00', '1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00', '8:00', '9:00', '10:00', '11:00','12:00','13:00','14:00','15:00','16:00','17:00','18:00','19:00','20:00','21:00','22:00','23:00'];

    loadCircularHeatMap(data,"#chart",radial_labels, segment_labels);


    function loadCircularHeatMap (dataset, dom_element_to_append_to,radial_labels,segment_labels) {

    var margin = {top: 50, right: 50, bottom: 50, left: 50};
    var width = 1000 - margin.left - margin.right;

    var height = width;
    var innerRadius = 100;// width/14;

    var segmentHeight = (width - margin.top - margin.bottom - 2*innerRadius )/(2*radial_labels.length);

    var chart = circularHeatChart()
    .innerRadius(innerRadius)
    .segmentHeight(segmentHeight)
    .domain([0,0.5,1])
    .range(["#ffffd9", "#7fcdbb" ,"#225ea8"])
    .radialLabels(radial_labels)
    .segmentLabels(segment_labels);

    chart.accessor(function(d) {return d.Average;})

    var svg = d3.select(dom_element_to_append_to)
    .selectAll('svg')
    .data([dataset])
    .enter()
    .append('svg')
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append('g')
    .attr("transform",
        "translate(" + ( (width )/2 - (radial_labels.length*segmentHeight + innerRadius)  ) + "," + margin.top + ")")
    .call(chart);




    var tooltip = d3.select(dom_element_to_append_to)
    .append('div')
    .attr('class', 'tooltip');

    tooltip.append('div')
    .attr('class', 'time');
    tooltip.append('div')
    .attr('class', 'average');
    tooltip.append('div')
    .attr('class', 'day');

    svg.selectAll("path")
    .on('mouseover', function(d) {
        console.log(d.Day);
        // increase the segment height of the one being hovered as well as all others of the same date
        // while decreasing the height of all others accordingly

        d3.selectAll("path.segment-"+d.Day).style("opacity", function (p) {return 0.6});

        tooltip.select('.time').html("<b> Time: " + d.Time + "</b>");
        tooltip.select('.day').html("<b> Date: " + d.Day + "</b>");
        tooltip.select('.average').html("<b> Value: " + d.Average + "</b>");
        tooltip.style('display', 'block');
        tooltip.style('opacity',2);
    })
    .on('mousemove', function(d) {
        tooltip.style('top', (d3.event.layerY + 10) + 'px')
        .style('left', (d3.event.layerX - 25) + 'px');
    })
    .on('mouseout', function(d) {
        tooltip.style('display', 'none');
        tooltip.style('opacity',0);
       //  var time = d.Time;
       //  var timeCleaned = time.split(":").join("-");
       //  var segment = d3.select("#segment-"+d.Day +"-"+timeCleaned); //designate selector variable for brevity
       //  var fillcolor = segment.select("desc").text();  //access original color from desc
       //  segment.style("fill", fillcolor);

        d3.selectAll("path.segment-"+d.Day).style("opacity", function (p) {return 1});
    })
    .append("desc") //append the current color as a desc element
    .text(function(d){ 
            var color = d3.scale.linear().domain([0,0.5,1]).range(["#ffffd9", "#7fcdbb" ,"#225ea8"]);
            // how to access a function within reusable charts
            console.log(color(d.Average));
            return color(d.Average);
        });
    }

function circularHeatChart() {
    var margin = {top: 20, right: 50, bottom: 50, left: 20},
    innerRadius = 20,
    numSegments = 24,
    segmentHeight = 20,
    domain = null,
    range = ["white", "red"],
    accessor = function(d) {return d;},
    radialLabels = segmentLabels = [];

    function chart(selection) {
        selection.each(function(data) {
            var svg = d3.select(this);

            var offset = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight;
            g = svg.append("g")
                .classed("circular-heat", true)
                .attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")");

            var autoDomain = false;
            if (domain === null) {
                domain = d3.extent(data, accessor);
                autoDomain = true;
            }
            var color = d3.scale.linear().domain(domain).range(range);
            if(autoDomain)
                domain = null;

            g.selectAll("path").data(data)
                .enter().append("path")
                // .attr("class","segment")
                .attr("class",function(d){return "segment-"+d.Day})
                .attr("id",function(d){
                     var time = d.Time;
                     var timeCleaned = time.split(":").join("-");
                     return "segment-"+d.Day +"-"+timeCleaned;})
                .attr("d", d3.svg.arc().innerRadius(ir).outerRadius(or).startAngle(sa).endAngle(ea))
                .attr("stroke", function(d) {return '#252525';})
                .attr("fill", function(d) {return color(accessor(d));});

            // Unique id so that the text path defs are unique - is there a better way to do this?
            var id = d3.selectAll(".circular-heat")[0].length;


            //Segment labels
            var segmentLabelOffset = 5;
            var r = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight + segmentLabelOffset;
            labels = svg.append("g")
                .classed("labels", true)
                .classed("segment", true)
                .attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")");

            labels.append("def")
                .append("path")
                .attr("id", "segment-label-path-"+id)
                .attr("d", "m0 -" + r + " a" + r + " " + r + " 0 1 1 -1 0");

            labels.selectAll("text")
                .data(segmentLabels).enter()
                .append("text")
                .append("textPath")
                .attr("xlink:href", "#segment-label-path-"+id)
                .style("font-size", "12px")
                .attr("startOffset", function(d, i) {return i * 100 / numSegments + 1.5+ "%";})
                .text(function(d) {return d;});
        });

    }

    /* Arc functions */
    ir = function(d, i) {
        return innerRadius + Math.floor(i/numSegments) * segmentHeight;
    }
    or = function(d, i) {
        return innerRadius + segmentHeight + Math.floor(i/numSegments) * segmentHeight;
    }
    sa = function(d, i) {
        return (i * 2 * Math.PI) / numSegments;
    }
    ea = function(d, i) {
        return ((i + 1) * 2 * Math.PI) / numSegments;
    }

    /* Configuration getters/setters */
    chart.margin = function(_) {
        if (!arguments.length) return margin;
        margin = _;
        return chart;
    };

    chart.innerRadius = function(_) {
        if (!arguments.length) return innerRadius;
        innerRadius = _;
        return chart;
    };

    chart.numSegments = function(_) {
        if (!arguments.length) return numSegments;
        numSegments = _;
        return chart;
    };

    chart.segmentHeight = function(_) {
        if (!arguments.length) return segmentHeight;
        segmentHeight = _;
        return chart;
    };

    chart.domain = function(_) {
        if (!arguments.length) return domain;
        domain = _;
        return chart;
    };

    chart.range = function(_) {
        if (!arguments.length) return range;
        range = _;
        return chart;
    };

    chart.radialLabels = function(_) {
        if (!arguments.length) return radialLabels;
        if (_ == null) _ = [];
        radialLabels = _;
        return chart;
    };

    chart.segmentLabels = function(_) {
        if (!arguments.length) return segmentLabels;
        if (_ == null) _ = [];
        segmentLabels = _;
        return chart;
    };

    chart.accessor = function(_) {
        if (!arguments.length) return accessor;
        accessor = _;
        return chart;
    };

    return chart;
}

      

And here's a demo of what currently looks like: http://jhjanicki.github.io/circular_heat_acoustic

+3


source to share


2 answers


My demo

The kernel code snippet looks like this:



var targetIndex=Math.floor(i/numSegments);//the layer you are hovering
var zoomSize=10;//inner 10px and outer 10px
var layerCnt=data.length/numSegments;


d3.selectAll("path.segment")//.arc indicates segment
    .transition().duration(200)//transtion effect
    .attr("d", d3.svg.arc()//set d again
        .innerRadius(ir)
        .outerRadius(or)
        .startAngle(sa)
        .endAngle(ea))


function getRadius(floor) {
    if(floor===0){//inner radius doesn't change
        return innerRadius;
    }
    if(floor===layerCnt){//outer radius doesn't change
        return innerRadius+layerCnt*segmentHeight;
    } 
    if(floor<=targetIndex){//it math
        return innerRadius + floor * segmentHeight - zoomSize *(floor/targetIndex);    
    }else{//math again
        return innerRadius + floor * segmentHeight + zoomSize*((layerCnt-floor)/(layerCnt-targetIndex));  
    }                    
}

function ir(d, i) {                    
    return getRadius(Math.floor(i / numSegments));
}

function or(d, i) {
    return getRadius(Math.floor(i / numSegments) + 1);
}

      

+2


source


Since this is a path, you cannot increase its height. But you can scale the path.

My algorithm

  • Get the center of gravity of a segment on the mouse.
  • Scale the mouse path to 1.5 wrt path centroid.
  • Move the selected path up
  • Scale mouse path to 1

Fragment for obtaining the centroid:

function getBoundingBoxCenter (selection) {
  // get the DOM element from a D3 selection
  // you could also use "this" inside .each()
  var element = selection.node();
  // use the native SVG interface to get the bounding box
  var bbox = element.getBBox();
  // return the center of the bounding box
  return [bbox.x + bbox.width/2, bbox.y + bbox.height/2];
}

      

Slice to move the selected item up

d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

      



On the mouse above you:

        var sel = d3.select(this);
    sel.moveToFront()

        var centroid = getBoundingBoxCenter(d3.select(this));
        //zoom in to the centroid
        d3.select(this).attr("transform", function(p) {
                return "translate(" + centroid[0] + "," + centroid[1] + ")"
                        + "scale(" + 1.5 + ")"
                        + "translate(" + -centroid[0] + "," + -centroid[1] + ")";
  });

      

On the mouse output, run:

        var centroid = getBoundingBoxCenter(d3.select(this));
        d3.select(this).attr("transform", function(p) {
                return "translate(" + centroid[0] + "," + centroid[1] + ")"
                        + "scale(" + 1 + ")"
                        + "translate(" + -centroid[0] + "," + -centroid[1] + ")";
  });

      

Special thanks to @Gerardo for putting this on the violin :)

working code here

+2


source







All Articles