Skip to content
D
Documentation

Edit UML classes

how-to
5 min readUpdated

Use the UML kit when your data describes classes, members and relationships. Build the diagram with umlDiagram, then edit its mounted classes with umlClass. You get class cards with name, attribute and method compartments, all seven relationship notations, and a scrolling body for a capped card.

1. Declare the classes and relationships

Put this shared file beside the framework sample you choose below. UmlDiagramOptions types the data; member signatures are strings, including their visibility prefixes. The kit returns nodes, edges and a wiring step rather than a separate UML model.

The sample places seven pairs of cards in four rows. Shape has enough members to demonstrate scrolling after the ready callback caps its height.

ts
import { umlDiagram, umlClass, type UmlDiagramOptions } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';

const data: UmlDiagramOptions = {
  editable: true,
  classes: [
    { id: 'Shape', position: { x: 40, y: 40 }, width: 220,
      attributes: ['# x: float', '# y: float', '# width: float', '# height: float',
        '# rotation: float', '# visible: bool'],
      methods: ['+ area(): float', '+ draw(): void', '+ bounds(): Rect'] },
    { id: 'Circle', position: { x: 350, y: 40 }, width: 220,
      attributes: ['+ radius: float'], methods: ['+ area(): float'] },
    { id: 'Drawable', stereotype: 'interface', position: { x: 670, y: 40 }, width: 220,
      methods: ['+ render(): void'] },
    { id: 'Button', position: { x: 980, y: 40 }, width: 220,
      attributes: ['+ label: String'], methods: ['+ render(): void'] },
    { id: 'Playlist', position: { x: 40, y: 300 }, width: 220,
      attributes: ['+ name: String'], methods: ['+ add(s): void'] },
    { id: 'Song', position: { x: 350, y: 300 }, width: 220,
      attributes: ['+ title: String'], methods: ['+ play(): void'] },
    { id: 'Window', position: { x: 670, y: 300 }, width: 220,
      attributes: ['+ title: String'], methods: ['+ close(): void'] },
    { id: 'TitleBar', position: { x: 980, y: 300 }, width: 220,
      attributes: ['+ text: String'], methods: ['+ paint(): void'] },
    { id: 'Student', position: { x: 40, y: 560 }, width: 220,
      attributes: ['+ name: String'], methods: ['+ enroll(): void'] },
    { id: 'Course', position: { x: 350, y: 560 }, width: 220,
      attributes: ['+ code: String'], methods: ['+ start(): void'] },
    { id: 'Order', position: { x: 670, y: 560 }, width: 220,
      attributes: ['+ id: int'], methods: ['+ total(): Money'] },
    { id: 'Product', position: { x: 980, y: 560 }, width: 220,
      attributes: ['+ sku: String'], methods: ['+ price(): Money'] },
    { id: 'OrderService', position: { x: 40, y: 820 }, width: 220,
      methods: ['+ checkout(o): void'] },
    { id: 'Logger', position: { x: 350, y: 820 }, width: 220,
      methods: ['+ log(msg): void'] },
  ],
  relationships: [
    { from: 'Circle', to: 'Shape', kind: 'inheritance',
      fromSide: 'left', toSide: 'right' },
    { from: 'Button', to: 'Drawable', kind: 'realization',
      fromSide: 'left', toSide: 'right' },
    { from: 'Playlist', to: 'Song', kind: 'aggregation',
      fromSide: 'right', toSide: 'left' },
    { from: 'Window', to: 'TitleBar', kind: 'composition',
      fromSide: 'right', toSide: 'left' },
    { from: 'Student', to: 'Course', kind: 'association',
      multiplicity: ['0..*', '1..*'], fromSide: 'right', toSide: 'left' },
    { from: 'Order', to: 'Product', kind: 'directed-association',
      multiplicity: ['1', '0..*'], fromSide: 'right', toSide: 'left' },
    { from: 'OrderService', to: 'Logger', kind: 'dependency', label: '«uses»',
      fromSide: 'right', toSide: 'left' },
  ],
};

export function buildUmlExample() {
  return umlDiagram(data);
}

export async function editShape(api: DiagramInstance): Promise<void> {
  const shape = umlClass(api, 'Shape');
  await shape.rename('AbstractShape');
  await shape.setAbstract(true);
  await shape.attributes.renameAt(0, '# left: float');
  await shape.methods.add('+ move(): void');
  await shape.resize({ height: 170 });
  api.fitView(40);
}

Read the relationship notation

Each UmlRelationshipSpec names class ids in from and to. Set the whole at from for aggregation and composition; set the parent or interface at to for inheritance and realization. The kit supplies orthogonal routing and markers—you do not need to configure arrow shapes yourself.

kindLineMarker
inheritanceSolidHollow triangle at to (generalization)
realizationDashedHollow triangle at to
associationSolidNo arrowheads
directed-associationSolidOpen arrow at to
aggregationSolidHollow diamond at from
compositionSolidFilled diamond at from
dependencyDashedOpen arrow at to

multiplicity supplies the chips at the [from, to] ends. fromSide and toSide pin the attachment sides; the sample uses facing sides for each horizontal pair.

2. Mount the kit in your framework

Choose one installation command and one host sample. These samples use the same shared data and run editShape() only after the diagram mounts. The ready callback receives a live DiagramInstance.

JavaScript / TypeScript

render mounts the kit and returns the instance. It runs the kit's wiring step automatically, including multiplicity labels; do not add them yourself.

bash
npm install @grafloria/element @grafloria/renderer @grafloria/engine
html
<div id="uml" style="height: 720px"></div>
<script type="module" src="/src/main.ts"></script>

Place uml-example.ts in src with this file. Keep the returned cleanup function for the close or unmount hook of the view that owns the diagram.

