import React, {Component} from 'react'
|
import PropTypes from 'prop-types'
|
import { is, fromJS } from 'immutable'
|
import { Popover, message } from 'antd'
|
import { ToolOutlined, DeleteOutlined, FontColorsOutlined } from '@ant-design/icons'
|
import G6 from '@antv/g6'
|
|
import MKEmitter from '@/utils/events.js'
|
import asyncComponent from '@/utils/asyncComponent'
|
import asyncIconComponent from '@/utils/asyncIconComponent'
|
import { resetStyle, getTables, getHeight } from '@/utils/utils-custom.js'
|
import './index.scss'
|
|
const { Util } = G6
|
|
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
|
const ChartCompileForm = asyncIconComponent(() => import('./chartcompile'))
|
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
|
const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
|
const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
|
|
const MindData = [
|
'Modeling Methods',
|
[
|
'Classification',
|
['Logistic regression', 'Linear discriminant analysis', 'Rules', 'Decision trees', 'Naive Bayes', 'K nearest neighbor', 'Probabilistic neural network', 'Support vector machine']
|
],
|
[
|
'Consensus',
|
[
|
'Models diversity',
|
['Different initializations', 'Different parameter choices', 'Different architectures', 'Different modeling methods', 'Different training sets', 'Different feature sets']
|
],
|
[
|
'Methods',
|
['Classifier selection', 'Classifier fusion']
|
],
|
[
|
'Common',
|
['Bagging', 'Boosting', 'AdaBoost']
|
]
|
],
|
[
|
'Regression',
|
['Multiple linear regression', 'Partial least squares', 'Multi-layer feedforward neural network', 'General regression neural network', 'Support vector regression']
|
]
|
]
|
|
const styles = {
|
blue: '#1890ff',
|
red: '#f5222d',
|
orange_red: '#fa541c',
|
orange: '#fa8c16',
|
orange_yellow: '#faad14',
|
yellow: '#fadb14',
|
yellow_green: '#a0d911',
|
green: '#52c41a',
|
cyan: '#13c2c2',
|
blue_purple: '#2f54eb',
|
purple: '#722ed1',
|
magenta: '#eb2f96',
|
grass_green: '#aeb303',
|
deep_red: '#c32539',
|
deep_blue: '#1d3661'
|
}
|
|
let systemColor = '#1890ff'
|
if (window.GLOB.style) {
|
let type = window.GLOB.style.replace(/bg_black_style_|bg_white_style_/, '')
|
systemColor = styles[type] || '#1890ff'
|
}
|
const COLORS = ['#5B8FF9', '#F6BD16', '#5AD8A6', '#945FB9', '#E86452', '#6DC8EC', '#FF99C3', '#1E9493', '#FF9845', '#5D7092']
|
|
// 思维导图
|
G6.registerNode(
|
'dice-mind-map-root', {
|
jsx: (cfg) => {
|
const width = Util.getTextSize(cfg.label, 16)[0] + 24
|
|
return `
|
<group>
|
<rect style={{width: ${width}, height: 42, stroke: ${systemColor}, radius: 4}} keyshape>
|
<text style={{ fontSize: 16, marginLeft: 6, marginTop: 12 }}>${cfg.label}</text>
|
</rect>
|
</group>
|
`
|
},
|
getAnchorPoints() {
|
return [
|
[0, 0.5],
|
[1, 0.5]
|
]
|
}
|
},
|
'single-node',
|
)
|
|
G6.registerNode(
|
'dice-mind-map-leaf', {
|
jsx: (cfg) => {
|
const width = Util.getTextSize(cfg.label, 12)[0] + 24
|
|
return `
|
<group>
|
<rect style={{width: ${width}, height: 26, fill: 'transparent', cursor: pointer }}>
|
<text style={{ fontSize: 12, fill: ${cfg.selected ? systemColor : '#000000'}, marginLeft: 12, marginTop: 6, cursor: pointer }}>${cfg.label}</text>
|
</rect>
|
<rect style={{ fill: ${cfg.color}, width: ${width}, height: 2, x: 0, y: 32, cursor: pointer }} />
|
</group>
|
`
|
},
|
getAnchorPoints() {
|
return [
|
[0, 0.965],
|
[1, 0.965]
|
]
|
}
|
},
|
'single-node',
|
)
|
G6.registerBehavior('dice-mindmap', {
|
getEvents() {
|
return {
|
'node:click': 'editNode',
|
'canvas:click': 'onCanvasClick'
|
}
|
},
|
editNode(evt) {
|
const item = evt.item
|
const model = item.get('model')
|
|
this.graph.getNodes().forEach(node => {
|
let _model = node.get('model')
|
if (_model.selected) {
|
_model.selected = false
|
this.graph.updateItem(node, _model, false)
|
}
|
})
|
|
model.selected = true
|
this.graph.updateItem(item, model, false)
|
},
|
onCanvasClick(e) {
|
this.graph.getNodes().forEach(node => {
|
let _model = node.get('model')
|
if (_model.selected) {
|
_model.selected = false
|
this.graph.updateItem(node, _model, false)
|
}
|
})
|
}
|
})
|
G6.registerBehavior('scroll-canvas', {
|
getEvents: function getEvents() {
|
return {
|
wheel: 'onWheel'
|
}
|
},
|
onWheel: function onWheel(ev) {
|
const { graph } = this
|
if (!graph) {
|
return
|
}
|
if (ev.ctrlKey) {
|
const canvas = graph.get('canvas')
|
const point = canvas.getPointByClient(ev.clientX, ev.clientY)
|
let ratio = graph.getZoom()
|
if (ev.wheelDelta > 0) {
|
ratio += ratio * 0.05
|
} else {
|
ratio *= ratio * 0.05
|
}
|
graph.zoomTo(ratio, {
|
x: point.x,
|
y: point.y
|
})
|
} else {
|
const x = ev.deltaX || ev.movementX
|
const y = ev.deltaY || ev.movementY || (-ev.wheelDelta * 125) / 3
|
graph.translate(-x, -y)
|
}
|
ev.preventDefault()
|
}
|
})
|
|
// 缩进文件树
|
G6.registerNode('indentedRoot', {
|
draw(model, group) {
|
const keyShape = group.addShape('rect', {
|
attrs: {
|
x: -46,
|
y: -16,
|
width: 92,
|
height: 32,
|
fill: systemColor,
|
radius: 2,
|
stroke: '#5B8FF9',
|
lineWidth: 0
|
},
|
name: 'key-shape'
|
})
|
|
const text = group.addShape('text', {
|
attrs: {
|
text: model.label || 'root',
|
fill: "#fff",
|
fontSize: 12,
|
x: 0,
|
y: 0,
|
textAlign: 'center',
|
textBaseline: 'middle'
|
},
|
name: 'root-text-shape'
|
})
|
const textBBox = text.getBBox()
|
const width = textBBox.width + 24
|
const height = textBBox.height + 12
|
keyShape.attr({
|
x: -width / 2,
|
y: -height / 2,
|
width,
|
height
|
})
|
|
return keyShape
|
},
|
getAnchorPoints() {
|
return [
|
[0.5, 1]
|
]
|
},
|
update: undefined
|
})
|
|
G6.registerNode('indentedNode', {
|
addChildCount(group, tag, props) {
|
const { collapsed, branchColor, count } = props
|
let clickCircleY = 10
|
// 子类数量 icon,绘制圆点在节点正下方
|
if (tag) {
|
const childCountGroup = group.addGroup({
|
name: 'child-count-group'
|
})
|
childCountGroup.setMatrix([1, 0, 0, 0, 1, 0, 0, clickCircleY, 1])
|
const countBackWidth = collapsed ? 26 : 12
|
childCountGroup.addShape('rect', {
|
attrs: {
|
width: countBackWidth,
|
height: 12,
|
radius: 6,
|
stroke: branchColor,
|
lineWidth: 2,
|
fill: collapsed ? branchColor : '#fff',
|
x: -countBackWidth / 2,
|
y: -6,
|
cursor: 'pointer',
|
},
|
name: 'child-count-rect-shape'
|
})
|
const childCountText = childCountGroup.addShape('text', {
|
attrs: {
|
text: count,
|
fill: '#fff',
|
x: 0,
|
y: 0,
|
fontSize: 10,
|
textAlign: 'center',
|
textBaseline: 'middle',
|
cursor: 'pointer',
|
},
|
name: 'child-count-text-shape'
|
})
|
const childHoverIcon = childCountGroup.addShape('path', {
|
attrs: {
|
stroke: '#fff',
|
lineWidth: 1,
|
cursor: 'pointer',
|
path: [['M', -3, 2], ['L', 0, -2], ['L', 3, 2]]
|
},
|
name: 'child-count-expand-icon',
|
capture: false
|
})
|
childHoverIcon.hide()
|
|
// 连接 count 的线段
|
const countLink = group.addShape('path', {
|
attrs: {
|
path: [['M', 0, 0], ['L', 0, 11]],
|
stroke: branchColor,
|
lineWidth: 2,
|
},
|
name: 'count-link'
|
})
|
countLink.toBack()
|
|
if (collapsed) {
|
childCountGroup.show()
|
childCountText.show()
|
countLink.show()
|
}
|
else {
|
childCountGroup.hide()
|
childCountText.hide()
|
countLink.hide()
|
}
|
|
clickCircleY += 16
|
}
|
},
|
addBottomLine(group, props) {
|
const { x, width, stroke, lineWidth } = props
|
return group.addShape('path', {
|
attrs: {
|
path: [
|
['M', x - 1, 0],
|
['L', width, 0],
|
],
|
stroke,
|
lineWidth,
|
},
|
name: 'node-path-shape'
|
})
|
},
|
addName(group, props) {
|
const { label, x = 0, y, fill } = props
|
return group.addShape('text', {
|
attrs: {
|
text: label,
|
x,
|
y,
|
textAlign: 'start',
|
textBaseline: 'top',
|
fill,
|
fontSize: 14,
|
fontFamily: 'PingFangSC-Regular',
|
cursor: 'pointer',
|
},
|
name: 'not-root-text-shape'
|
})
|
},
|
draw(model, group) {
|
const { collapsed, depth, label, children, selected } = model
|
// 是否为根节点
|
const rootNode = depth === 0
|
// 子节点数量
|
const childCount = children ? children.length : 0
|
|
let width = 0
|
const height = 24
|
const x = 0
|
const y = -height / 2
|
const borderRadius = 4
|
// 名称文本
|
const text = this.addName(group, { label, x, y })
|
|
let textWidth = text.getBBox().width
|
width = textWidth + 20
|
|
const keyShapeAttrs = {
|
x,
|
y,
|
width,
|
height,
|
radius: borderRadius,
|
fill: undefined,
|
stroke: undefined,
|
}
|
|
const keyShape = group.addShape('rect', {
|
attrs: keyShapeAttrs,
|
name: 'root-key-shape-rect-shape'
|
})
|
|
// 底部横线
|
const bottomLine = this.addBottomLine(group, {
|
stroke: model.branchColor || '#AAB7C4',
|
lineWidth: 3,
|
x,
|
width
|
})
|
|
let nameColor = 'rgba(0, 0, 0, 0.85)'
|
|
if (selected) {
|
nameColor = systemColor
|
}
|
|
// 名称
|
text.attr({
|
y: y - 12,
|
fill: nameColor
|
})
|
text.toFront()
|
textWidth = text.getBBox().width
|
|
if (bottomLine) bottomLine.toFront()
|
|
this.addChildCount(group, childCount && !rootNode, {
|
collapsed,
|
branchColor: model.branchColor,
|
count: childCount ? `${childCount}` : undefined
|
})
|
|
const bbox = group.getBBox()
|
const backContainer = group.addShape('path', {
|
attrs: {
|
path: childCount ? [
|
['M', bbox.minX, bbox.minY],
|
['L', bbox.maxX, bbox.minY],
|
['L', bbox.maxX, bbox.maxY],
|
['L', bbox.minX + 20, bbox.maxY],
|
['L', bbox.minX + 20, bbox.maxY + 20],
|
['L', bbox.minX, bbox.maxY + 20],
|
['Z']
|
] : [
|
['M', bbox.minX, bbox.minY],
|
['L', bbox.maxX, bbox.minY],
|
['L', bbox.maxX, bbox.maxY],
|
['L', bbox.minX, bbox.maxY],
|
['Z']
|
],
|
fill: '#fff',
|
opacity: 0
|
}
|
})
|
backContainer.toBack()
|
return keyShape
|
}
|
})
|
|
G6.registerEdge('indentedEdge', {
|
afterDraw: (cfg, group) => {
|
const sourceNode = cfg.sourceNode && cfg.sourceNode.getModel()
|
const targetNode = cfg.targetNode && cfg.targetNode.getModel()
|
const color = sourceNode.branchColor || targetNode.branchColor || cfg.color || '#000'
|
|
const keyShape = group.get('children')[0]
|
keyShape.attr({
|
stroke: color,
|
lineWidth: 3 // branchThick
|
})
|
group.toBack()
|
},
|
getControlPoints: (cfg) => {
|
const startPoint = cfg.startPoint
|
const endPoint = cfg.endPoint
|
return [
|
startPoint,
|
{
|
x: startPoint.x,
|
y: endPoint.y,
|
},
|
endPoint
|
]
|
},
|
update: undefined
|
}, 'polyline')
|
|
G6.registerBehavior('wheel-scroll', {
|
getDefaultCfg() {
|
return {
|
direction: 'y',
|
zoomKey: 'ctrl',
|
sensitivity: 3,
|
scalableRange: -64
|
}
|
},
|
getEvents() {
|
return {
|
wheel: 'onWheel'
|
}
|
},
|
onWheel(ev) {
|
const graph = this.graph
|
let keyDown = ev[`${this.zoomKey}Key`]
|
if (this.zoomKey === 'control') keyDown = ev.ctrlKey
|
if (keyDown) {
|
const sensitivity = this.get('sensitivity')
|
const canvas = graph.get('canvas')
|
const point = canvas.getPointByClient(ev.clientX, ev.clientY)
|
let ratio = graph.getZoom()
|
if (ev.wheelDelta > 0) {
|
ratio *= (1 + 0.01 * sensitivity)
|
} else {
|
ratio *= (1 - 0.01 * sensitivity)
|
}
|
graph.zoomTo(ratio, {
|
x: point.x,
|
y: point.y
|
})
|
graph.emit('wheelzoom', ev)
|
} else {
|
let dx = ev.deltaX || ev.movementX
|
let dy = ev.deltaY || ev.movementY
|
if (!dy && navigator.userAgent.indexOf('Firefox') > -1) dy = (-ev.wheelDelta * 125) / 3
|
|
const width = this.graph.get('width')
|
const height = this.graph.get('height')
|
const graphCanvasBBox = this.graph.get('group').getCanvasBBox()
|
|
let expandWidth = this.scalableRange
|
let expandHeight = this.scalableRange
|
// 若 scalableRange 是 0~1 的小数,则作为比例考虑
|
if (expandWidth < 1 && expandWidth > -1) {
|
expandWidth = width * expandWidth
|
expandHeight = height * expandHeight
|
}
|
|
const { minX, maxX, minY, maxY } = graphCanvasBBox
|
|
if (dx > 0) {
|
if (maxX < -expandWidth) {
|
dx = 0
|
} else if (maxX - dx < -expandWidth) {
|
dx = maxX + expandWidth
|
}
|
} else if (dx < 0) {
|
if (minX > width + expandWidth) {
|
dx = 0
|
} else if (minX - dx > width + expandWidth) {
|
dx = minX - (width + expandWidth)
|
}
|
}
|
|
if (dy > 0) {
|
if (maxY < -expandHeight) {
|
dy = 0
|
} else if (maxY - dy < -expandHeight) {
|
dy = maxY + expandHeight
|
}
|
} else if (dy < 0) {
|
if (minY > height + expandHeight) {
|
dy = 0
|
} else if (minY - dy > height + expandHeight) {
|
dy = minY - (height + expandHeight)
|
}
|
}
|
|
if (this.get('direction') === 'x') {
|
dy = 0
|
} else if (this.get('direction') === 'y') {
|
dx = 0
|
}
|
|
graph.translate(-dx, -dy)
|
}
|
ev.preventDefault()
|
}
|
})
|
G6.registerBehavior('hover-node', {
|
getEvents() {
|
return {
|
'node:mouseover': 'onNodeMouseOver',
|
'node:mouseleave': 'onNodeMouseLeave',
|
'node:mouseenter': 'onNodeMouseEnter'
|
}
|
},
|
onNodeMouseEnter(ev) {
|
const { item } = ev
|
if (!item || item.get('destroyed')) return
|
item.toFront()
|
const model = item.getModel()
|
const { collapsed, depth } = model
|
const rootNode = depth === 0 || model.isRoot
|
const group = item.getContainer()
|
|
if (rootNode) return
|
|
// 控制子节点个数标记
|
if (!collapsed) {
|
const childCountGroup = group.find(e => e.get('name') === 'child-count-group')
|
if (childCountGroup) {
|
childCountGroup.show()
|
}
|
}
|
},
|
onNodeMouseOver(ev) {
|
const shape = ev.target
|
|
// tooltip显示、隐藏
|
this.graph.emit('tooltip: show', ev)
|
|
// expand 状态下,若 hover 到子节点个数标记,填充背景+显示收起 icon
|
const { item } = ev
|
const group = item.getContainer()
|
const model = item.getModel()
|
if (!model.collapsed) {
|
const childCountGroup = group.find(e => e.get('name') === 'child-count-group')
|
if (childCountGroup) {
|
childCountGroup.show()
|
const back = childCountGroup.find(e => e.get('name') === 'child-count-rect-shape')
|
const expandIcon = childCountGroup.find(e => e.get('name') === 'child-count-expand-icon')
|
const rootNode = model.depth === 0 || model.isRoot
|
const branchColor = rootNode ? '#576286' : model.branchColor
|
if (shape.get('parent').get('name') === 'child-count-group') {
|
if (back) {
|
back.attr('fill', branchColor || '#fff')
|
}
|
if (expandIcon) {
|
expandIcon.show()
|
}
|
} else {
|
if (back) {
|
back.attr('fill', '#fff')
|
}
|
if (expandIcon) {
|
expandIcon.hide()
|
}
|
}
|
}
|
}
|
},
|
onNodeMouseLeave(ev) {
|
const { item } = ev
|
const model = item.getModel()
|
const group = item.getContainer()
|
const { collapsed } = model
|
|
if (!collapsed) {
|
const childCountGroup = group.find(e => e.get('name') === 'child-count-group')
|
if (childCountGroup) {
|
childCountGroup.hide()
|
}
|
|
const iconsLinkPath = group.find(e => e.get('name') === 'icons-link-path')
|
if (iconsLinkPath) {
|
iconsLinkPath.hide()
|
}
|
}
|
|
this.graph.emit('tooltip: hide', ev)
|
}
|
})
|
G6.registerBehavior('click-node', {
|
getEvents() {
|
return {
|
'node:click': 'onNodeClick',
|
'canvas:click': 'onCanvasClick'
|
}
|
},
|
onNodeClick(e) {
|
const { item, target } = e
|
const shape = target
|
const shapeName = shape.cfg.name
|
let model = item.getModel()
|
|
// 点击收起/展开 icon
|
if (shapeName === 'child-count-rect-shape' || shapeName === 'child-count-text-shape') {
|
const updatedCollapsed = !model.collapsed
|
this.graph.updateItem(item, { collapsed: updatedCollapsed })
|
this.graph.layout()
|
return
|
}
|
|
// 选中节点
|
this.graph.getNodes().forEach(node => {
|
let _model = node.get('model')
|
if (_model.selected) {
|
_model.selected = false
|
this.graph.updateItem(node, _model, false)
|
}
|
})
|
|
model.selected = true
|
this.graph.updateItem(item, model, false)
|
|
return
|
},
|
onCanvasClick(e) {
|
this.graph.getNodes().forEach(node => {
|
let _model = node.get('model')
|
if (_model.selected) {
|
_model.selected = false
|
this.graph.updateItem(node, _model, false)
|
}
|
})
|
}
|
})
|
const dataIndTransform = (data) => {
|
const changeData = (d) => {
|
let data = { ...d }
|
|
data.type = data.isRoot ? 'indentedRoot' : 'indentedNode'
|
|
if (d.children) {
|
data.children = d.children.map((child) => changeData(child))
|
}
|
// 给定 branchColor 和 0-2 层节点 depth
|
if (data.children && data.children.length) {
|
data.depth = 0
|
data.children.forEach((subtree, i) => {
|
subtree.branchColor = COLORS[i % COLORS.length]
|
// dfs
|
let currentDepth = 1
|
subtree.depth = currentDepth
|
Util.traverseTree(subtree, child => {
|
child.branchColor = COLORS[i % COLORS.length]
|
|
if (!child.depth) {
|
child.depth = currentDepth + 1
|
}
|
else currentDepth = subtree.depth
|
if (child.children) {
|
child.children.forEach(subChild => {
|
subChild.depth = child.depth + 1
|
})
|
}
|
|
if (!data.isRoot) {
|
child.collapsed = data.collapsed || false
|
}
|
return true
|
})
|
})
|
}
|
|
return data
|
}
|
return changeData(data)
|
}
|
|
// 知识图谱树
|
G6.registerNode('treeNode', {
|
draw: (cfg, group) => {
|
const { label, selected, children, isRoot } = cfg
|
const rootNode = !!isRoot
|
const hasChildren = children && children.length !== 0
|
|
let width = 0
|
const height = 28
|
const x = 0
|
const y = -height / 2
|
|
// 名称文本
|
const text = group.addShape('text', {
|
attrs: {
|
text: label,
|
x: x * 2,
|
y,
|
textAlign: 'left',
|
textBaseline: 'top',
|
fontFamily: 'PingFangSC-Regular',
|
},
|
cursor: 'pointer',
|
name: 'name-text-shape',
|
})
|
const textWidth = text.getBBox().width
|
width = textWidth + 20
|
|
width = width < 60 ? 60 : width
|
|
if (!rootNode && hasChildren) {
|
width += 22
|
}
|
|
const keyShapeAttrs = {
|
x,
|
y,
|
width,
|
height,
|
radius: 4
|
}
|
|
const keyShape = group.addShape('rect', {
|
attrs: keyShapeAttrs,
|
name: 'root-key-shape-rect-shape'
|
})
|
|
if (!rootNode) {
|
// 底部横线
|
group.addShape('path', {
|
attrs: {
|
path: [
|
['M', x - 1, 0],
|
['L', width, 0],
|
],
|
stroke: '#AAB7C4',
|
lineWidth: 1,
|
},
|
name: 'node-path-shape'
|
})
|
}
|
|
const mainX = x - 10
|
const mainY = -height + 15
|
|
if (rootNode) {
|
group.addShape('rect', {
|
attrs: {
|
x: mainX,
|
y: mainY,
|
width: width + 12,
|
height,
|
radius: 14,
|
fill: systemColor,
|
cursor: 'pointer',
|
},
|
name: 'main-shape'
|
})
|
}
|
|
let nameColor = 'rgba(0, 0, 0, 0.85)'
|
if (selected) {
|
nameColor = systemColor
|
}
|
|
// 名称
|
if (rootNode) {
|
group.addShape('text', {
|
attrs: {
|
text: label,
|
x: mainX + 18,
|
y: 1,
|
textAlign: 'left',
|
textBaseline: 'middle',
|
fill: '#ffffff',
|
fontSize: 12,
|
fontFamily: 'PingFangSC-Regular',
|
cursor: 'pointer',
|
},
|
name: 'root-text-shape'
|
})
|
} else {
|
group.addShape('text', {
|
attrs: {
|
text: label,
|
x: mainX + 6,
|
y: y - 5,
|
textAlign: 'start',
|
textBaseline: 'top',
|
fill: nameColor,
|
fontSize: 12,
|
fontFamily: 'PingFangSC-Regular',
|
cursor: 'pointer',
|
},
|
name: 'not-root-text-shape'
|
})
|
}
|
|
// 子类数量
|
if (hasChildren && !rootNode) {
|
const childCountHeight = 12
|
const childCountX = width - 22
|
const childCountY = -childCountHeight / 2
|
|
group.addShape('rect', {
|
attrs: {
|
width: 22,
|
height: 12,
|
stroke: systemColor,
|
fill: '#fff',
|
x: childCountX,
|
y: childCountY,
|
radius: 6,
|
cursor: 'pointer',
|
},
|
name: 'child-count-rect-shape',
|
})
|
group.addShape('text', {
|
attrs: {
|
text: `${children.length}`,
|
fill: 'rgba(0, 0, 0, .65)',
|
x: childCountX + 11,
|
y: childCountY + 12,
|
fontSize: 10,
|
width: 22,
|
textAlign: 'center',
|
cursor: 'pointer',
|
},
|
name: 'child-count-text-shape'
|
})
|
}
|
|
return keyShape
|
}
|
})
|
|
G6.registerEdge('smooth', {
|
draw(cfg, group) {
|
const { startPoint, endPoint } = cfg
|
const hgap = Math.abs(endPoint.x - startPoint.x)
|
|
const path = [
|
['M', startPoint.x, startPoint.y],
|
[
|
'C',
|
startPoint.x + hgap / 4,
|
startPoint.y,
|
endPoint.x - hgap / 2,
|
endPoint.y,
|
endPoint.x,
|
endPoint.y,
|
],
|
]
|
|
const shape = group.addShape('path', {
|
attrs: {
|
stroke: '#AAB7C4',
|
path,
|
},
|
name: 'smooth-path-shape',
|
})
|
return shape
|
},
|
})
|
|
G6.registerBehavior('click-item', {
|
getEvents() {
|
return {
|
'node:click': 'onNodeClick',
|
'canvas:click': 'onCanvasClick'
|
}
|
},
|
onNodeClick(e) {
|
const { item } = e
|
let model = item.getModel()
|
|
if (model.children) return
|
// 选中节点
|
this.graph.getNodes().forEach(node => {
|
let _model = node.get('model')
|
if (_model.selected) {
|
_model.selected = false
|
this.graph.updateItem(node, _model, false)
|
}
|
})
|
|
model.selected = true
|
this.graph.updateItem(item, model, false)
|
|
return
|
},
|
onCanvasClick(e) {
|
this.graph.getNodes().forEach(node => {
|
let _model = node.get('model')
|
if (_model.selected) {
|
_model.selected = false
|
this.graph.updateItem(node, _model, false)
|
}
|
})
|
}
|
})
|
|
class antvG6Chart extends Component {
|
static propTpyes = {
|
card: PropTypes.object,
|
updateConfig: PropTypes.func,
|
deletecomponent: PropTypes.func,
|
}
|
|
state = {
|
card: null,
|
eventListener: null
|
}
|
|
UNSAFE_componentWillMount () {
|
const { card } = this.props
|
|
if (card.isNew) {
|
let _plot = {
|
width: card.width || 24,
|
height: 400,
|
subtype: card.subtype,
|
name: card.name
|
}
|
|
let _card = {
|
uuid: card.uuid,
|
type: card.type,
|
format: 'array', // 组件属性 - 数据格式
|
pageable: false, // 组件属性 - 是否可分页
|
switchable: true, // 组件属性 - 数据是否可切换
|
width: _plot.width,
|
name: _plot.name,
|
subtype: card.subtype,
|
setting: { interType: 'system' },
|
style: {
|
borderWidth: '1px', borderColor: 'rgb(217, 217, 217)',
|
marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px'
|
},
|
headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
|
columns: [],
|
scripts: [],
|
search: [],
|
action: [],
|
plot: _plot,
|
}
|
|
if (card.config) {
|
let config = fromJS(card.config).toJS()
|
|
_card.plot = config.plot
|
_card.plot.name = card.name
|
_card.style = config.style
|
_card.headerStyle = config.headerStyle
|
|
_card.setting = config.setting
|
_card.columns = config.columns
|
_card.scripts = config.scripts
|
}
|
|
this.updateComponent(_card)
|
} else {
|
this.setState({
|
card: fromJS(card).toJS()
|
})
|
}
|
}
|
|
componentDidMount () {
|
MKEmitter.addListener('tabsChange', this.handleTabsChange)
|
setTimeout(() => {
|
this.viewrender()
|
}, 1000)
|
}
|
|
shouldComponentUpdate (nextProps, nextState) {
|
return !is(fromJS(this.state), fromJS(nextState))
|
}
|
|
/**
|
* @description 组件销毁,清除state更新,清除快捷键设置
|
*/
|
componentWillUnmount () {
|
this.setState = () => {
|
return
|
}
|
MKEmitter.removeListener('tabsChange', this.handleTabsChange)
|
}
|
|
handleTabsChange = (parentId) => {
|
const { card } = this.state
|
|
if (parentId.indexOf(card.uuid) > -1 || parentId === 'all') {
|
let _element = document.getElementById(card.uuid + 'canvas')
|
if (_element) {
|
_element.innerHTML = ''
|
}
|
|
this.$timer && clearTimeout(this.$timer)
|
this.$timer = setTimeout(this.viewrender, 100)
|
}
|
}
|
|
getdata = () => {
|
const { card } = this.state
|
|
const setData = (list) => {
|
let item = {
|
label: list[0],
|
id: list[0],
|
children: []
|
}
|
if (!list[1]) {
|
delete item.children
|
|
return item
|
} else if (!Array.isArray(list[1])) {
|
return list.map(m => ({label: m, id: m}))
|
}
|
|
for (let i = 1; i < list.length; i++) {
|
let cell = setData(list[i])
|
|
if (Array.isArray(cell)) {
|
item.children.push(...cell)
|
} else {
|
item.children.push(cell)
|
}
|
}
|
|
return item
|
}
|
|
let data = setData(MindData)
|
|
if (card.plot.subtype === 'mindmap') {
|
if (card.plot.dirField) {
|
data.children[0].direction = 'left'
|
data.children[2].direction = 'left'
|
}
|
|
data.children.forEach(item => {
|
if (item.direction === 'left') {
|
item.color = card.plot.leftColor || '#26C281'
|
} else {
|
item.direction = 'right'
|
item.color = card.plot.nodeColor || '#1890ff'
|
}
|
})
|
|
data.collapsed = false
|
data.type = 'dice-mind-map-root'
|
|
const collapse = (item) => {
|
if (!item.children) return
|
|
item.children.forEach(cell => {
|
cell.collapsed = card.plot.collapsed === 'true'
|
cell.direction = cell.direction || 'right'
|
cell.type = 'dice-mind-map-leaf'
|
cell.color = cell.color || item.color
|
collapse(cell)
|
})
|
}
|
|
collapse(data)
|
} else if (card.plot.subtype === 'indentTree') {
|
data.isRoot = true
|
data.collapsed = false
|
|
data.children.forEach(item => {
|
item.collapsed = card.plot.collapsed === 'true'
|
})
|
} else if (card.plot.subtype === 'kapmap') {
|
data.isRoot = true
|
data.collapsed = false
|
|
if (card.plot.collapsed === 'true') {
|
const collapse = (item) => {
|
if (!item.children) return
|
|
item.children.forEach(cell => {
|
cell.collapsed = true
|
collapse(cell)
|
})
|
}
|
|
collapse(data)
|
}
|
}
|
|
return data
|
}
|
|
viewrender = () => {
|
const { card } = this.state
|
|
if (card.plot.subtype === 'mindmap') {
|
this.ponitrender()
|
} else if (card.plot.subtype === 'indentTree') {
|
this.indentrender()
|
} else if (card.plot.subtype === 'kapmap') {
|
this.kapmaprender()
|
}
|
}
|
|
kapmaprender = () => {
|
const { card } = this.state
|
const plot = card.plot
|
const data = this.getdata()
|
const height = getHeight(plot.height)
|
|
const graph = new G6.TreeGraph({
|
container: card.uuid + 'canvas',
|
width: this.wrap.scrollWidth - 30,
|
height: height,
|
modes: {
|
default: [
|
{
|
type: 'collapse-expand',
|
},
|
'drag-canvas',
|
'zoom-canvas',
|
'click-item'
|
],
|
},
|
defaultNode: {
|
type: 'treeNode',
|
anchorPoints: [
|
[0, 0.5],
|
[1, 0.5],
|
],
|
},
|
defaultEdge: {
|
type: 'smooth',
|
},
|
layout: {
|
type: 'compactBox',
|
direction: 'LR',
|
getId: function getId(d) {
|
return d.id
|
},
|
getHeight: function getHeight() {
|
return 16
|
},
|
getWidth: function getWidth(d) {
|
const labelWidth = G6.Util.getTextSize(d.label, 12)[0]
|
const width = 60 + labelWidth
|
return width
|
},
|
getVGap: function getVGap() {
|
return 15
|
},
|
getHGap: function getHGap() {
|
return 30
|
}
|
}
|
})
|
|
graph.data(data)
|
graph.render()
|
graph.fitView()
|
|
if (plot.collapsed === 'true') {
|
graph.zoomTo(1, { x: 0, y: height / 2 })
|
}
|
}
|
|
indentrender = () => {
|
const { card } = this.state
|
const plot = card.plot
|
const data = this.getdata()
|
|
const tree = new G6.TreeGraph({
|
container: card.uuid + 'canvas',
|
width: this.wrap.scrollWidth - 30,
|
height: getHeight(plot.height),
|
layout: {
|
type: 'indented',
|
direction: 'LR',
|
isHorizontal: true,
|
indent: 40,
|
getHeight: (d) => {
|
if (d.isRoot) {
|
return 30
|
}
|
if (d.collapsed && d.children && d.children.length) {
|
return 36
|
}
|
return 22
|
},
|
getVGap: () => {
|
return 10
|
},
|
},
|
defaultEdge: {
|
type: 'indentedEdge',
|
style: {
|
lineWidth: 2,
|
radius: 16
|
}
|
},
|
minZoom: 0.5,
|
modes: {
|
default: [
|
'drag-canvas',
|
'wheel-scroll',
|
'hover-node',
|
'click-node'
|
]
|
}
|
})
|
|
tree.on('afterrender', e => {
|
tree.getEdges().forEach(edge => {
|
const targetNode = edge.getTarget().getModel()
|
const color = targetNode.branchColor
|
tree.updateItem(edge, { color })
|
})
|
setTimeout(() => {
|
tree.moveTo(32, 32)
|
tree.zoomTo(0.7)
|
}, 16)
|
})
|
|
tree.data(dataIndTransform(data))
|
|
tree.render()
|
}
|
|
/**
|
* @description 散点图
|
*/
|
ponitrender = () => {
|
const { card } = this.state
|
const plot = card.plot
|
const data = this.getdata()
|
const width = this.wrap.scrollWidth - 30
|
const height = getHeight(plot.height)
|
let modes = ['drag-canvas', 'zoom-canvas', 'dice-mindmap']
|
|
if (plot.collapsed === 'true') {
|
modes = [{ type: 'collapse-expand' },'drag-canvas', 'zoom-canvas', 'dice-mindmap']
|
}
|
|
const tree = new G6.TreeGraph({
|
container: card.uuid + 'canvas',
|
width: width,
|
height: height,
|
fitView: true,
|
layout: {
|
type: 'mindmap',
|
direction: 'H',
|
getHeight: () => {
|
return 16
|
},
|
getWidth: (node) => {
|
return node.level === 0 ?
|
Util.getTextSize(node.label, 16)[0] + 12 :
|
Util.getTextSize(node.label, 12)[0]
|
},
|
getVGap: () => {
|
return 10
|
},
|
getHGap: () => {
|
return 60
|
},
|
getSide: (node) => {
|
return node.data.direction
|
}
|
},
|
defaultEdge: {
|
type: 'cubic-horizontal',
|
style: {
|
lineWidth: 2
|
}
|
},
|
minZoom: 0.5,
|
modes: {
|
default: modes
|
}
|
})
|
|
tree.data(data)
|
|
tree.render()
|
|
if (plot.collapsed === 'true' && plot.dirField) {
|
tree.zoomTo(1, { x: width / 2, y: height / 2 })
|
} else if (plot.collapsed === 'true') {
|
tree.zoomTo(1, { x: 0, y: height / 2 })
|
}
|
}
|
|
updateComponent = (card) => {
|
if (this.state.card && (!is(fromJS(card.plot), fromJS(this.state.card.plot)) || !is(fromJS(card.style), fromJS(this.state.card.style)))) {
|
let _element = document.getElementById(card.uuid + 'canvas')
|
if (_element) {
|
_element.innerHTML = ''
|
}
|
this.$timer && clearTimeout(this.$timer)
|
this.$timer = setTimeout(() => {
|
this.viewrender()
|
}, 150)
|
}
|
|
card.width = card.plot.width
|
card.name = card.plot.name
|
card.subtype = card.plot.subtype
|
card.errors = []
|
|
let columns = card.columns.map(c => c.field)
|
|
if (card.setting.interType === 'system' && card.setting.execute !== 'false' && !card.setting.dataresource) {
|
card.errors.push({ level: 0, detail: '未设置数据源!'})
|
} else if (card.setting.interType === 'system' && card.setting.execute === 'false' && card.scripts.filter(script => script.status !== 'false').length === 0) {
|
card.errors.push({ level: 0, detail: '数据源中无可用脚本!'})
|
} else if (!card.setting.primaryKey) {
|
card.errors.push({ level: 0, detail: '未设置主键!'})
|
} else if (!columns.includes(card.setting.primaryKey)) {
|
card.errors.push({ level: 0, detail: '主键已失效!'})
|
} else if (!card.setting.supModule) {
|
card.errors.push({ level: 0, detail: '未设置上级组件!'})
|
}
|
|
if (card.errors.length === 0) {
|
card.$tables = getTables(card)
|
}
|
|
if (!card.plot.valueField) {
|
card.errors.push({ level: 0, detail: '图表信息尚未设置!'})
|
}
|
|
this.setState({
|
card: card
|
})
|
this.props.updateConfig(card)
|
}
|
|
changeStyle = () => {
|
const { card } = this.state
|
|
MKEmitter.emit('changeStyle', ['background', 'border', 'padding', 'margin', 'shadow', 'clear'], card.style, this.getStyle)
|
}
|
|
getStyle = (style) => {
|
let _card = {...this.state.card, style}
|
|
this.updateComponent(_card)
|
}
|
|
clickComponent = (e) => {
|
if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
|
e.stopPropagation()
|
MKEmitter.emit('clickComponent', this.state.card.uuid, null, (style) => {
|
let _card = {...this.state.card}
|
_card.style = {..._card.style, ...style}
|
|
this.updateComponent(_card)
|
})
|
}
|
}
|
|
render() {
|
const { card } = this.state
|
let _style = resetStyle(card.style)
|
|
return (
|
<div className="menu-scatter-chart-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
|
<Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
|
<div className="mk-popover-control">
|
<ChartCompileForm config={card} plotchange={this.updateComponent}/>
|
<CopyComponent type="antvG6" card={card}/>
|
<FontColorsOutlined className="style" title="调整样式" onClick={this.changeStyle}/>
|
<UserComponent config={card}/>
|
<DeleteOutlined className="close" title="delete" onClick={() => this.props.deletecomponent(card.uuid)}/>
|
<SettingComponent config={card} updateConfig={this.updateComponent}/>
|
</div>
|
} trigger="hover">
|
<ToolOutlined/>
|
</Popover>
|
<NormalHeader config={card} updateComponent={this.updateComponent}/>
|
<div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
|
<div className="component-name">
|
<div className="center">
|
<div className="title" onDoubleClick={() => {
|
let oInput = document.createElement('input')
|
oInput.value = 'anchor' + card.uuid
|
document.body.appendChild(oInput)
|
oInput.select()
|
document.execCommand('Copy')
|
document.body.removeChild(oInput)
|
message.success('复制成功。')
|
}}>{card.name}</div>
|
<div className="content">
|
{card.errors && card.errors.map((err, index) => {
|
if (err.level === 0) {
|
return <span key={index} className="error">{err.detail}</span>
|
} else {
|
return <span key={index} className="waring">{err.detail};</span>
|
}
|
})}
|
</div>
|
</div>
|
</div>
|
</div>
|
)
|
}
|
}
|
|
export default antvG6Chart
|