The problem, the solutionRecently, while making a mashup in Angular.js using the Capability APIs I noticed that my UI was performing very poorly. In particular, making a selection in one of my custom selection boxes was basically freezing the up the entire UI for up to a few seconds! After poking around a bit, I discovered the problem: there was just too many dom elements being watched for changes.You may have seen this problem yourself if you've ever tried to use Angular.js to create a mashup. And this problem isn't inherent to only mashups; the same problem applies to extensions. So how do we deal with it? We can use virtual scrolling.Virtual scrolling is a way to render and watch only the dom elements that are currently in view, and changing those elements to other elements as we scroll, as opposed to actually rendering and watching all of the elements. The Qlik Sense client already does this, which, in a minute, you'll see we're going to use to our advantage.Here's two examples to show you just what I mean. You can see that the render time is much faster on the page that implements virtual scrolling, and that the UI responsiveness when making selections on the Case IDs is drastically better.Virtual Scrolling Example No Virtual Scrolling ExampleSo how can you implement virtual scrolling in your mashup or extension? I'm going to walk through an example implementing virtual scrolling in a custom listbox for a mashup, but the same idea can be applied to extensions as well.Using what's already thereRemember I said that the Qlik Sense client already implements virtual scrolling, and that we can use that to our advantage? We're simply going to make use of the same library that Qlik Sense objects already use to implement virtual scrolling. When building mashups or extensions, it's possible to use any library the client is already using. To see the full list, you can go to the "Menu" in the Qlik Sense client and click "About," then "Third-party Software."We can see in the images above that something called "angular-vs-repeat" is used by Qlik Sense, and, in this case, that's what we're after. The angular-vs-repeat library is an angular directive for virtual scrolling.It's super easy to include this in your Angular.js based mashup.
var app = angular.module('app', ['vs-repeat']);
That's all you need to include it, since it's already loaded with the Capability APIs.Implementing virtual scrollUsing it is pretty simple, too. In the most simple of cases, all you need to do is include the vs-repeat directive on the direct parent of an element with ng-repeat. In our example, that looks like this:
<ul vs-repeat class="cases-container">
<li ng-repeat="case in cases" ng-click="selectCase(case)" class="{{case.qState}} case">{{case.qText}}</li>
</ul>
Resources and wrap-upAnd that's basically the idea. The entire code for the example mashup is attached, and the link to the angular-vs-repeat repository on github is below. Hope this helps you improve the performance of your mashups and extensions https://github.com/kamilkp/angular-vs-repeat
...View More
The Lookup function is a script function that allows you to look up and return the first occurrence of a value in a field that has already been loaded in the script. The lookup can occur in the current table or a previously loaded table. Here is the syntax:lookup(field_name, match_field_name, match_field_value [, table_name])The first parameter field_name is the field from which the returned value will come from. The field_name must be entered as a string so enclose the field name in single quotes.The second parameter match_field_name is the field where you will be looking up the value. This parameter also needs to be entered as a string. This parameter and the first parameter, field_name, must be fields in the same table in order for the Lookup function to work.The third parameter match_field_value is the value that you are looking for in the match_field_name field.The fourth and last parameter is the table_name. This is an optional parameter. If the lookup is occurring in the current table that is being loaded, then this parameter can be omitted. If the lookup is in another previously loaded table, then this parameter should be the table name enclosed in single quotes.So, let’s take a look at a small example of the Lookup function. In the script below, the first two inline load scripts load a ProductData and a CustomerData table. The loading of the Temp table is where we can see the Lookup function in action. In this example, I am looking up the customer name with a specified customer ID. The Lookup function will return the value of the Customer field (first occurrence) from the ProductData table where the loaded CustomerID matches the value in the CustomerID field in the ProductData table.Once this script is run, the Temp table looks like this:The field CustomerName has the customer name that corresponded to the first occurrence of the CustomerID being loaded in the Temp table. This value was captured by looking it up in the ProductData table.If the Lookup function does not find a match, null will be returned. The Lookup function is one line of code that is fairly easy to add to your script to look up a value in a field but it has some limitations. First, the order of the search is the load order. You are not able to sort the data so the first occurrence will be based on the load order of the value. Second, the Lookup function is not as fast as the ApplyMap function. While the Lookup function is flexible and easy to use once you know the parameters, ApplyMap should be your first choice when you need to look up a value based on the content of a field. You can read more about the ApplyMap function in the blogs listed below:Mapping … and not the geographical kindDon't join - use Applymap insteadThanks,Jennell
...View More
It’s been a little bit over a year since last time I posted about extensions here so I thought it would be nice to update this topic with fresh extensions submitted to our developer community Qlik Branch. Remember you can submit, contribute to others, and download extensions from Qlik Branch.My favorite 5 extensions (as of April, 2016)1. qsVariable by erikwettqsVariable is a super useful UI variable handler. The extension will let you not only create a variable while you set it up but also it will let you add a UI layer (buttons, selectors, input fields and sliders) so users can interact with your variable(s).2. Qlik Sense Trellis Chart by agilos.mlaTrellis or more commonly known as Small Multiples (by Edward Tufte) is one of the well-known techniques available to represent the same measure across two dimensions. It’s especially useful when it comes to compare values in across charts using the same scale.The extension uses the power of D3 to represent the data letting you to pick from pie, bar, or line chart. At this point the extension still needs some work to make it mobile friendly but it's a very promising starting point.3 Simple KPI by alex.nerushSimple KPI is all about flexibility and options, it's a great example of how customizable an extension can be. Simple KPI will let you create your very own style KPI object adding dimensions, setting up conditional colors and fonts and many more options. Give it a try4 Measure Builder by LorisLombardo87Creating complex expressions is sometimes a bit tricky, especially for new users so any help can make a difference. Measure Builder is a wizard style editor extension that will let users to create expressions easily by just simple completing a step by step form in a very visual way. It's really helpful to understand and learn set analysis syntax so if you are new to Qlik I would always recommend you to get this one installed on your computer.5 Circular KPI by JSNWould you like to use KPI to show target achievements but your values typically are over 100% making classic progress bar useless? Well, then you may want to try Circular KPIs, it supports percentage (%) KPIs up to 300% in a very smart way. Simple but fun extension.If you are using any other extension that you think may be worth it to share with the community please let us all know in the comment section!Enjoy Qliking!AMZ
...View More
There were times in my Qlik Demo Team projects that the objects coming from the Capabilites Api were not enough and we wanted a custom chart in our website like the one we did for PGA Championship Data VisualizerIn this tutorial I will just add the D3 library (https://d3js.org/) and create a simple bar chart having loaded Angularjs and the Capabilities API. At this point, I assume you have read the first article on setting the template up with Angularjs and the Capabilities API Creating a website with Angular and the Capabilities APIWe will use the same template and the same qvf.First, we will add d3 into bower.json"d3": "3.5.9"from the command line, we need to run bower installthen, load it from js/lin/main.js L7'd3': scriptsUrl + 'js/vendor/d3/d3.min',and L83 add 'd3', copy a controller and name it d3.js (controllers/d3.j)smake you sure you name it as 'controller.d3 in L12'add it to main.js'controller.d3': scriptsUrl + 'js/controllers/d3',and load it in L87 'controller.d3',copy a view and name it as d3 (views/d3.html)Add the html holder
<div class="row">
<div class="col-md-12">
<div id="chart"></div>
</div>
</div>
in order to get the data from the qvf as a hypercube, we just add in d3.js the function, passing the dimension and measure as the default chart:
me.getData = function() {
api.getHyperCube(['Case Owner Group'], ['Avg([Case Duration Time])'], function(data){
me.createBarChart(data);
});
}
Below is the code that will generate the Bar chart with the labels. I am borrowing the code from the barchart extension that I created and can be found on Branch Qlik Branch
me.createBarChart = function (data) {
var vars = {
id: 'chart',
data: data,
height: 300,
width: 500,
bar: {
height: 35,
padding: 3,
border: 1,
color: '#4477AA',
colorHover: '#77b62a',
borderColor: '#404040'
},
label: {
visible: true,
width: 200,
padding: 15
},
footer: {
visible: true,
height: 20
},
canvasHeight: null,
template: '',
};
var element = $('#'+vars.id);
vars.data = vars.data.map(function(d) {
return {
"dimension":d[0].qText,
"measure":d[1].qText,
"measureNum":d[1].qNum,
"qElemNumber":d[0].qElemNumber,
}
});
var dMax = d3.max(vars.data, function(d) { return d.measureNum; });
vars.canvasHeight = (vars.data.length * (vars.bar.height+(vars.bar.padding*2)+3));
vars.template = '\
<div class="barchart" id="barchart">\
<div class="content"></div>\
';
if (vars.footer.visible) {
vars.template += '<div class="footer"></div>';
};
vars.template += '</div>';
element.html($(vars.template).width(vars.width).height(vars.height));
if (vars.footer.visible) {
$('#' + vars.id + ' .content').height(vars.height-vars.footer.height);
$('#' + vars.id + ' .footer').height(vars.footer.height);
} else {
$('#' + vars.id + ' .content').height(vars.height);
}
var x = d3.scale.linear()
.domain([0,dMax])
.range([0, (vars.label.visible)?vars.width-vars.label.width-(vars.label.padding*2):vars.width]);
var y = d3.scale.linear()
.domain([0,vars.data.length])
.range([10,vars.canvasHeight]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickSize(1)
.tickFormat(function(d,i){
return vars.data.dimension;
})
.tickValues(d3.range(vars.data.length)); //1167
var svg = d3.select('#barchart .content')
.append('svg')
.attr({'width':vars.width,'height':vars.canvasHeight});
var svgFooter = d3.select('#barchart .footer')
.append('svg')
.attr({'width':vars.width,'height':vars.footer.height});
// Y Axis labels
var y_xis = svg.append('g')
.attr("transform", "translate("+vars.label.width+",10)")
.attr('id','yaxis')
.call(yAxis)
.selectAll("text")
.style("text-anchor", "start")
.attr("x", "-"+vars.label.width);
// X Axis labels
var x_xis = svgFooter.append('g')
.attr("transform", "translate("+((vars.label.visible)?vars.label.width:0)+",0)")
.attr('id','xaxis')
.call(xAxis
.tickSize(1)
.ticks(vars.verticalGridLines)
);
// Draw bars
svg.append('g')
.attr("transform", "translate("+((vars.label.visible)?vars.label.width:0)+",-20)") //-20
.attr('id','bars')
.selectAll('#barchart rect')
.data(vars.data)
.enter()
.append('rect')
.attr('height', function(d,i){ return vars.bar.height; })
.attr({'x':0,'y':function(d,i){ return y(i)+19; }})
.attr('style', '\
fill: ' + vars.bar.color + '; \
stroke-width:' + vars.bar.border + '; \
stroke: ' + vars.bar.borderColor + ';\
cursor: pointer;\
')
.attr('width',function(d){
return x(d.measureNum);
})
.on('mouseover', function(d, i){
d3.select(this).style("fill", vars.bar.colorHover);
})
.on('mouseout', function(d, i){
d3.select(this).style("fill", vars.bar.color);
})
.on('click', function(d, i) {
console.log(d);
app.obj.app.field('Case Owner Group').select([d.qElemNumber], false, false)
});
// Draw text
svg.append('g')
.attr("transform", "translate("+((vars.label.visible)?vars.label.width:0)+",-20)") //-20
.attr('id','text')
.selectAll('#barchart text')
.data(vars.data)
.enter()
.append('text')
.attr({'x':function(d) {
return x(d.measure)+10;
},'y':function(d,i){
return y(i)+40;
}})
.text(function(d){ return parseInt(d.measureNum); })
.attr("class", function(d) {
return 'barTextOut';
});
}
The template with the new addition can be found at branch and on gitBranch: Qlik Branch
...View More
You may be familiar with the search functionality in the Qlik Sense client, and also available through the "CurrentSelections" object with the App API using app.getObject(<elementID>, "CurrentSelections"). But it's also possible to build your own custom search using methods in the App API of Qlik Sense 2.2, in particular the searchResults() method and the selectAssociations() method.
note - if you have a version of Qlik Sense earlier than 2.2, the searchResults() method is not available to you. However, the searchAssociations() method can be used instead.
The first method we need to be familiar with, the searchResults() method, does basically what you think it would. It takes an array of terms to search for, has a few useful options that can be passed to it, and has a callback which provides results. An example below
app.searchResults(
["it"], //array of search terms
{qOffset: 0, qCount: 15}, //how many results to return, and offset
{qSearchFields: 'Case Owner Group', qContext: 'CurrentSelections'}, //options object
function(reply) {
//do something with reply here
});
The array of search terms and number of results are relatively straightforward. The options object gives you the ability to define which fields to search (leaving this out will search all fields) and what context to search in, including the ability to search through values not excluded by current selections, or searching through all values, regardless of selections.The next method you want to be familiar with is the selectAssociations() method. This method makes selections based on an array of search terms, and the position of the result. An example below
app.selectAssociations(
0, //position of result to select
["it"], //array of search terms
{qSearchFields: 'Case Owner Group', qContext: 'CurrentSelections'} //options object
);
So in the above, the first result (index of 0) would be selected from the results returned from searching the "Case Owner Group" field for the search term "it" in the context of current selections.There's a million cool ways that these methods could be used, but I'm going to walk through a really super basic example by creating an input box and using the input to search through all fields in the app in the context of current selections, and display results which you can make selections from.The first step is simply to create a container for the input field and the results div, as well as the the input field and results div themselves in HTML
<div class="qs-search-container">
<input class="qs-search" type="text" placeholder="Search">
<div class="qs-search-results"></div>
</div>
The next step is to listen for input in the input field using javascript
$(".qs-search").keyup(function() {
//the rest of our code will go in here
});
Now comes the bulk of the code. I'm going to include it below, along with comments
// Use value of input field as search term in searchResults method
app.searchResults([$(this).val()],
{qOffset: 0, qCount: 15},
{qContext: 'CurrentSelections'},
function(reply) {
//assign searchGroupArray of results to variable named searchResults for readability
var searchResults = reply.qResult.qSearchGroupArray;
// clear any old results
$(".qs-search-results").html("");
// append new list to hold results
$(".qs-search-results").append("<ul>");
//loop through results and add to dom
searchResults.forEach(function (result, i) {
result.qItems.forEach(function (item) {
//append list element for result
$(".qs-search-results ul").append("<li data-index='" + i + "'>");
//append identifier to result list element
$(".qs-search-results ul li:last-child")
.append("<div class='qs-search-item-identifier'>" + item.qIdentifier + "</div>");
//initialize string for matches
var matchString = "";
//loop through matches
item.qItemMatches.forEach(function (match) {
//add match to match string
matchString += match.qText;
});
//add match string to result list element
$(".qs-search-results ul li:last-child").append("<div class='qs-search-item-string'>" + matchString + "</div>");
});
});
//Add click handler to each result list item that will make selection
$(".qs-search-results li").click(function() {
app.selectAssociations($(this).data("index"), [$(".qs-search").val()], {qContext: 'CurrentSelections'});
//clear results after selection is made since old results are no longer valid
$(".qs-search").val("");
$(".qs-search-results").html("");
});
});
With a little added CSS, we should end up with something like below.That's the basic idea. Obviously, the example above could be taken a lot further, but this is a decent starting point for creating your own custom search functionality with the Capability APIs.some useful links -searchResults method ‒ Qlik SenseselectAssociations method ‒ Qlik SenseCode for the example created above is attached, and can be downloaded and put into the extensions folder of your Qlik Sense Desktop installation to play around with.
...View More
Set Analysis is a commonly used tool when creating advanced formulas in Qlik Sense or QlikView. The basics are fairly simple, but it’s clear that some aspects of it are very complex. In this post, I will try to answer some simple questions that touch the core of the complexity, such as: Can all sets have set modifiers? Are some sets more fundamental than others?
Today I decided to blog about the Autonumber function that can be used to create a “compact memory representation of a complex key.” Having recently learned about this function, I realized there have been times in the past when this would have been helpful to use. For instance, when I need to build a link table in my data model, I often create keys that I use to link the tables. Sometime these key fields are lengthy and are a combination of 3 or 4 fields. In this blog, I will show you how you can use the Autonumber function to create a “compact memory representation of a complex key.”Assume I load a data set that looks like this:And I want to load another data set that looks like this:These two data sets have the same first four fields so if I were to load them as is, I would get a synthetic table in my data model. To avoid that I will set up a key field in each of the tables that includes the FoodCategory, StoreNo, Year and Month fields. This key field will be the field that links these two tables. I will do this using a preceding load when I load both of these tables. The script would look like this:In the first table, I am using a preceding load to load all fields and then I am using the Autonumber function to create a key field that represents the four fields: FoodCategory, StoreNo, Year and Month. I am doing the same thing in the second table I am loading but the difference here is that I am not loading the key fields. By not loading the key fields, I am preventing a synthetic table from being loaded. The end result looks like this:Notice the FSYMkey field. In this example, it is a unique integer that represents a larger expression. In the past, I would have created the key field like this (see the FSYMkey2 field in the table below):FSYMkey2 is a more complex field that would have taken up more memory. This example is small but if you had thousands of unique key fields like this, the consumed memory would add up. By using the Autonumber function, I was able to use an integer to represent a long string thus minimizing the memory usage in my app. This is one of many tricks that can be used to reduce the memory usage in your app. Henric Cronstrom has some other ideas in his Symbol Tables and Bit-Stuffed Pointers blog. Check it out.Thanks,Jennell
...View More
Hello Qlik Community, today I have the pleasure of introducing our latest and newest guest blogger, Bruno Calver. Bruno is a Principal Solution Architect working in the UK with some of Qlik’s largest enterprise customers. His passion is working with business people to turn disparate and otherwise mundane data sets into insights and stories that can engage their audience, drive change and inspire new ways of thinking. He comes to us today with an introductory article that presents a paper that will cover best practices when developing the "user experience" (UX) within Qlik Sense.Reaching more peopleQlik Sense is all about extending the reach of Visual Analytics to more people in the business. In many organizations, people make decisions. The more people that use Visual Analytics the more value an organization will get from their data investment.But, how do we engage users and drive adoption of Visual Analytics solutions such as Qlik Sense? There are a number of ways this can be achieved, both in and outside the box. But in terms of what we can do within Qlik Sense in general, the "user experience" is one of the most important factors which can help drive adoption.The User ExperienceThe user experience (UX) refers to an individual's expressed emotion and attitude when using a particular product, system or service. User experience includes the practical, experiential, effective, meaningful and valuable aspects of human–computer interaction and product ownership. Additionally, it includes a person’s perceptions of system aspects such as utility, ease of use and efficiency.Qlik Sense is the business’ window into the world of data and insight that can help drive even better business decisions. This window needs to be beautiful, like a top department store showcases to entice shoppers and draws them in to find the products (insights) they want or need.There are some basic guidelines that can be followed to help ensure your "window" is looking tip top. You also want to make sure that as your users enter your shop (application), there are immediately impressed with the aesthetics of the environment, that the most important products are prominently displayed and that as they navigate their way through they get to see other relevant products. In the following article I will cover these basic guidelines:Less is moreSpace and symmetryDon’t be afraid of logos, icons and imagesThink of your audienceCarefully consider the use of extension objects and more exotic visualizations Read more in the attached article.Now, let's go window shopping.
...View More
Qlik Sense is getting better and better on every release!I was working on a large website using Qlik Sense 2.1 and the Capability APIs where I used the template that I had created and blogged about Creating a website with Angular and the Capabilities API. I had almost finished the project, when we decided to upgrade to 2.2. Everything was fine, I was cleaning up my code and I was ready to deliver the project when we realized that most of the charts, had no interactivity!So, lets fix our template to work properly in 2.2.One of the two issues was the css. In 2.1 we had to use qlikui.css and client.css. In 2.2 these were replaced by qlik-styles.css. So in index.html replace L17-L18
<link rel="stylesheet" href="http://localhost:4848/resources/autogenerated/qlikui.css">
<link rel="stylesheet" href="http://localhost:4848/resources/assets/client/client.css" media="all">
with
<link rel="stylesheet" href="http://localhost:4848/resources/autogenerated/qlik-styles.css">
Also, remove the icon fix we had in index.less and remove L134-L136. This has been fixed in the new stylesheet.
.sel-toolbar-span-icon {
top: -10px;
}
Another issue we found is that, we have to make sure that overflow-y: auto; is in both the html and body. We had that from beginning in this template but it seems to break things in 2.2 if it is only on body and not the html.In the js/lib/main.js I used in the grid-service in order to make angular work. This has also been fixed and you can remove L30
define( "client.services/grid-service", {} );
Now, going to my last and more important issue, on why the interactivity was broken in 2.2. I started the blog by stating that "Qlik Sense is getting better and better on every release!" and this is so true. I spend several days trying to figure out why the interactivity was broken when I was moving from pages to pages and on random times. The issue was that simply the engine got so much faster on delivering the objects, that Angular could not keep up with it! Really, when the Capability App API app.getObject() is looking for the id to replace the html with, is looking for the parent element width and height. In my case, all of the objects, most of the time, had the canvas drawn with 0 height and width due to that! So, in order to fix this, unfortunately, I had to add a 1/2 second delay on getting my objects. So in the js/services/api.js, wrap L18-L24 in a timeout function
setTimeout(function(){
angular.forEach(obj, function(value, key) {
app.obj.app.getObject(value, value).then(function(model){
app.obj.model.push(model);
deferred.resolve(value);
});
promises.push(deferred.promise);
});
}, 500);
I would add this only if you have the interactivity issues like I have described above. In cases that you have small projects like this one and you do not have a lot of dependencies, this may not be needed at all!Updated Git: GitHub - yianni-ververis/capabilities-api-angular-template: A simple mvc template for building webpages with Qlik Sense …Capability APIs documentation: https://help.qlik.com/en-US/sense-developer/2.2/Subsystems/APIs/Content/mashup-api-reference.htmHappy coding!!Yianni
...View More
Hello Qlik Community, in this post our resident guest blogger and Principal Enterprise Architect, Marcus Spitzmiller, introduces us to visual analyitcs scaling. Marcus is a member of the Qlik Enterprise Architecture team focusing on enterprise deployments and best practices. His areas of expertise include scalability and performance, deployment best practices, integration, and security.Scaling Visual AnalyticsWhen we talk about scalability, it is often centered around data volumes, users, and the technology behind a given product. However, one should also consider the capabilities that a product provides which enable broad deployment and governance within an organization. It is within this context that we will look at Qlik Sense. Of course, data volumes and users matter too, so if you have a few moments, check out the Qlik Sense Performance Benchmark for that.Instead of detailing everything in this blog I created a video presentation on this topic. In this video, we will look at Qlik Sense from the standpoint of four main topics that allow you to scale your visual analytics initiatives:Delivery – Qlik Sense’s platform approach to visual analytics enables authoring, self-service, and consumption with the patterns that fit to your organization, not the other way around.Security - Qlik Sense’s “attribute based access control” (ABAC) security model enables role and attribute based administration of the resources and content within Qlik Sense.Administration – Qlik Sense’s default, customizable, and delegated roles and out of the box monitoring enable simple and scalable administration.Infrastructure – Qlik Sense’s powerful In-Memory, Associative Data Indexing Engine and distributed architecture enables physical, virtual, and cloud based deployments with best in class performance.Organizations are continually faced with market pressures to make decisions more quickly. The Qlik Sense platform enables visual analytics to be widely deployed within an organization so people have access to their data, with security and governance, and at scale.Watch or download (.mp4) this video to learn more:
...View More