Внедрение General Update Pattern D3 в Расширение Qlik Sense

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

В этом интерактивном примере, Mike Bostock демонстрирует концепцию постоянства объекта.

графический элемент, который представляет определенную точку данных … можно отслеживать визуально через переход. Уменьшает когнитивное бремя с помощью преаттентивной обработки движения, а не последовательного сканирования меток.

Следовательно, анимация, может помочь пользователям отслеживать изменения в данных. При фильтрации, это может помочь пользователю увидеть, какие изменения вызваны фильтрами и не сбрасывать информацию на новый график. При фильтрации графиков есть три вещи, которые могут произойти: элементы могут обновиться с новыми свойствами данных, выйти, поскольку они были отфильтрованы из набора данных, или войти, потому что они фильтруются в наборе данных. Таковы основные ситуации, с которыми мы сталкиваемся в типичных расширениях Qlik Sense. Более подробную информацию о возможностях анимации, Вы сможете найти тут httpss://www.youtube.com/watch?v=vLk7mlAtEXI#t=54

D3 умеет отлично анимировать эти переходы через общую картину обновлений. Эта модель включает в себя добавления данных в набор элементов, а затем определение переходов для обновления, ввода и вывода элементов. Bostock предлагает хороший визуальный пример здесь https://bl.ocks.org/mbostock/3808234.

Остальная часть этого поста покажет Вам пример реализации общей картины обновления в расширении Qlik Sense. Исходный код доступен на GitHub.

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

Рис.1

Обзор типичного расширения

Давайте начнем с примера расширения, который создает простую диаграмму, используя d3.js. В этом примере используется типичный метод создания расширения: каждый раз, когда расширение нуждается в визуализации, оно выпадает из диаграммы, которая была ранее обновлена. График не обновляется; на самом деле, он полностью перерисовывается с нуля каждый раз, когда данные фильтруются.

Это расширение доступно в хранилище этой папки . Просмотрите файл JS, в нем расширение поделено по разделам:

 

