转换d3堆栈布局的不同状态

时间:2014-03-11 23:22:16

标签: d3.js

我无法在堆积条形图的不同状态之间转换(使用d3.layout.stack创建)。

我一直在分析The General Update Pattern III中的转换和连接以及较旧但很棒的Bar Chart Tutorial 2

从概念上讲,我陷入了困境。一个是需要转换两个数据集:堆栈的数量以及每个堆栈的。也就是说,一方面我可能有3个堆叠变成4(或2)。而另一方面,我可以让第二个堆栈的值从15变为18。

是否有任何堆叠条形图在不同数量的堆栈之间转换以及堆栈的不同值的示例?

如果我靠近,并附上代码,我会这样做。现在我还处于概念层面。我想知道d3.layout.stack是否无效,我将不得不像this block一样在d3中使用不同的“堆积条形图”路线。

更新

到目前为止,这是我的代码,穿插了我的问题。因为它有点长,我已经删除了所有我认为必要的东西。 FWIW,代码显示新数据,它只是不转换。

此外,我知道尝试更改UPDATE转换中的y值和height值并没有多大意义,因为数据没有那些因为我们'仍处于“类别”级别的数据,而不是“类别的价值”级别。我想我正在尝试解决问题并放入接缝(函数),我可以在其中添加控制台日志以添加断点。所有这一切都是为了尝试理解数据流。

