7

I've got some data where I have categorized symbology for point data with different colors showing different categories. I would also like to display some data using the pie charts function. I would like to have the different colored point on top of the pie charts to get a symbol that looks something like the mock-up below.

enter image description here

I can't find a way to get the points on top of the charts. Any suggestion?

  • Is the pie chart solution strictly necessary? Or you may also recur to other symbology types? You could create a custom symbology through the geometry generator, for example. – mgri Mar 25 '17 at 20:22
6
+100

Another approach would be recurring to symbology only: the main advantage is that the result will be customizable with practically infinite combinations of parameters.

The following procedure will use a custom function and it will work independently from the number of categories you will use.

Assuming you are working on a projected CRS (instead, if you are using a Geographic Coordinate System, see the note at the end of the answer), I want to underline that I will focus the attention on the explanation of the minimal things to do for reproducing the desired result: this means that some other minor parameters (like sizes, widths and so on) should be easily adjusted by you for better fitting your needs.

You will discover that reading the following solution will require more time than applying it directly (since the custom function will automatically do the most of the job).


Context

Let assume to start from this point vector layer:

enter image description here

which stores several categories that we want to display:

enter image description here


Solution

Assuming we want to categorize the "cat_1", "cat_2" and "cat_3" fields, we will render the point at the center (the yellow point in your original question) with one Geometry generator symbol layer. Then, we will render the several categories using a number of Geometry generator symbol layers which is equal to the number of categories themselves. So, for this case, we will use three Geometry generator symbol layers:

enter image description here

In the further explanation, the colors of the squares in the above image will help you to understand which geometry we are creating.

1) Geometry Generator No. 1

Add a new symbol layer and select the Geometry generator and the Polygon / MultiPolygon types:

enter image description here

Insert this expression in the Expression field:

buffer($geometry,10)

We have just defined a circle which has a radius of 10 meters.

2) Geometry Generator No. 2

From the Style dialog, add a new Geometry generator symbol layer having Polygon / MultiPolygon as geometry type. Then click on the Function Editor tab:

enter image description here

Then, click on New file and type piechart as the name of the new function:

enter image description here

You will see that a new function has been created and it is listed on the left side of the dialog. Now, click on the name of the function and replace the default @qgsfunction with the following code (don't delete the libraries imported by default and remember to add the import math line):

from qgis.core import *
from qgis.gui import *
import math

@qgsfunction(args=-1, group='Custom')
def piechart(in_list, feature, parent):
    to_show =in_list[-1] - 1
    fields =in_list[:-2]
    len_fields = len(in_list)
    sum_val = sum([feature[k] for k in fields])
    geom = feature.geometry()
    radius = in_list[-2]
    buffered = geom.buffer(radius, -1)
    first = True
    slices = []
    for field in fields:
        point_1 = geom.asPoint()
        points = [point_1]
        perim = buffered.length()
        percent = float(feature[field])/(sum_val)
        l = percent * perim
        azimuth = l/radius
        if first:
            start = 0
            end = azimuth
            first = False
        else:
            start = end
            end += azimuth
        if abs(math.degrees(start - end)) <= 180:
            dist_x, dist_y = (2 * radius * math.cos(math.radians(90) - start), 2 *radius* math.sin(math.radians(90) - start))
            point_2 = QgsPoint(point_1[0] + dist_x, point_1[1] + dist_y)
            dist_x, dist_y = (2 * radius * math.cos(math.radians(90) - (start + end)/2), 2 *radius* math.sin(math.radians(90) - (start + end)/2))
            point_3 = QgsPoint(point_1[0] + dist_x, point_1[1] + dist_y)
            dist_x, dist_y = (2 * radius * math.cos(math.radians(90) - end), 2 *radius* math.sin(math.radians(90) - end))
            point_4 = QgsPoint(point_1[0] + dist_x, point_1[1] + dist_y)
        else:
            dist_x, dist_y = (2 * radius * math.cos(math.radians(90) - start), 2 *radius* math.sin(math.radians(90) - start))
            point_2 = QgsPoint(point_1[0] + dist_x, point_1[1] + dist_y)
            dist_x, dist_y = (2 * radius * math.cos(math.radians(90) - (start + end)/2), 2 *radius* math.sin(math.radians(90) - (start + end)/2))
            point_3 = QgsPoint(point_1[0] - dist_x, point_1[1] - dist_y)
            dist_x, dist_y = (2 * radius * math.cos(math.radians(90) - end), 2 *radius* math.sin(math.radians(90) - end))
            point_4 = QgsPoint(point_1[0] + dist_x, point_1[1] + dist_y)
        points.append(point_2)
        points.append(point_3)
        points.append(point_4)
        trGeom = QgsGeometry().fromPolygon([points])
        if  math.degrees(azimuth) <= 180:
            if start >= end:
                slice = buffered.difference(trGeom)
            else:
                slice = buffered.intersection(trGeom)
        else:
            if start >= end:
                slice = buffered.intersection(trGeom)
            else:
                slice = buffered.difference(trGeom)
        slices.append(slice)
    return slices[to_show]

Once you have done this, click on the Load button and you will be able to see the function from the Custom Menu of the Expression dialog.

Now, type this expression (see the image below as reference):

piechart('cat_1', 'cat_2', 'cat_3', 20, 1)

enter image description here

We have just defined the first sector of the pie chart by entering the names of the fields to categorize ('cat_1', 'cat_2', 'cat_3'), the radius of the pie chart (20) and a number which indicates the category (starting from 1) to display: for this case, we will render the first category from the list, so we need to set 1 as value. As you will see in the following steps, this number will be the unique value to change.

3) Geometry Generator No. 3

