无法使用新的JSON数据获取树图

时间:2014-01-09 22:46:57

标签: json d3.js treemap

我在d3.js的应用程序中使用treemap backbone.js。 treemap使用第一个JSOn数据正确呈现,但后续使用不同JSON数据的调用不会导致树形图重新呈现。

我的HTML看起来像这样:

<html>
<head>
    <title>Jenkins analytics</title>

    <!-- stylesheets -->
    <link rel="stylesheet" href="css/spa.css" type="text/css"/>
    <link rel="stylesheet" href="css/treemap.css" type="text/css"/>
</head>

<body>
    <nav>
        <form>
            <fieldset>
                <label for="chart">Chart:</label>
                <select id="chart" name="chart">
                    <option value="treemap" selected>Treemap</option>
                    <option value="motion">MotionChart</option>
                </select>
            </fieldset>
        </form>
        <form>
            <fieldset>
                <label for="period">Period:</label>
                <select id="period" name="period">
                    <option value="lastday" selected>Day</option>
                    <option value="lastweek">Week</option>
                    <option value="lastmonth">Month</option>
                </select>

                <label for="team">Team:</label>
                <select id="team" name="team">
                    <option value="all" selected>all</option>
                    <option value="spg">spg</option>
                    <option value="beacon">beacon</option>
                    <option value="disco">disco</option>
                </select>

                <label for="status">Status:</label>
                <select id="status" name="status">
                    <option value="" selected>all</option>
                    <option value="SUCCESS">success</option>
                    <option value="FAILURE">failure</option>
                    <option value="ABORTED">aborted</option>
                </select>
            </fieldset>
        </form>
        <form>
            <fieldset>
                <label for="duration">Duration</label>
                <input id="duration" type="radio" name="mode" value="size" checked />

                <label for="count">Count</label>
                <input id="count" type="radio" name="mode" value="count" />

                <label for="average">Average</label>
                <input id="average" type="radio" name="mode" value="avg" />
            </fieldset>
        </form>
    </nav>

    <div id="container" />

    <!-- Third party javascript -->
    <script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js" charset="utf-8"></script>
    <script type="text/javascript" src="script/underscore/underscore.js" charset="utf-8"></script>
    <script type="text/javascript" src="script/backbone/backbone.js" charset="utf-8"></script>
    <script type="text/javascript" src="script/d3/d3.v3.js" charset="utf-8"></script>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>

    <!-- Application javascript -->
    <script type="text/javascript" src="script/module.js"></script>

    <!-- Startup -->
    <script>
        var navview = new NavViewer();
    </script>
</body>
</html>

script/module.js看起来像这样:

var NavViewer = Backbone.View.extend({
    el: 'nav',

    events: {
        "change #chart": "change_chart",
        "change #period": "change_period",
        "change #team": "change_team",
        "change #status": "change_status"
    },

    initialize: function() {
        console.log("NavViewer.initialize");
        this.d3view = new D3Viewer();
        this.active_view = this.d3view;
    },

    change_chart: function(e) {
        console.log("NavViewer.change_chart");
    },
    change_period: function(e) {
        var _period = $('#period').val();
        console.log("NavViewer.change_period to " + _period);
        this.active_view.load();
    },
    change_team: function(e) {
        var _team = $('#team').val();
        console.log("NavViewer.change_team to "+ _team);
        this.active_view.load();
    },
    change_status: function(e) {
        var _status = $('#status').val();
        console.log("NavViewer.change_status to " + _status);
        this.active_view.load();
    }
});

var JenkinsViewer = Backbone.View.extend({
    el: '#container',
    server: "http://192.168.1.100:5000",
    url_fragment: function() {
        var _period = $('#period').val();
        var _team = $('#team').val();
        var _status = $('#status').val();
        return "when=" + _period +
            (_team == "all" ? "" : ("&" + "team=" + _team)) +
            (_status == "" ? "" : ("&" + "status=" + _status));
    }
});

