Svg
import { Svg } from 'cx/svg'; Copied CxJS has excellent support for Scalable Vector Graphics (SVG) and enables responsive layouts and charts using the concept of bounded objects.
<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 likex,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 usinganchors,offset, andmarginproperties
Bounded Objects
Use the
Svgcontainer instead of the nativesvgelement 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:
0aligns to the left/top edge1aligns to the right/bottom edge- Values between
0and1position proportionally
<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.
<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 offsetsmargin={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:
<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> Full |
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.
<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. |