Новые API Qlik Sense упрощают создание расширений, которые создают новые визуализации. Распространенным источником этих визуализаций является d3.js, мощная библиотека javascript для визуализации данных.
Следующее руководство покажет вам, как создать расширение, используя существующий код d3. Процесс будет описан в деталях, поэтому выполнение каждого шага займет некоторое время. Однако сами шаги довольно просты и могут быть выполнены менее чем за 10 минут без дополнительных объяснений. Данное руководство поделено на части, но есть 4 основных шага:
- Получить код d3 для использования
- Получить тестовые данные для использования и загрузить их в Qlik Sense
- Инициализировать новое расширение и настроить его свойства.
- Вставьте код d3 в расширение и изменить его для получения исходных данных из расширения.
Предварительные условия: В этом руководстве предполагается, что вы обладаете базовыми знаниями о сценариях Qlik Sense и JavaScript, поэтому этот вопрос не будет затрагиваться. Однако, все примеры кодов и шаги будут сопровождаться краткими пояснениями, так что вы сможете скопировать и вставить те части, которые вы не полностью понимаете.
Ресурсы:
Руководство покажет вам, как создать следующую диаграмму:
Теперь перейдем собственно к самому процессу.
Получение кода D3 для использования
Я собираюсь построить диаграмму рассеяния d3, показанную здесь: http://bl.ocks.org/mbostock/3887118. Диаграмма отображает точки по 2 метрикам, а также окрашивает их в зависимости от измерения атрибута. Qlik Sense имеет встроенную диаграмму рассеяния, которая строит точки на основе одного измерения. Там нет возможности добавить второе измерение, которое может группировать точки по цвету. Диаграмма рассеяния d3 представляет эту новую функциональность.
Если вы посмотрите на исходный код под диаграммой, то увидите два файла: html-файл, который отображает диаграмму, и файл *.tsv, который содержит данные. Мы будем использовать подмножества html-файла и данные Qlik Sense вместо tsv-файла.
Мы вернемся к этому примеру, когда будем готовы реализовать код d3.
Получите тестовые данные и загрузите их в Qlik Sense
Для диаграммы рассеяния нам нужен набор данных с 2 метриками и 2 измерениями. 2 метрики будут нанесены на оси X и Y. 1 измерение будет использоваться для отрисовки каждой точки; второе измерение будет использоваться для назначения цвета точкам. Я взял примерный набор данных из Отчета о развитии человека за 2014 год. Этот набор данных включен выше в разделе Ресурсы.
Заголовки таблицы:
Исследуемая группа | Страна | Ожирение в% | Ожидаемая продолжительность жизни в возрасте 60 лет |
В качестве двух показателей я выбрал «Ожирение в%» и «Ожидаемая продолжительность жизни в возрасте 60 лет». Отображаемые измерения будут представлять отдельные страны. Для цвета я буду использовать исследуемую группу, в которую входит каждая страна. Я также удалил все страны, в которых отсутствовали данные.
Сохраним ваш набор данных в папку, откуда вы сможете загрузить его.
Создаем новое приложение в Qlik Sense
Первое, что нам нужно сделать, это создать новое приложение в Qlik Sense. Откройте Qlik Sense. Вы должны увидеть кнопку «Create new app» (Создать новое приложение) в правом верхнем углу.
Дайте вашему приложению имя, а затем щелкните по нему, чтобы открыть.
Изменение скрипта загрузки и загрузка ваших данных
Итак, мы создали и открыли новое приложение, оно пустое. Теперь нам нужно загрузить наши данные. Данные можно загрузить с помощью редактора загрузки данных. Нажмите на значок компаса в верхнем левом углу и выберите «Data load editor» (Редактор загрузки данных), чтобы открыть это окно.
Редактор загрузки данных содержит язык сценариев для загрузки данных в Qlik Sense. Слева вы увидите панель, которая организует ваш скрипт загрузки в разные вкладки. Главная вкладка автоматически заполняется некоторыми переменными конфигурации. Здесь не нужно ничего изменять. Нажмите на знак «+», чтобы создать новую вкладку под Главной. Дайте вкладке имя.
Теперь справа должна отображаться пустая вкладка сценариев. Чтобы сгенерировать наш скрипт загрузки, нам нужно установить соединение с папкой, из которой мы будем загружать данные. Справа от области сценариев находится кнопка с надписью «Create connection» (Создать соединение). Нажмите эту кнопку и перейдите к папке, в которой сохранен ваш CSV. Дайте этой папке имя и нажмите «Сохранить».
После того, как мы создали соединение с папкой, мы можем использовать это соединение для загрузки файлов из этой папки. Справа только что созданное нами соединение отображается вместе со значком таблицы. Нажатие на значок таблицы вызывает меню для загрузки определенного файла из созданного нами соединения. Нажмите на образец файла данных и затем кнопку «Select» (Выбрать).
Откроется мастер загрузки таблицы, который поможет нам настроить нашу загрузку. Он обеспечивает предварительный просмотр поступающих данных, а также параметры для изменения. У нас есть источник данных с разделенными запятыми значениями с метками в первой строке, которые будут действовать как имена наших столбцов. Измените настройки оператора загрузки, чтобы отразить это:
Нажмите «Insert script» (Вставить скрипт), чтобы импортировать сгенерированный скрипт загрузки в ваш редактор скриптов. Должно выглядеть примерно так:
Теперь мы можем загрузить данные в наше приложение, нажав кнопку «Load Data» (Загрузить данные) в правом верхнем углу.
Инициализация нового расширения и настройка
Теперь, когда у нас есть данные, готовые к тестированию, мы можем настроить основу для нашего нового расширения. Вместо того, чтобы создавать наш код расширения с нуля, мы будем использовать существующее расширение, которое поставляется с Qlik Sense, и модифицируем код для наших целей.
Перейдите в папку «Extensions» (Расширения). Эта папка находится в папке Мои документы\Qlik\Sense\Extensions. Вы найдете папку для каждого установленного вами расширения. Для этого урока мы продублируем расширение «SimpleTable».
Инициализация расширения
Создайте дубликат папки расширения «SimpleTable» и переименуйте его в «TwoDimScatter». Затем откройте папку. Вы должны увидеть следующие файлы:
- com-qliktech-simpletable.js – файл Javascript, который извлекает данные из Qlik Sense и создает пользовательскую визуализацию
- com-qliktech-simpletable.qext – файл свойств, который содержит такие атрибуты, как имя расширения
- simpletable.css – файл стилей, в котором мы можем определить визуальные атрибуты, такие как шрифты и цвета
- wbfolder.wbl – нам не нужно будет его менять
Обновите все имена файлов, кроме файла wbfolder.wbl, с помощью «twodimscatter», названия нашего расширения. Например, «com-qliktech-simpletable.js» должен стать «twodimscatter.js». Также нам нужно включить в эту папку файл d3.min.js, что позволит использовать библиотеку D3. Последняя версия привязана к разделу Resources выше. Полученная папка должна выглядеть примерно так:
Настройка файла свойств расширения
Откройте файл «twodimscatter.qext» в текстовом редакторе. Вы должны увидеть JSON со свойствами, такими как имя, автор и т. д. Эти значения будут видны во внешнем интерфейсе Qlik Sense, где вы выбираете объекты для добавления на лист. Обновите значения, чтобы отразить ваше новое расширение. Например, мой код такой:
[code lang="js"]{ "name" : "Two Dimensional Scatter", "description" : "A scatter plot that uses two dimensions", "icon" : "table", "type" : "visualization", "version": "1.0", "preview" : "table", "author": "Speros" }[/code]
Сохраните и закройте этот файл.
Подготовка CSS
Откройте файл «twodimscatter.css» в текстовом редакторе. Здесь мы собираемся вставить CSS, специфичный для расширения d3. Файл CSS будет содержать правила стиля, такие как размеры шрифта и цвета для элементов нашей визуализации. А пока давайте очистим содержимое этого файла и оставим только то, что нам нужно. В первой строке есть запись для «.qv-object-com-qliktech-simpletable div.qv-object-content-container». Div.qv-object-content-container — это div, который будет содержать наше расширение. Первый класс, указанный там, «.qv-object-com-qliktech-simpletable», — это класс, к которому будет применяться наш объект расширения. Мы хотим включить это различие в класс, чтобы наш CSS применялся только к элементам, которые мы создаем в своем расширении. Мы сохраним эту запись, но с 2 изменениями:
- Переименуйте первый класс из «.qv-object-com-qliktech-simpletable» в «.qv-object-twodimscatter»
- Уберите строку «overflow: auto;»
Остальную часть текста в файле CSS можно удалить. Ваш CSS файл должен выглядеть так:
[code lang="css"].qv-object-twodimscatter div.qv-object-content-container { }[/code]
Настройка файла JS
Откройте файл Javascript с помощью текстового редактора. Этот файл определяет, что делает объект расширения. Мы собираемся сначала установить начальные свойства и функции нашего расширения.
Зависимости файлов
В самой первой строке файла вы увидите оператор define(), который принимает список файлов для загрузки, а затем запускает функцию после их загрузки. Этот оператор позволяет нам загружать файлы, от которых будет зависеть наш код расширения, прежде чем мы попытаемся запустить этот код расширения. Define() является частью AMD API и доступен для нас в расширениях благодаря использованию Qlik Sense RequireJS.
Мы должны изменить этот оператор define(), чтобы получить файлы, которые нам нужны для нашего расширения: jQuery, наш файл css и наш файл d3.min.js. Измените массив определения:
["jquery", "text!./simpletable.css"]
на
["jquery", "text!./twodimscatter.css","./d3.min"]
Наш код расширения теперь загрузит эти файлы, прежде чем пытаться что-то нарисовать. Примечание: Qlik Sense поставляется с jQuery, поэтому нам не нужно хранить локальную копию в своем расширении. В своем расширении мы будем использовать jQuery, чтобы добавить нашу визуализацию в контейнер объекта расширения
Свойства и определения
После того, как мы определили необходимые файлы, нам нужно изменить свойства и определение нашего расширения. Эти свойства включают в себя ширину и длину набора данных для извлечения, а также ограничения на количество измерений и выражений, необходимых для запуска расширения.
В верхней части файла вы должны увидеть следующий код, который устанавливает эти свойства:
[code lang="js"] return { initialProperties : { version: 1.0, qHyperCubeDef : { qDimensions : [], qMeasures : [], qInitialDataFetch : [{ qWidth : 10, qHeight : 50 }] } }, definition : { type : "items", component : "accordion", items : { dimensions : { uses : "dimensions", min : 1 }, measures : { uses : "measures", min : 0 }, sorting : { uses : "sorting" }, settings : { uses : "settings", items : { initFetchRows : { ref : "qHyperCubeDef.qInitialDataFetch.0.qHeight", label : "Initial fetch rows", type : "number", defaultValue : 50 }, } } } },[/code]
В строке 2 создается объект initialProperties. В строке 10 qWidth указано, сколько столбцов должно иметь расширение. Измените qWidth с «10» на «4», так как у нас есть два измерения и две меры. Атрибут qHeight ниже указывает, сколько строк можно загрузить. Измените значение с «50» на «1000».
В строке 13 определяется объект определения. Этот объект включает измерение и пределы измерения. В строках 19 и 23 вы увидите, что минимальные измерения установлены на «1», а минимальные меры были установлены в «0». Для нашего расширения мы хотим ровно 2 измерения и 2 меры.
В строке 19 измените минимальные размеры с «1» на «2». Затем добавьте запятую и в новой строке добавьте максимальное значение «2». Сделайте то же самое для мер. Это ограничивает наше расширение необходимостью выполнения ровно 2 измерений и 2 мер. Результат должен выглядеть так:
[code lang="js"]dimensions : { uses : "dimensions", min : 2, max: 2 }, measures : { uses : "measures", min : 2, max: 2 },[/code]
В строке 32 создается объект «items». Для нашего расширения этот объект можно удалить. Нужно удалить строки с 32 по 39. Вы также можете удалить предыдущую запятую. Результат должен выглядеть так:
[code lang="js"]settings : { uses : "settings" }[/code]
Функция рисования
Наш последний шаг в настройке функции – очистить функцию рисования. Функция рисования запускается каждый раз, когда должна отображаться визуализация. Например, фильтрация данных в Qlik Sense приведет к перерисовке визуализации. Для наших целей мы можем создать следующие модификации:
- Измените функцию так, чтобы она принимала два параметра: $element и layout
function($element,layout) {
- Очистите текущее содержимое функции
function($element,layout) {}
- Выведите эти два элемента, чтобы можно было исследовать их содержание
function($element,layout) { console.log($element); console.log(layout); }
Console.log() принимает любые введенные данные и отображает их в консоль JavaScript, которую мы можем просмотреть в Qlik Sense и в веб-браузерах. Регистрируя эти элементы, мы можем использовать консоль, чтобы выяснить, что в них содержится и как мы можем использовать их для рисования нашей визуализации.
Наша функция рисования должна выглядеть так:
[code lang="js"]paint : function($element,layout) { console.log($element); console.log(layout); }[/code]
Инициализация расширения в Qlik Sense и его просмотр
Давайте сделаем перерыв в написании кода для расширения и настроим его, чтобы убедиться, что все работает. Мы также можем посмотреть объекты, которые мы ввели в консоль.
Откройте созданное приложение Qlik Sense и создайте новый лист на экране «App Overview».
Присвойте листу имя и откройте его. Сначала он будет пустым. В верхнем правом углу нажмите кнопку «Edit», чтобы войти в режим редактирования. В левой части страницы есть панель, где вы можете выбрать диаграммы для добавления на страницу. Прокрутите вниз до диаграммы двумерного рассеяния и перетащите ее на панель инструментов.
После добавления диаграммы Qlik Sense предоставит вам возможность добавить ваши измерения и меры. Добавьте «Development Group» в качестве первого измерения и «Country» в качестве второго измерения. Для измерений нам нужно будет выбрать поле, которое будет использоваться, и вычисления, которые должны быть выполнены на нем. Выберите «Ожирение в%» и «Ожидаемая продолжительность жизни в возрасте 60 лет» в качестве показателей. Для функций можно использовать «sum» или «avg». Поскольку эти значения записываются на уровне страны, мы будем суммировать или усреднять только одно число, поэтому результат будет таким же. Я предлагаю функцию avg, чтобы в случае, если данные будут реструктурированы на более детальном уровне позже, показатель все равно будет рассчитывать действительный результат.
После добавления измерений и мер мы можем настроить метки мер. На правой панели щелкните заголовок «Measure», чтобы развернуть панель. Каждая мера должна быть перечислена. Нажатие на меры расширит опции для мер, включая добавление метки. Добавьте метки, такие как «Ожирение в %» и «Ожидаемая продолжительность жизни в возрасте 60 лет». Мы будем использовать эти метки для наших осей позже.
Теперь у нас есть пустое расширение с загруженными в него примерами данных. Прежде чем мы перейдем к последнему этапу реализации кода d3, давайте посмотрим на элементы, которые мы выводим на консоль в нашем JavaScript.
Откройте представление инструментов разработчика, нажав Ctrl + Shift + правую кнопку мыши на странице и выбрав «Show Dev Tools». Выберите «Console» сверху, чтобы просмотреть консоль JavaScript. Вы должны увидеть два элемента, которые мы выводили раньше. Вы можете использовать консоль для изучения этих элементов. Например, если я открою объект макета, я смогу найти данные, которые мы хотим получить для нашего расширения:
На изображении выше показано, где хранится одна строка данных по Албании. Мы видим, что макет содержит объект с именем qHyperCube, который содержит массив с именем qDataPages, который содержит объект с массивом с именем qMatrix, который содержит массив объектов для каждой строки данных. Каждый объект в строке представляет отдельный столбец. Мы можем использовать эту информацию для написания нашего кода JavaScript, который будет собирать данные для визуализации d3.
Мы также можем найти заголовки меток в объекте макета в qHyperCube.qMeasureInfo.qFallbackTitle:
В общем, консоль является хорошим инструментом для отслеживания и отладки кода.
Вставляем и изменяем код D3
Откройте файл «twodimscatter.js» с помощью текстового редактора.
Настройка данных и контейнера div
Первое, что нам нужно сделать, это извлечь необходимую информацию из нашего расширения. Нам нужны данные для визуализации, ширина и высота объекта, с которым мы можем работать, и уникальный идентификатор нашего объекта диаграммы. Уникальный идентификатор пригодится, когда нам нужно будет создать новый элемент DOM для хранения нашей визуализации.
Теперь, когда мы использовали консоль, чтобы узнать, где находятся наши исходные данные и где находятся метки мер, мы можем построить нашу область данных в JavaScript с помощью следующих строк кода:
[code lang="js"] // получить массив данных qMatrix var qMatrix = layout.qHyperCube.qDataPages[0].qMatrix; // создаем новый массив, который содержит метки меры var measureLabels = layout.qHyperCube.qMeasureInfo.map(function(d) { return d.qFallbackTitle; }); // Создать новый массив для нашего расширения со строкой для каждой строки в qMatrix var data = qMatrix.map(function(d) { // для каждого элемента в матрице создаем новый объект со свойство // для измерения группировки, первой метрики и второй метрики { "Dim1":d[0].qText, "Metric1":d[2].qNum, "Metric2":d[3].qNum } }); [/code]
Теперь у нас есть массив measureLabels, который содержит текстовые значения наших меток мер, и у нас есть массив данных с нашими данными в нем. Обратите внимание, что мы не храним оба измерения в этом массиве; мы оставляем только исследуемую группу, которая используется для окрашивания точек. Это должно имитировать данные, которые принимает код d3 диаграммы рассеяния; это не требует от нас перечислять идентификаторы каждой записи, которые в нашем примере являются названиями страны.
Теперь давайте захватим ширину, высоту и идентификатор объекта диаграммы. Как только мы получим эту информацию, мы будем использовать jQuery для добавления нового контейнера div на график, который будет содержать нашу визуализацию d3. Если div уже существует, мы очистим его содержимое, чтобы перерисовать диаграмму с нуля.
[code lang="js"] // Ширина объекта диаграммы var width = $element.width(); // Высота объекта диаграммы var height = $element.height(); // Идентификатор объекта диаграммы var id = "container_" + layout.qInfo.qId; // Проверяем, был ли элемент диаграммы уже создан if (document.getElementById(id)) { // если он был создан, очистите его содержимое, чтобы мы могли перерисовать его $("#" + id).empty(); } else { // если он не был создан, создайте его с соответствующим идентификатором и размером $element.append($('<div />').attr("id", id).width(width).height(height)); }[/code]
Наконец, давайте назовем нашу функцию viz, которая фактически будет содержать наш код d3. Мы будем держать функцию viz отдельно от функции рисования для организационных целей.
[code lang="js"]viz(data,measureLabels,width,height,id);[/code]
Полная функция рисования должна выглядеть так:
[code lang="js"] paint : function($element,layout) { // получить массив данных qMatrix var qMatrix = layout.qHyperCube.qDataPages[0].qMatrix; // создаем новый массив, который содержит метки меры var measureLabels = layout.qHyperCube.qMeasureInfo.map(function(d) { return d.qFallbackTitle; }); // создаем новый массив для нашего расширения со строкой для каждой строки в qMatrix var data = qMatrix.map(function(d) { // для каждого элемента в матрице создаем новый объект со свойством // для измерения группировки, первой метрики и второй метрики return { "Dim1":d[0].qText, "Metric1":d[2].qNum, "Metric2":d[3].qNum } }); // Ширина объекта диаграммы var width = $element.width(); // Высота объекта диаграммы var height = $element.height(); // Идентификатор объекта диаграммы var id = "container_" + layout.qInfo.qId; // Проверяем, был ли элемент диаграммы уже создан if (document.getElementById(id)) { // если он был создан, очистите его содержимое, чтобы мы могли перерисовать его $("#" + id).empty(); } else { // если он не был создан, создайте его с соответствующим идентификатором и размером $element.append($('<div />;').attr("id", id).width(width).height(height)); } viz(data,measureLabels,width,height,id); }[/code]
Мы можем создать нашу новую функцию viz в нижней части нашего JS-файла вне оператора define(). Нам нужно определить его с соответствующим количеством входов
[code lang="js"]var viz = function(data,labels,width,height,id) { };[/code]
Копирование кода D3
Давайте вернемся к примеру с d3, который мы хотим использовать, и скопируем JavaScript. Часть JavaScript-файла находится внутри html-файла, внутри тега <script> в теле. Мы хотим скопировать фрагмент от начала оператора «var margin» до начала оператора «</script>». Не копируйте строку «</script>»; только до линии перед ней. Затем мы можем вставить этот код в нашу пустую функцию viz. Код должен выглядеть так:
[code lang="js"]var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scale.linear() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var color = d3.scale.category10(); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); d3.tsv("data.tsv", function(error, data) { data.forEach(function(d) { d.sepalLength = +d.sepalLength; d.sepalWidth = +d.sepalWidth; }); x.domain(d3.extent(data, function(d) { return d.sepalWidth; })).nice(); y.domain(d3.extent(data, function(d) { return d.sepalLength; })).nice(); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text("Sepal Width (cm)"); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("class", "label") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Sepal Length (cm)") svg.selectAll(".dot") .data(data) .enter().append("circle") .attr("class", "dot") .attr("r", 3.5) .attr("cx", function(d) { return x(d.sepalWidth); }) .attr("cy", function(d) { return y(d.sepalLength); }) .style("fill", function(d) { return color(d.species); }); var legend = svg.selectAll(".legend") .data(color.domain()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", color); legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; }); });[/code]
Изменение кода D3
Теперь нам нужно внести некоторые изменения в код D3, чтобы он соответствовал нашему коду расширения. Должны быть выполнены следующие изменения:
- Мы можем удалить часть по загрузке данных кода D3. Наши данные уже загружены через расширение.
- Измените жестко заданную ширину и высоту, чтобы использовать наши входные значения
- Добавьте наш элемент svg, который будет содержать нашу диаграмму, к элементу div, который мы создали вместо тела html.
- Измените все ссылки на данные из исходного набора данных для ссылки на столбцы в нашем наборе данных расширения
- Удаление загрузки данных D3
В примере кода d3 примерно на полпути вниз есть строка,
[code lang="js"]d3.tsv("data.tsv", function(error, data) { data.forEach(function(d) { d.sepalLength = +d.sepalLength; d.sepalWidth = +d.sepalWidth; });[/code]
Функция d3.tsv принимает два параметра: путь к файлу и функцию обратного вызова для выполнения. Функция обратного вызова запускает код построения визуализации после загрузки файла «data.tsv». Мы хотим удалить этот вызов, а также оператор data.forEach под ним. Наша переменная данных уже заполняется, когда мы вызываем функцию viz в нашем методе рисования, поэтому нам не нужно ее изменять. Обратите внимание, что функция обратного вызова инкапсулирует не только этот оператор data.forEach, но и остальную часть кода визуализации. Поэтому, когда мы удаляем этот раздел, мы также должны удалить закрывающий «});» в конце нашего кода визуализации, который заканчивается после оператора legend.append («text»). Результат должен выглядеть примерно так:
[code lang="js"]var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scale.linear() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var color = d3.scale.category10(); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); x.domain(d3.extent(data, function(d) { return d.sepalWidth; })).nice(); y.domain(d3.extent(data, function(d) { return d.sepalLength; })).nice(); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text("Sepal Width (cm)"); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("class", "label") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Sepal Length (cm)") svg.selectAll(".dot") .data(data) .enter().append("circle") .attr("class", "dot") .attr("r", 3.5) .attr("cx", function(d) { return x(d.sepalWidth); }) .attr("cy", function(d) { return y(d.sepalLength); }) .style("fill", function(d) { return color(d.species); }); var legend = svg.selectAll(".legend") .data(color.domain()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", color); legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; });[/code]
- Обновление значений ширины и высоты
Вторая и третья строки кода D3 указывают, что ширина и высота должны быть жестко закодированы значениями минус размер полей. В этом случае ширина и высота кодируются до 960 и 500. Мы можем заменить это нашими входными значениями ширины и высоты:
[code lang="js"] width = width - margin.left - margin.right, height = height - margin.top - margin.bottom;[/code]
- Добавление элемента svg в наш контейнер
В строке var svg код d3 добавляет новый элемент svg в тело страницы. Мы можем изменить этот оператор так, чтобы код d3 выбирал созданный нами контейнерный div и вместо него добавлял новый svg.
[code lang="js"]var svg = d3.select("#"+id).append("svg")[/code]
- Обновление ссылки на данные, чтобы использовать правильные имена свойств
Если мы рассмотрим код D3, то увидим, что разделы, которые рисуют визуальные элементы на основе данных, ссылаются на заголовки столбцов исходной таблицы данных. Например:
[code lang="js" highlight="6,7,8"]svg.selectAll(".dot") .data(data) .enter().append("circle") .attr("class", "dot") .attr("r", 3.5) .attr("cx", function(d) { return x(d.sepalWidth); }) .attr("cy", function(d) { return y(d.sepalLength); }) .style("fill", function(d) { return color(d.species); });[/code]
Нам нужно заменить все ссылки в коде на d.sepalWidth на d.Metric1; d.sepalLength до d.Metric2 и d.side до d.Dim1.
Метки для осей также жестко закодированы, как в определении оси X, где для .text() задана строка «Sepal Width (cm)»:
[code lang="js" highlight="10"]svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text("Sepal Width (cm)");[/code]
Вместо жесткого кодирования меток для наших осей мы можем использовать введенную нами переменную для динамического применения соответствующих меток. Переменная метки – это массив, первый элемент которого является меткой для метрики 1, а второй элемент – меткой для метрики 2. Мы можем переписать ось X с помощью этой переменной:
[code lang="js" highlight="10"] svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text(labels[0]);[/code]
Результирующая функция viz со всеми этими обновлениями должна выглядеть следующим образом:
[code lang="js" highlight="4,5,29,30,41,52,59,60,61"]var viz = function (data,labels,width,height,id) { var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = width - margin.left - margin.right, height = height - margin.top - margin.bottom; var x = d3.scale.linear() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var color = d3.scale.category10(); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var svg = d3.select("#"+id).append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); x.domain(d3.extent(data, function(d) { return d.Metric1; })).nice(); y.domain(d3.extent(data, function(d) { return d.Metric2; })).nice(); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text(labels[0]); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("class", "label") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text(labels[1]) svg.selectAll(".dot") .data(data) .enter().append("circle") .attr("class", "dot") .attr("r", 3.5) .attr("cx", function(d) { return x(d.Metric1); }) .attr("cy", function(d) { return y(d.Metric2); }) .style("fill", function(d) { return color(d.Dim1); }); var legend = svg.selectAll(".legend") .data(color.domain()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", color); legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; }); }[/code]
Изменение CSS
Последний шаг в настройке нашего расширения – изменение CSS. Если вы посмотрите на HTML-код исходной диаграммы d3, верхняя часть кода содержит тег стиля со следующими значениями:
[code lang="css"]body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .dot { stroke: #000; }[/code]
Мы можем изменить этот код и поместить его в наш файл twodimscatter.css. Мы должны внести следующие изменения:
- Измените стиль «body», чтобы применить к нашему объекту контейнера вместо него
- Добавьте класс «.qv-object-twodimscatter» чтобы наш CSS не влиял ни на какие элементы вне нашего объекта диаграммы, которые имеют классы, такие как «dot», или элементы, такие как пути и линии.
Добавление этих изменений приводит к файлу twodimscatter.css, например, так:
[code lang="css"].qv-object-twodimscatter div.qv-object-content-container { font: 10px sans-serif; } .qv-object-twodimscatter .axis path, .qv-object-twodimscatter .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .qv-object-twodimscatter .dot { stroke: #000; }[/code]
Просмотр расширения
После внесения этих изменений мы можем сохранить все наши файлы и просмотреть изменения в нашем приложении. Откройте приложение Qlik Sense и перейдите к листу с объектом расширения. Если лист уже открыт, нажмите «F5», чтобы обновить страницу. Расширение теперь будет отображать диаграмму рассеяния с цветной легендой. Вы можете изменить лист, чтобы добавить списки и другие диаграммы, чтобы использовать ассоциативную модель Qlik Sense с этим расширением d3.
Следующие шаги
Мы можем расширить это расширение, изменив файл JavaScript, добавив интерактивные компоненты, такие как выбор Qlik Sense и создание всплывающих ярлыков для точек с названиями их стран. В следующих статьях будут рассмотрены эти улучшения.
Весь исходный код этого расширения можно найти здесь.
Дополнительные ресурсы
Если вы хотите узнать больше о любом из компонентов этого решения, вот несколько полезных ресурсов:
- Qlik Sense
- JavaScript
- D3
- Учебный центр по технологиям анализа данных и BI: расписание/запись на учебные курсы, тестирование разработчиков — https://education.biconsult.ru/
- Присоединяйтесь к QUBIC – сообщество профессионалов в области BI! Наши страницы в соц.сетях – расписание учебных курсов, бесплатные учебные материалы, анонсы мероприятий: https://vk.com/club165575964 и https://www.facebook.com/qubicspb
- Неофициальный форум разработчиков QlikView & Qlik Sense Russian forum
- Канал на Youtube – много обучающих видео и записи вебинаров
- Готовые решения “Конструктор финансовой отчетности” и “Анализ продаж”