var D3Viewer = JenkinsViewer.extend({
    initialize: function() {
        this.margin = {top: 8, right: 0, bottom: 0, left: 0};
        this.width = 1200 - this.margin.left - this.margin.right;
        this.height = 800 - this.margin.top - this.margin.bottom - 60;
        this.container = d3.select(this.el);
        this.color = d3.scale.category20c();
        this.base_url = this.server + "/team_build";

        this.treemap = d3.layout.treemap()
            .size([this.width, this.height])
            .sticky(true)
            .value(function(d) { return d.size; });

        this.position = function() {
            this.style("left", function(d) { return d.x + "px"; })
                .style("top", function(d) { return d.y + "px"; })
                .style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; })
                .style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; });
        };

        /* style the container */
        this.container
            .style("position", "relative")
            .style("width", this.width + "px")
            .style("height", this.height + "px")
            .style("left", this.margin.left + "px")
            .style("top", this.margin.top + "px")
            .style("border", "1px solid black");

        /* tootlip is appended to container */
        this.tooltip = this.container.append("div")
            .attr('class', 'tooltip')
            .style("visibility", "hidden")
            .style("background-color", "#ffffff");

        this.load();
    },

    load: function() {
        var $container = this.container;
        var color = this.color;
        var treemap = this.treemap;
        var position = this.position;
        var tooltip = this.tooltip;
        var url = this.base_url + "?" + this.url_fragment();

        console.log("D3View.load: " + url);

        d3.json(url, function(error, root) {
            /* 'root' actually means the data retrieved by the xhr call */
            var node = $container.datum(root)
                .selectAll(".node")
                .data(treemap.nodes);

            node.enter().append("div")
                .attr("class", "node")
                .call(position)
                .style("background", function(d) { return d.children ? color(d.name) : null; })
                .text(function(d) { return d.children ? null : d.name; })
                .on("mouseover", function(d) {
                    tooltip.html(d.name + ": " + Math.floor(d.value))
                      .style("visibility", "visible");
                    this.style.cursor = "hand";
                })
                .on("mouseout", function(){
                    tooltip.style("visibility", "hidden");
                });

            d3.selectAll("input").on("change", function change() {
                var functions = {
                    count: function(d) { return d.count; },
                    size: function(d) { return d.size; },
                    avg: function(d) { return d.size / d.count; }
                };
                var value = functions[this.value];

                node
                    .data(treemap.value(value).nodes)
                .transition()
                    .duration(1500)
                    .call(position);
            });
        });

        return true;
    }
});

以下是我所做的事情:

我认为问题可能与粘性或没有通话node.exit().remove()有关。我试过修改这两个没有成功。但是,要获得交互式客户端用户界面,我认为我需要使用treemap.sticky(true)

我已经确认每次访问REST API服务时都会获得不同的JSON。我已经确认调用之间container.datum().children的大小发生了变化,这证实了这是树形图不能重新渲染的问题。

D3.js: How to handle dynamic JSON Data看起来非常相关:

  • “值得一提的是,如果您已经拥有相同密钥的数据,d3.js会将数据存储在节点中,但仍会使用原始数据。”
  • “当我开始玩d3.js时,我认为渲染的数据会被调整并对我的尺度,域和诸如此类的变化作出反应。它不是。你必须告诉d3.js更新当前的东西或它将留在那里,不同意。“
  • “我将data()的结果分配给变量,因为enter()仅影响未绑定到节点的新数据。”

1 个答案:

答案 0 :(得分:2)

以下是我在load方法中所做的事情:

load: function() {
    var $container = this.container;
    var color = this.color;
    var treemap = this.treemap;
    var position = this.position;
    var tooltip = this.tooltip;
    var url = this.base_url + "?" + this.url_fragment();

    console.log("D3View.load: " + url);

    d3.json(url, function(error, root) {
        var tooltip_mouseover = function(d) {
            tooltip.html(d.name + ": " + Math.floor(d.value))
              .style("visibility", "visible");
            this.style.cursor = "hand";
        };
        var tooltip_mouseout = function(){
            tooltip.style("visibility", "hidden");
        };
        var background_color = function(d) { return d.children ? color(d.name) : null; };
        var text_format = function(d) { return d.children ? null : d.name; };

        /*
         * Refresh sticky bit
         * https://github.com/mbostock/d3/wiki/Treemap-Layout
         * "Implementation note: sticky treemaps cache the array of nodes internally; therefore, it
         * is not possible to reuse the same layout instance on multiple datasets. To reset the
         * cached state when switching datasets with a sticky layout, call sticky(true) again."
         */
        treemap.sticky(true);

        /* 'root' actually means the data retrieved by the xhr call */
        var nodes = $container.datum(root)
            .selectAll(".node")
            .data(treemap.nodes);

        var enter = nodes.enter().append("div")
            .attr("class", "node")
            .call(position)
            .style("background", background_color)
            .text(text_format)
            .on("mouseover", tooltip_mouseover)
            .on("mouseout", tooltip_mouseout);

        var exit = nodes.exit().remove();

        var update = nodes.style("background", background_color)
            .call(position)
            .text(text_format);

        d3.selectAll("input").on("change", function change() {
            var functions = {
                count: function(d) { return d.count; },
                size: function(d) { return d.size; },
                avg: function(d) { return d.size / d.count; }
            };
            var value = functions[this.value];

            $container.selectAll(".node")
                .data(treemap.value(value).nodes)
            .transition()
                .duration(1500)
                .call(position);
        });
    });

有两个重要的变化:

  1. 重置树形图粘贴位
  2. 对新/缺少/更改的节点/数据
  3. 使用输入/退出/更新选择

    另请注意,enter个节点会产生添加divnode的副作用,而exitupdate节点不会引用该div -class - 除了它们之外,在创建nodes时。如果在这些地方node-class添加更多选择,则您的选择将为空,exitupdate代码将为无操作。

    感谢AmeliaBR发布了一条非常有用的评论,其中包含她撰写的SO答案的链接。

    其他有用的读物​​: