Step3- Making it a bit slicker

Introduction

OK we have a chart built and it is working from Qlik data, but let’s just make a few other changes to make it look a little nicer.
Make a copy of the folder we have just used and call this whatever you like, but remember to change the name in the required places!
These are the things we’re going to do:
1- Change our bar background colour
2- Put our text ON the bar rather than after it
3- Add a checkbox to round the data for the user
4- Use the divname rather than innerHtml
5- Make everything size properly

Change the bar colour

If you remember, we’ve got a style.css file which is perfect for this situation.
As I said I don’t have much web experience so it took me a little trial and error to get this one to work and it is definetly the one area I need to go back and understand better.
But basically as we have a class of chart, we need to initiate this and then we can apply a style to the .bar rect object using this code in style.css

<style>
.chart {
}
.chart rect {
fill: steelblue;
}
</style>

Hopefully your object now looks like this (Remember you can just hit F5 to refresh the object in Qlikview)

qv1

Put the text ON the bar

We can clean up the chart a bit more by putting the data label on the bars rather than on the end.
Again we can do this in css using the anchor tag on the text which is done by adding this simple code in the css file

.chart text {
text-anchor: end;
}

Hopefully your chart now looks like this

qv2

We’re getting there but we might want to change the font and the colour, which again is really simple expansion of the existing code for the text

 
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}

qv3

This is now a bit easier to read but the last figure is a bit tricky to ready so lets change our script.js slightly to offset it. We just need to alter the x attribute of our bar text so find the section of code

bar.append("text")
.attr("x", function(d) { return x(d) ; })
.attr("y", (barHeight-1) / 2)
.attr("dy", ".35em")
.text(function(d) { return d; });

and just change the second line and offset by 5 to the end of the bar

.attr("x", function(d) { return x(d)-5 ; })

qv4

Use a checkbox to allow the user to round

This one is just a bit of a random example, obviously you’d normally do this a different way but this shows you how you can activate another property of the chart and read it in to the script.
There are two steps to this process
1- Add the checkbox to the definition xml file (read here QlikView Extension Definition file )
2- Call and use this in the script file.
Un-comment this line and change the label to something more obvious

<!--<Text Label="Checkbox 1" Type="checkbox" Initial="" />;-->
<Text Label="Round to 2dp?" Type="checkbox" Initial="" />;

You should now see the option if you right click on the chart and go to properties

qv5

As you can see from the line in the definition, this is just a text object of type checkbox.
So to use this within our code we just need to declare a new variable and load the value which is done with this simple line of code

var checkbox1 = _this.Layout.Text0.text.toInt();

*note* At this point I spent the best part of an hour trying to work out why adding this line of code stopped my chart generating. In the end closing Qlik and re-opening it made it work, so I assume something wasn’t loading correctly when I was refreshing. Just keep this in mind, my pain is your gain!

If like me you like to check things are working, then you can add this little bit of code in just below the variable declaration to check:

window.alert(checkbox1);

as you check and un-check the checkbox it should refresh and alert you to the value which we can see is just a binary.

Finally we need to use this in our code to either round or not depending on what the user has selected.
In this case we can just do a straight forward if statement and I’ve chosen to add this when we build the data array.
This is the code used:

for (var f=0; f > _this.Data.Rows.length; f++ ){
var row = _this.Data.Rows[f];
//Dimension 1 : Defined in the Definition.xml
var dim1 = row[0].text;
//measure 1 : Defined in the Definition.xml
if(checkbox1==0){
var measure1 = row[1].text;
}
else{
var measure1 = parseFloat(row[1].text).toFixed(2);
}
textarray = textarray.concat(dim1);
}

You can see here all we have done is alter our data call by wrapping the measure1 call in an if statement.
So if the user hasn’t checked the checkbox

