Кастомизация
12
Июн
0

D3.js: дерево

Третья и последняя статья посвящённая D3.js. Сегодня будет строить иерархическое дерево на основе структуры подразделений. Но дерево будет не простое – его узлы можно будет сворачивать по клику на них, а при клике на названии Подразделения будет открываться соответствующая карточка.

Как всегда нам понадобится:

  • 10-ый ослик – потому что в нем имеются некоторые необходимы для D3 CSS3 свойства;
  • CRM 2011 с => 12 ролапом – потому что только после 12 ролапа CRM 2011 работает в 10-ом ослике не в режиме совместимости.

Создайте в CRM следующие Веб-ресурсы:

Создайте следующие HTM Веб-ресурс tree.html:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <link type="text/css" rel="stylesheet" href="/WebResources/new_style.css" />
    <script src="/WebResources/ClientGlobalContext.js.aspx" type="text/javascript" ></script>
    <script type="text/jscript" src="/WebResources/new_jquery.1.9.1.min.js"></script>
    <script type="text/jscript" src="/WebResources/new_json2.js"></script>
    <script type="text/jscript" src="/WebResources/new_CrmRestKit.js"></script>
    <script type="text/javascript" src="/WebResources/new_d3.min.js"></script>
    <style type="text/css">
        body {
            margin: 0px; padding: 0px;
        }
        .node circle 
        {
            cursor: pointer;
            fill: #fff;
            stroke: steelblue;
            stroke-width: 1.5px;
        }
        .node text 
        {
            font-size: 11px;
        }        
        path.link 
        {
            fill: none;
            stroke: #ccc;
            stroke-width: 1.5px;
        }
    </style>
  </head>
  <body id="body">
    <script type="text/javascript">
        var m = [20, 120, 20, 120],
        w = document.documentElement.offsetHeight/1.5 - m[1] - m[3],
        h = document.documentElement.offsetWidth/1.5 - m[0] - m[2],
        i = 0,
        root = new Object();

        var tree = d3.layout.tree().size([h, w]);
        var diagonal = d3.svg.diagonal().projection(function(d) { return [d.y, d.x]; });

        var vis = d3.select("#body").append("svg:svg")
        .attr("width", w + m[1] + m[3])
        .attr("height", h + m[0] + m[2])
        .append("svg:g")
        .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

        /*
        root = {
            "name": "Суперфирма",
            "children": [
                {
                    "name": "Org1_root",
                    "children": [
                        { "name": "Org1_root" },
                        { "name": "Org2" }
                    ]
                },
                {
                    "name": "Org3_root",
                    "children": [
                        { "name": "Org3_root" },
                        { "name": "Org4" }
                    ]
                }
            ]
        };
        */

        // Начинаем строить иерархию
        var children = new Object();
        children["name"] = "Суперфирма";
        children["id"] = "A67C174E-9881-E011-8FD5-000C29CDB72E";

        // GUID первого подразделения вбиваем ручками
        retrieveBusinessUnitHierarchy("A67C174E-9881-E011-8FD5-000C29CDB72E", children);

        root = children;

        // Функция строит иерархию подразделений
        function retrieveBusinessUnitHierarchy(Id, parent) {
            CrmRestKit.ByQuery('BusinessUnit', ['BusinessUnitId', 'Name'], "ParentBusinessUnitId/Id eq guid'" + Id + "'", false)
            .fail(window.globalFail)
            .done(function (data, status, xhr) {
                var results = data.d.results;

                if (results.length > 0) parent["children"] = [];

                for (var item in results) {
                    var children = new Object();
                    children["name"] = results[item].Name;
                    children["id"] = results[item].BusinessUnitId;

                    retrieveBusinessUnitHierarchy(results[item].BusinessUnitId, children);

                    parent.children.push(children);
                }
            });
        }

        root.x0 = h / 2;
        root.y0 = 0;

        function toggleAll(d) {
            if (d.children) {
                d.children.forEach(toggleAll);
                toggle(d);
            }
        }

        // Разворачиваем/сворачиваем (в зависимости от текущего состояния) все узлы
        //root.children.forEach(toggleAll);

        /* Ручное разворачивание/сворачивание узлов
        toggle(root.children[1]);
        toggle(root.children[1].children[2]);
        toggle(root.children[9]);
        toggle(root.children[9].children[0]);
        */

        update(root);

        function update(source) {
            var duration = d3.event && d3.event.altKey ? 5000 : 500;

            // Compute the new tree layout.
            var nodes = tree.nodes(root).reverse();

            // Normalize for fixed-depth.
            nodes.forEach(function(d) { d.y = d.depth * 180; });

            // Update the nodes…
            var node = vis.selectAll("g.node")
            .data(nodes, function(d) { return d.id || (d.id = ++i); });

            // Enter any new nodes at the parent's previous position.
            var nodeEnter = node.enter().append("svg:g")
            .attr("class", "node")
            .attr("transform", function (d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
            
            // Рисуем кружок
            nodeEnter.append("svg:circle")
            .attr("r", 1e-6)
            .style("fill", function (d) { return d._children ? "lightsteelblue" : "#fff"; })
            .on("click", function (d) { toggle(d); update(d); });

            // Текстовая надпись и ссылка
            nodeEnter
                .append("a")
                .attr("xlink:href", function (d) { return "http://crm2011:5555/superfirma/biz/business/edit.aspx?id=" + d.id; })
                .attr("target", "_blank")
                    .append("svg:text")
                    .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
                    .attr("dy", ".35em")
                    .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
                    .text(function (d) { return d.name; })
                    .style("fill-opacity", 1e-6)

            // Transition nodes to their new position.
            var nodeUpdate = node.transition()
            .duration(duration)
            .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

            nodeUpdate.select("circle")
            .attr("r", 4.5)
            .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

            nodeUpdate.select("text")
            .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            var nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
            .remove();

            nodeExit.select("circle")
            .attr("r", 1e-6);

            nodeExit.select("text")
            .style("fill-opacity", 1e-6);

            // Update the links…
            var link = vis.selectAll("path.link")
            .data(tree.links(nodes), function(d) { return d.target.id; });

            // Enter any new links at the parent's previous position.
            link.enter().insert("svg:path", "g")
            .attr("class", "link")
            .attr("d", function(d) {
                var o = { x: source.x0, y: source.y0 };
                return diagonal({ source: o, target: o });
            })
            .transition()
            .duration(duration)
            .attr("d", diagonal);

            // Transition links to their new position.
            link.transition()
            .duration(duration)
            .attr("d", diagonal);

            // Transition exiting nodes to the parent's new position.
            link.exit().transition()
            .duration(duration)
            .attr("d", function(d) {        
                var o = { x: source.x, y: source.y };
                return diagonal({ source: o, target: o });
            })
            .remove();

            // Stash the old positions for transition.
            nodes.forEach(function(d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });
        }

        // Функция переключает состояние узла
        function toggle(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
        }
    </script>
  </body>
</html>

В коде в целом делается следующее:

  • Подключаем различные JS-библиотеки и CSS-стиль, которые мы загрузили в Веб-ресурсы;
  • Посредством циклического REST-запроса формирует иерархию Подразделений, начиная с головной (для нее мы указываем GUID вручную). Сформированную иерархию помещаем в JSON-объект определенного вида;
  • Далее подсовываем JSON-объект библиотеки D3.js.

Выгрузите сайтмап и добавьте в него ссылку на созданный HTML Веб-ресурс:

<SubArea Id="BU" Url="$webresource:new_tree.html" Icon="$webresource:new_tree16.png" Title="Оргструктура" />

Готово…


Комментарии (0)

*

code