From what you say, it seems like you wanted to start from the points
layer. As you want the number of points within the polygon
layer, it's easier to place the logic in the polygon
layer (also allowing you to use the native label positioning features, centroid by default).
You can use this formula in the polygon
layer Label (or as a Virtual Field in the polygon
Attribute Table)
to_string(
round(
array_length(overlay_intersects('points', $id)) ---number of points in the polygon
/
aggregate('points','count',$id) ---number of points in points layer
,2)*100
) || ' %'
If you want this to automatically apply to any new Point layer, you can update the code to be the following
:
to_string(
round(
array_length(overlay_intersects( array_to_string( array_filter( @map_layer_ids ,@element!=@layer_id)), $id)) ---number of points in the polygon
/
aggregate( array_to_string( array_filter( @map_layer_ids ,@element!=@layer_id)),'count',$id) ---number of points in points layer
,2)*100
) || ' %'
with the point
layer name replaced by array_to_string( array_filter( @map_layer_ids ,@element!=@layer_id))
which is the only active layer besides the polygon one. If there are other 'static' layers you want to exclude, you can add them to the filter.
Swithching to another point layer, without changing the Style in the polygon layer:
If you have static layers that you want to keep active: add them to the layer filter. Set the filter part to:
@element not in (@layer_id, 'any other layer name you need to remove', 'any other layer to remove',...)
`@layer_id' filters the polygon layer, then add a comma separated list of layers you want to keep active.
For instance I added an OtherLayer displayed on the map.
Use the @map_layer_ids to identify its id
and added it to the filter list
to_string(round(array_length(overlay_intersects( array_to_string( array_filter( @map_layer_ids ,@element not in (@layer_id,'OtherLayer_a9983d8b_9323_4fac_83de_e8e24e78c8a1'))), $id)) /aggregate( array_to_string( array_filter( @map_layer_ids ,@element not in (@layer_id,'OtherLayer_a9983d8b_9323_4fac_83de_e8e24e78c8a1'))),'count',$id) ,1)*100) || ' %'
It can be achieved by aggregating the various values into arrays (using filters to choose the attribute value), then finding the length of those arrays.
-- this case statement is to ensure the label is rendered only once per group (else it is rendered for every feature)
case when $id = array_min(array_agg($id, group_by:="TYPE"))
then
-- get total count
with_variable(
'total',
array_length(
array_agg($id)
),
-- count how many instances of "Normal"
with_variable(
'normal',
array_length(
array_agg(
"TYPE",
filter:="TYPE"='Normal'
)
),
-- count how many instances of "Special"
with_variable(
'special',
array_length(
array_agg(
"TYPE",
filter:="TYPE"='Special'
)
),
-- generate the label string based on the value of "TYPE" ( || is the concatenator operator, the @ symbol is to call the variables that were created above)
case
when "TYPE" = 'Normal'
then 'Normal: ' || @normal || ' (' || to_string(@normal / @total * 100) || '%)'
when "TYPE" = 'Special'
then 'Special: ' || @special || ' (' || to_string(@special / @total * 100) || '%)'
end
)
)
)
end
The adapted placement expression:
case
when "TYPE" = 'Special'
then
point_on_surface(
convex_hull(collect($geometry,filter:="TYPE"='Special'))
)
when "TYPE" = 'Normal'
then
point_on_surface(
convex_hull(collect($geometry,filter:="TYPE"='Normal'))
)
end
Best Answer
As you want to number the points per hull and the hull is based on the value in the
class
field, you can base your expression on this and don't have to complicate yourself with the hull's geometries:round (count (class,group_by:=class) / count (class) * 100, 0)
By the way, better construct your hull with a simpler expression: