如果源 json 更新(添加或删除项目),则动态更新 D3 Sunburst

Posted

技术标签:

【中文标题】如果源 json 更新(添加或删除项目),则动态更新 D3 Sunburst【英文标题】:Dynamically update D3 Sunburst if the source json is updated (item added or deleted) 【发布时间】:2016-06-29 23:34:39 【问题描述】:

我是 D3 的新手,如果源 json 被修改,我会尝试动态更新图表。但我无法做到这一点。

请查看this plunkr

Js:

var width = 500,
    height = 500,
    radius = Math.min(width, height) / 2;

var x = d3.scale.linear()
    .range([0, 2 * Math.PI]);

var y = d3.scale.sqrt()
    .range([0, radius]);

var color = d3.scale.category10();

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ") rotate(-90 0 0)");

var partition = d3.layout.partition()
    .value(function(d) 
        return d.size;
    );

var arc = d3.svg.arc()
    .startAngle(function(d) 
        return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
    )
    .endAngle(function(d) 
        return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
    )
    .innerRadius(function(d) 
        return Math.max(0, y(d.y));
    )
    .outerRadius(function(d) 
        return Math.max(0, y(d.y + d.dy));
    );

//d3.json("/d/4063550/flare.json", function(error, root) 
var root = initItems;

var g = svg.selectAll("g")
    .data(partition.nodes(root))
    .enter().append("g");

var path = g.append("path")
    .attr("d", arc)
    .style("fill", function(d) 
        return color((d.children ? d : d.parent).name);
    )
    .on("click", click)
    .each(function(d) 
        this.x0 = d.x;
        this.dx0 = d.dx;
    );


//.append("text")
var text = g.append("text")
    .attr("x", function(d) 
        return y(d.y);
    )
    .attr("dx", "6") // margin
    .attr("dy", ".35em") // vertical-align
    .attr("transform", function(d) 
        return "rotate(" + computeTextRotation(d) + ")";
    )
    .text(function(d) 
        return d.name;
    )
    .style("fill", "white");

function computeTextRotation(d) 
    var angle = x(d.x + d.dx / 2) - Math.PI / 2;
    return angle / Math.PI * 180;




function click(d) 
    console.log(d)
    // fade out all text elements
    if (d.size !== undefined) 
        d.size += 100;
    ;
    text.transition().attr("opacity", 0);

    path.transition()
        .duration(750)
        .attrTween("d", arcTween(d))
        .each("end", function(e, i) 
            // check if the animated element's data e lies within the visible angle span given in d
            if (e.x >= d.x && e.x < (d.x + d.dx)) 
                // get a selection of the associated text element
                var arcText = d3.select(this.parentNode).select("text");
                // fade in the text element and recalculate positions
                arcText.transition().duration(750)
                    .attr("opacity", 1)
                    .attr("transform", function() 
                        return "rotate(" + computeTextRotation(e) + ")"
                    )
                    .attr("x", function(d) 
                        return y(d.y);
                    );
            
        );
 //);

// Word wrap!
var insertLinebreaks = function(t, d, width) 
    alert(0)
    var el = d3.select(t);
    var p = d3.select(t.parentNode);
    p.append("g")
        .attr("x", function(d) 
            return y(d.y);
        )
        //    .attr("dx", "6") // margin
        //.attr("dy", ".35em") // vertical-align
        .attr("transform", function(d) 
            return "rotate(" + computeTextRotation(d) + ")";
        )
        //p
        .append("foreignObject")
        .attr('x', -width / 2)
        .attr("width", width)
        .attr("height", 200)
        .append("xhtml:p")
        .attr('style', 'word-wrap: break-word; text-align:center;')
        .html(d.name);
    alert(1)
    el.remove();
    alert(2)
;

//g.selectAll("text")
//    .each(function(d,i) insertLinebreaks(this, d, 50 ); );


d3.select(self.frameElement).style("height", height + "px");

// Interpolate the scales!
function arcTween(d) 
    var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
        yd = d3.interpolate(y.domain(), [d.y, 1]),
        yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
    return function(d, i) 
        return i ? function(t) 
            return arc(d);
         : function(t) 
            x.domain(xd(t));
            y.domain(yd(t)).range(yr(t));
            return arc(d);
        ;
    ;


function arcTweenUpdate(a) 
    console.log(path);
    var _self = this;
    var i = d3.interpolate( x: this.x0, dx: this.dx0 , a);
    return function(t) 
        var b = i(t);
        console.log(window);
        _self.x0 = b.x;
        _self.dx0 = b.dx;
        return arc(b);
    ;


setTimeout(function() 
    path.data(partition.nodes(newItems))
        .transition()
        .duration(750)
        .attrTween("d", arcTweenUpdate)

, 2000);

