Browse Source
* [Improvement][UI] migrate version 2.x workflow definition d3 tree view to version 3.x * remove unnessnary code * fix code smells * update lock file to fix front-end CI Build error * update package.json3.2.1-prepare
yeahhhz
1 year ago
committed by
GitHub
8 changed files with 2057 additions and 1703 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
.tree-model { |
||||||
|
width: calc(100%); |
||||||
|
height: calc(100vh - 300px); |
||||||
|
overflow-x: scroll; |
||||||
|
.d3-tree { |
||||||
|
padding-left: 30px; |
||||||
|
.node { |
||||||
|
text { |
||||||
|
font: 11px sans-serif; |
||||||
|
pointer-events: none; |
||||||
|
} |
||||||
|
} |
||||||
|
rect { |
||||||
|
cursor: pointer; |
||||||
|
&.state { |
||||||
|
stroke: #666; |
||||||
|
shape-rendering: crispEdges; |
||||||
|
} |
||||||
|
} |
||||||
|
path { |
||||||
|
&.link{ |
||||||
|
fill: none; |
||||||
|
stroke: #666; |
||||||
|
stroke-width: 2px; |
||||||
|
} |
||||||
|
} |
||||||
|
circle { |
||||||
|
stroke: #666; |
||||||
|
fill: #0097e0; |
||||||
|
stroke-width: 1.5px; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import { defineComponent } from 'vue' |
||||||
|
import './index.scss' |
||||||
|
|
||||||
|
const UseTree = defineComponent({ |
||||||
|
name: 'D3Tree', |
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<div class='tree-model'> |
||||||
|
<div class='d3-tree'> |
||||||
|
<svg class='tree-svg' width='100%'></svg> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
export default UseTree |
@ -0,0 +1,366 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
import * as d3 from 'd3' |
||||||
|
import { rtInstancesTooltip, rtCountMethod } from './util' |
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
|
let self = this |
||||||
|
|
||||||
|
const Tree = function () { |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
|
self = this |
||||||
|
this.selfTree = {} |
||||||
|
this.tree = function () {} |
||||||
|
// basic configuration
|
||||||
|
this.config = { |
||||||
|
barHeight: 26, |
||||||
|
axisHeight: 40, |
||||||
|
squareSize: 10, |
||||||
|
squarePading: 4, |
||||||
|
taskNum: 25, |
||||||
|
nodesMax: 0 |
||||||
|
} |
||||||
|
// Margin configuration
|
||||||
|
this.config.margin = { |
||||||
|
top: this.config.barHeight / 2 + this.config.axisHeight, |
||||||
|
right: 0, |
||||||
|
bottom: 0, |
||||||
|
left: this.config.barHeight / 2 |
||||||
|
} |
||||||
|
// width
|
||||||
|
this.config.margin.width = |
||||||
|
960 - this.config.margin.left - this.config.margin.right |
||||||
|
// bar width
|
||||||
|
this.config.barWidth = parseInt(this.config.margin.width * 0.9) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* init |
||||||
|
*/ |
||||||
|
Tree.prototype.init = function ({ |
||||||
|
data, |
||||||
|
limit, |
||||||
|
selfTree, |
||||||
|
taskTypeNodeOptions, |
||||||
|
tasksStateObj |
||||||
|
}) { |
||||||
|
return new Promise((resolve) => { |
||||||
|
this.selfTree = selfTree |
||||||
|
this.config.taskNum = limit |
||||||
|
this.duration = 400 |
||||||
|
this.i = 0 |
||||||
|
this.tree = d3.tree().size([0, 46]) |
||||||
|
this.taskTypeNodeOptions = taskTypeNodeOptions |
||||||
|
this.tasksStateObj = tasksStateObj |
||||||
|
|
||||||
|
const root = d3.hierarchy(data) |
||||||
|
const treeData = this.tree(root) |
||||||
|
|
||||||
|
const tasks = treeData.descendants() |
||||||
|
const links = treeData.links() |
||||||
|
this.tasks = tasks |
||||||
|
this.links = links |
||||||
|
|
||||||
|
this.diagonal = d3 |
||||||
|
.linkHorizontal() |
||||||
|
.x((d) => d.y) |
||||||
|
.y((d) => d.x) |
||||||
|
|
||||||
|
this.svg = d3 |
||||||
|
.select('.tree-svg') |
||||||
|
.append('g') |
||||||
|
.attr('class', 'level') |
||||||
|
.attr( |
||||||
|
'transform', |
||||||
|
'translate(' + |
||||||
|
this.config.margin.left + |
||||||
|
',' + |
||||||
|
this.config.margin.top + |
||||||
|
')' |
||||||
|
) |
||||||
|
|
||||||
|
data.x0 = 0 |
||||||
|
data.y0 = 0 |
||||||
|
|
||||||
|
this.squareNum = tasks[tasks.length === 1 ? 0 : 1]?.data?.instances.length |
||||||
|
|
||||||
|
// Calculate the maximum node length
|
||||||
|
this.config.nodesMax = rtCountMethod(data.children) |
||||||
|
|
||||||
|
this.treeUpdate((this.root = data)).then(() => { |
||||||
|
this.treeTooltip() |
||||||
|
resolve() |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* tasks |
||||||
|
*/ |
||||||
|
Tree.prototype.nodesClass = function (d) { |
||||||
|
let sclass = 'node' |
||||||
|
if (d.children === undefined && d._children === undefined) { |
||||||
|
sclass += ' leaf' |
||||||
|
} else { |
||||||
|
sclass += ' parent' |
||||||
|
if (d.children === undefined) { |
||||||
|
sclass += ' collapsed' |
||||||
|
} else { |
||||||
|
sclass += ' expanded' |
||||||
|
} |
||||||
|
} |
||||||
|
return sclass |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* tree Expand hidden |
||||||
|
*/ |
||||||
|
Tree.prototype.treeToggles = function (e,clicked_d) { // eslint-disable-line
|
||||||
|
|
||||||
|
self.removeTooltip() |
||||||
|
|
||||||
|
// eslint-disable-next-line quotes
|
||||||
|
d3.selectAll("[task_id='" + clicked_d.data.uuid + "']").each((d) => { |
||||||
|
if (clicked_d !== d && d.children) { |
||||||
|
// eslint-disable-line
|
||||||
|
d._children = d.children |
||||||
|
d.children = null |
||||||
|
self.treeUpdate(d) |
||||||
|
} |
||||||
|
}) |
||||||
|
if (clicked_d._children) { |
||||||
|
clicked_d.children = clicked_d._children |
||||||
|
clicked_d._children = null |
||||||
|
} else { |
||||||
|
clicked_d._children = clicked_d.children |
||||||
|
clicked_d.children = null |
||||||
|
} |
||||||
|
self.treeUpdate(clicked_d) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* update tree |
||||||
|
*/ |
||||||
|
Tree.prototype.treeUpdate = function (source) { |
||||||
|
const tasksStateObj = this.tasksStateObj |
||||||
|
|
||||||
|
const tasksType = {} |
||||||
|
|
||||||
|
this.taskTypeNodeOptions.map((v) => { |
||||||
|
tasksType[v.taskType] = { |
||||||
|
color: v.color |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
return new Promise((resolve) => { |
||||||
|
const tasks = this.tasks |
||||||
|
const height = Math.max( |
||||||
|
500, |
||||||
|
tasks.length * this.config.barHeight + |
||||||
|
this.config.margin.top + |
||||||
|
this.config.margin.bottom |
||||||
|
) |
||||||
|
|
||||||
|
d3.select('.tree-svg') |
||||||
|
.transition() |
||||||
|
.duration(this.duration) |
||||||
|
.attr('height', height) |
||||||
|
|
||||||
|
tasks.forEach((n, i) => { |
||||||
|
n.x = i * this.config.barHeight |
||||||
|
}) |
||||||
|
|
||||||
|
const task = this.svg.selectAll('g.node').data(tasks, (d) => { |
||||||
|
return d.id || (d.id = ++this.i) |
||||||
|
}) |
||||||
|
|
||||||
|
const nodeEnter = task |
||||||
|
.enter() |
||||||
|
.append('g') |
||||||
|
.attr('class', this.nodesClass) |
||||||
|
.attr('transform', () => 'translate(' + source.y0 + ',' + source.x0 + ')') |
||||||
|
.style('opacity', 1e-6) |
||||||
|
|
||||||
|
// Node circle
|
||||||
|
nodeEnter |
||||||
|
.append('circle') |
||||||
|
.attr('r', this.config.barHeight / 3) |
||||||
|
.attr('class', 'task') |
||||||
|
.attr('title', (d) => { |
||||||
|
return d.data.type ? d.data.type : '' |
||||||
|
}) |
||||||
|
.attr('height', this.config.barHeight) |
||||||
|
.attr('width', (d) => this.config.barWidth - d.y) |
||||||
|
.style('fill', (d) => |
||||||
|
d.data.type ? tasksType[d.data.type]?.color : '#fff' |
||||||
|
) |
||||||
|
.attr('task_id', (d) => { |
||||||
|
return d.data.uuid |
||||||
|
}) |
||||||
|
.on('click', this.treeToggles) |
||||||
|
.on('mouseover', (e, d) => { |
||||||
|
self.treeTooltip(d.data.type, e) |
||||||
|
}) |
||||||
|
.on('mouseout', () => { |
||||||
|
self.removeTooltip() |
||||||
|
}) |
||||||
|
|
||||||
|
// Node text
|
||||||
|
nodeEnter |
||||||
|
.append('text') |
||||||
|
.attr('dy', 3.5) |
||||||
|
.attr('dx', this.config.barHeight / 2) |
||||||
|
.text((d) => { |
||||||
|
return d.data.name |
||||||
|
}) |
||||||
|
.style('fill', 'var(--n-title-text-color)') |
||||||
|
|
||||||
|
const translateRatio = |
||||||
|
this.config.nodesMax > 10 ? (this.config.nodesMax > 20 ? 10 : 30) : 60 |
||||||
|
|
||||||
|
// Right node information
|
||||||
|
nodeEnter |
||||||
|
.append('g') |
||||||
|
.attr('class', 'stateboxes') |
||||||
|
.attr( |
||||||
|
'transform', |
||||||
|
(d) => |
||||||
|
'translate(' + (this.config.nodesMax * translateRatio - d.y) + ',0)' |
||||||
|
) |
||||||
|
.selectAll('rect') |
||||||
|
.data((d) => d.data.instances) |
||||||
|
.enter() |
||||||
|
.append('rect') |
||||||
|
.on('click', () => { |
||||||
|
this.removeTooltip() |
||||||
|
}) |
||||||
|
.attr('class', 'state') |
||||||
|
.style( |
||||||
|
'fill', |
||||||
|
(d) => (d.state && tasksStateObj[d.state].color) || '#ffffff' |
||||||
|
) |
||||||
|
.attr('rx', (d) => (d.type ? 0 : 12)) |
||||||
|
.attr('ry', (d) => (d.type ? 0 : 12)) |
||||||
|
.style('shape-rendering', (d) => (d.type ? 'crispEdges' : 'auto')) |
||||||
|
.attr( |
||||||
|
'x', |
||||||
|
(d, i) => i * (this.config.squareSize + this.config.squarePading) |
||||||
|
) |
||||||
|
.attr('y', -(this.config.squareSize / 2)) |
||||||
|
.attr('width', 10) |
||||||
|
.attr('height', 10) |
||||||
|
.on('mouseover', (e, d) => { |
||||||
|
self.treeTooltip(rtInstancesTooltip(d, tasksStateObj), e) |
||||||
|
}) |
||||||
|
.on('mouseout', () => { |
||||||
|
self.removeTooltip() |
||||||
|
}) |
||||||
|
|
||||||
|
// Convert nodes to their new location。
|
||||||
|
nodeEnter |
||||||
|
.transition() |
||||||
|
.duration(this.duration) |
||||||
|
.attr('transform', (d) => 'translate(' + d.y + ',' + d.x + ')') |
||||||
|
.style('opacity', 1) |
||||||
|
|
||||||
|
// Node line
|
||||||
|
task |
||||||
|
.transition() |
||||||
|
.duration(this.duration) |
||||||
|
.attr('class', this.nodesClass) |
||||||
|
.attr('transform', (d) => 'translate(' + d.y + ',' + d.x + ')') |
||||||
|
.style('opacity', 1) |
||||||
|
|
||||||
|
// Convert the exit node to the new location of the parent node。
|
||||||
|
task |
||||||
|
.exit() |
||||||
|
.transition() |
||||||
|
.duration(this.duration) |
||||||
|
.attr('transform', () => 'translate(' + source.y + ',' + source.x + ')') |
||||||
|
.style('opacity', 1e-6) |
||||||
|
.remove() |
||||||
|
|
||||||
|
// Update link
|
||||||
|
const link = this.svg |
||||||
|
.selectAll('path.link') |
||||||
|
.data(this.links, (d) => d.target.id) |
||||||
|
|
||||||
|
// Enter any new links in the previous location of the parent node。
|
||||||
|
link |
||||||
|
.enter() |
||||||
|
.insert('path', 'g') |
||||||
|
.attr('class', 'link') |
||||||
|
.attr('d', () => { |
||||||
|
const o = { x: source.x0, y: source.y0 } |
||||||
|
return this.diagonal({ source: o, target: o }) |
||||||
|
}) |
||||||
|
.transition() |
||||||
|
.duration(this.duration) |
||||||
|
.attr('d', this.diagonal) |
||||||
|
|
||||||
|
// Transition link
|
||||||
|
link.transition().duration(this.duration).attr('d', this.diagonal) |
||||||
|
|
||||||
|
// Convert the exit node to the new location of the parent node
|
||||||
|
link |
||||||
|
.exit() |
||||||
|
.transition() |
||||||
|
.duration(this.duration) |
||||||
|
.attr('d', () => { |
||||||
|
const o = { x: source.x, y: source.y } |
||||||
|
return this.diagonal({ source: o, target: o }) |
||||||
|
}) |
||||||
|
.remove() |
||||||
|
|
||||||
|
// Hide the old position for a transition.
|
||||||
|
tasks.forEach((d) => { |
||||||
|
d.x0 = d.x |
||||||
|
d.y0 = d.y |
||||||
|
}) |
||||||
|
resolve() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* reset |
||||||
|
*/ |
||||||
|
Tree.prototype.reset = function () { |
||||||
|
// $('.d3-tree .tree').html('')
|
||||||
|
d3.select('.d3-tree .tree-svg').html('') |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* toottip handle |
||||||
|
*/ |
||||||
|
Tree.prototype.treeTooltip = function (str, e) { |
||||||
|
if (!str) return |
||||||
|
|
||||||
|
this.selfTree.proxy.showTooltip = true |
||||||
|
this.selfTree.proxy.tooltipText = str |
||||||
|
this.selfTree.proxy.changeTooltip(e) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Manually clear tooltip |
||||||
|
*/ |
||||||
|
Tree.prototype.removeTooltip = function () { |
||||||
|
this.selfTree.proxy.showTooltip = false |
||||||
|
} |
||||||
|
|
||||||
|
export default new Tree() |
@ -0,0 +1,73 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
import { renderTableTime } from '@/common/common' |
||||||
|
/** |
||||||
|
* Node prompt dom |
||||||
|
*/ |
||||||
|
const rtInstancesTooltip = (data, tasksStateObj) => { |
||||||
|
let str = '<div style="text-align: left;word-break:break-all">' |
||||||
|
str += `id : ${data.id}</br>` |
||||||
|
str += `host : ${data.host}</br>` |
||||||
|
str += `name : ${data.name}</br>` |
||||||
|
str += `state : ${data.state ? tasksStateObj[data.state].desc : '-'}(${ |
||||||
|
data.state |
||||||
|
})</br>` |
||||||
|
if (data.type) { |
||||||
|
str += `type : ${data.type}</br>` |
||||||
|
} |
||||||
|
str += `startTime : ${ |
||||||
|
data.startTime ? renderTableTime(data.startTime) : '-' |
||||||
|
}</br>` |
||||||
|
str += `endTime : ${data.endTime ? renderTableTime(data.endTime) : '-'}</br>` |
||||||
|
str += `duration : ${data.duration}</br>` |
||||||
|
str += '</div>' |
||||||
|
|
||||||
|
return str |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the maximum node length |
||||||
|
* Easy to calculate the width dynamically |
||||||
|
*/ |
||||||
|
const rtCountMethod = (list: any) => { |
||||||
|
const arr: any = [] |
||||||
|
function count(list: any, t: string) { |
||||||
|
let toggle = false |
||||||
|
list.forEach((v) => { |
||||||
|
if (v.children && v.children.length > 0) { |
||||||
|
if (!toggle) { |
||||||
|
toggle = true |
||||||
|
t += '*' |
||||||
|
arr.push(t) |
||||||
|
} |
||||||
|
count(v.children, t) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
count(list, '*') |
||||||
|
let num = 6 |
||||||
|
arr.forEach((v) => { |
||||||
|
if (v.length > num) { |
||||||
|
num = v.length |
||||||
|
} |
||||||
|
}) |
||||||
|
return num |
||||||
|
} |
||||||
|
|
||||||
|
export { rtInstancesTooltip, rtCountMethod } |
Loading…
Reference in new issue