# Svg

```ts
import { Svg } from 'cx/svg';
```



CxJS has excellent support for _Scalable Vector Graphics_ (SVG) and enables responsive layouts and charts using the concept of **bounded objects**.

```tsx
import { Svg, Rectangle } from "cx/svg";

export default (
  <Svg style="width: 300px; height: 200px; border: 1px solid #ddd">
    <Rectangle fill="#f0f0f0" />
    <rect x={20} y={20} width={80} height={60} fill="lightblue" stroke="steelblue" />
    <ellipse cx={180} cy={50} rx={40} ry={30} fill="lightgreen" stroke="green" />
    <line x1={20} y1={120} x2={280} y2={120} stroke="#999" />
    <line x1={20} y1={140} x2={280} y2={180} stroke="coral" stroke-width={2} />
    <text x={150} y={170} text-anchor="middle" style="font-size: 14px">
      SVG Elements
    </text>
  </Svg>
);

```

The `Svg` component serves as a container for SVG elements. You can mix two approaches:

- **Native SVG elements** (`rect`, `ellipse`, `line`, `text`) — positioned with standard attributes like `x`, `y`, `width`, `height`, but you must calculate positions manually
- **CxJS components** (`Rectangle`, `Ellipse`, `Line`, `Text`) — work as **bounded objects** that automatically adapt to their container size using `anchors`, `offset`, and `margin` properties

## Bounded Objects

> Use the `Svg` container instead of the native `svg` element to enable bounded objects.

The `Svg` component measures its size and passes bounding box information to child elements. Child elements use parent bounds to calculate their own size and pass it to their children.

Bounds are defined using the `anchors`, `offset`, and `margin` properties. Each property consists of four components `t r b l` (top, right, bottom, left) in clockwise order. Do not use suffixes like `%` or `px`.

### Anchors

Anchors define how child bounds are tied to the parent:

- `0` aligns to the left/top edge
- `1` aligns to the right/bottom edge
- Values between `0` and `1` position proportionally

```tsx
import { Svg, Rectangle, Text } from "cx/svg";

export default (
  <div class="flex flex-wrap gap-2">
    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0 1 1 0" style="fill: lightblue" />
      <Text textAnchor="middle" dy="0.4em" style="font-size: 10px">
        0 1 1 0
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0.25 0.75 0.75 0.25" style="fill: lightblue" />
      <Text textAnchor="middle" dy="0.4em" style="font-size: 10px">
        0.25 0.75 0.75 0.25
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0 0.5 1 0" style="fill: lightblue" />
      <Text textAnchor="middle" dy="0.4em" style="font-size: 10px">
        0 0.5 1 0
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0 1 0.5 0" style="fill: lightblue" />
      <Text textAnchor="middle" dy="0.4em" style="font-size: 10px">
        0 1 0.5 0
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0.5 1 1 0.5" style="fill: lightblue" />
      <Text textAnchor="middle" dy="0.4em" style="font-size: 10px">
        0.5 1 1 0.5
      </Text>
    </Svg>
  </div>
);

```

### Offset and Margin

The `offset` property translates the edges of the bounding box. It always works in the top-to-bottom and left-to-right direction, so use negative values for right and bottom edges.

The `margin` property works like CSS margin — positive values shrink the box inward, negative values expand it outward.

```tsx
import { Svg, Rectangle, Text } from "cx/svg";

export default (
  <div class="flex flex-wrap gap-2">
    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0 1 1 0" offset="5 -5 -5 5" style="fill: lightblue" />
      <Text textAnchor="middle" dy="-0.1em" style="font-size: 10px">
        A: 0 1 1 0
      </Text>
      <Text textAnchor="middle" dy="0.9em" style="font-size: 10px">
        O: 5 -5 -5 5
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0.5 0.5 0.5 0.5" offset="-30 30 30 -30" style="fill: lightblue" />
      <Text textAnchor="middle" dy="-0.1em" style="font-size: 10px">
        A: 0.5 0.5 0.5 0.5
      </Text>
      <Text textAnchor="middle" dy="0.9em" style="font-size: 10px">
        O: -30 30 30 -30
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0 1 1 0" margin={5} style="fill: lightblue" />
      <Text textAnchor="middle" dy="-0.1em" style="font-size: 10px">
        A: 0 1 1 0
      </Text>
      <Text textAnchor="middle" dy="0.9em" style="font-size: 10px">
        M: 5
      </Text>
    </Svg>

    <Svg style="width: 100px; height: 100px; background: white; border: 1px dotted #ccc">
      <Rectangle anchors="0.5 0.5 0.5 0.5" margin={-30} style="fill: lightblue" />
      <Text textAnchor="middle" dy="-0.1em" style="font-size: 10px">
        A: 0.5 0.5 0.5 0.5
      </Text>
      <Text textAnchor="middle" dy="0.9em" style="font-size: 10px">
        M: -30
      </Text>
    </Svg>
  </div>
);

```

**Key difference:**

- `offset="5 -5 -5 5"` — explicit directional offsets
- `margin={5}` — uniform inset (equivalent result)

### Interactive Example

Try resizing the container and adjusting the values to see how bounded objects respond. Use the **mouse wheel** while focused on a field to quickly adjust values:

```tsx
import { createModel } from "cx/data";
import { bind, tpl, expr, LabelsTopLayout, Controller } from "cx/ui";
import { Svg, Rectangle, Line } from "cx/svg";
import { NumberField, Resizer, LookupField } from "cx/widgets";

interface Model {
  width: number;
  height: number;
  anchorT: number;
  anchorR: number;
  anchorB: number;
  anchorL: number;
  offsetT: number;
  offsetR: number;
  offsetB: number;
  offsetL: number;
  preset: string;
  focusAnchorT: boolean;
  focusAnchorR: boolean;
  focusAnchorB: boolean;
  focusAnchorL: boolean;
  focusOffsetT: boolean;
  focusOffsetR: boolean;
  focusOffsetB: boolean;
  focusOffsetL: boolean;
}

const m = createModel<Model>();

const presets = [
  {
    id: "full",
    text: "Full",
    anchors: [0, 1, 1, 0],
    offset: [10, -10, -10, 10],
  },
  {
    id: "left-half",
    text: "Left Half",
    anchors: [0, 0.5, 1, 0],
    offset: [10, 0, -10, 10],
  },
  {
    id: "right-half",
    text: "Right Half",
    anchors: [0, 1, 1, 0.5],
    offset: [10, -10, -10, 0],
  },
  {
    id: "top-half",
    text: "Top Half",
    anchors: [0, 1, 0.5, 0],
    offset: [10, -10, 0, 10],
  },
  {
    id: "bottom-half",
    text: "Bottom Half",
    anchors: [0.5, 1, 1, 0],
    offset: [0, -10, -10, 10],
  },
  {
    id: "top-left",
    text: "Top Left",
    anchors: [0, 0.5, 0.5, 0],
    offset: [10, 0, 0, 10],
  },
  {
    id: "top-right",
    text: "Top Right",
    anchors: [0, 1, 0.5, 0.5],
    offset: [10, -10, 0, 0],
  },
  {
    id: "bottom-left",
    text: "Bottom Left",
    anchors: [0.5, 0.5, 1, 0],
    offset: [0, 0, -10, 10],
  },
  {
    id: "bottom-right",
    text: "Bottom Right",
    anchors: [0.5, 1, 1, 0.5],
    offset: [0, -10, -10, 0],
  },
  {
    id: "center",
    text: "Center",
    anchors: [0.25, 0.75, 0.75, 0.25],
    offset: [0, 0, 0, 0],
  },
  {
    id: "center-offset",
    text: "Center (Offset)",
    anchors: [0.5, 0.5, 0.5, 0.5],
    offset: [-50, 50, 50, -50],
  },
];

class PageController extends Controller {
  onInit() {
    this.addTrigger("preset-changed", [m.preset], (preset) => {
      const p = presets.find((x) => x.id === preset);
      if (p) {
        this.store.set(m.anchorT, p.anchors[0]);
        this.store.set(m.anchorR, p.anchors[1]);
        this.store.set(m.anchorB, p.anchors[2]);
        this.store.set(m.anchorL, p.anchors[3]);
        this.store.set(m.offsetT, p.offset[0]);
        this.store.set(m.offsetR, p.offset[1]);
        this.store.set(m.offsetB, p.offset[2]);
        this.store.set(m.offsetL, p.offset[3]);
      }
    });
  }
}

export default (
  <div controller={PageController}>
    <LabelsTopLayout>
      <LookupField
        value={bind(m.preset, "full")}
        label="Preset"
        options={presets}
        style="width: 220px"
        required
      />
    </LabelsTopLayout>
    <strong class="block -mb-2">Anchors</strong>
    <LabelsTopLayout>
      <NumberField
        value={bind(m.anchorT, 0)}
        label="Top"
        style="width: 60px"
        minValue={0}
        maxValue={1}
        step={0.1}
        format="n;1"
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusAnchorT}
      />
      <NumberField
        value={bind(m.anchorR, 1)}
        label="Right"
        style="width: 60px"
        minValue={0}
        maxValue={1}
        step={0.1}
        format="n;1"
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusAnchorR}
      />
      <NumberField
        value={bind(m.anchorB, 1)}
        label="Bottom"
        style="width: 60px"
        minValue={0}
        maxValue={1}
        step={0.1}
        format="n;1"
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusAnchorB}
      />
      <NumberField
        value={bind(m.anchorL, 0)}
        label="Left"
        style="width: 60px"
        minValue={0}
        maxValue={1}
        step={0.1}
        format="n;1"
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusAnchorL}
      />
    </LabelsTopLayout>

    <strong class="block mt-4 -mb-2">Offset</strong>
    <LabelsTopLayout>
      <NumberField
        value={bind(m.offsetT, 10)}
        label="Top"
        style="width: 60px"
        step={5}
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusOffsetT}
      />
      <NumberField
        value={bind(m.offsetR, -10)}
        label="Right"
        style="width: 60px"
        step={5}
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusOffsetR}
      />
      <NumberField
        value={bind(m.offsetB, -10)}
        label="Bottom"
        style="width: 60px"
        step={5}
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusOffsetB}
      />
      <NumberField
        value={bind(m.offsetL, 10)}
        label="Left"
        style="width: 60px"
        step={5}
        reactOn="enter blur wheel"
        trackFocus
        focused={m.focusOffsetL}
      />
    </LabelsTopLayout>

    <div class="flex mt-4">
      <div>
        <Svg
          style={{
            width: bind(m.width, 300),
            height: bind(m.height, 200),
          }}
          padding={1}
        >
          {/* Anchor lines - show where percentage anchors are positioned */}
          <Line
            anchors={tpl(m.anchorT, "{0} 1 {0} 0")}
            style={expr(
              m.focusAnchorT,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          <Line
            anchors={tpl(m.anchorB, "{0} 1 {0} 0")}
            style={expr(
              m.focusAnchorB,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          <Line
            anchors={tpl(m.anchorL, "0 {0} 1 {0}")}
            style={expr(
              m.focusAnchorL,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          <Line
            anchors={tpl(m.anchorR, "0 {0} 1 {0}")}
            style={expr(
              m.focusAnchorR,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          {/* Offset lines - show pixel displacement from anchor to rectangle edge */}
          <Line
            anchors={expr(
              m.anchorT,
              m.anchorL,
              m.anchorR,
              (t, l, r) => `${t} ${(l + r) / 2} ${t} ${(l + r) / 2}`,
            )}
            offset={tpl(m.offsetT, "0 0 {0} 0")}
            style={expr(
              m.focusOffsetT,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          <Line
            anchors={expr(
              m.anchorB,
              m.anchorL,
              m.anchorR,
              (b, l, r) => `${b} ${(l + r) / 2} ${b} ${(l + r) / 2}`,
            )}
            offset={tpl(m.offsetB, "0 0 {0} 0")}
            style={expr(
              m.focusOffsetB,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          <Line
            anchors={expr(
              m.anchorL,
              m.anchorT,
              m.anchorB,
              (l, t, b) => `${(t + b) / 2} ${l} ${(t + b) / 2} ${l}`,
            )}
            offset={tpl(m.offsetL, "0 {0} 0 0")}
            style={expr(
              m.focusOffsetL,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          <Line
            anchors={expr(
              m.anchorR,
              m.anchorT,
              m.anchorB,
              (r, t, b) => `${(t + b) / 2} ${r} ${(t + b) / 2} ${r}`,
            )}
            offset={tpl(m.offsetR, "0 {0} 0 0")}
            style={expr(
              m.focusOffsetR,
              (f) =>
                `stroke: ${f ? "red" : "#ccc"}; stroke-width: ${f ? 2 : 1}`,
            )}
          />
          {/* Rectangle rendered last to appear on top */}
          <Rectangle
            anchors={tpl(
              m.anchorT,
              m.anchorR,
              m.anchorB,
              m.anchorL,
              "{0} {1} {2} {3}",
            )}
            offset={tpl(
              m.offsetT,
              m.offsetR,
              m.offsetB,
              m.offsetL,
              "{0} {1} {2} {3}",
            )}
            style="fill: lightblue; stroke: steelblue"
          />
        </Svg>
        <Resizer size={bind(m.height, 200)} horizontal />
      </div>
      <Resizer size={bind(m.width, 300)} />
    </div>
  </div>
);

```

## Aspect Ratio

When you can't give fixed dimensions to an SVG element, use `aspectRatio` with `autoHeight` or `autoWidth` to automatically size the element based on available space.

```tsx
import { Svg, Rectangle } from "cx/svg";

export default (
  <Svg style="width: 100%" aspectRatio={4} autoHeight>
    <Rectangle anchors="0 1 1 0" style="fill: lightblue" />
  </Svg>
);

```

In this example, the height is automatically calculated to be 4 times smaller than the width.

## Configuration

| Property      | Type            | Description                                                 |
| ------------- | --------------- | ----------------------------------------------------------- |
| `anchors`     | `string/number` | Defines how bounds are tied to parent. Format: `"t r b l"`. |
| `offset`      | `string/number` | Translates edges of the bounding box. Format: `"t r b l"`.  |
| `margin`      | `string/number` | Applies margin to boundaries (CSS-like behavior).           |
| `padding`     | `string/number` | Padding applied before passing bounds to children.          |
| `aspectRatio` | `number`        | Aspect ratio (width/height). Default: `1.618`.              |
| `autoWidth`   | `boolean`       | Calculate width from height and aspect ratio.               |
| `autoHeight`  | `boolean`       | Calculate height from width and aspect ratio.               |