Unlock a world of possibilities! Login now and discover the exclusive benefits awaiting you.
Qlik Sense documentation and resources.
If you search after custom area or custom maps in Qlik you will find after a while some articles on how to use custom maps or how to visualize them. But most of them are lacking information about how to map points or even shapes into the datamodel.
So I wanted to fully understand how to build a custom map with custom points and shapes! And after some hours of research, creative thinking and a lot of trial and error, here is the Complete Guide to Custom Maps in Qlik Sense.
So we frist start with a custom picture, for example the floor plan of my apartment*
When you take a further look into the other articles you will always find some kind of existing mapping table for mapping points on the picture to coordinates in the map.
But How did they come to this mapping table?
1. Print out the image on your companies printer and then take your ruler from high school and measure the... No!
I am a lazy kind of person and don't want to leave my Qlik environment. So my picture has an aspect ratio of 5x3.5. Now I have to decide how small I want my slices. I decided to make 10 slices each so my grid should be 50 x 35.
Let's put these two magical numbers and a random name (e.g. 'Floorplan') for my grid into the following sub routine:
SUB CREATEMAPGRID(SUB_VAR_NAME,SUB_VAR_X, SUB_VAR_Y)
[temp_]:
Load
Iterno()-1 As [$(SUB_VAR_NAME).X]
AutoGenerate 1 While IterNo() -1 <= $(SUB_VAR_X);
Join([temp_])
Load
Iterno()-1 AS [$(SUB_VAR_NAME).Y]
AutoGenerate 1 While IterNo() -1 <= $(SUB_VAR_Y);
[$(SUB_VAR_NAME)_grid]:
Load *,
'('&[$(SUB_VAR_NAME).X]&'|'&[$(SUB_VAR_NAME).Y]&')' as [$(SUB_VAR_NAME).Point]
resident [temp_];
drop tables [temp_];
END SUB;
Call CREATEMAPGRID('Floorplan',50,35);
Next: Upload your picture for example via the QMC (content libraries) and save the URL path (/content/Default/unnamed.jpg) for later.
Create a new sheet and drop in the Map-Chart. Add a new background layer:
Switch format option to "image", copy the URL path of the image in to the URL input field.
Now fill in the image position options:
top (width): 0
left (height): 0
bottom (width): 35
right (height): 50
Go to map settings and chose 'none' as your base map, deactivate automatic zoom.
Navigate to your apartment on the map and define the standard view.
Add a new point layer and choose 'Floorplan.Point' as your dimension, switch to location and check "Fields for Long/Lat". For the Latitude chose 'Floorplan.Y' and for Longitude chose 'Floorplan.X'
Go to "size and shape" and change shape to square and move the size slider to the left.
Congratulations we have now a grid map on our background image (see image above).
Thanks to our 'Floorplan.Point' field we can now read the exact location when we hover over a grid point.
We can now define our sweet spots by hovering over the desired grid point and typing those coordinates into a mapping table - either in Excel or just use an inline load, so you don't need to leave Qlik.
SweetSpots:
Load * Inline
[
Spot, X,Y, maxPerson
table, 11,27, 8
kitchen, 31,25, 4
living room,11,10, 6
stairs, 21,17, 1
];
As you can see I already added my measure of interest (analyzing bottle necks before starting my birthday party) to the sweet spots. In a real world scenario you could just link the sweat spots to a big fact table using Qlik's associative logic. I can now delete (or hide) the point layer with the grid information and replace it with a point layer for my SweetSpots. May use the measure =1/Sum(maxPerson) for the size of the bubbles to indicate the 'bottle necks' visually.
And that's it!?
Of course not. Now we come to the cool part: Adding custom areas (custom shapes)!
I hope you haven't deleted your Grid-Layer yet - if so please repeat the steps above to restore the grid.
To create your own areas or shapes we need some more information than X and Y.
We need to know which coordinates belong togehter, and the most important information: the order of the coords to define how they should be connected.
So I created a new inline table with all the information. Please pay attention that I enumerate the shapes.
This will be beneficial if we want to combine 2 shapes to a single entity (which we will do later).
RoomArea:
load *,
//you have to switch X and Y using GeoMakePoint, don't ask why. i may answer in the comments
GeoMakePoint(Y,X) as geopoint,
;
Load * Inline
[
shape,AreaName, X, Y, pathorder
1, technic room, 35,22, 1
1, technic room, 44,22, 2
1, technic room, 44,31, 3
1, technic room, 35,31, 4
2, wc, 35,9, 1
2, wc, 33,5, 2
2, wc, 35,5, 3
2, wc, 44,5, 4
2, wc, 44,9, 5
3, kitchen, 24,31, 1
3, kitchen, 24,28, 2
3, kitchen, 32,28, 3
3, kitchen, 32,22, 4
3, kitchen, 35,22, 5
3, kitchen, 35,31, 6
4, dining room, 7,23, 1
4, dining room, 18,31, 2
4, dining room, 19,19, 3
5, kitchen, 25,26, 7
5, kitchen, 29,26, 8
5, kitchen, 29,21, 9
5, kitchen, 25,21, 10
];
The next step is only needed, if you have multi-shaped areas, like 'kitchen' in this example.
//Helper to idnetifiy if feature consists of multiple shapes
idCount_MAP:
mapping load
AreaName,
count(DISTINCT shape)
resident RoomArea
group by AreaName
;
Now we want to create or custom shapes. Most articles say you need .kml files and this is almost true.
But you can also fake an .kml file by immitate its output and tag the field correctly.
A .kml files will deliver a definition of an shape by giving you the coords in correct order.
in such a way that if you take a pen and follow each coord you will draw the outline.
.kml will output them in this kind of format: [[[53.452452,12.745464][53.322345],[12.543345],[53.37644,12.11224]]]
so each shape is encapsuled in [,] so building sub-shapes is done by: [[[a,b],[b,c],[c,d]],[[c,d],[d,e],[e,f]]]. With this in mind we are ready to fake our own kml shapes.
So in the frist steps lets create areas that only consists of one shape.
//First only Features with single shapes
RoomAreaMap:
Load
AreaName,
//construct the path that makes the area; specify the order of the points along the path
// you need a pair of '[' and ']' for each level of possible sub-areas. in most cases 3 pairs holds.
'[[['&concat(geopoint,',',pathorder)&']]]' as area
Resident RoomArea
where Applymap('idCount_MAP',AreaName,0) = 1
group by AreaName
;
//IMPORTANT works only when this tag is applied to the area
Tag field area with $geomultipolygon;
With the Tag field statement we can define the field 'area' (which is in string format) to be interpreted as if it was loaded from an .kml file (= polygon definition).
Got to your Map-Chart and add a new area layer, choose 'AreaName' as dimension and 'area' as location field. Define color by dimension and change opacity to 50%.
Congrats, your first shapes are on the map! 🎉
Let's go to the last and most fun part: getting areas wih combined shapes into the custom map.
Here we use a stacked load to first create the single areas or sub-areas for each 'AreaName' - differentiated by the field 'shape'. With the stacked load we can now combine those sub-areas:
//Features with multiple shapes
Load
AreaName,
//2. concat the shapes for each area
'[['& concat(sub_area,',',shape)&']]' as area
group by AreaName;
Load
AreaName,
shape,
//1. construt path for each area per shape
'['&concat(geopoint,',',pathorder)&']' as sub_area
Resident RoomArea
where Applymap('idCount_MAP',AreaName,0) > 1
group by AreaName,shape
;
//IMPORTANT works only when this tag is applied to the area
Tag field area with $geomultipolygon;
If we now came back to our Map-Chart, we see the updated area layer, with our 2 shaped kitchen area (the yellow one in the picture) appearing on the screen. Yay! 🤓
final result:
Some real world apllication hints: use association to attach several measures for each feature (RoomArea, Spot).
for example to monitor how long your colleagues are sitting in front of their screen by tracking desktop activity (admin needed). Often used are traffic light indicators (red, green, yellow) on spots: use the coloring expression to make changes of states visible. in combination with sensor-data in different rooms this Feautre allows much more use-cases, but this is a whole different story.
I hope you enjoyed the post and can get use of it.
best regards Chris!