This question could be very complicated, so I'll try to be as clear as I can.

I have a polygon (hundreds of cells) that has a quite rich attribute table (more than 30 fields).

Each field contains the value of a certain pollutant (As, Sn, V and so on). So the attribute table has this aspect:

enter image description here

I would like to create maps with graduated colors for each field. Something like that:

enter image description here

Now I have two questions:

  • Which is the best, simplest and quickest way to create a map for each pollutant (so, actually, for each field)?

  • In addition to the map, it could be very very good for me to add a plot to the map composer. This plot is based on the same pollutant of the map:

enter image description here

I can easily prepare the plot with R and put them in a directory with the same name of the pollutant. For example, if the map is classified on Al values, and Al is the name that compares in the attribute table, the plot will be named Al.png.

Is it possible to speed up this process using the Atlas of the print composer together with R?

Does anybody have some hint?

  • Hello matteo, a few more questions before I can try to answer it. Does your pollutants have the same value ranges? This is important because it might need different classes for symbolization. Feb 26, 2015 at 10:13
  • Hi @Alexandre, thanks for the answer. Well, I'd say no.. I have pollutants such as Al that has ranges from 9000 to 50000 while I have also minor pollutants where ranges goes from 10 to 50..
    – matteo
    Feb 26, 2015 at 10:24
  • How about the printing area, is always the same? Feb 26, 2015 at 10:57
  • Yes the area is the same.
    – matteo
    Feb 26, 2015 at 11:01

1 Answer 1


Here is a solution, with some limitation on the legend and classification due to data normalization.

The steps are the following ones

  1. Unpivot your data and normalize the fields
  2. Create a coverage layer
  3. Use QGIS Atlas to generate the output

We will use PostGIS to do the data processing part.

Sample data

We will use the following small sample dataset for our example.

create table concentration (id serial, geom geometry(Polygon, 2154), a double precision, sn double precision, v double precision, cd double precision);
insert into concentration (geom, a, sn, v, cd) 
        st_setsrid(st_buffer(st_makepoint(843514 + (random() * 1000)::int, 6513144 + (random() * 1000)::int), random()*50), 2154)
        , random() * 1000 as a
        , random() * 100 as sn
        , random() * 10000 as v
        , random() * 5 as cd
    from generate_series(1, 100);

Prepare the data

We unpivot the data and normalize it at the same time. We can use a view on initial data to do it dynamically, or create a table instead.

create view conc_unpivot as
    with unpivot as (
            id, geom
            , unnest(array['a','sn','v','cd']) as eltname
            , unnest(array[a, sn, v, cd]) as concentration
        id, geom, eltname,
        (concentration - min(concentration) over w) / (max(concentration) over w - min(concentration) over w ) as concentration
    window w as (partition by eltname);

Now we create the coverage layer, with the same extent for all. We could adjust the geometry for each element to match the location where they are present, using ST_Extent(geom) instead of the global extent. The following query is faster though.

create table coverage as 
    st_setsrid(st_estimated_extent('public', 'conc_unpivot', 'geom')::geometry, 2154) as geom
    , eltname
group by 

Now be sure to have your concentration plots in a specific directory, with the element name as a file name.

Configure the style

Open QGIS, and open both conc_unpivot and coverage layers.

In the styling dialog, configure a graduated styling on the concentration field.

Check visually that the result looks ok.

Create a composer with desired elements. Note that the legend will only display normalized values, this is the limitation of this solution. I do not see any solution for now to overcome this problem.

Now back to the styling dialog, configure the graduated styling on the following expression :

CASE WHEN  "eltname" = attribute( $atlasfeature ,'eltname') THEN concentration ELSE -1 END

This will only send back values for which the corresponding eltname attribute is set to the current coverage feature being processed.

Set the style for value -1 of concentration to fully transparent.

Generate the atlas

Create your composer, with a map, and any other element you want. You can insert the chemical element name in a Text item, using the following content :

Concentration for [% "eltname" %]

And you can include any external image, setting the image source to an expression ( "data defined override" -> Edit) with something like this :

'/path/to/your/images/'  ||  "eltname" || '.png'

If you switch to Atlas Preview mode, you can browse for the concentration of the elements.


This mostly does what you want to, except for two limitations :

  • The data is normalized and therefore the legend will display normalized values and not specific values for each element
  • You cannot classify using quantiles, as the quantiles would be computed for all elements, not the current one only

An alternative method would be to write a Python script to drive the Atlas generation, recomputing the style classes for every element, which is the blocking problem in my solution.

The best way of overcoming the limitations would probably be to implement an option in QGIS for each layer to recompute the classification on each rendering according to the data displayed.

  • Hi Vincent. Thanks for your help. So you think that the only ways are either put the data in a database or write a python script right?
    – matteo
    Feb 27, 2015 at 11:08
  • It is not either the one or the other. The database is just a convenient way to unpivot your data, but you could do it otherwise (in R directly, or any other tool). If you do not want to go through unpivoted data you will have to write code I think. Same if you want to overcome the mentionned limitations.
    – Vincent
    Feb 27, 2015 at 12:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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