【问题讨论】:

【参考方案1】:

除了@Cyril 建议删除以下行:

d3.select(self.frameElement).style("height", height + "px");

我在你的小提琴中做了进一步的修改:working fiddle

这里使用的想法是添加一个函数updateChart,它接受items,然后生成图表:

var updateChart = function (items) 
    // code to update the chart with new items


updateChart(initItems);

setTimeout(function ()  updateChart(newItems); , 2000);

这不使用您创建的arcTweenUpdate 函数,但我将尝试解释基本概念:

首先,您需要使用现有数据JOIN 新数据:

// DATA JOIN - Join new data with old elements, if any.
var gs = svg.selectAll("g").data(partition.nodes(root));

然后,ENTER 在需要时创建新元素:

// ENTER
var g = gs.enter().append("g").on("click", click);

但是,我们还需要 UPDATE 现有/新的 pathtext 具有新数据的节点:

// UPDATE
var path = g.append("path");

 gs.select('path')
  .style("fill", function(d) 
      return color((d.children ? d : d.parent).name);
  )
  //.on("click", click)
  .each(function(d) 
      this.x0 = d.x;
      this.dx0 = d.dx;
  )
  .transition().duration(500)
  .attr("d", arc);


  var text = g.append("text");

  gs.select('text')
  .attr("x", function(d) 
      return y(d.y);
  )
  .attr("dx", "6") // margin
  .attr("dy", ".35em") // vertical-align
  .attr("transform", function(d) 
      return "rotate(" + computeTextRotation(d) + ")";
  )
  .text(function(d) 
      return d.name;
  )
  .style("fill", "white");

并且,在创建/更新所有内容后,删除未被使用的 g 节点,即 EXIT

// EXIT - Remove old elements as needed.
gs.exit().transition().duration(500).style("fill-opacity", 1e-6).remove();

JOIN + ENTER + UPDATE + EXIT 的整个模式在 Mike Bostock 的以下文章中得到了展示:

    General Update Pattern - I General Update Pattern - II General Update Pattern - III

【讨论】:

很好的答案。我们如何为图表的每个部分提供工具提示(名称和值)?【参考方案2】:

在小提琴旁边,setTimeout 没有运行,因为:

d3.select(self.frameElement).style("height", height + "px");

你会得到Uncaught SecurityError: Failed to read the 'frame' property from 'Window': Blocked a frame with origin "https://fiddle.jshell.net" from access a frame with origin 并且 setTimeout 永远不会得到调用。

所以你可以删除这行 d3.select(self.frameElement).style("height", height + "px"); 只是为了小提琴。

除此之外:

你的超时函数应该是这样的:

setTimeout(function() 
    //remove the old graph 
  svg.selectAll("*").remove();
  root = newItems;
  g = svg.selectAll("g")
    .data(partition.nodes(newItems))
    .enter().append("g");
  /make path
  path = g.append("path")
    .attr("d", arc)
    .style("fill", function(d) 
      return color((d.children ? d : d.parent).name);
    )
    .on("click", click)
    .each(function(d) 
      this.x0 = d.x;
      this.dx0 = d.dx;
    );
  //make text
  text = g.append("text")
    .attr("x", function(d) 
      return y(d.y);
    )
    .attr("dx", "6") // margin
    .attr("dy", ".35em") // vertical-align
    .attr("transform", function(d) 
      return "rotate(" + computeTextRotation(d) + ")";
    )
    .text(function(d) 
      return d.name;
    )
    .style("fill", "white");


工作小提琴here

【讨论】:

我需要一种解决方案,即我不会完全删除整个图表并重新绘制整个图表,而是应该类似于点击我们使用 Twean 动画更新图表时发生的情况。【参考方案3】:

为了让 enter() 和转换起作用,您需要为 d3 提供一种方法来识别数据中的每个项目。 .data() 函数有第二个参数,可以让你返回一些东西作为 id 使用。 enter() 将使用 id 来决定对象是否是新的。

尝试改变

path.data(partition.nodes(newItems))
.data(partition.nodes(root));

path.data(partition.nodes(newItems), function(d)return d.name);
.data(partition.nodes(root), function(d)return d.name);

【讨论】:

以上是关于如果源 json 更新(添加或删除项目),则动态更新 D3 Sunburst的主要内容,如果未能解决你的问题,请参考以下文章

如果不在列表中,则 MS 访问将项目添加到组合框

hadoop集群 动态添加或删除节点

从列表中删除项目后更新 UI

大家好,你能帮我解决一个问题,当从 json 删除、添加或更新数据时,我的 recyclerview 应该自动更新

ODI中删除数据的处理

如何更新 json 类型的 json 中的任何字段?它应该接受一个对象或一个键数组,如果它存在则更新键,否则创建