【问题标题】:A better way for a stacked area chart toggle between a percent scale and absolute scale?堆叠面积图在百分比比例和绝对比例之间切换的更好方法?
【发布时间】:2013-06-27 17:37:08
【问题描述】:

我是 javascript 和 D3 的新手。

我正在尝试创建一个堆积面积图,它可以在以绝对比例和百分比比例显示数据之间切换。我意识到这涉及到玩弄

d3.layout.stack().offset("零") 和 d3.layout.stack().offset("expand").

我已经成功地让图表做我想做的事:

http://jsfiddle.net/dSJ4E/

...但我并不为我的方法感到自豪,并且确信有更好的方法来做到这一点。有任何想法吗?有没有您可能知道的简单示例?

我对我的代码不满意,因为在设置 if/else 语句时,我只是重新声明了之前编写的所有内容,只更改了偏移量变量。这似乎是一种笨拙的方法。另外,我认为这不会让我设置过渡。

data = [{"type": "Group1",
             "values": [
                {"x":0, "y": 2.5},
                {"x":1, "y": 2.4},
                {"x":2, "y": 0.3}]},
            {"type": "Group2",
             "values": [
                {"x":0, "y": 1.5},
                {"x":1, "y": 1.3},
                {"x":2, "y": 1.1}]}
            ];
var stackZero = d3.layout.stack()
        .values(function(d){return d.values;})  
        .offset("zero");

stackZero(data);
var xScale = d3.scale.linear()
        .domain([0,2])
        .range([0, width]);

    var yScale = d3.scale.linear()
        .range([height,0])
        .domain([0, d3.max(data, function(d){return d3.max(d.values, function(d){return d.y0 + d.y;});})]);

    var area = d3.svg.area()
        .x(function(d){return xScale(d.x);})
        .y0(function(d){return yScale(d.y0);})
        .y1(function(d){return yScale(d.y0 + d.y);});       

    var svg = d3.selectAll("body")
        .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top +")");

    svg.selectAll(".layers")
        .data(data)
        .enter()
        .append("path")
        .attr("class", "layer")
        .attr("d", function(d){return area(d.values);})
        .style("fill", function (d,i){return colors(i)});

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("left");

    svg.append("g")
        .attr("class", "y axis")
        .attr("transform", "translate(0,0)")
        .call(yAxis);   

    d3.select("p") //now we start to interact with the chart
        .on("click", function() { 

            console.log("entering variable is " + stackType);

            svg.selectAll("path").data([]).exit().remove();
            svg.selectAll(".y.axis").data([]).exit().remove();
if(stackType){ //enter true, or expanded data

            var stackExpand = d3.layout.stack()
                .values(function(d){return d.values;})
                .offset("expand");

            stackExpand(data);

            console.log(data);

            var yScale = d3.scale.linear()
                .range([height,0])
                .domain([0, d3.max(data, function(d){return d3.max(d.values, function(d){return d.y0 + d.y;});})]);

            var area = d3.svg.area()
                .x(function(d){return xScale(d.x);})
                .y0(function(d){return yScale(d.y0);})
                .y1(function(d){return yScale(d.y0 + d.y);});

            svg.selectAll(".layers")
                //.data(stackZero(data))
                .data(stackExpand(data))
                .enter()
                .append("path")
                .attr("class", "layer")
                .attr("d", function(d){return area(d.values);})
                .style("fill", function (d,i){return colors(i)});

            formatter = d3.format(".0%");

            var yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left")
                .tickFormat(formatter);

            svg.append("g")
                .attr("class", "y axis")
                .attr("transform", "translate(0,0)")
                .call(yAxis);


            stackType = false;
            console.log("exiting variable is " + stackType);

            } else { //enter false, or zero data

            data = [{"type": "Group1",
             "values": [
                {"x":0, "y": 2.5},
                {"x":1, "y": 2.4},
                {"x":2, "y": 0.3}]},
            {"type": "Group2",
             "values": [
                {"x":0, "y": 1.5},
                {"x":1, "y": 1.3},
                {"x":2, "y": 1.1}]}
            ];

            var stackZero = d3.layout.stack()
                .values(function(d){return d.values;})
                .offset("zero");

            stackZero(data);

            console.log(data);

            var yScale = d3.scale.linear()
                .range([height,0])
                .domain([0, d3.max(data, function(d){return d3.max(d.values, function(d){return d.y0 + d.y;});})]);

            var area = d3.svg.area()
                .x(function(d){return xScale(d.x);})
                .y0(function(d){return yScale(d.y0);})
                .y1(function(d){return yScale(d.y0 + d.y);});

            svg.selectAll(".layers")
                .data(stackZero(data))
                //.data(stackExpand(data))
                .enter()
                .append("path")
                .attr("class", "layer")
                .attr("d", function(d){return area(d.values);})
                .style("fill", function (d,i){return colors(i)});   

            stackType = true;
            console.log("exiting variable is" + stackType);

            var yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left");

            svg.append("g")
                .attr("class", "y axis")
                .attr("transform", "translate(0,0)")
                .call(yAxis);

            };  

});
    </script>

