Feat:支持自定义创建节点形状的方法

This commit is contained in:
街角小林 2024-01-19 15:50:32 +08:00
parent e590161f0a
commit 9b1f26f6e9
9 changed files with 124 additions and 53 deletions

View File

@ -265,5 +265,15 @@ export const defaultOpt = {
to: '' // 关联线目标节点上端点的位置 to: '' // 关联线目标节点上端点的位置
}, },
// 是否允许调整关联线两个端点的位置 // 是否允许调整关联线两个端点的位置
enableAdjustAssociativeLinePoints: true enableAdjustAssociativeLinePoints: true,
// 自定义创建节点形状的方法,可以传一个函数,均接收一个参数
// 矩形、圆角矩形、椭圆、圆等形状会调用该方法
// 接收svg path字符串返回svg节点
customCreateNodePath: null,
// 菱形、平行四边形、八角矩形、外三角矩形、内三角矩形等形状会调用该方法
// 接收points数组点位返回svg节点
customCreateNodePolygon: null,
// 自定义转换节点连线路径的方法
// 接收svg path字符串返回转换后的svg path字符串
customTransformNodeLinePath: null
} }

View File

@ -1,10 +1,11 @@
import { Rect, Polygon, Path } from '@svgdotjs/svg.js' import { Polygon, Path, SVG } from '@svgdotjs/svg.js'
import { CONSTANTS } from '../../../constants/constant' import { CONSTANTS } from '../../../constants/constant'
// 节点形状类 // 节点形状类
export default class Shape { export default class Shape {
constructor(node) { constructor(node) {
this.node = node this.node = node
this.mindMap = node.mindMap
} }
// 形状需要的padding // 形状需要的padding
@ -106,11 +107,29 @@ export default class Shape {
} }
} }
// 创建路径节点
createPath(pathStr) {
const { customCreateNodePath } = this.mindMap.opt
if (customCreateNodePath) {
return SVG(customCreateNodePath(pathStr))
}
return new Path().plot(pathStr)
}
// 创建多边形节点
createPolygon(points) {
const { customCreateNodePolygon } = this.mindMap.opt
if (customCreateNodePolygon) {
return SVG(customCreateNodePolygon(points))
}
return new Polygon().plot(points)
}
// 创建矩形 // 创建矩形
createRect() { createRect() {
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
let borderRadius = this.node.style.merge('borderRadius') let borderRadius = this.node.style.merge('borderRadius')
return new Path().plot(` const pathStr = `
M${borderRadius},0 M${borderRadius},0
L${width - borderRadius},0 L${width - borderRadius},0
C${width - borderRadius},0 ${width},${0} ${width},${borderRadius} C${width - borderRadius},0 ${width},${0} ${width},${borderRadius}
@ -123,7 +142,8 @@ export default class Shape {
L${0},${borderRadius} L${0},${borderRadius}
C${0},${borderRadius} ${0},${0} ${borderRadius},${0} C${0},${borderRadius} ${0},${0} ${borderRadius},${0}
Z Z
`) `
return this.createPath(pathStr)
} }
// 创建菱形 // 创建菱形
@ -139,12 +159,13 @@ export default class Shape {
let bottomY = height let bottomY = height
let leftX = 0 let leftX = 0
let leftY = halfHeight let leftY = halfHeight
return new Polygon().plot([ const points = [
[topX, topY], [topX, topY],
[rightX, rightY], [rightX, rightY],
[bottomX, bottomY], [bottomX, bottomY],
[leftX, leftY] [leftX, leftY]
]) ]
return this.createPolygon(points)
} }
// 创建平行四边形 // 创建平行四边形
@ -152,32 +173,34 @@ export default class Shape {
let { paddingX } = this.node.getPaddingVale() let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
return new Polygon().plot([ const points = [
[paddingX, 0], [paddingX, 0],
[width, 0], [width, 0],
[width - paddingX, height], [width - paddingX, height],
[0, height] [0, height]
]) ]
return this.createPolygon(points)
} }
// 创建圆角矩形 // 创建圆角矩形
createRoundedRectangle() { createRoundedRectangle() {
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
let halfHeight = height / 2 let halfHeight = height / 2
return new Path().plot(` const pathStr = `
M${halfHeight},0 M${halfHeight},0
L${width - halfHeight},0 L${width - halfHeight},0
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height} A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
L${halfHeight},${height} L${halfHeight},${height}
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0} A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
`) `
return this.createPath(pathStr)
} }
// 创建八角矩形 // 创建八角矩形
createOctagonalRectangle() { createOctagonalRectangle() {
let w = 5 let w = 5
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
return new Polygon().plot([ const points = [
[0, w], [0, w],
[w, 0], [w, 0],
[width - w, 0], [width - w, 0],
@ -186,7 +209,8 @@ export default class Shape {
[width - w, height], [width - w, height],
[w, height], [w, height],
[0, height - w] [0, height - w]
]) ]
return this.createPolygon(points)
} }
// 创建外三角矩形 // 创建外三角矩形
@ -194,14 +218,15 @@ export default class Shape {
let { paddingX } = this.node.getPaddingVale() let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
return new Polygon().plot([ const points = [
[paddingX, 0], [paddingX, 0],
[width - paddingX, 0], [width - paddingX, 0],
[width, height / 2], [width, height / 2],
[width - paddingX, height], [width - paddingX, height],
[paddingX, height], [paddingX, height],
[0, height / 2] [0, height / 2]
]) ]
return this.createPolygon(points)
} }
// 创建内三角矩形 // 创建内三角矩形
@ -209,14 +234,15 @@ export default class Shape {
let { paddingX } = this.node.getPaddingVale() let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
return new Polygon().plot([ const points = [
[0, 0], [0, 0],
[width, 0], [width, 0],
[width - paddingX / 2, height / 2], [width - paddingX / 2, height / 2],
[width, height], [width, height],
[0, height], [0, height],
[paddingX / 2, height / 2] [paddingX / 2, height / 2]
]) ]
return this.createPolygon(points)
} }
// 创建椭圆 // 创建椭圆
@ -224,12 +250,13 @@ export default class Shape {
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
let halfWidth = width / 2 let halfWidth = width / 2
let halfHeight = height / 2 let halfHeight = height / 2
return new Path().plot(` const pathStr = `
M${halfWidth},0 M${halfWidth},0
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height} A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
M${halfWidth},${height} M${halfWidth},${height}
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0} A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
`) `
return this.createPath(pathStr)
} }
// 创建圆 // 创建圆
@ -237,12 +264,13 @@ export default class Shape {
let { width, height } = this.getNodeSize() let { width, height } = this.getNodeSize()
let halfWidth = width / 2 let halfWidth = width / 2
let halfHeight = height / 2 let halfHeight = height / 2
return new Path().plot(` const pathStr = `
M${halfWidth},0 M${halfWidth},0
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height} A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
M${halfWidth},${height} M${halfWidth},${height}
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0} A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
`) `
return this.createPath(pathStr)
} }
} }

