Как нарисовать несколько масштабируемых древовидной карты d3js - PullRequest
3 голосов
/ 13 июня 2019

Я пытаюсь нарисовать несколько масштабируемых древовидных карт d3js.

Мне удалось нарисовать одну на основе этого примера https://codepen.io/moktc/pen/XMGgwP.Когда я снова использую функцию рисования, первая диаграмма отображается пусто, как если бы ей нужны данные для отображения.

Функция рисования

function drawTreeMap(tree) {


        treemap = d3.treemap()
                .tile(d3.treemapSquarify.ratio(height / width * 0.69 *(1 + Math.sqrt(5))))
                .size([width, height])
                .round(false)
                .paddingInner(1);

        var id = "#" +tree;
      svg = d3.select(id).append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.bottom + margin.top)
      .style("margin-left", -margin.left + "px")
      .style("margin.right", -margin.right + "px")
        .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .style("shape-rendering", "crispEdges");

      grandparent = svg.append("g")
      .attr("class", "grandparent");

      grandparent.append("rect")
      .attr("y", -margin.top)
      .attr("width", width)
      .attr("height", margin.top);

      grandparent.append("text")
      .attr("x", 6)
      .attr("y", 6 - margin.top)
      .attr("dy", ".75em");
};

Когда я использую эту функцию twite для отображения второй карты деревас теми же данными первого.первый становится пустым

1 Ответ

0 голосов
/ 16 июня 2019

svg, grandparent и treemap - все объекты, которые объявлены вне вашей функции drawTreemap() (это не видно из кода, который вы опубликовали, но можно увидеть в полном примере кодана CodePen), поэтому вызов функции дважды будет воздействовать на одни и те же объекты дважды.Поскольку эти объекты являются частью древовидной карты, вам нужны отдельные копии этих объектов для второй древовидной карты, поэтому они должны быть объявлены внутри drawTreemap().

Кроме того, обратите внимание, что функция display() содержит ссылку наgrandparent, который не передается display() в качестве аргумента, поэтому две копии display() должны быть объявлены отдельно для каждой копии grandparent.Следовательно, display() также должен быть объявлен внутри drawTreemap() (обратите внимание, что функции Javascript являются замыканиями , то есть их определение включает ссылки на все переменные, которые находились в области действия во время объявления функции, поэтомуможно объявлять функции, которые ссылаются на переменные, которые не находятся в глобальной области видимости, не передавая их в функцию в качестве аргументов).

В-третьих, функции initialize(), accumulate(), layout(), treemap()и display() необходимо вызывать отдельно для каждой древовидной карты, поэтому имеет смысл включить эти вызовы в drawTreemap().

Поэтому вы должны объявить svg, grandparent, treemap и display() локально внутри drawTreemap() и включает вызовы initialize(), accumulate(), layout(), treemap() и display(), что можно сделать следующим образом:

function drawTreemap(treeId) {

    var id = "#" + treeId;
    var svg = d3.select(id).append("svg")
        .attr("width", width - margin.left - margin.right)
        .attr("height", height - margin.bottom - margin.top)
        .style("margin-left", -margin.left + "px")
        .style("margin.right", -margin.right + "px")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .style("shape-rendering", "crispEdges");

    var grandparent = svg.append("g")
        .attr("class", "grandparent");

    grandparent.append("rect")
        .attr("y", -margin.top)
        .attr("width", width)
        .attr("height", margin.top);

    grandparent.append("text")
        .attr("x", 6)
        .attr("y", 6 - margin.top)
        .attr("dy", ".75em");

    var treemap = d3.treemap()
        //.tile(d3.treemapResquarify)
        .size([width, height])
        .round(false)
        .paddingInner(1);

    initialize(root);
    accumulate(root);
    layout(root);
    treemap(root);
    display(root);

    function display(d) {

        //...
    }

}

Вотполный код, основанный на примере CodePen, на который вы ссылались:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
    <style>
        text {
          pointer-events: none;
        }

        .grandparent text {
          font-weight: bold;
        }

        rect {
          fill: none;
          stroke: #fff;
        }

        rect.parent,
        .grandparent rect {
          stroke-width: 2px;
        }

        .grandparent rect {
          fill: orange;
        }

        .grandparent:hover rect {
          fill: #ee9700;
        }

        .children rect.parent,
        .grandparent rect {
          cursor: pointer;
        }

        .children rect.parent {
          fill: #bbb;
          fill-opacity: .5;
        }

        .children:hover rect.child {
          fill: #bbb;
        }
    </style>
