Skip to content

Commit

Permalink
fix: classDiagram relation direction, and add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hikerpig committed Jan 6, 2024
1 parent 6777231 commit 9bdb57e
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 26 deletions.
8 changes: 8 additions & 0 deletions .changeset/fair-poems-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'pintora-demo': patch
'@pintora/diagrams': patch
'@pintora/test-shared': patch
'pintora-website': patch
---

fix: classDiagram relation direction, and add docs
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ jspm_packages/

# dotenv environment variables file
.env
.envrc


# End of https://www.gitignore.io/api/node
Expand Down
29 changes: 29 additions & 0 deletions demo/cypress/e2e/class/class.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { stripStartEmptyLines } from '@pintora/test-shared'
import { makeSnapshotCases } from '../test-utils/render'

describe('Class Diagram', () => {
makeSnapshotCases([
{
description: 'Should render class diagram example',
code: stripStartEmptyLines(`
classDiagram
class Fruit {
float sweetness
-float age
float getAge()
}
class Apple {
}
%% There are so many kind of fruits
Fruit <|-- Apple
Fruit <|-- Kiwi
Fruit <|-- Banana
Container "1" *-- "many" Fruit : holds
`),
},
])
})
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ exports[`class parser can parse relationship between classes 1`] = `
"labelRight": null,
"left": "A1",
"relation": "INHERITANCE",
"reversed": true,
"right": "A2",
"type": "addRelation",
},
Expand All @@ -92,6 +93,7 @@ exports[`class parser can parse relationship between classes 1`] = `
"labelRight": null,
"left": "B1",
"relation": "COMPOSITION",
"reversed": true,
"right": "B2",
"type": "addRelation",
},
Expand All @@ -102,6 +104,7 @@ exports[`class parser can parse relationship between classes 1`] = `
"labelRight": null,
"left": "C1",
"relation": "AGGREGATION",
"reversed": true,
"right": "C2",
"type": "addRelation",
},
Expand All @@ -112,6 +115,7 @@ exports[`class parser can parse relationship between classes 1`] = `
"labelRight": null,
"left": "D1",
"relation": "ASSOCIATION",
"reversed": false,
"right": "D2",
"type": "addRelation",
},
Expand Down
19 changes: 12 additions & 7 deletions packages/pintora-diagrams/src/class/artist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,14 @@ class ClassDiagramDraw {
fontFamily: conf.fontFamily,
}

const startNodeId = r.reversed ? r.right : r.left
const ednNodeId = r.reversed ? r.left : r.right

let minlen = 1
if (r.label) {
labelDims = calculateTextDimensions(r.label, fontConfig)
minlen = Math.ceil(labelDims.height / conf.ranksep) + 1
const startNode = g.node(r.left)
const startNode = g.node(startNodeId)
const extraPad = (labelDims.width - startNode.width) / 2
if (extraPad > 0) {
// so label won't overlap
Expand Down Expand Up @@ -180,8 +183,10 @@ class ClassDiagramDraw {
})
relationGroupMark.children.push(rightLabelMark)
}
const startLabelMark = r.reversed ? rightLabelMark : leftLabelMark
const endLabelMark = r.reversed ? leftLabelMark : rightLabelMark