From the Style dialog, add a new Geometry generator symbol layer having Polygon / MultiPolygon as geometry type. Then click on the Function Editor tab:

enter image description here

Then, type this expression in the Expression field:

piechart('cat_1', 'cat_2', 'cat_3', 20, 2)

We have just defined the second sector of the pie chart. The unique difference with the previous step is that we set 2 as the last parameter: this because we want to render the second category field (i.e. "cat_2").

4) Geometry Generator No. 4

From the Style dialog, add a new Geometry generator symbol layer having Polygon / MultiPolygon as geometry type. Then click on the Function Editor tab:

enter image description here

Then, type this expression in the Expression field:

piechart('cat_1', 'cat_2', 'cat_3', 20, 3)

We have just defined the third sector of the pie chart. The unique difference with the previous step is that we set 3 as the last parameter: this because we want to render the third category field (i.e. "cat_3").

Final result

Click on the Apply button for applying the renderer: we have finished. If you correctly performed the previous tasks, you should be able to get something like this:

enter image description here

Final notes

If you are using a Geographic Coordinate System, i.e. if you are dealing with degrees and not with distances, it should be enough using the proper value for the fourth parameter of the piechart function: this means that you need to replace it with other arbitrary values expressed in degrees (for example, 0.0002, 0.002 and so on).

Please note that my example will work fon any number of fields to categorize, assuming that you will create the proper number of geometry generators and set the correct values for the fourth parameter in the piechart function.

  • 1
    Many thanks! This is so great. I can only say wow. :) – Kazuhito Apr 1 '17 at 2:05
  • 2
    Very creative solution, although there should be an easier solution than creating custom functions. +1 from me. – ahmadhanb Apr 1 '17 at 4:52
  • @Kazuhito, thanks, I really appreciate! I hope it will be useful for you or other users! =) – mgri Apr 1 '17 at 9:31
  • @ahmadhanb thanks. I'm sure there will be some faster way in the next releases for doing this: in the meanwhile, I'm just exploring the power of custom function! :) – mgri Apr 1 '17 at 9:35
  • Thanks @mgri. Got this to work on 2.18.20 but on 3.0.3 only the first symbol appeared. I would guess this has to do with the changes to Python 3. Any thoughts what needs to be modified to get this to work with QGIS 3? – Techie_Gus Jun 27 '18 at 14:00
3

I think this is a bug. I have the same issue, and I tested on both QGIS 2.14.10 and QGIS 2.18.2, and there is no way show the point simple style above the graphs, although the point shapefile is located above the graph, as you can see below:

enter image description here

Here is the point shapefile without the graphs:

enter image description here

The only I can show the point is by setting the layer transparency for the pie chart appearance:

enter image description here

enter image description here

You can report this bug here.

  • Thanks @ahmadhanb , so helpful as always. I am getting more anxious about the timing of the fix (if possible at all), so I think the other approach proposed by mgri would be beneficial for us all. – Kazuhito Apr 1 '17 at 2:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.