View File

@ -505,9 +505,19 @@ class Base {
// 设置连线样式 // 设置连线样式
setLineStyle(style, line, path, childNode) { setLineStyle(style, line, path, childNode) {
line.plot(path) line.plot(this.transformPath(path))
style && style(line, childNode, true) style && style(line, childNode, true)
} }
// 转换路径,可以转换成特殊风格的线条样式
transformPath(path) {
const { customTransformNodeLinePath } = this.mindMap.opt
if (customTransformNodeLinePath) {
return customTransformNodeLinePath(path)
} else {
return path
}
}
} }
export default Base export default Base

View File

@ -240,14 +240,14 @@ class CatalogOrganization extends Base {
// 父节点的竖线 // 父节点的竖线
let line1 = this.lineDraw.path() let line1 = this.lineDraw.path()
node.style.line(line1) node.style.line(line1)
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`) line1.plot(this.transformPath(`M ${x1},${y1} L ${x1},${y1 + s1}`))
node._lines.push(line1) node._lines.push(line1)
style && style(line1, node) style && style(line1, node)
// 水平线 // 水平线
if (len > 0) { if (len > 0) {
let lin2 = this.lineDraw.path() let lin2 = this.lineDraw.path()
node.style.line(lin2) node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`) lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
node._lines.push(lin2) node._lines.push(lin2)
style && style(lin2, node) style && style(lin2, node)
} }
@ -311,7 +311,9 @@ class CatalogOrganization extends Base {
if (maxy < y1 + expandBtnSize) { if (maxy < y1 + expandBtnSize) {
lin2.hide() lin2.hide()
} else { } else {
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`) lin2.plot(
this.transformPath(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
)
lin2.show() lin2.show()
} }
node._lines.push(lin2) node._lines.push(lin2)
@ -349,7 +351,7 @@ class CatalogOrganization extends Base {
let cx = x1 + 20 let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2 let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path) item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = right + generalizationNodeMargin item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top = item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2 top + (bottom - top - item.generalizationNode.height) / 2

View File

@ -253,15 +253,19 @@ class Fishbone extends Base {
let line = this.lineDraw.path() let line = this.lineDraw.path()
if (this.checkIsTop(item)) { if (this.checkIsTop(item)) {
line.plot( line.plot(
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${ this.transformPath(
item.left `M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
},${item.top + item.height}` item.left
},${item.top + item.height}`
)
) )
} else { } else {
line.plot( line.plot(
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${ this.transformPath(
item.top `M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
}` item.top
}`
)
) )
} }
node.style.line(line) node.style.line(line)
@ -273,9 +277,11 @@ class Fishbone extends Base {
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1) let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
let line = this.lineDraw.path() let line = this.lineDraw.path()
line.plot( line.plot(
`M ${node.left + node.width},${nodeHalfTop} L ${ this.transformPath(
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) `M ${node.left + node.width},${nodeHalfTop} L ${
},${nodeHalfTop}` maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
},${nodeHalfTop}`
)
) )
node.style.line(line) node.style.line(line)
node._lines.push(line) node._lines.push(line)
@ -372,7 +378,7 @@ class Fishbone extends Base {
let cx = x1 + 20 let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2 let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path) item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = right + generalizationNodeMargin item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top = item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2 top + (bottom - top - item.generalizationNode.height) / 2

View File

@ -213,14 +213,16 @@ class OrganizationStructure extends Base {
let line1 = this.lineDraw.path() let line1 = this.lineDraw.path()
node.style.line(line1) node.style.line(line1)
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0 expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`) line1.plot(
this.transformPath(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
)
node._lines.push(line1) node._lines.push(line1)
style && style(line1, node) style && style(line1, node)
// 水平线 // 水平线
if (len > 0) { if (len > 0) {
let lin2 = this.lineDraw.path() let lin2 = this.lineDraw.path()
node.style.line(lin2) node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`) lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
node._lines.push(lin2) node._lines.push(lin2)
style && style(lin2, node) style && style(lin2, node)
} }
@ -253,7 +255,7 @@ class OrganizationStructure extends Base {
let cx = x1 + (x2 - x1) / 2 let cx = x1 + (x2 - x1) / 2
let cy = y1 + 20 let cy = y1 + 20
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path) item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.top = bottom + generalizationNodeMargin item.generalizationNode.top = bottom + generalizationNodeMargin
item.generalizationNode.left = item.generalizationNode.left =
left + (right - left - item.generalizationNode.width) / 2 left + (right - left - item.generalizationNode.width) / 2

View File

@ -274,9 +274,13 @@ class Timeline extends Base {
node.parent.isRoot && node.parent.isRoot &&
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
) { ) {
line.plot(`M ${x},${top} L ${x},${miny}`) line.plot(this.transformPath(`M ${x},${top} L ${x},${miny}`))
} else { } else {
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`) line.plot(
this.transformPath(
`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`
)
)
} }
node.style.line(line) node.style.line(line)
node._lines.push(line) node._lines.push(line)
@ -325,9 +329,10 @@ class Timeline extends Base {
let cx = x1 + 20 let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2 let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path) item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = right + generalizationNodeMargin item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2 item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2
}) })
} }