g.setEdge(r.left, r.right, {
g.setEdge(startNodeId, ednNodeId, {
label: r.relation,
minlen,
onLayout: (data: BaseEdgeData) => {
Expand Down Expand Up @@ -235,13 +240,13 @@ class ClassDiagramDraw {

// draw label beside intersection of node and line
const LABEL_X_OFFSET = 5
if (leftLabelMark) {
if (startLabelMark) {
const startIntersectionPoint = data.points[2]
safeAssign(leftLabelMark.attrs, movePointPosition(startIntersectionPoint, { x: LABEL_X_OFFSET, y: 0 }))
safeAssign(startLabelMark.attrs, movePointPosition(startIntersectionPoint, { x: LABEL_X_OFFSET, y: 0 }))
}

if (rightLabelMark) {
safeAssign(rightLabelMark.attrs, movePointPosition(lastPoint, { x: LABEL_X_OFFSET, y: 0 }))
if (endLabelMark) {
safeAssign(endLabelMark.attrs, movePointPosition(lastPoint, { x: LABEL_X_OFFSET, y: 0 }))
}
},
})
Expand Down Expand Up @@ -273,7 +278,7 @@ type EntitySection = {
*/
class EntityMarkBuilder {
group: Group = makeEmptyGroup()
rowPadding = 5
rowPadding = 8
/** y offset inside entity */
curY = 0
curHeight = 0
Expand Down
4 changes: 4 additions & 0 deletions packages/pintora-diagrams/src/class/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export type ClassRelation = {
labelLeft?: string
labelRight?: string
dashed?: boolean
/**
* normally we draw the relation from left to right, if reversed is true, we draw the relation from right to left
*/
reversed?: boolean
}

type Access = 'public' | 'private' | 'protected'
Expand Down
25 changes: 16 additions & 9 deletions packages/pintora-diagrams/src/class/parser/classDiagram.ne
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ memberLabelStatement ->
relationStatement ->
classInRelation textInQuote:? relation textInQuote:? classInRelation (%WS:* %COLON %WS:* words):? {%
function(d) {
let relationRaw = { type: d[2], dashed: false }
let relationRaw = { type: d[2], dashed: false, reversed: false }
let labelLeft = d[1]
let labelRight = d[3]
if (d[2].type) {
Expand All @@ -207,6 +207,7 @@ relationStatement ->
labelRight,
label,
dashed: Boolean(relationRaw.dashed),
reversed: Boolean(relationRaw.reversed)
} as Action
}
%}
Expand All @@ -215,16 +216,22 @@ classInRelation ->
%VALID_TEXT {% (d) => ({ name: tv(d[0])}) %}

relation ->
"<|--" {% (d) => { return { type: Relation.INHERITANCE } } %}
| "<|.." {% (d) => { return { type: Relation.INHERITANCE, dashed: true } } %}
| "*--" {% (d) => { return { type: Relation.COMPOSITION } } %}
| "*.." {% (d) => { return { type: Relation.COMPOSITION, dashed: true } } %}
| "o--" {% (d) => { return { type: Relation.AGGREGATION } } %}
| "o.." {% (d) => { return { type: Relation.AGGREGATION, dashed: true } } %}
"<|--" {% (d) => { return { type: Relation.INHERITANCE, reversed: true } } %}
| "<|.." {% (d) => { return { type: Relation.INHERITANCE, reversed: true, dashed: true } } %}
| "--|>" {% (d) => { return { type: Relation.INHERITANCE } } %}
| "..|>" {% (d) => { return { type: Relation.INHERITANCE, dashed: true } } %}
| "*--" {% (d) => { return { type: Relation.COMPOSITION, reversed: true } } %}
| "*.." {% (d) => { return { type: Relation.COMPOSITION, reversed: true, dashed: true } } %}
| "--*" {% (d) => { return { type: Relation.COMPOSITION } } %}
| "..*" {% (d) => { return { type: Relation.COMPOSITION, dashed: true } } %}
| "o--" {% (d) => { return { type: Relation.AGGREGATION, reversed: true } } %}
| "o.." {% (d) => { return { type: Relation.AGGREGATION, reversed: true, dashed: true } } %}
| "--o" {% (d) => { return { type: Relation.AGGREGATION } } %}
| "..o" {% (d) => { return { type: Relation.AGGREGATION, dashed: true } } %}
| "-->" {% (d) => { return { type: Relation.ASSOCIATION } } %}
| "..>" {% (d) => { return { type: Relation.ASSOCIATION, dashed: true } } %}
| "<--" {% (d) => { return { type: Relation.ASSOCIATION } } %}
| "<.." {% (d) => { return { type: Relation.ASSOCIATION } } %}
| "<--" {% (d) => { return { type: Relation.ASSOCIATION, reversed: true } } %}
| "<.." {% (d) => { return { type: Relation.ASSOCIATION, reversed: true, dashed: true } } %}
| "--" {% (d) => { return { type: Relation.LINK } } %}
| ".." {% (d) => { return { type: Relation.LINK, dashed: true } } %}

Expand Down
21 changes: 14 additions & 7 deletions packages/test-shared/src/data/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,17 +327,24 @@ export const classExample: DiagramExample = {
description: 'Sample for a classDiagram',
code: stripStartEmptyLines(`
classDiagram
class Fruit {
<<interface>>
float sweetness
-float age
class Fruit {
float sweetness
}
float getAge()
}
class Apple {
}
class Apple {
float softness
{static} Apple fromString(str)
}
Fruit <|-- Apple
%% There are so many kind of fruits
Fruit <|-- Apple
Fruit <|-- Banana
Container "1" *-- "many" Fruit : holds
Fruit "many" --* "1" Bag: packed into
`),
}

Expand Down
31 changes: 31 additions & 0 deletions website/docs/configuration/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,37 @@ export type DOTConf = {
}
```
### class
Config for class diagram. For more detail, check the [latest code](https://github.com/hikerpig/pintora/blob/master/packages/pintora-diagrams/src/class/config.ts).
```ts
export type ClassConf = {
diagramPadding: number

layoutDirection: string
ranksep: number
nodesep: number
edgesep: number
edgeType: EdgeType

entityBackground: string
entityBorderColor: string
entityBodyBackground: string
entityTextColor: string
labelBackground: string
relationLineColor: string
relationTextColor: string

entityRadius: number

fontSize: number
fontWeight: MarkAttrs['fontWeight']
fontFamily: string
}
```
## Override config by directive
If you don't have the access to add JS script into the page or in the Node.js module, it's also possible to override some configs of the builtin diagrams through the `@param` or `@config` directive.
Expand Down
54 changes: 51 additions & 3 deletions website/docs/diagrams/class-diagram.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ title: Class Diagram

```pintora play
classDiagram
class Fruit {
<<interface>>
float sweetness
-float age
float getAge()
}
class Apple {
float softness
{static} Apple fromString(str)
}
%% There are so many kind of fruits
Fruit <|-- Apple
Fruit <|-- Kiwi
Fruit <|-- Banana
Container "1" *-- "many" Fruit : holds
Fruit "many" --* "1" Bag: packed into
```

## Define a class
Expand Down Expand Up @@ -58,6 +62,30 @@ Define member visibility with these symbols. The notaiton is optional but it sho
- `-` private
- `~` package/internal

## Class annotations

You can annotate classes with markers to provide additional metadata about the class. This can give a clearer indication about its nature. Some common annotations include:

- `<<interface>>` To represent an interface class
- `<<enumeration>>` To represent an enum

Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class:

```pintora play
classDiagram
class Shape
<<interface>> Shape
Shape : vertices
Shape : draw()
class Color {
<<enumeration>>
Red
Green
Blue
}
```

## Relations between classes

You can define relations between classes using following symbols :
Expand Down Expand Up @@ -86,7 +114,7 @@ classO .. classP

### Label on relations

It is possible to add a label on the relation, using `:`, followed by the text of the label.
You can add a label on the relation, using `:`, followed by the text of the label.

Also, you can use double-quotes `""` on each side of the relation for cardinality.

Expand All @@ -96,3 +124,23 @@ classDiagram
classC o-- classD : aggregation
classE --> "1" classF
```

## Override config

You can override diagarm config through `@param` directive.

All available configs can be seen in the [Config page](../configuration/config.md#class).

```pintora play
classDiagram
@param entityBackground #61afef
class Animal {
}
class Dog {
void bark()
}
Animal <|-- Dog
```

0 comments on commit 9bdb57e

Please sign in to comment.