</head>
<body>
    <p id="chart">
        Sample zoomable treemap
    </p>
    <div id="tree1"></div>
    <div id="tree2"></div>

    <script src="js/d3.v4.js"></script>
    <script>
        var data = {
            "name": 'Sample',
            "shortName": 'Sample',
            "children": [
              {
                "name": "6.1 Identify and plan learning needs",
                "shortName": "AITSL-A61",
                "size": null,
                "children": [
                  {
                    "name": "Analyse the Standards for.",
                    "shortName": "AITSL-A61-H",
                    "size": 59,
                    "children": [

                    ]
                  },
                  {
                    "name": "Demonstrate an  of the role of the",
                    "shortName": "AITSL-A61-G",
                    "size": 448,
                    "children": [

                    ]
                  },
                  {
                    "name": "Use  knowledge of the Standards for ",
                    "shortName": "AITSL-A61-L",
                    "size": 59,
                    "children": [

                    ]
                  },
                  {
                    "name": "Use the  plan learning needs.",
                    "shortName": "AITSL-A61-P",
                    "size": 101,
                    "children": [

                    ]
                  }
                ]
              },
              {
                "name": "6.2 Engage in improve practice",
                "shortName": "AITSL-A62",
                "size": null,
                "children": [
                  {
                    "name": "Participate in to update knowledge .",
                    "shortName": "AITSL-A62-P",
                    "size": 92,
                    "children": [

                    ]
                  },
                  {
                    "name": "Understand appropriate sources of .",
                    "shortName": "AITSL-A62-G",
                    "size": 405,
                    "children": [

                    ]
                  },
                  {
                    "name": "Plan for  and critiquing ",
                    "shortName": "AITSL-A62-H",
                    "size": 49,
                    "children": [

                    ]
                  },
                  {
                    "name": "Initiate to expand opportunities.",
                    "shortName": "AITSL-A62-L",
                    "size": 47,
                    "children": [

                    ]
                  }
                ]
              },
              {
                "name": "6.3 Engage with  and improve practice",
                "shortName": "AITSL-A63",
                "size": null,
                "children": [
                  {
                    "name": "Contribute to collegial  and apply.",
                    "shortName": "AITSL-A63-P",
                    "size": 84,
                    "children": [

                    ]
                  },
                  {
                    "name": "Initiate and engage in  discussions.",
                    "shortName": "AITSL-A63-H",
                    "size": 51,
                    "children": [

                    ]
                  },
                  {
                    "name": "Seek and feedback from .",
                    "shortName": "AITSL-A63-G",
                    "size": 458,
                    "children": [

                    ]
                  },
                  {
                    "name": "Implement  dialogue within   by .",
                    "shortName": "AITSL-A63-L",
                    "size": 40,
                    "children": [

                    ]
                  }
                ]
              },
              {
                "name": "6.4 Apply  improve learning",
                "shortName": "AITSL-A64",
                "size": null,
                "children": [
                  {
                    "name": "Undertake  .",
                    "shortName": "AITSL-A64-P",
                    "size": 76,
                    "children": [

                    ]
                  },
                  {
                    "name": "Demonstrate an  of the rationale.",
                    "shortName": "AITSL-A64-G",
                    "size": 426,
                    "children": [

                    ]
                  },
                  {
                    "name": "Engage with  to evaluate the .",
                    "shortName": "AITSL-A64-H",
                    "size": 54,
                    "children": [

                    ]
                  },
                  {
                    "name": "Advocate,  in and lead high-quality .",
                    "shortName": "AITSL-A64-L",
                    "size": 43,
                    "children": [

                    ]
                  }
                ]
              }
            ]
        };

        var margin = { top: 20, right: 0, bottom: 0, left: 0 },
            width = 640, //640
            height = 530,
            formatNumber = d3.format(",d"),
            transitioning;

        var x = d3.scaleLinear()
            .domain([0, width])
            .range([0, width]);

        var y = d3.scaleLinear()
            .domain([0, height - margin.top - margin.bottom])
            .range([0, height - margin.top - margin.bottom]);


        var color = d3.scaleOrdinal()
            .range(d3.schemeCategory10
            .map(function (c) { c = d3.rgb(c); c.opacity = 0.6; return c; }));

        var fader = function (color) { return d3.interpolateRgb(color, "#fff")(0.2); };

        var format = d3.format(",d");

        //var treemap;

        //var svg, grandparent;


        var root = d3.hierarchy(data)
            .eachBefore(function (d) { d.id = (d.parent ? d.parent.id + "." : "") + d.data.shortName; })
            .sum(function (d) { return d.size; })  // access the numeric attribute of the data
            .sort(function (a, b) {
                console.log('initial root sort a ' + a.value + ' b ' + b.value);
                return b.height - a.height || b.value - a.value;
            });

        function updateDrillDown() {

            drawTreemap("tree1");
            drawTreemap("tree2");

        };


        function drawTreemap(treeId) {

            var id = "#" + treeId;
            var svg = d3.select(id).append("svg")
                .attr("width", width - margin.left - margin.right)
                .attr("height", height - margin.bottom - margin.top)
                .style("margin-left", -margin.left + "px")
                .style("margin.right", -margin.right + "px")
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                .style("shape-rendering", "crispEdges");

            var grandparent = svg.append("g")
                .attr("class", "grandparent");

            grandparent.append("rect")
                .attr("y", -margin.top)
                .attr("width", width)
                .attr("height", margin.top);

            grandparent.append("text")
                .attr("x", 6)
                .attr("y", 6 - margin.top)
                .attr("dy", ".75em");

            var treemap = d3.treemap()
                //.tile(d3.treemapResquarify)
                .size([width, height])
                .round(false)
                .paddingInner(1);

            initialize(root);
            accumulate(root);
            layout(root);
            treemap(root);
            display(root);



            function display(d) {

                grandparent
                    .datum(d.parent)
                    .on("click", transition)
                  .select("text")
                    .text(name(d));

                var g1 = svg.insert("g", ".grandparent")
                    .datum(d)
                    .attr("class", "depth");

                var g = g1.selectAll("g")
                    .data(d._children)
                  .enter().append("g");

                g.filter(function (d) { return d._children; })
                    .classed("children", true)
                    .on("click", transition);

                var children = g.selectAll(".child")
                    .data(function (d) { return d._children || [d]; })
                  .enter().append("g");

                children.append("rect")
                    .attr("class", "child")
                    .call(rect)
                  .append("title")
                    .text(function (d) { return d.data.shortName + " (" + formatNumber(d.value) + ")"; });

                children.append("text")
                    .attr("class", "ctext")
                    .text(function (d) { return d.data.shortName; })
                    .call(text2);

                g.append("rect")
                    .attr("class", "parent")
                    .call(rect);


                var t = g.append("text")
                  .attr("class", "ptext")
                  .attr("dy", ".75em")

                t.append("tspan")
                  .text(function (d) { return d.data.shortName; });
                t.append("tspan")
                  .attr("dy", "1.0em")
                  .text(function (d) { return formatNumber(d.value); });
                t.call(text);

                g.selectAll("rect")
                  .style("fill", function (d) { return color(d.data.shortName); });



                function transition(d) {
                    if (transitioning || !d) return;
                    transitioning = true;

                    var g2 = display(d),
                        t1 = g1.transition().duration(750),
                        t2 = g2.transition().duration(750);


                    // Update the domain only after entering new elements.
                    x.domain([d.x0, d.x0 + d.x1]);
                    y.domain([d.y0, d.y0 + d.y1]);

                    // Enable anti-aliasing during the transition.
                    svg.style("shape-rendering", null);

                    // Draw child nodes on top of parent nodes.
                    svg.selectAll(".depth").sort(function (a, b) {
                        console.log('.depth sort a ' + a.depth + ' b ' + b.depth);
                        return a.depth - b.depth;
                    });

                    // Fade-in entering text.
                    g2.selectAll("text").style("fill-opacity", 0);

                    // Transition to the new view.
                    t1.selectAll("text").call(text).style("fill-opacity", 0);
                    t2.selectAll("text").call(text).style("fill-opacity", 1);
                    t1.selectAll("rect").call(rect);
                    t2.selectAll("rect").call(rect);

                    // Remove the old node when the transition is finished.
                    t1.remove().on("end", function () {
                        svg.style("shape-rendering", "crispEdges");
                        transitioning = false;
                    });
                }

                return g;
            }


        }  // end of drawTreemap()



        function initialize(root) {
            root.x = root.y = 0;
            root.x1 = width;
            root.y1 = height;
            root.depth = 0;
        }

        // Aggregate the values for internal nodes. This is normally done by the
        // treemap layout, but not here because of our custom implementation.
        // We also take a snapshot of the original children (_children) to avoid
        // the children being overwritten when when layout is computed.
        function accumulate(d) {
            console.log('accumulate called ' + d.data.name);
            return (d._children = d.children)
                ? d.value = d.children.reduce(function (p, v) { return p + accumulate(v); }, 0)
                : d.value;
        }

        // Compute the treemap layout recursively such that each group of siblings
        // uses the same size (1×1) rather than the dimensions of the parent cell.
        // This optimizes the layout for the current zoom state. Note that a wrapper
        // object is created for the parent node for each group of siblings so that
        // the parent’s dimensions are not discarded as we recurse. Since each group
        // of sibling was laid out in 1×1, we must rescale to fit using absolute
        // coordinates. This lets us use a viewport to zoom.
        function layout(d) {
            if (d._children) {
                //    treemap.nodes({_children: d._children});
                //    treemap(d);
                d._children.forEach(function (c) {
                    c.x0 = d.x0 + c.x0 * d.x1;
                    c.y0 = d.y0 + c.y0 * d.y1;
                    c.x1 *= d.x1;
                    c.y1 *= d.y1;
                    c.parent = d;
                    layout(c);
                });
            }
        }


        function text(text) {
            text.selectAll("tspan")
                .attr("x", function (d) { return x(d.x0) + 6; })
            text.attr("x", function (d) { return x(d.x0) + 6; })
                .attr("y", function (d) { return y(d.y0) + 10; })
                .style("opacity", function (d) {
                    console.log("text opacity setting textlength " + this.getComputedTextLength() + " d size " + (x(d.x0 + d.x1) - x(d.x0)));
                    return this.getComputedTextLength() < x(d.x0 + d.x1) - x(d.x0) ? 1 : 0;
                });
        }

        function text2(text) {
            text.attr("x", function (d) { return x(d.x0 + d.x1) - this.getComputedTextLength() - 6; })
                .attr("y", function (d) { return y(d.y0 + d.y1) - 6; })
                .style("opacity", function (d) { return this.getComputedTextLength() < x(d.x0 + d.x1) - x(d.x0) ? 1 : 0; });
        }

        function rect(rect) {
            rect.attr("x", function (d) { return x(d.x0); })
                .attr("y", function (d) { return y(d.y0); })
                .attr("width", function (d) {
                    console.log('id ' + d.id + ' rect width ' + (d.x1 - d.x0));
                    return x(d.x0 + d.x1) - x(d.x0);
                    //return (d.x1 -d.x0);

                })
                .attr("height", function (d) {
                    console.log('id ' + d.id + ' rect height ' + (d.y1 - d.y0) + ' ordinal ' + (y(d.y1 + d.y0) - y(d.y0)));
                    return y(d.y0 + d.y1) - y(d.y0);
                    //return y(d.y1 - d.y0);

                });
        }

        function name(d) {
            return d.parent
                ? name(d.parent) + " / " + d.data.shortName + " (" + formatNumber(d.value) + ")"
                : d.data.shortName + " (" + formatNumber(d.value) + ")";
        }

        //$(function () {  // I have commented this out because I have not included jQuery.js
            updateDrillDown();
        //});

    </script>