【问题讨论】:

  • 你见过this吗?您可以按原样使用它,也可以查看源代码以获取灵感。
  • 是的,这些功能正是我感兴趣的。不幸的是,目前代码似乎有点超出我的能力。

标签: javascript d3.js stacked-area-chart


【解决方案1】:

因此,您可以做很多事情来避免在代码中重复自己。首先,您正在使用的许多 D3 函数都可以重用——它们是通用函数。这与 javascript 闭包的概念一起意味着您只需声明它们一次并初始化不会更改的部分。

堆栈布局确实会覆盖 y 和 y0 值,因此为了使切换器正常工作,我将您的初始数据重命名为 raw_x 和 raw_y 并适当地调整了访问器。

然后是找出 yScale 域并实际更新我包装到函数中的路径的工作:

function drawChart() {
    yScale.domain([0, d3.max(data, function (d) {
        return d3.max(d.values, function (d) {
            return d.y0 + d.y;
        });
    })]);

    // new data items need to get added
    var areas = svg.selectAll(".layer")
    .data(data, function(d) { return d.type; });

    areas.enter()
        .append("path")
        .attr("class", "layer")
        .style("fill", function (d, i) {
            return colors(i)
    });

    // Added and updated items need to be updated
    areas.attr("d", function (d) {
        return area(d.values);
    })

    // Old items need to be removed - we should not actually need this with the data as it is
    areas.exit().remove();

    svg.selectAll("g.y.axis").call(yAxis);
}

这实际上只是一个开始,您将希望根据自己的需要进行重组,但请查看updated fiddle

【讨论】:

  • 我注意到我可以通过删除 function(d) { return d.type; } 来缩短 var areas = svg.selectAll(".layer").data(data, function(d) { return d.type; }); 并且它仍然有效。你包含那部分是有原因的吗?
  • 这是否意味着在未来的应用程序中我应该避免为我的数据使用标签“x”和“y”?毕竟,由于 svg.stack.layout 已经使用了“x”和“y”(您提到堆栈会覆盖值),因此包含这些标签的数据似乎会增加不必要的混乱。
  • 很多布局都会设置 x 和 y 属性,所以我经常喜欢将原始数据分开并使用访问器。尤其是您经常需要多个部分的原始数据 - 例如标签或工具提示。像素位置值很少有意义。这也意味着您不必跟踪当前 x 或 y 值在切换布局时显示的内容 - 正如您所发现的那样。
  • 这个问题现在已经很老了,所以你可能已经发现function(d) { return d.type; } 位是一个关键函数,它允许 D3 将每条数据与一个唯一的键相关联,以提供对象恒定性。这允许 D3 以更有意义的方式为您的数据设置动画。详情见这里:bost.ocks.org/mike/constancy
猜你喜欢
  • 2020-03-19
  • 1970-01-01
  • 1970-01-01
  • 2020-03-09
  • 1970-01-01
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-31
相关资源
最近更新 更多