You can use the Geometry Generator in the Layer Style definition.
With one Geometry Generator entry for each relevant category.
These will be used to generate:
- "Bananas" points, starting from $geometry, spaced by a given amount;
- "Apples" points, starting from where Bananas left us with;
- "Oranges" points, starting from where Apples + Bananas left us with;
The generic code will be:
collect_geometries(array_foreach(generate_series(0,number_items-1,1),translate($geometry,@element*0.005,0)))
generate_series(0,number_items-1,1)
to get the number of items;
translate($geometry,@element*0.005,0)
to generate points right from the initial $geometry
. 0.005
is the horizontal spacing, 0
the vertical spacing. Alternatives can be used to pile symbols instead of aligning them.
collect_geometries(array_foreach(...))
to generate multipart geometry for the array of translated points
Results with the following sample data:
pt_number |
bananas |
apples |
oranges |
1 |
1 |
2 |
2 |
2 |
3 |
1 |
0 |
3 |
4 |
0 |
4 |
4 |
0 |
1 |
0 |

Settings the Style for 3 item categories:
In the Layer Style
, use the 3 separate levels of Geometry Generator
:
The first one:

The code for each of the 3 levels:
case when "Bananas" >0 then
collect_geometries(array_foreach(
generate_series(0,"Bananas"-1,1),
translate($geometry,@element*0.005,0)))
end
then:
collect_geometries(array_foreach(
generate_series(0,"Apples"-1,1),
translate($geometry,@element*0.005+"Bananas"*0.005,0)))
(note the offset by number of Bananas
)
and then:
collect_geometries(array_foreach(
generate_series(0,"Oranges"-1,1),
translate($geometry,@element*0.005+("Bananas"+"Apples")*0.005,0)))
(note the offset by number of Bananas + number of Apples
)

Alternate styling :
That's the baseline that should allow you to customize your Symbols.
For something that matches your initial representation even more closely, you can do the following (keep the Simple Marker for the Point geometry + add one offset in X and Y direction in the translate