View File

@ -398,7 +398,7 @@ class VerticalTimeline extends Base {
let cx = x1 + (isLeft ? -20 : 20) let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2 let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path) item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = item.generalizationNode.left =
x + x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) - (isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -

View File

@ -36,12 +36,18 @@ export default {
}) { }) {
if (node.parent && node.parent.isRoot) { if (node.parent && node.parent.isRoot) {
line.plot( line.plot(
`M ${x},${top} L ${x + lineLength},${ ctx.transformPath(
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength `M ${x},${top} L ${x + lineLength},${
}` top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
}`
)
) )
} else { } else {
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`) line.plot(
ctx.transformPath(
`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`
)
)
} }
}, },
computedLeftTopValue({ layerIndex, node, ctx }) { computedLeftTopValue({ layerIndex, node, ctx }) {
@ -135,14 +141,16 @@ export default {
renderLine({ node, line, top, x, lineLength, height, miny, ctx }) { renderLine({ node, line, top, x, lineLength, height, miny, ctx }) {
if (node.parent && node.parent.isRoot) { if (node.parent && node.parent.isRoot) {
line.plot( line.plot(
`M ${x},${top + height} L ${x + lineLength},${ ctx.transformPath(
top + `M ${x},${top + height} L ${x + lineLength},${
height + top +
Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength height +
}` Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
}`
)
) )
} else { } else {
line.plot(`M ${x},${top} L ${x},${miny}`) line.plot(ctx.transformPath(`M ${x},${top} L ${x},${miny}`))
} }
}, },
computedLeftTopValue({ layerIndex, node, ctx }) { computedLeftTopValue({ layerIndex, node, ctx }) {