Here is a solution, with some limitation on the legend and classification due to data normalization.
The steps are the following ones
- Unpivot your data and normalize the fields
- Create a coverage layer
- Use QGIS Atlas to generate the output
We will use PostGIS to do the data processing part.
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 (
, 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
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.