define(["jquery", "text!./simplebar.css","./d3.min"], function($, cssContent) {'use strict';
    $("<style>").html(cssContent).appendTo("head");
    return {
        initialProperties : {
            version: 1.0,
            qHyperCubeDef : {
                qDimensions : [],
                qMeasures : [],
                qInitialDataFetch : [{
                    qWidth : 2,
                    qHeight : 1000
                }]
            }
        },
        definition : {
            type : "items",
            component : "accordion",
            items : {
                dimensions : {
                    uses : "dimensions",
                    min : 1,
                    max:1
                },
                measures : {
                    uses : "measures",
                    min : 1,
                    max:1
                },
                sorting : {
                    uses : "sorting"
                },
                settings : {
                    uses : "settings"
                }
            }
        },
        snapshot : {
            canTakeSnapshot : true
        },
        paint : function($element,layout) {

Следующим шагом является определение метода окраски расширения. Простая диаграмма начинается с извлечения данных в формате, который подходит для кода d3, и создания элементов и свойств для визуализации:Блок выше определяет, какие файлы необходимо загрузить в первую очередь. Они включают в себя JQuery, файл для стайлинга CSS и d3. После того, как мы загрузили данные, мы определяем свойства расширения. Это расширение является довольно простым: это основная диаграмма, поэтому ей необходимо 1 размерность и 1 мера.

paint : function($element,layout) {
            // Create a reference to the app, which will be used later to make selections
            var self = this;
            // Get the data
            var qMatrix = layout.qHyperCube.qDataPages[0].qMatrix;
            var data = qMatrix.map(function(d) {
                return {
                    "Dim":d[0].qText,
                    "Dim_key":d[0].qElemNumber,
                    "Value":d[1].qNum
                }
            });
            // Get the extension container properties
            var ext_height = $element.height(), // height
                ext_width = $element.width(),   // width
                ext_id = "ext_" + layout.qInfo.qId; // chart id
            // Create or empty the chart container
            if(document.getElementById(ext_id)) {
                // If the element already exists, empty it out so we can start from scratch
                $("#" + ext_id).empty();
            }
            else {
                // If the element doesn't exist, create it
                $element.append($("<div />").attr("id",ext_id).width(ext_width).height(ext_height));
            }
            // Call the visualization function
            viz();

После настройки данных и элемента-контейнера для расширения, запускается функция для создания диаграммы:Ключевой момент в методе окраски находится в блоке if-else, начиная со строки 61. Этот блок проверяет, был ли создан элемент-контейнер для визуализации. Если это не так, он создает этот элемент в другом блоке. Это действие происходит в первый раз после создания расширения. Тем не менее, если элемент существует, то блок удаляет все его содержимое. Это является ключевым отличием между  методом визуального расширения и стандартным методом расширения. В стандартном методе расширение очищается каждый раз, когда данные обновляются. Расширение каждый раз создает визуализацию с нуля.

function viz() {
    // define margins
    var margin = {
        top:10,
        left:30,
        right:10,
        bottom:20
    };
    // Plot dimensions
    var plot_width = ext_width - margin.left - margin.right,
        plot_height = ext_height - margin.top - margin.bottom;
    var svg = d3.select("#" + ext_id).append("svg")
                .attr("width",ext_width)
                .attr("height",ext_height)
                .attr("id",ext_id + "_svg");
    var plot = svg
                .append("g")
                .attr("id",ext_id+"_svg_g");
    // Scales
    var x = d3.scale.linear()
                .domain([0,d3.max(data,function(d) {return d.Value})])
                .range([0,plot_width]),
        y = d3.scale.ordinal()
                .domain(data.map(function(d) {return d.Dim}))
                .rangeRoundBands([0, plot_height], .25),
        xAxis = d3.svg.axis()
                    .scale(x)
                    .tickSize(5)
                    .tickFormat(d3.format(",.0f")),
        yAxis = d3.svg.axis()
                .scale(y)
                .orient("left");
    // Create a temporary yAxis to get the width needed for labels and add to the margin
    svg.append("g")
        .attr("class","y axis temp")
        .attr("transform","translate(0," + 0 + ")")
        .call(yAxis);
    // Get the temp axis max label width
    var label_width = d3.max(svg.selectAll(".y.axis.temp text")[0], function(d) {return d.clientWidth});
    // Remove the temp axis
    svg.selectAll(".y.axis.temp").remove();
    // Update the margins, plot width, and x scale range based on the label size
    margin.left = margin.left + label_width;
    plot_width = ext_width - margin.left - margin.right;
    x.range([0,plot_width]);
    // Adjust the plot area for the labels
    plot
        .attr("transform","translate(" + margin.left + "," + margin.top + ")");
    // Get the bar height from the y scale range band
    var bar_height = y.rangeBand();
    // Add the bars to the plot area
    var bars = plot.selectAll("#" + ext_id + " .simplebar")
                .data(data)
                .enter()
                .append("rect")
                .attr("class","simplebar")
                .attr("x",0)
                .attr("y",function(d) {return y(d.Dim)})
                .attr("height",bar_height)
                .attr("width",function(d) {return x(d.Value)})
                .on("click",function(d) {self.backendApi.selectValues(0,[d.Dim_key],true);});
    // Add the axes
    plot.append("g")
        .attr("class","x axis")
        .attr("transform","translate(0," + plot_height + ")")
        .call(xAxis);
    plot.append("g")
        .attr("class","y axis")
        .attr("transform","translate(0," + 0 + ")")
        .call(yAxis);
    // Add a click function to the y axis
    plot.selectAll("#" + ext_id + " .y.axis .tick")
        .on("click",function(d) {self.backendApi.selectValues(0,[getProp(data,"Dim",d,"Dim_key")],true);})
}

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

Один важный элемент, который необходимо принять к сведению, это блок от строк 38 до 57. Этот блок создает временные оси и определяет размер метки, а затем удаляет эту ось и обновляет поля, участки и масштаб для учета размера метки. Этот процесс необходим для динамического определения параметров оси на основании длины меток. При помощи Qlik Sense пользователь может загрузить данные с расширением меток различной длины, так что расширение должно быть в состоянии соответствующим образом корректировать поля диаграммы.

Изменение расширения для анимации обновлений

Для того, чтобы анимировать изменения в этом расширении, необходимо сделать несколько модификаций для построения графиков данных. В приведенном выше примере, расширение каждый раз очищает график, содержащий элемент. Таким образом, единственным показателем, который определен для столбцов был d3 показатель d3 .enter(). Поступающие данные изображаются как новые столбцы, потому что нет существующих столбцов для изменения.

Для того, чтобы анимировать обновления, мы должны отслеживать элементы, которые уже находятся на странице. Мы не можем просто очистить график и каждый раз начинать с нуля. Скорее мы можем классифицировать элементы данных по трем категориям:

  • ввод: данные, поступающие в расширение – новые; в настоящее время для них нет столбцов, так что расширение должно нарисовать новое изображение
  • обновление: элемент данных уже есть в расширении; столбец уже подготовлен, так что расширение должно изменить свойства этого столбца на основе изменений в значениях
  • вывод: элемент данных есть в расширении, но его уже нет в расширении Qlik Sense, благодаря фильтрации. Расширение должно удалить этот столбец из графика

Помня о этих трех категориях, мы можем определить последовательность действий при изменении данных. В данном примере, последовательность определяется следующим образом:

  1. Любое выходные данные сначала удаляются из расширения. Выходные столбцы исчезнут.
  2. Любые данные обновления обновляются. Столбцы меняют размер и позицию, если это необходимо.
  3. Любые входные данные добавляются в расширение. Новые столбцы исчезнут.

В обновленной гистограмме, столбцы разделены на 3 части, чтобы отразить эту последовательность:

// Transition duration for update animations
    var dur = 750;
    // Add the data first, with a key
    var bars = plot.selectAll(".updatingbar")
                .data(data,function(d) {return d.Dim});
    // Update logic
    var updatedBars = bars
                        .transition()
                        .duration(dur)
                        .delay(!bars.exit().empty() * dur) // if there are no exiting bars, update immediately. otherwise, wait the duration before updating
                        .attr("y",function(d) {return y(d.Dim)})
                        .attr("width",function(d) {return x(d.Value)})
                        .attr("height",bar_height)
                        .attr("opacity",1);
    // Enter logic
    bars
        .enter()
        .append("rect")
        .attr("class","updatingbar")
        .attr("x",0)
        .attr("y",function(d) {return y(d.Dim)})
        .attr("opacity",0)
        .attr("height",bar_height)
        .attr("width",function(d) {return x(d.Value)})
        .on("click",function(d) {self.backendApi.selectValues(0,[d.Dim_key],true);})
        .transition()
        .duration(dur)
        .delay((!bars.exit().empty() + !updatedBars.empty()) * dur) // if there are no exiting bars and no updating bars, enter new bars immediately. otherwise, wait for the other animations to finish
        .attr("opacity",1);
    // Exit logic
    bars
        .exit()
        .transition()
        .duration(dur) // exit immediately
        .attr("opacity",0)
        .remove();

Продолжительность анимации
В приведенном выше коде, продолжительность перехода определяется в миллисекундах. Эта переменная будет использоваться для управления временем, которое необходимо для каждой анимации.

Добавление данных с помощью ключа
Данные должны быть привязаны к стержневым элементам. Ранее это делалось следующим выражением:

var bars = plot.selectAll(".simplebar").data(data);

Эта строка добавляет данные в стержневые элементы. Однако расширение обновления должно отслеживать, как меняются данные — какие элементы являются новыми или выходными? Для того, чтобы сделать это правильно с данными Qlik Sense, мы должны определить ключ, который будет использовать D3 для отслеживания данных. Этот ключ может быть определен в качестве второго параметра в .data ():

var bars = plot.selectAll(".updatingbar") .data(data,function(d) {return d.Dim});

Теперь, D3 будет использовать значение в свойстве «DIM», чтобы отслеживать каждый элемент данных. Он может использовать это измерение, чтобы увидеть, есть ли уже часть данных, завершился ли ввод в график.

Вывод данных
Код, показанный  выше, определяется в обновлении — порядок ввода — вывода, но давайте посмотрим на фрагменты на выходе — обновление — порядок ввода, поскольку это – последовательность в режиме реального времени. Сначала фрагмент выхода:

// Exit logic
bars
    .exit()
    .transition()
    .duration(dur) // exit immediately
    .attr("opacity",0)
    .remove();

Строка 3 дает сигнал D3 выбрать выходящие элементы и применить для них последующий код. Последующий код определяет переход с продолжительностью переменной в 750мс. Любые изменения атрибутов, определенные в переходе, произойдут в течение 750мс. Задержки нет, таким образом, анимация начинается, как только элементы выходят из графика. Атрибут, который изменяется, —  это прозрачность, установленная в «0». Это заставит столбец медленно исчезнуть из поля зрения. В конце переходного периода, я вызываю функцию remove () и удаляю столбец из DOM, который удаляет его полностью.

Примечание: Атрибут не может переместиться без заданных точек начала и конца перемещения. Конечная точка этого перехода для непрозрачности – «0». Таким образом, при создании столбцов на входе, должна быть установлена непрозрачность в исходном значении этого перехода, чтобы переход сработал. В этом расширении, я установил начальную непрозрачность в «1», что соответствует 100% видимости.

 

Обновление данных
После того как данные вышли, в расширении необходимо обновить уже существующие значения:

// Update logic
var updatedBars = bars
                    .transition()
                    .duration(dur)
                    .delay(!bars.exit().empty() * dur) // if there are no exiting bars, update immediately. otherwise, wait the duration before updating
                    .attr("y",function(d) {return y(d.Dim)})
                    .attr("width",function(d) {return x(d.Value)})
                    .attr("height",bar_height)
                    .attr("opacity",1);

Согласно логике обновления необходимо принять существующие столбцы и изменить их размер и положение на основе изменений данных. Y, ширина, высота и атрибуты являются переходными на определенной продолжительности. Ширина учитывает изменение длины столбца на основании изменений значений. Показатель Y для изменения вертикального положения столбца основан на сортировке или нумерации столбцов на графике. Значение высоты учитывает изменение высоты столбца, когда в область построения передается другое количество брусков.

Существует дополнительный параметр перехода, необходимый для обновления столбцов: задержка. Последовательность обновления должна начаться только после того, как последовательность закончится. На первый взгляд, представляется целесообразным, чтобы задержка равнялась длительности перехода. Таким образом, когда прошло 750 мс для выхода, запускается функция обновления. Но это не объясняет сценариев, где нет выходящих. Если нет выходящих, которые должны анимироваться, обновление не должно ждать 750 мс. Оно должно запускаться немедленно. Параметр проверяет, есть ли на выходе, что-либо, что нужно анимировать с помощью функции .empty (). .empty () покажет правильные показатели, если есть выходящие элементы, и неправильные в противном случае. Умножив обратное .empty () на длительность, задержка переключается между 750 мс, или 0 мс, в зависимости от того есть выходящие элементы, или нет.

Примечание: Логика обновления также устанавливает непрозрачность в «1». Это может показаться излишним, но на самом деле это важно, потому что анимация может быть прервана, если пользователь быстро фильтрует данные в расширении. Например, пользователь может применить фильтр, который отправляет столбец на выход. На полпути до завершения, пользователь удаляет этот фильтр. Это прерывает выход анимации и переводит его к анимации обновлений, так как столбец является частью данных. Тем не менее, из-за выхода, анимация прервана на полпути, столбец может иметь непрозрачность «.5», потому что не удалось закончить переход на «0». Добавление атрибута непрозрачности  обновлению гарантирует, что этот столбец получит сброс непрозрачности на соответствующем значении, независимо от того, что происходит, когда анимация начала обновления.

Ввод данных

Последний шаг в определении –создание новых столбцов:

// Enter logic
bars
    .enter()
    .append("rect")
    .attr("class","updatingbar")
    .attr("x",0)
    .attr("y",function(d) {return y(d.Dim)})
    .attr("opacity",0)
    .attr("height",bar_height)
    .attr("width",function(d) {return x(d.Value)})
    .on("click",function(d) {self.backendApi.selectValues(0,[d.Dim_key],true);})
    .transition()
    .duration(dur)
    .delay((!bars.exit().empty() + !updatedBars.empty()) * dur) // if there are no exiting bars and no updating bars, enter new bars immediately. otherwise, wait for the other animations to finish
    .attr("opacity",1);

Эта функция должна быть Вам знакома. Позиционироапние, размер и атрибуты столбца устанавливаются до начала перехода. Начальная непрозрачность установлена в «0». Переход устанавливает непрозрачность в «1», в результате чего столбец исчезает. Задержки из логики обновления повторяются. Тем не менее, логика ввода является третьей в последовательности, так что нужно проверить как выходящие элементы, так и элементы обновления, прежде чем вводить новые данные. Если есть элементы выхода, следует подождать 750 мс. Если нет выхода или обновлений, столбцы вводятся сразу. Этот сценарий происходит, когда расширение передается впервые. Нет данных для выхода или обновления и расширение запускает анимацию сразу же.

Исследуйте остальную часть кода

Остальная часть кода имеет дополнительные модификации и включает анимацию графика. Есть примеры того, как настроить svg и впоследствии захватить его, настроить ось и участки переходов и т.д. Если у вас есть какие-либо вопросы по поводу процесса реализации общей картины обновления, пожалуйста, задавайте их в комментариях или спросите меня на Twitter.

Найти решение у бизнес-партнера QlikTech (QlikView) в России.

Форум разработчиков QlikView и Qlik Sense. Получите ответы на все вопросы по QlikView и Qlik Sense!

Вы можете оставить комментарий, или ссылку на Ваш сайт.

Оставить комментарий

64 queries in 0,206 seconds
Website nonton bokep jepang