</body>
</html>

Изменить, чтобы объяснить, как отображать только один график одновременно:

Один способ отображениятолько один из графиков одновременно, в зависимостиЗависеть от значения поля <select> - нарисовать обе диаграммы, но спрятать одну из них, используя свойство стиля display.Например:

    <!--Add this HTML-->
    <select id="chart_selector" onchange="toggleChartVisibility();">
        <option value="1" selected="selected">Chart 1</option>
        <option value="2">Chart 2</option>
    </select>
    <br />
    <div id="tree1"></div>
    <div id="tree2" style="display:none;"></div><!--Add this style to the second <div>-->

    <!-- ... -->
    <script>
        function toggleChartVisibility() {
            var chosen = document.getElementById("chart_selector").value;
            if (chosen == "1") {
                document.getElementById("tree1").style.display = "block";
                document.getElementById("tree2").style.display = "none";
            } else {
                document.getElementById("tree1").style.display = "none";
                document.getElementById("tree2").style.display = "block";
            }
        }
    </script>

Использование display для скрытия одной из диаграмм имеет то преимущество, что диаграммы не перерисовываются с нуля каждый раз, когда пользователь изменяет выбранную диаграмму.

В качестве альтернативы,если вы хотите, чтобы в любой момент времени в документе отображалась только одна диаграмма, и рисовать диаграмму каждый раз, когда пользователь изменяет выбранную диаграмму, вы можете сделать это, вызвав updateDrillDown() непосредственно из изменений в поле <select>и включая код в updateDrillDown(), который очищает старую диаграмму перед рисованием новой:

    <select id="chart_selector" onchange="updateDrillDown();">
        <option value="1" selected="selected">Chart 1</option>
        <option value="2">Chart 2</option>
    </select>
    <br />
    <div id="tree1"></div>
    <div id="tree2"></div>

и

        function updateDrillDown() {

            var chosen = document.getElementById("chart_selector").value;
            if (chosen == "1") {
                document.getElementById("tree2").innerHTML = "";
                drawTreemap("tree1");
            } else {
                document.getElementById("tree1").innerHTML = "";
                drawTreemap("tree2");
            }
        };

Если у вас есть разные данные для каждой диаграммы, вы должны также включитькод для создания root внутри drawTreemap() и добавления условия к treeId, определяющего, какие данные использовать.

...