ts
import { render } from '@grafloria/element';
import { buildUmlExample, editShape } from './uml-example';

export function mountUml(container: HTMLElement): () => void {
  container.style.height = '720px';
  const api = render(buildUmlExample(), container);
  void editShape(api);
  return () => api.dispose();
}

const container = document.getElementById('uml');
if (!container) throw new Error('Missing #uml container');
export const unmountUml = mountUml(container);

React

Pass the kit to GrafloriaDiagram and edit through onReady. The component owns disposal on unmount.

bash
npm install @grafloria/react @grafloria/element @grafloria/renderer @grafloria/engine react react-dom
tsx
import { GrafloriaDiagram } from '@grafloria/react';
import { buildUmlExample, editShape } from './uml-example';

const spec = buildUmlExample();

export default function App() {
  return (
    <div style={{ height: '720px' }}>
      <GrafloriaDiagram spec={spec} onReady={editShape} />
    </div>
  );
}
Four rows contain seven UML card pairs with the italic AbstractShape title, triangles, diamonds, multiplicity labels and member-add controls.

Vue

Use GrafloriaDiagram's ready event to run editShape() on the mounted UML classes; see Documents and kits for the shared kit-host setup and lifecycle.

bash
npm install @grafloria/vue @grafloria/element @grafloria/renderer @grafloria/engine vue
vue
<script setup lang="ts">
import { GrafloriaDiagram } from '@grafloria/vue';
import { buildUmlExample, editShape } from './uml-example';

const spec = buildUmlExample();
</script>

<template>
  <GrafloriaDiagram :spec="spec" @ready="editShape" style="height: 720px" />
</template>

Angular

Import GrafloriaDiagramComponent into your standalone component. Its ready output supplies the instance; the host disposes it on destruction.

bash
npm install @grafloria/angular @grafloria/element @grafloria/renderer @grafloria/engine @angular/common @angular/core @angular/forms @angular/platform-browser rxjs
ts
import { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { buildUmlExample, editShape } from './uml-example';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [GrafloriaDiagramComponent],
  template: `
    <grafloria-diagram [spec]="spec" (ready)="editShape($event)"
      style="display: block; height: 720px" />
  `,
})
export class AppComponent {
  readonly spec = buildUmlExample();
  readonly editShape = editShape;
}

Qwik

Use GrafloriaDiagram with spec$ to build the function-bearing kit spec in the browser, then edit through onReady$. No live instance enters resumable state in this sample. The component owns disposal on unmount. See Documents and kits for resumable kit state.

bash
npm install @grafloria/qwik @grafloria/element @grafloria/renderer @grafloria/engine @builder.io/qwik
tsx
import { component$ } from '@builder.io/qwik';
import { GrafloriaDiagram } from '@grafloria/qwik';
import { buildUmlExample, editShape } from './uml-example';

export default component$(() => (
  <div style={{ height: '720px' }}>
    <GrafloriaDiagram
      spec$={() => buildUmlExample()}
      onReady$={(api) => { void editShape(api); }}
    />
  </div>
));

3. Read and edit the live class

After the ready callback completes, Shape displays the italic title AbstractShape. Its first attribute reads # left: float, and its methods include + move(): void. The 170-pixel card keeps its name compartment outside a scrolling body: scroll inside the card to reach its lower members. The other pairs show triangles, diamonds, plain and directed associations, multiplicity chips and a dashed dependency.

umlClass(api, 'Shape') returns a typed UmlClass handle. The id remains Shape after a rename. Getters read the live class; spec returns a deep copy, so changing that copy does not edit the diagram.

  • Read members with attributes.list() or methods.list(), read their count with length, and read one with at(index).
  • Append with add(member), or insert with add(member, { at: index }).
  • Replace a member string with renameAt(index, member) and delete one with removeAt(index).
  • Change the title with rename(), the italic flag with setAbstract(), and the displayed stereotype with setStereotype().
  • Cap the body with resize({ height: 170 }), as the shared callback does. You can also declare height in the initial class data.

The edit methods return Promise<boolean>. On this mounted instance each call enters the engine's command history and repaints the card; await one edit before deriving the next from the live members. The five calls in editShape() are five history steps, not one transaction. For toolbar undo and redo, see Commands and history.

Because editable: true is set, you also get the kit's in-canvas editing: double-click a class name or member to rename it, use + attribute or + method to add a member, and use the member's × control to delete it. These edits use the same undoable update path. Typed-handle edits do not require this editing chrome.

Options that matter

UmlClassSpec controls each card's data and dimensions.

OptionTypeDefaultWhat it does
editablebooleanfalseAdds rename, add and delete interactions to the cards.
rowSelectionbooleanEnabled unless falseEnables selectable members.
Class namestringClass idSets the displayed title without changing its id.
Class stereotypestringNoneDisplays «stereotype» above the title; abstract and interface also italicize the title.
Class heightnumberDerived from membersFixes the card height and enables a scrolling body when content exceeds it.
Class widthnumberDerived from content, between 200 and 420 pixelsSets an explicit width instead of auto-sizing.
Relationship kindSeven string literals listed above'association'Chooses the line and marker notation.
Relationship multiplicity[string, string]NoneAdds chips at the from and to ends.

Pitfalls

  • Acquire handles after mounting. umlClass() throws if the id is absent or the node is not a kit UML class.
  • Use the handle to edit, not a modified spec copy. A changed kit spec passed to the React, Vue or Angular host replaces the diagram; keep the seed spec stable while editing the live instance.
  • A fixed height caps the body, not the member list: members remain in the data and are reached by scrolling. Long member strings stay on one line and ellipsize when they exceed the available width; set an explicit width when you need more space.
  • The kit suppresses canvas resize handles on its cards. Use class dimensions or the typed resize() method to size them.

Was this page helpful?