D3似乎更新了错误的SVG元素

时间:2017-03-13 20:59:30

标签: javascript d3.js svg

我有一个d3布局,旨在生成3个图表:

  1. 自定义项目布局w / transitions
  2. 按类别划分的销售饼图(在某些时候有过渡)
  3. 前5个表演项目的条形图,带有过渡。
  4. 1& 2工作正常但是当我添加第三张图表时,我看到了一些奇怪的行为。目的是创建一个条形图,其中每个条形的宽度与前N个项目的销售指标相关联,如下所示:

    data = data.filter(function(d){ return d.date === someDate});
    
    var cf = crossfilter(data);
    var salesDimension = cf.dimension(function(d){ return d.sales; });
    
    topData = salesDimension.top(5);
    

    问题在于,我的代码不是绘制条形图,而是以某种方式覆盖图表1中5个项目的位置并将它们移回原点。如果我将5更改为10,则会覆盖10个项目,依此类推。

    我仔细检查了变量的范围,甚至尝试更改drawTopItems()中所有内容的名称,这没有任何区别。我怀疑我在选择svg元素或将类应用于我想要修改的svg组元素时做错了但是我不能为我的生活看到什么。谁能告诉我我可能做错了什么?

    这是我的小问题:https://jsfiddle.net/Sledge/4eggpd5e/12/

    这是我的javascript代码:

    var item_width = 40, item_height = 60;
    var margin = {top: 50, right: 50, bottom: 75, left: 40},
        width = 700 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;
    var x = d3.scaleLinear().range([0, width]);
    var y = d3.scaleLinear().range([0, height]);
    var colorScale = d3.scaleLinear().domain([500,3000]).range(["white","#4169e1"]);
    
    // Pie Chart parameters
    var pieWidth = 300, pieHeight = 300;
    var outerRadius = Math.min(pieWidth, pieHeight) / 2,
            innerRadius = outerRadius * .50;
    var pieColor = d3.scaleOrdinal(['#42b9f4','#3791f2','#374ff1','#25b22e','#107222']);            // custom color scale
    var legendRectSize = 18;                                  // NEW
    var legendSpacing = 4;                                    // NEW
    
    // Top Item Parameters
    var topItemMargin = {top:25, right:25, bottom: 25, left: 25};
    var topItemWidth = 300 - topItemMargin.left - topItemMargin.right, 
          topItemHeight = 300 - topItemMargin.top - topItemMargin.bottom;
    var topItemXScale = d3.scaleLinear().range([0, topItemWidth]);
    var barHeight = 20, barSpacing = 5;
    
    
    /* SVG */
    var svgItemLayout = d3.select("#item_layout")
      .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 + ")");
    
    var svgPieChart = d3.select("#pie_chart")
        .append("svg") 
            .attr("width", pieWidth)
            .attr("height", pieHeight)
        .append("g")
            .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")") ;
    
    var svgTopItems = d3.select("#top_items")
        .append("svg")
            .attr("width", topItemWidth)
            .attr("height", topItemHeight)
        .append("g")
            .attr("transform", "translate(" + topItemMargin.left + "," + topItemMargin.top + ")");
    
    
    /* DRAW FUNCTIONS */
    // a single function to draw 
    function drawItemLayout(data, someDate){
    
      data.forEach(function(d) {
        d.x_pos = +d.x_pos;
        d.y_pos = +d.y_pos;
        d.sales = +d.sales;
      });
    
      // pre-filter data
      data = data.filter(function(d){ return d.date === someDate});
    
      var x_offset = 5, y_offset = 5;
    
      x.domain(d3.extent(data, function(d) { return d.x_pos; }));            // set the x domain
      y.domain(d3.extent(data, function(d) { return d.y_pos; }));            // set the y domain
    
      // create an update selection with a key function
      var g_sel = svgItemLayout.selectAll("g")
          .data(data, function(d){
            return d.item_name;
          });
    
      // get rid of those leaving the update
      g_sel.exit().remove();
    
      // our entering g
      var g_ent = g_sel.enter()
        .append("g");
    
      // add our rects to our g
      g_ent.append("rect")
        .attr("class", "dot")                   // wonder if I really need this class?
        .attr("width", item_width)
        .attr("height", item_height)
        .attr("rx", 3)
        .attr("ry", 3)
        .style("fill", function(d){ return colorScale(d.sales); })     // color factor variable
        .style("fill-opacity", 0.5);
    
      // add our text to our g
      g_ent.append("text")
        .attr("font-size", 10)
         .attr("text-anchor", "middle")
         .attr("fill", "black")
         .attr("dx", item_width/2)
         .attr("dy", item_height/2)
         .text(function(d){ return d.item_name; });
    
      // UPDATE + ENTER selection
      g_sel = g_ent.merge(g_sel);
    
      // move them into position with transition
      g_sel
        .transition()
        .duration(1200)
        .delay(function(d, i) { return i *40; })
        .attr("transform", function(d){
          return "translate(" + (x(d.x_pos) + x_offset)  + "," + (y(d.y_pos) + y_offset) + ")";
        });
    
    }
    
    function drawPieChart(data, someDate) {
    
        data.forEach(function(d) {
        d.x_pos = +d.x_pos;
        d.y_pos = +d.y_pos;
        d.sales = +d.sales;
      });
    
       // pre-filter data
      data = data.filter(function(d){ return d.date === someDate});
    
        var cf = crossfilter(data);
        var salesDimension = cf.dimension(function(d){ return d.sales; });
        var categoryDimension = cf.dimension(function(d){ return d.category; });
        var categoryGroup = categoryDimension.group();
    
        function reduceInitial(p, v) {
            return {
                sales : 0,
                count : 0
            };
        }
    
        function reduceAdd(p, v) {
            p.sales = p.sales + v.sales;
            p.count = p.count + 1;
            return p;
        }
    
        function reduceRemove(p, v) {
            p.sales = p.sales - v.sales;
            p.count = p.count - 1;
            return p;
        }
    
        categoryAggregated = categoryGroup.reduce(reduceAdd, reduceRemove, reduceInitial).all();
    
        var arc = d3.arc()
          .innerRadius(innerRadius)
          .outerRadius(outerRadius);
    
        var pie = d3.pie()
        .value(function(d) { return d.value.sales; })
        .sort(null);
    
      var path = svgPieChart.selectAll('path')
          .data(pie(categoryAggregated))
          .enter()
          .append('path')
          .attr('d', arc)
          .attr('fill', function(d, i) { return pieColor(i);});
    
        // Add a legend:
        var legend = svgPieChart.selectAll('.legend')
          .data(pieColor.domain())
          .enter()
          .append('g')
          .attr('class', 'legend')
          .attr('transform', function(d, i) {
            var height = legendRectSize + legendSpacing;
            var offset =  height * pieColor.domain().length / 2;
            var horz = -3 * legendRectSize;
            var vert = i * height - offset;
            return 'translate(' + horz + ',' + vert + ')';
          });
    
        legend.append('rect')
          .attr('width', legendRectSize)
          .attr('height', legendRectSize)
          .style('fill', pieColor)
          .style('stroke', pieColor);
    
        legend.append('text')
          .attr('x', legendRectSize + legendSpacing)
          .attr('y', legendRectSize - legendSpacing)
            .text(function(d) { return categoryAggregated[d].key; });                       // returns text based on data index
    
    }
    
    function drawTopItems (data, someDate) {
    
        data.forEach(function(d) {
        d.x_pos = +d.x_pos;
        d.y_pos = +d.y_pos;
        d.sales = +d.sales;
      });
    
       // pre-filter data
      data = data.filter(function(d){ return d.date === someDate});
    
        var cf = crossfilter(data);
        var salesDimension = cf.dimension(function(d){ return d.sales; });
    
        topData = salesDimension.top(5);
    
        topItemXScale.domain(d3.extent(topData, function(d) { return d.sales; }));        // set the x domain
    
        var f_sel = svgTopItems.selectAll("g")
            .data(topData,function(d){ return d.item_name; }).enter();
    
        f_sel.exit().remove();
    
        var f_ent = f_sel.enter().append("g");
    
        f_ent.append("rect")
        .attr("class", "dot")                   // wonder if I really need this class?
        .attr("width", function(d){ return d.sales })
        .attr("height", barHeight)
        .style("fill","#351eff")     // color factor variable
        .style("fill-opacity", 0.75);
    
      // add our text to our g
      f_ent.append("text")
        .attr("font-size", 10)
         .attr("text-anchor", "left")
         .attr("fill", "black")
         .attr("dx", item_width/2)
         .attr("dy", item_height/2)
         .text(function(d){ return d.item_name});
    
      // UPDATE + ENTER selection
      f_sel = f_ent.merge(f_sel);
    
      f_sel.transition()
        .duration(1200)
        .delay(function(d, i) { return i *40; })
        .attr("transform", function(d, i){
          return "translate( 0, "+ i*25 +")" + ")";
        });
    
    }
    
    /* MAIN */
    var data = d3.csvParse( d3.select("pre#data").text()); 
    drawItemLayout(data, '1-20-2017');
    drawPieChart(data, '1-20-2017');
    drawTopItems(data, '1-20-2017');
    
    /* UPDATE DATA */
    function updateData(date) {
    
      //d3.csv("http://localhost:8080/udacity_test_vis_1/output_fixture_data.csv", function(data) {
      var data = d3.csvParse( d3.select("pre#data").text()); 
      drawItemLayout (data, date);
      drawPieChart(data, date);
      drawTopItems(data, date);
    
    }
    
    /* GET SELECTION */
    $("#select_params").change(function(){
        var date = $("#select_params :selected").val();
        console.log(date);
        updateData(date);
    })
    

1 个答案:

答案 0 :(得分:2)

只有三个问题:

  1. 您正在重复enter()功能:

    var f_sel = svgTopItems.selectAll("g")
        .data(topData,function(d){ return d.item_name; }).enter();
    //this is an enter selection...
    
    var f_ent = f_sel.enter().append("g");
    //and you have enter() again here
    

    因此,删除第一个enterf_sel应该只是数据绑定选择。

  2. 在合并矩形

  3. 之前将合并的选择移动到
  4. 您的translate有一个额外的括号:

    return "translate( 0, "+ i*25 +")" + ")";
    
  5. 纠正这些问题后,这是您更新的小提琴:https://jsfiddle.net/utf5hva2/