【问题标题】:D3 Circular Heat Chart increase segment height on mouseoverD3 圆形热图在鼠标悬停时增加段高度
【发布时间】:2017-04-10 02:21:14
【问题描述】:

我创建了一个声学数据的圆形热图,一个圆圈的每一层都是同一天,圆圈有 24 个部分代表一天中的 24 小时。我想实现一些东西,以便我可以增加与鼠标悬停的段相同日期的所有段的高度,同时相应地降低所有其他段的高度以保持圆弧半径相同。目前我只能选择同一日期的所有细分,但我无法弄清楚如何操纵高度。有人能指出我正确的方向吗?

这是您现在将鼠标悬停在某个片段上的图像:

这是我的代码:

    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;
}

下面是它当前的演示: http://jhjanicki.github.io/circular_heat_acoustic

【问题讨论】:

标签: d3.js charts


【解决方案1】:

My demo

核心代码sn-p如下:

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's 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);
}

【讨论】:

  • 非常感谢,这正是我想要的!
  • 我又读了一遍代码,你能解释一下为什么必须把它放在一个绘图函数中并使用 setTimeout 吗?
  • @jhjanicki 我把草稿留在那里...我更新了答案以尽量减少更改。
  • 谢谢。你能解释一下 getRadius 函数吗?
  • @jhjanicki 这是纯数学,很难解释 :(
【解决方案2】:

因为这是一条路径,所以不能增加它的高度。但是你可以缩放路径。

我的算法是

  • 鼠标悬停时获取线段的质心。
  • 在鼠标悬停时将路径缩放到路径质心的 1.5 w.r.t。
  • 将所选路径移至顶部
  • 将鼠标路径缩小到 1

获取质心的片段:

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];
}

将所选元素移到顶部的片段

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

鼠标悬停在你身上:

        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] + ")";
  });

鼠标移开:

        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] + ")";
  });

特别感谢 @Gerardo 把它放在小提琴上 :)

工作代码here

【讨论】:

  • 感谢您的解释,我现在更了解路径是如何工作的。扩展您的代码是否非常简单,因此同一层中的所有段都被放大而不是一个段,而其余的层(那些与悬停的日期不同的层)被缩小?
  • 哦,缩放整行的问题是段会相互重叠......看起来不太好,但是你可以试一试。
  • 好吧,我还是试一试吧。所以据我了解,也许我想要完成的事情很难通过路径?
  • 不,您可以缩放路径以增加补丁大小...这并不困难
  • 如果你想缩放所有路径,那么它会看起来像这样笨拙jsfiddle.net/m7gqvjxk/1
猜你喜欢
  • 1970-01-01
  • 2013-11-28
  • 2018-10-15
  • 1970-01-01
  • 1970-01-01
  • 2021-12-20
  • 2015-04-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多