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

D3.js: диаграмма усилий

Продолжаем изучение библиотеки D3. На этот раз будем строить иерархическую диаграмму, в центре которой расположена наша собственная организация и от нее лучами расходятся Организации, у которых нет Родительской организации. Далее от них лучами расходятся их дочерние Организации первого уровня и т.д. выстраивая таким образом иерархию. Также для каждой Организации выстраивается иерархия Контактов.

Нам понадобится:

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

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

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

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title></title>
    <style>
        body {
            text-align: center;
            margin: 0px;
            padding: 0px;
        }

        .node {
            stroke: #fff;
            stroke-width: 1.0px;
        }

        .link {
            stroke: #999;
            stroke-opacity: .6;
        }
    </style>

    <link rel="stylesheet" href="/WebResources/new_bootstrap.min.css" />
    <link rel="stylesheet" href="/WebResources/new_style.css" />
</head>
<body>
    <script src="/WebResources/ClientGlobalContext.js.aspx" type="text/javascript" ></script>
    <script type="text/jscript" src="/WebResources/new_jquery1.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/jscript" src="/WebResources/new_d3.v3.min.js"></script>
    <script type="text/jscript" src="/WebResources/new_tooltip.js"></script>

    <script>
        var width = document.documentElement.offsetWidth;
        var height = document.documentElement.offsetHeight;

        var color = d3.scale.category20();

        var force = d3.layout.force()
            .charge(-200)
            .linkDistance(50)
            .size([width, height]);

        var svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height);

        linksG = svg.append("g").attr("id", "links");
        nodesG = svg.append("g").attr("id", "nodes");
        
        // Заготовка узлов и связей, которую заполним далее (первая - собственная организация)
        graph = {
            "nodes": [
                { "name": "Superfirma", "description": "Наша контора", "size": 10, "group": 0 }
            ],
            "links": []
        };

        // Используем CrmRestKit для возвращения Организаций и Контактов
        CrmRestKit.ByQuery('Account', ['AccountId', 'Name', 'Revenue', 'Description'], "ParentAccountId/Id eq null", false)
            .fail(window.globalFail)
            .done(function (data, status, xhr) {
                var results = data.d.results;
                var group = 0;
                itemSource = 0;

                for (var item in results) {
                    var record = { name: results[item].Name, description: results[item].Description, size: results[item].Revenue.Value, group: ++group };
                    var link = { source: ++itemSource, target: 0, value: 6 };

                    graph.nodes.push(record);
                    graph.links.push(link);

                    // Запрашиваем всю цепочку иерархии начиная с головной в том числе по Контактам
                    retrieveAccountHierarchy(results[item].AccountId, group, itemSource);
                    retrieveContactHierarchy(results[item].AccountId, group, itemSource);
                }
            });

        force
            .nodes(graph.nodes)
            .links(graph.links)
            .start();

        // Инициируем подсказки
        tooltip = Tooltip("vis-tooltip", 230);

        // Вычисляем размер узла Организаций в зависимости от уровня дохода
        countExtent = d3.extent(graph.nodes, function (d) { return d.size });
        circleRadius = d3.scale.sqrt().range([5, 10]).domain(countExtent);

        graph.nodes.forEach(function (n) {
            n.x = randomnumber = Math.floor(Math.random() * width);
            n.y = randomnumber = Math.floor(Math.random() * height);
            n.radius = circleRadius(n.size);
        });
        
        // Соединяем узлы
        var link = linksG.selectAll("line.link")
            .data(graph.links)
            .enter().append("line")
            .attr("class", "link")
            .style("stroke-width", function (d) { return Math.sqrt(d.value); });

        // Рисуем узлы
        var node = nodesG.selectAll("circle.node")
            .data(graph.nodes)
            .enter().append("circle")
            .attr("class", "node")
            .attr("cx", function (d) { return d.x })
            .attr("cy", function (d) { return d.y })
            .attr("r", function (d) { return d.radius })
            .style("fill", function (d) { return color(d.group) })

        // Прицепляем к узлам отображение и скрытие подсказок
        node.on("mouseover", showDetails)
            .on("mouseout", hideDetails);
                
        force.on("tick", function () {
            link.attr("x1", function (d) { return d.source.x; })
                .attr("y1", function (d) { return d.source.y; })
                .attr("x2", function (d) { return d.target.x; })
                .attr("y2", function (d) { return d.target.y; });

            node.attr("cx", function (d) { return d.x; })
                .attr("cy", function (d) { return d.y; });
        });

        // Функция показывает всплывающую подсказку
        function showDetails(d, i) {
            content = '<p class="main">' + d.name + '</span></p>';
            content += '<hr class="tooltip-hr">';
            content += '<p class="main">' + d.description + '</span></p>';
            tooltip.showTooltip(content, d3.event);
            
            d3.select(this).style("stroke","black")
              .style("stroke-width", 2.0)
        }

        function hideDetails(d, i) {
            tooltip.hideTooltip();

            node.style("stroke", function (n) {
                if (!n.searched) { return "#fff" }
                else { return "#555" }
            })
                .style("stroke-width", function (n) {
                    if (!n.searched) { return 1.0 }
                    else { return 2.0 }
                })
        }

        // Функция которая шерстит иерархию Организаций начиная с присланной корневой Организации
        function retrieveAccountHierarchy(Id, group, itemTarget) {
            CrmRestKit.ByQuery('Account', ['AccountId', 'Name', 'Revenue', 'Description'], "ParentAccountId/Id eq guid'" + Id + "'", false)
            .fail(window.globalFail)
            .done(function (data, status, xhr) {
                var results = data.d.results;

                for (var item in results) {
                    var record = { name: results[item].Name, description: results[item].Description, size: results[item].Revenue.Value, group: group };
                    var link = { source: ++itemSource, target: itemTarget, value: 6 };

                    graph.nodes.push(record);
                    graph.links.push(link);

                    retrieveAccountHierarchy(results[item].AccountId, group, itemSource);
                    retrieveContactHierarchy(results[item].AccountId, group, itemSource);
                }
            });
        }

        // Функция ищет Контакты связанные c Организацией или другими Контактами и строит из них иерархию
        function retrieveContactHierarchy(Id, group, itemTarget) {
            CrmRestKit.ByQuery('Contact', ['ContactId', 'FullName', 'Description'], "ParentCustomerId/Id eq guid'" + Id + "'", false)
            .fail(window.globalFail)
            .done(function (data, status, xhr) {
                var results = data.d.results;

                for (var item in results) {
                    var record = { name: results[item].FullName, description: results[item].Description, size: 10, group: 0 };
                    var link = { source: ++itemSource, target: itemTarget, value: 1 };

                    graph.nodes.push(record);
                    graph.links.push(link);

                    retrieveContactHierarchy(results[item].ContactId, group, itemSource);
                }
            });
        }
    </script>
</body>
</html>

Ну здесь мы делаем следующее:

  • Подключаем необходимые JS-библиотеки и CSS-стили;
  • В JSON-объект специального формата помещаем иерархию Организаций и Контактов;
  • Вычисляем размер узлов Организаций в зависимости от их Годового дохода;
  • Цепляем на событие наведения мыши вывод подсказки;
  • Формируем саму диаграмму. Заметьте, что Организации относящиеся к одной цепочки иерархии закрашиваются одинаковым цветом. Контакты выводятся всегда одним цветом.

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

Смотрим…


З.Ы. Если к переменной узлов (node) добавить такой атрибут:

.call(force.drag);

то узлы диаграммы можно будет перетаскивать мышкой.

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

*

code