drawChart: function(data) {
    var that = this,
        histogramContainer = d3.select(".histogram-container"),
        histogramContainerWidth = parseInt(histogramContainer.style('width'), 10),
        histogramContainerHeight = parseInt(histogramContainer.style('height'), 10),
        width = histogramContainerWidth,
        height = histogramContainerHeight,
        layers, yScale,
        then = new Date(), now;

    this.stack = d3.layout.stack()
        .values(function (d) { return d.values; })
        .x(function (dd) { return dd.timestamp; })
        .y(function (dd) { return dd.value; });

    layers = this.stack(data);

    yScale = d3.scale.linear()
        .domain([0, this.maxY * this.yHeader])
        .rangeRound([0, height * this.yHeightScalor]);

    var svg = histogramContainer.append("svg")
            .attr('class', 'chart')
            .attr('width', width)
            .attr('height', height)
            .call(zoom)
            .append('g')
            .attr('transform', 'translate(' + this.margin.left  + ' , ' +
                  (height - this.margin.bottom) + ')');

    var clipPath = svg.append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("x", 0)
            .attr("y", (-height + this.margin.bottom + this.margin.top))
            .attr("width", width)
            .attr("height", height - this.margin.bottom - this.margin.top);

    var chartBody = svg.append("g")
            .attr("clip-path", "url(#clip)");

    // Add a group for each layer
            // TRYING TO JOIN ON CATEGORY NAME
    var nameGroups = chartBody.selectAll("g.name")
            .data(layers, function (d) { 
                return d.name;
            });

    // UPDATE
    nameGroups.transition()
        .duration(750)
        .attr("y", function(d) { return height - yScale(d.value) - .5; })
        .attr("height", function(d) { return yScale(d.value); });

    // ENTER
    nameGroups.enter().append("svg:g")
        .attr("class", "nameGroup")
        .style("fill", function(d,i) {
            var color = (that.colors[d.name]) ?
                    that.colors[d.name].value :
                    Moonshadow.helpers.rw5(d.name);
            return "#" + color;
        })
        .transition()
        .duration(750)
        .attr("y", function(d) { return height - yScale(d.value) - .5; })
        .attr("height", function(d) { return yScale(d.value); });

    // EXIT
    nameGroups.exit().remove();

    var rect = nameGroups.selectAll("rect")
            .data(function (d) { return d.values; })
            .enter().append("svg:rect")
            .attr("class", "stackedBar")
            .attr("x", function (d) { return that.xScale(d.timestamp); })
            .attr("y", function (d) { return -yScale(d.y0) - yScale(d.y); })
            .attr("width", this.barWidth)
            .attr("height",function (d) { return +yScale(d.y); });

更新

以下是我的数据:

我将收到的JSON转换为d3.layout.stack期望的内容。即,一个对象数组,每个对象都有一个类别名称和一个values数组 - values数组中的对象是x值(时间戳)和y值(简单整数值)。例如:

data = [{ name: "Pete Peterson"
          values: [{ timestamp: 1394064493342,
                     value: 3 },
                   { timestamp: 1394064502343,
                     value: 5 },
                   ...]},
        { name: "Mary Jones"
          values: [{ timestamp: 1394064493342,
                     value: 0 },
                   { timestamp: 1394064502343,
                     value: 7 },
                   ...]},
        .....]

1 个答案:

答案 0 :(得分:2)

在仔细查看d3.layout.stack()代码后,我对如何处理使用不断变化的数据类别转换堆积条形图的诚实答案是我不会使用d3.layout.stack()。以预期的格式获取数据的数据操作量是一项非常有用的工作,因为它通过计算部分总数来节省。

但是 ,您说您已经获得了正确格式的数据,只需要从那里开始。使用您的代码,使用注释和更正:

drawChart: function(data) {

/* // All this should go in a separate initialization function, distinct
   // from the function for drawing and updating data
    var that = this,
        histogramContainer = d3.select(".histogram-container"),
        histogramContainerWidth = parseInt(histogramContainer.style('width'), 10),
        histogramContainerHeight = parseInt(histogramContainer.style('height'), 10),
        width = histogramContainerWidth,
        height = histogramContainerHeight,
        layers, yScale,
        then = new Date(), now;


    var svg = histogramContainer.append("svg")
            .attr('class', 'chart')
            .attr('width', width)
            .attr('height', height)
            .call(zoom)
            .append('g')
            .attr('transform', 'translate(' + this.margin.left  + ' , ' +
                  (height - this.margin.bottom) + ')');

    var clipPath = svg.append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("x", 0)
            .attr("y", (-height + this.margin.bottom + this.margin.top))
            .attr("width", width)
            .attr("height", height - this.margin.bottom - this.margin.top);

    var chartBody = svg.append("g")
            .attr("clip-path", "url(#clip)");

    this.stack = d3.layout.stack()
        .values(function (d) { return d.values; })
        .x(function (dd) { return dd.timestamp; })
        .y(function (dd) { return dd.value; });

*/

    layers = this.stack(data);

/*  //Make the scales chart properties, and set the range in your initialization function
    yScale = d3.scale.linear()
        .rangeRound([0, height * this.yHeightScalor]);
*/  
    this.yScale.domain([0, this.maxY * this.yHeader])


    // Add a group for each layer, using a key function to match
    var nameGroups = chartBody.selectAll("g.name")
            .data(layers, function (d) { 
                return d.name;
            });
/*
    // attributes "y" and "height" are meaningless on <g> elements
    // UPDATE
    nameGroups.transition()
        .duration(750)
        .attr("y", function(d) { return height - yScale(d.value) - .5; })
        .attr("height", function(d) { return yScale(d.value); });
*/

    // ENTER
    nameGroups.enter().append("svg:g")
        .attr("class", "nameGroup")
        .style("fill", function(d,i) {
            var color = (that.colors[d.name]) ?
                    that.colors[d.name].value :
                    Moonshadow.helpers.rw5(d.name);
            return "#" + color;
        })
/*  // It's not relevant here (since this code doesn't do anything), but in general, 
    // if you put your .enter() chain *before* the update chain, 
    // the newly added elements will become part of the main selection
    // and will have their attributes set along with the updating elements
    // so you only need to set *initial* values on enter
        .transition()
        .duration(750)
        .attr("y", function(d) { return height - yScale(d.value) - .5; })
        .attr("height", function(d) { return yScale(d.value); })
*/;

    // EXIT
    nameGroups.exit().remove();
/*  //This will remove all the rectangles immediately.  If you want to transition
    //the rectangles for removing groups, you need a separate transition like

    nameGroups.exit().transition().duration(750)
              .remove() //schedule the <g> elements to be deleted
                        // at the *end* of the transition
              .selectAll("rect")
              .attr("height", 0); //transition to nothing
               //you don't need to explicitly remove the rectangles,
               //as they will disappear when the parent <g> is removed
*/

    var rect = nameGroups.selectAll("rect")
            .data(function (d) { return d.values; });
    //split up the enter and update of the rectangles
    //(not worrying about exit, since the stack layout requires you to create a data object
    // for each x value, even if the value is zero, and you said x values aren't changing)

    rect.enter().append("svg:rect")
            .attr("class", "stackedBar")
            .attr("height", 0) //set initial values for vertical positions
            .attr("y", -yScale(0) );

    rect.attr("x", function (d) { return that.xScale(d.timestamp); })
        .attr("width", this.barWidth)
         //set the horizontal positions for both new and updating rectangles
         //(not worrying about transitioning horizontal, since you said it probably
         // wouldn't change on update)
        .transition().duration(750)
            .attr("y", function (d) { return -yScale(d.y0) - yScale(d.y); })
            .attr("height",function (d) { return +yScale(d.y); });
        //transition the vertical positions

}

这应该可行,但是当你有更新的新图层时,重叠的矩形会有点混乱。为了获得平稳过渡,正如我在评论中所说,你需要找出一个更好的初始&#34; y&#34;基于相邻矩形的旧值,每个矩形的值。一种选择是在堆栈中跟踪它下面的矩形:

    rect.enter().append("svg:rect")
            .attr("class", "stackedBar")
            .attr("height", 0) //set initial values for vertical positions
            .attr("y", function(d,i,j){
                 var lower = d3.select(this.parentNode.previousSibling)
                               .selectAll("rect")
                               .filter(function(d2, i2){ return i2 === i; })
                 return lower.empty()? //no such element existed
                           -yScale(0) :  
                           lower.attr("y");
             });

如果您向DOM添加<g>图层的顺序与从下到上的矩形的堆叠顺序匹配,则此操作只能 。否则(如果图层正在排序,因此新图层不会总是添加在顶部)您需要(a)浏览已排序的数据数组以找到正确的组,然后找到低于它的组在堆栈中,以及(b)保留对旧layers对象的引用,以便找到合适的起始位置。