Working with Groups

The most common reason to reach for a group is a transform that should apply to several elements at once - move, rotate, or scale them together without touching each one individually. Groups also let you set shared opacity, attach an id or class for later querying, and layer content into named sections.

Build a group from scratch

Svg::group() opens a group and returns a Builder. Add elements to it, then call end() to close the group.

<?php

use Atelier\Svg\Svg;

$svg = Svg::create(400, 200)
    ->group()
        ->transform('translate(100, 50)')
        ->rect(0, 0, 80, 80)->fill('#4f46e5')
        ->circle(120, 40, 30)->fill('#e11d48')
    ->end()
    ->toString();

Both shapes shift together. Change the translate once and the whole group moves.

Layer content with multiple groups

After end() closes a group, the builder is back at the root level. Open another group with g().

<?php

use Atelier\Svg\Svg;

$svg = Svg::create(400, 300)
    ->group()
        ->id('background')
        ->rect(0, 0, 400, 300)->fill('#f8f8f8')
        ->rect(0, 0, 400, 40)->fill('#e0e0e0')
    ->end()
    ->g()
        ->id('content')->transform('translate(20, 60)')
        ->text(0, 0, 'Hello')->fill('#111')
    ->end()
    ->toString();

id() on a group is useful for querying it later with querySelector('#background').

Nest groups for compound transforms

Inner groups inherit the outer group's coordinate system.

<?php

use Atelier\Svg\Svg;

// An icon rotated 45° and shifted to the centre
$svg = Svg::create(200, 200)
    ->group()
        ->transform('translate(100, 100)')   // move origin to centre
        ->g()
            ->transform('rotate(45)')        // rotate around new origin
            ->rect(-30, -30, 60, 60)->fill('#4f46e5')
        ->end()
    ->end()
    ->toString();

Group existing elements in a parsed document

When you load an SVG and want to wrap a set of elements in a group after the fact, use Document::groupElements().

<?php

use Atelier\Svg\Svg;

$doc = Svg::load('chart.svg')->getDocument();

// Wrap all data points in a group so they can be toggled together
$points = $doc->querySelectorAll('.data-point');
$doc->groupElements(
    $points->toArray(),
    id: 'data-layer',
    attributes: ['opacity' => '1'],
);

// Now you can fade the whole layer with one attribute change
$doc->querySelector('#data-layer')?->setAttribute('opacity', '0.3');

Ungroup and flatten

ungroup() removes a group and hoists its children into the parent - the group's transform is lost.

<?php

use Atelier\Svg\Element\Structural\GroupElement;
use Atelier\Svg\Svg;

$doc = Svg::load('exported.svg')->getDocument();

// Design tools often leave unnecessary wrapper groups - remove them
$wrapper = $doc->querySelector('#Layer_1');
if ($wrapper instanceof GroupElement) {
    $doc->ungroup($wrapper);
}

To strip all groups at once - useful before merging paths or running the optimizer:

<?php

$doc->flattenGroups();            // all levels
$doc->flattenGroups(maxDepth: 2); // stop after 2 levels

Shared opacity and compositing

One practical difference between group opacity and per-element opacity: a group composites before blending. If two shapes overlap inside a group, they do not show through each other - the whole group is rendered first, then made transparent.

<?php

use Atelier\Svg\Svg;

// Watermark overlay - the rect and text blend together, not separately
$svg = Svg::create(400, 300)
    ->group()
        ->attr('opacity', '0.15')
        ->rect(0, 0, 400, 300)->fill('#000')
        ->text(200, 160, 'DRAFT')
            ->attr('text-anchor', 'middle')
            ->attr('font-size', '72')
            ->fill('#fff')
    ->end()
    ->toString();

Set opacity per element if you want them to blend independently.

See also