How to Develop Qlik Sense Extensions from D3.js: Importing the D3 visualization into Qlik Sense

    1 - Introduction

    2 - Testing the D3 visualization in HTML

    [3 - Importing the D3 visualization into Qlik Sense]

    4 - Customizing the D3 with Qlik's data

    5 - Drawing a second indicator as a reference line [coming soon]

    6 - Creating Custom Settings by editing CSS from Javascript [coming soon]



    3 - Importing the D3 visualization into Qlik Sense:

    Now let's open the speedometerd3twoindicators.js file and explore its structure. Starting from the top, we'll first define what files we want to load in before executing the rest of the Javascript code, as some of our code will be dependent on these resources, such as the D3 library. These resources need to be in the extension's main folder, unless otherwise specified in their path written in the "define" part. Here are the files we'll be using:

    define(["jquery",
            "text!./speedometerd3twoindicators.css",
            "text!./googleplay.css",
            "./d3.min",
            "./iopctrl",
            "./pointergestures",
            "./pointerevents"
           ],
    
    
    
    
    

     

    The main properties:

    Next, we'll be setting the extension's hypercube's settings, how much data do we want it to pull from the Qlik Sense engine? The maximum number of cells a normal extension can load is 10,000, so the product of the qWidth and qHeight should not exceed that. If we need to pull more data than 10,000 cells, we'll use specific functions to do that (another article coming soon). Fortunately, with a speedometer, we only need one cell for one KPI, so as long as our qWidth/qHeight are beyond that, we should be fine. It is noteworthy that this part gets read by Qlik Sense only once a new object is created, as opposed to refreshing it every time we press F5 (while DevTools is open) like the rest of the code. So if you change the settings of the initial datafetch, you'd have to delete the current extension instance object from your Qlik Sense app's sheet and create a new instance to see the changes.

     

    define(["jquery",
            "text!./speedometerd3twoindicators.css",
            "text!./googleplay.css",
            "./d3.min",
            "./iopctrl",
            "./pointergestures",
            "./pointerevents"
           ],
           function($, cssContent) {
       'use strict';
        $("<style>").html(cssContent).appendTo("head");
    
       return {
            initialProperties : {
                qHyperCubeDef : {
                    qDimensions : [],
                    qMeasures : [],
                    qInitialDataFetch : [{
                        qWidth : 10,
                        qHeight : 1000
                    }]
                }
            },
            definition : {
                type : "items",
                component : "accordion",
                items : {
                    dimensions : {
                        uses : "dimensions",
                        min : 0,
                        max : 0
                    },
                    measures : {
                        uses : "measures",
                        min : 0,
                        max : 0
                    },
                    sorting : {
                        uses : "sorting"
                    },
                    //Custom Settings
                    settings : {
                        uses : "settings",
                        items : {
                        }
                    }
                }
            },
           snapshot : {
                canTakeSnapshot : true
            },
            paint : function($element,layout) {
                console.log($element);
                console.log(layout);
            }
        };
    });
    
    
    
    
    

     

    Next, we'll be defining the main properties of the extension, including the max/min number of dimensions and measures the extension will allow, if we want to specify sorting settings, and if we want to add specific settings such as text, dropdowns, or expressions the user can add (in the Appearance part in the bottom right side in Edit mode). We'll be including the settings here dynamicaaly in our extension's code. Right below the definition is a snapshot component, allowing or disallowing snapshots for Stories.

     

    The paint function:

    The paint function is the meat of any extension, it's what gets called every time an app is refreshed, when a selection is made, when the extension instance is resized, etc. It's where most of the code lies, where we'll pull the data from Qlik's engine as well as call a new visualization, so that every time the object refreshes for any reason (selections, resizing, etc.) it's both pulling new data and drawing a new visualization. It takes two parameters, $element and layout. $element is what holds the object's properties, such as its width, its height, etc. layout is what holds the object's ID as well as its input data of dimensions and measures, their labels, and the custom settings selected by the user. The data in the extension's hypercube (that we pulled earlier) will include the selected portion of the dimensions and measures specified in the extension's settings in Edit mode. For now, we'll just have a couple of if-else statements checking if the object's id already exists (meaning the object has already been created but we just need to repaint it using our new data/visualization). If it already exists, we'll empty the contents of that ID so we can repaint it. Otherwise if we can't find the ID, we'll create a new div with our object's ID that came from layout, with the width/height that came from $element:

     

                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));
                }
    
    
    
    
    

     

    Now that we understand the basic structure of any extension, let's import our D3 code in! We'll create a new function after the very end of our existing code that will hold the D3 visualization. The function will only need the object's id for now, we just want to test if it works in Qlik Sense. We'll copy everything between the <script> tags in our Test.html file into that function. The only change we need to make for now is on the very first line where we'll need to append our visualization to our extension instance's div ID, instead of its default body. Our D3 function will look like this:

     

    var vizwithoutref = function(id) {
        var svg = d3.select("#"+id)
        .append("svg")
        .attr("width", 400)
        .attr("height", 400);
    
      var gauge = iopctrl.arcslider()
        .radius(120)
        .events(false)
        .indicator(iopctrl.defaultGaugeIndicator);
        gauge.axis().orient("in")
            .normalize(true)
            .ticks(12)
            .tickSubdivide(3)
            .tickSize(10, 8, 10)
            .tickPadding(5)
            .scale(d3.scale.linear()
                   .domain([0, 160])
                   .range([-3*Math.PI/4, 3*Math.PI/4]));
    
       var segDisplay = iopctrl.segdisplay()
           .width(80)
           .digitCount(6)
           .negative(false)
           .decimals(0);
    
      svg.append("g")
            .attr("class", "segdisplay")
            .attr("transform", "translate(130, 200)")
            .call(segDisplay);
    
       svg.append("g")
            .attr("class", "gauge")
            .call(gauge);
    
       segDisplay.value(56749);
        gauge.value(92);
    }
    
    
    
    
    

     

    We'll then simply call this visualization function from our paint function:

     

    vizwithoutref(id);
    
    
    
    
    


    Before we run off to check what our extension looks like, let's copy our CSS code in the original.css file to our extension's speedometerd3twoindicators.css. To make it easier in the future to find our CSS rules, we'll add the extension's name to each property's name:


    .qv-object-speedometerd3twoindicators div.qv-object-content-container {
        font: 16px arial;
        background-color: #515151;
    }
    .qv-object-speedometerd3twoindicators .unselectable {
        -moz-user-select: -moz-none;
        -khtml-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }
    /* css formats for the gauge */
    .qv-object-speedometerd3twoindicators .gauge .domain {
        stroke-width: 2px;
        stroke: #fff;
    }
    .qv-object-speedometerd3twoindicators .gauge .tick line {
        stroke: #fff;
        stroke-width: 2px;
    }
    .qv-object-speedometerd3twoindicators .gauge line {
        stroke: #fff;
    }
    .qv-object-speedometerd3twoindicators .gauge .arc, .gauge .cursor {
        opacity: 0;
    }
    .qv-object-speedometerd3twoindicators .gauge .major {
        fill: #fff;
        font-size: 20px;
        font-family: 'Play', verdana, sans-serif;
        font-weight: normal;
    }
    .qv-object-speedometerd3twoindicators .gauge .indicator {
        stroke: #EE3311;
        fill: #000;
        stroke-width: 4px;
    }
    /* css formats for the segment display */
    .qv-object-speedometerd3twoindicators .segdisplay .on {
        fill: #00FFFF;
    }
    qv-object-speedometerd3twoindicators .segdisplay .off {
        fill: #00FFFF;
        opacity: 0.15;
    
    
    
    
    

     

    Now our extension is ready! Link to our extension so far. Open a new sheet in an app, drag and drop the extension and if there aren't dimensions/measures required, you should see the speedometer we had in our Test.html file!

     

    2016-06-12 12_58_48-Qlik Sense Desktop.png

     

    In the next section, we will be injecting Qlik's selected data into the visualization, so the visualization can change every time a selection is made.