Skip to content

Commit

Permalink
feat: [classDiagram] initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hikerpig committed Dec 30, 2023
1 parent 66ec825 commit e7393b9
Show file tree
Hide file tree
Showing 22 changed files with 1,518 additions and 14 deletions.
9 changes: 9 additions & 0 deletions .changeset/unlucky-geckos-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@pintora/core': minor
'@pintora/diagrams': minor
'@pintora/renderer': minor
'@pintora/test-shared': minor
'pintora-website': minor
---

Add classDiagram
7 changes: 7 additions & 0 deletions packages/pintora-core/src/util/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ export function getPositionOfRect(rect: TRect, position: number) {
return { x, y }
}

export function movePointPosition(p: Point, offsets: Point) {
return {
x: p.x + offsets.x,
y: p.y + offsets.y,
}
}

export function moveRectPosition(rect: TRect, offsets: Point) {
return {
width: rect.width,
Expand Down
13 changes: 10 additions & 3 deletions packages/pintora-core/src/util/text-metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ export function calculateTextDimensions(text: string, font?: IFont) {
let width = 0
let height = 0
const fontSize = font?.fontSize || 14
lines.forEach(line => {
lines.forEach((line, i) => {
const lineMetric = getLineMetric(line, font)
// console.log('line metric', line, lineMetric)
const w = lineMetric.width
width = Math.max(w, width)
let lineHeight = fontSize
if ('actualBoundingBoxDescent' in lineMetric) {
lineHeight = lineMetric.actualBoundingBoxAscent + lineMetric.actualBoundingBoxDescent
if (i === 0) {
if ('actualBoundingBoxDescent' in lineMetric) {
lineHeight = lineMetric.actualBoundingBoxAscent + lineMetric.actualBoundingBoxDescent
}
} else {
// svg renderer antv/g currently adds tspan dy with '1em', which matches fontSize
// so we will calculate height with similar method
// TODO: but it has some differences with canvas
lineHeight = fontSize
}
height += lineHeight
})
Expand Down
1 change: 1 addition & 0 deletions packages/pintora-diagrams/scripts/build-grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const grammarFiles = [
{ input: 'src/mindmap/parser/mindmap.ne', output: 'src/mindmap/parser/mindmap.ts' },
{ input: 'src/gantt/parser/gantt.ne', output: 'src/gantt/parser/gantt.ts' },
{ input: 'src/dot/parser/dotDiagram.ne', output: 'src/dot/parser/dotDiagram.ts' },
{ input: 'src/class/parser/classDiagram.ne', output: 'src/class/parser/classDiagram.ts' },
]

grammarFiles.forEach(async ({ input, output }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`class parser can parse class label 1`] = `
{
"class1": {
"annotation": "",
"fullName": "class1",
"label": "class1",
"members": [
{
"access": "public",
"isMethod": false,
"modifier": null,
"name": "RED",
"raw": "RED",
"typeName": "",
},
],
"name": "class1",
"namespace": "",
},
}
`;

exports[`class parser can parse methods and fields 1`] = `
{
"classes": {
"C1": {
"annotation": "",
"fullName": "C1",
"label": "C1",
"members": [
{
"access": "public",
"isMethod": false,
"modifier": null,
"name": "field1",
"raw": "string field1",
"typeName": "string",
},
{
"access": "public",
"isMethod": true,
"modifier": null,
"name": "method1()",
"raw": "int method1()",
"typeName": "int",
},
{
"access": "public",
"isMethod": false,
"modifier": null,
"name": "field2",
"raw": "field2: number",
"typeName": "number",
},
{
"access": "public",
"isMethod": true,
"name": "method2()",
"raw": "method2()",
"typeName": "",
},
],
"name": "C1",
"namespace": "",
},
},
"configParams": [],
"overrideConfig": {},
"relations": [],
"title": "",
}
`;

exports[`class parser can parse relationship between classes 1`] = `
[
{
"dashed": false,
"label": "",
"labelLeft": null,
"labelRight": null,
"left": "A1",
"relation": "INHERITANCE",
"right": "A2",
"type": "addRelation",
},
{
"dashed": false,
"label": "",
"labelLeft": null,
"labelRight": null,
"left": "B1",
"relation": "COMPOSITION",
"right": "B2",
"type": "addRelation",
},
{
"dashed": false,
"label": "",
"labelLeft": null,
"labelRight": null,
"left": "C1",
"relation": "AGGREGATION",
"right": "C2",
"type": "addRelation",
},
{
"dashed": false,
"label": "association",
"labelLeft": null,
"labelRight": null,
"left": "D1",
"relation": "ASSOCIATION",
"right": "D2",
"type": "addRelation",
},
]
`;
119 changes: 119 additions & 0 deletions packages/pintora-diagrams/src/class/__tests__/class-parser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { parse } from '../parser'
import db from '../db'
import { stripStartEmptyLines } from '@pintora/test-shared'

describe('class parser', () => {
afterEach(() => {
db.clear()
})

it('can parse class name', () => {
const example = stripStartEmptyLines(`
classDiagram
class C1
class N1.N2.C2
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir.classes['C1']).toMatchObject({ name: 'C1' })
expect(ir.classes['N1.N2.C2']).toMatchObject({ name: 'C2', fullName: 'N1.N2.C2', namespace: 'N1.N2' })
})

it('can parse methods and fields', () => {
const example = stripStartEmptyLines(`
classDiagram
class C1 {
string field1
int method1()
field2: number
}
C1: method2()
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir).toMatchSnapshot()
// console.log(JSON.stringify(ir, null, 2))
})

it('can parse member with modifier', () => {
const example = stripStartEmptyLines(`
classDiagram
class C1 {
{ static } int method1()
{abstract} string field1
}
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir.classes['C1'].members[0].modifier).toEqual('static')
expect(ir.classes['C1'].members[1].modifier).toEqual('abstract')
})

it('can parse relationship between classes', () => {
const example = stripStartEmptyLines(`
classDiagram
A1 <|-- A2
B1 *-- B2
C1 o-- C2
D1 --> D2 : association
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir.relations).toMatchSnapshot()
})

it('can parse quoted labels on relations', () => {
const example = stripStartEmptyLines(`
classDiagram
C1 "1" *-- "many" C2 : contains
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir.relations[0]).toMatchObject({
type: 'addRelation',
left: 'C1',
right: 'C2',
relation: 'COMPOSITION',
labelLeft: '1',
labelRight: 'many',
label: 'contains',
dashed: false,
})
})

it('can parse class annotation', () => {
const example = stripStartEmptyLines(`
classDiagram
<< Interface >> C1
class C2 {
<< Serializable >>
string test
}
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir.classes).toMatchObject({
C1: {
annotation: 'Interface',
},
C2: {
annotation: 'Serializable',
},
})
})

it('can parse class label', () => {
const example = stripStartEmptyLines(`
classDiagram
class "This is class label" as class1
class class1 {
RED
}
`)
parse(example)
const ir = db.getDiagramIR()
expect(ir.classes).toMatchSnapshot()
})
})
Loading

0 comments on commit e7393b9

Please sign in to comment.