if(checkbox1==0){

Then load the data normally

var measure1 = row[1].text;

Otherwise the user must have checked the box so parse the data to a float and round it to 2dp

var measure1 = parseFloat(row[1].text).toFixed(2);

And after reloading our extension, hey presto!

qv6

qv7

Use the divname & append the svg rather than innerHTML

The final step is just a nicety in coding terms. Rather than using innerHTML as we learned in the examples on The Qlik Fix we can simply use the unique div name that we’ve generated as a part of the template. This ensures that we don’t clash with any other extensions that might exist.
To do this we simply:
1- Remove the line:

 _this.Element.innerHTML= '<svg class="chart"></svg>';

2- Alter our D3 select from this

 var chart = d3.select(".chart") to this var chart = d3.select("#"+divName)

However our extension doesn’t know that it is an svg now so we also have to add this line to our chart object

.append("svg")
select("#"+divName)
.append("svg")
.attr("width", width)
.attr("height", barHeight * dataarray.length) ;

Refresh your object and oh no our formatting has gone!

Why? Well quite simply the chart no longer knows it is a chart class, it only knows itself by the divName.
Therefore one final quick attribute to add to our chart is the final piece of the puzzle

.attr("class", "chart")

Making everything size properly

At the moment our chart size isn’t linked to the system in any way. I am sure you have noticed that if you re-size your object window in Qlik it is possible to loose the bars as they don’t move at all.
There are a few little tweaks we need to make to the code to do this.

Firstly lets change the variables for width and barHeight to this:

var width = _this.GetWidth(),
barHeight = _this.GetHeight()/dataarray.length;

All this does is dynamically get the actual height and width of the extension object (remember back when we set it to _this) obviously the bar height needs to be divided by the number of bars we are expecting.
Now, save this and refresh your extension. If you’re unlucky then you’ll see that your bars still don’t fit in the window. It took me a while to figure this out, but it is a handy example of debugging other people’s code.
This is how my chart now looks:

qv8

What I noticed was that no matter how I resized it, the bar for 83.63 was always full width.
Now the code for scaling the bar width is this:

var x = d3.scale.linear()
.domain([0, d3.max(dataarray)])
.range([0, width]);

Ok so why doesn’t this work? It should just be converting the domain to the range fine, the code looks great.
Try adding this line below this code and refreshing your app

window.alert(d3.max(dataarray));

qv9

Wait what the damn hell? We know that 83.63 definitely isn’t the maximum value as we can clearly see we have bars which are longer than that.

OK so let’s just check one more thing, add this little line of code and again refresh the app

window.alert(typeof(d3.max(dataarray)));

qv10

Ah! Now we have a good idea why our max function isn’t working. Until this point it didn’t really matter as we hadn’t linked the width of the bar to the data, we had just fixed it.

I think we can comfortably assume that the max function isn’t going to work with a string, so a little bit of a conversion is needed here.

Note that this error happens regardless of our selection in the rounding checkbox, despite the fact we are parsing the data to a float if we want to round it. This is because we’re also using the .toFixed function which actually converts it back to a string.

Someone might be able to comment on the best way of doing this, there are a couple that I have found.
The first is simply lets re-parse both of our methods of loading measure1,if you note I have just added parseFloat to the first method and added a second one to the second method

if(checkbox1==0){
var measure1 = parseFloat(row[1].text);
}
else{
var measure1 = parseFloat(parseFloat(row[1].text).toFixed(2));

If we refresh our app we can see that this now works fine (rounding or not):

qv11

qv12

The second method is just to change the formula for finding the maximum value.

var x = d3.scale.linear()
.domain([0, Math.max.apply(Math, dataset)])
.range([0, width]);

Out of interest I did test both methods by upping my autogenerate to 1,000 rows, adding rowno() in to the table and then using this as the dimension to see if there was any performance hit or not.
Note I have included my test file in the qar and you can comment / uncomment the code in the script file as you see fit.

I couldn’t actually notice any performance difference between the two but to my mind it just seems correct to use the first method and make sure the array has the type that we want it to have.
The final thing we could do is add in some space to ‘pad’ the extension out a bit so it isn’t touching the edges.

We can do this with a new object called padding, which is really handy as with an object we can create a bunch of properties which can be called individually.

var padding = {top: 20, right: 20, bottom: 40, left: 40};

In this example I have made the bottom and left margins slightly bigger so that we can add our axis in the next chapter.
Now all we need to do is tell the system to use these margins in the appropriate places as below:
In our core width and bar height variables:

var padding = {top: 20, right: 20, bottom: 40, left: 40},
width = _this.GetWidth() - padding.right - padding.left,
barHeight = (_this.GetHeight() - padding.top - padding.bottom)/dataarray.length;

Now for our main chart object we can get clever with the SVG “g” element and there is a very good explanation of this here which I suggest reading: https://www.dashingd3js.com/svg-group-element-and-d3js
So all we need to do is append a g to our chart and all subsequent objects should follow the transform rule we apply to it:

var chart = d3.select("#"+divName)
.append("svg")
.attr("width", width)
.attr("height", barHeight * dataarray.length)
.attr("class", "chart")
.append("g")
.attr("transform", "translate(" + padding.left + "," + padding.top + ")");

And hey presto your chart should now be padded in the qlikview object:

qv13

Supporting Files

03 Making It Slicker

Leave a Reply