${str}
@@ -271,7 +267,7 @@ export default { if (height < 16) { return '' } - return `${str} @@ -282,7 +278,7 @@ export default { if (height < 16) { return '' } - return `
${valueStr}
diff --git a/nezha-fronted/src/components/chart/chart/chartRank.vue b/nezha-fronted/src/components/chart/chart/chartRank.vue
index 0a15d7e78..dd8c832c5 100644
--- a/nezha-fronted/src/components/chart/chart/chartRank.vue
+++ b/nezha-fronted/src/components/chart/chart/chartRank.vue
@@ -115,114 +115,112 @@ export default {
},
drawRankChart () {
- this.$nextTick(() => {
- this.dispose()
- // 获取svg宽高 初始化画布
- const svgDom = document.getElementById(`rank-svg-${this.chartId}`)
- if (!svgDom) {
- return false
- }
- const width = svgDom.getBoundingClientRect().width
- // 柱子高度
- const barHeight = 24
- // 柱子间隔
- const margin = 40
- // 计算svg高度
- const height = this.rankData.length * (barHeight + margin) + margin
- this.svg = d3.select(`#rank-svg-${this.chartId}`).attr('height', height)
- const bodyX = 50
- const bodyWidth = width - 3 * bodyX
+ this.dispose()
+ // 获取svg宽高 初始化画布
+ const svgDom = document.getElementById(`rank-svg-${this.chartId}`)
+ if (!svgDom) {
+ return false
+ }
+ const width = svgDom.getBoundingClientRect().width
+ // 柱子高度
+ const barHeight = 24
+ // 柱子间隔
+ const margin = 40
+ // 计算svg高度
+ const height = this.rankData.length * (barHeight + margin) + margin
+ this.svg = d3.select(`#rank-svg-${this.chartId}`).attr('height', height)
+ const bodyX = 50
+ const bodyWidth = width - 3 * bodyX
- // 从大到小排序
- let rankData = lodash.cloneDeep(this.rankData)
- rankData.sort((a, b) => b.value - a.value)
- rankData = rankData.map((item, index) => {
- return {
- rank: index,
- background: item.mapping ? item.mapping.color.bac : this.colorList[index],
- ...item
+ // 从大到小排序
+ let rankData = lodash.cloneDeep(this.rankData)
+ rankData.sort((a, b) => b.value - a.value)
+ rankData = rankData.map((item, index) => {
+ return {
+ rank: index,
+ background: item.mapping ? item.mapping.color.bac : this.colorList[index],
+ ...item
+ }
+ })
+
+ // 尺度转换
+ const scaleX = d3.scaleLinear()
+ .domain([0, d3.max(rankData, (d) => parseFloat(d.value))])
+ .range([0, bodyWidth])
+
+ // 柱子最小宽度
+ const minWidth = 2
+ // 渲染柱形
+ const bars = this.svg.append('g')
+ .attr('transform', `translate(${bodyX})`)
+ .selectAll()
+ .data(rankData)
+ bars.enter()
+ .append('rect')
+ .merge(bars)
+ .attr('x', 0)
+ .attr('y', (d) => {
+ return (d.rank * barHeight) + (d.rank + 1) * margin
+ })
+ .attr('height', barHeight)
+ .attr('fill', (d) => d.background)
+ .attr('width', (d) => {
+ return scaleX(d.value) > minWidth ? scaleX(d.value) : minWidth
+ })
+ .style('cursor', 'pointer')
+ .on('mouseenter', this.rankEnter)
+ .on('mousemove', this.rankMove)
+ .on('mouseleave', this.rankLeave)
+ bars.exit().remove()
+
+ // 文本标签
+ this.svg.append('g')
+ .attr('transform', `translate(${bodyX})`)
+ .selectAll()
+ .data(rankData)
+ .enter()
+ .append('foreignObject')
+ .on('mouseenter', this.rankEnter)
+ .on('mousemove', this.rankMove)
+ .on('mouseleave', this.rankLeave)
+ .html((d) => {
+ return this.rankFormatterLabel(d)
+ })
+ .attr('x', (d) => {
+ let x = 0
+ if (scaleX(d.value) > minWidth) {
+ x = scaleX(d.value) + 10
+ } else {
+ x = minWidth + 10
+ }
+ return x
+ })
+ .attr('y', (d) => {
+ return (d.rank * barHeight) + (d.rank + 1) * margin
+ })
+ .attr('height', barHeight)
+ .style('cursor', 'pointer')
+ .style('overflow', 'visible')
+
+ // 生成标签和矩形
+ this.svg.append('g')
+ .attr('transform', `translate(${bodyX})`)
+ .selectAll('text')
+ .data(rankData)
+ .enter()
+ .append('text')
+ .attr('class', 'chart-label-text')
+ .attr('x', 4)
+ .attr('y', d => {
+ return (d.rank * barHeight) + (d.rank + 1) * margin - 12
+ })
+ .text(d => {
+ if (this.chartInfo.param.text !== 'all' && this.chartInfo.param.text !== 'legend') {
+ return ''
+ } else {
+ return d.alias
}
})
-
- // 尺度转换
- const scaleX = d3.scaleLinear()
- .domain([0, d3.max(rankData, (d) => parseFloat(d.value))])
- .range([0, bodyWidth])
-
- // 柱子最小宽度
- const minWidth = 2
- // 渲染柱形
- const bars = this.svg.append('g')
- .attr('transform', `translate(${bodyX})`)
- .selectAll()
- .data(rankData)
- bars.enter()
- .append('rect')
- .merge(bars)
- .attr('x', 0)
- .attr('y', (d) => {
- return (d.rank * barHeight) + (d.rank + 1) * margin
- })
- .attr('height', barHeight)
- .attr('fill', (d) => d.background)
- .attr('width', (d) => {
- return scaleX(d.value) > minWidth ? scaleX(d.value) : minWidth
- })
- .style('cursor', 'pointer')
- .on('mouseenter', this.rankEnter)
- .on('mousemove', this.rankMove)
- .on('mouseleave', this.rankLeave)
- bars.exit().remove()
-
- // 文本标签
- this.svg.append('g')
- .attr('transform', `translate(${bodyX})`)
- .selectAll()
- .data(rankData)
- .enter()
- .append('foreignObject')
- .on('mouseenter', this.rankEnter)
- .on('mousemove', this.rankMove)
- .on('mouseleave', this.rankLeave)
- .html((d) => {
- return this.rankFormatterLabel(d)
- })
- .attr('x', (d) => {
- let x = 0
- if (scaleX(d.value) > minWidth) {
- x = scaleX(d.value) + 10
- } else {
- x = minWidth + 10
- }
- return x
- })
- .attr('y', (d) => {
- return (d.rank * barHeight) + (d.rank + 1) * margin
- })
- .attr('height', barHeight)
- .style('cursor', 'pointer')
- .style('overflow', 'visible')
-
- // 生成标签和矩形
- this.svg.append('g')
- .attr('transform', `translate(${bodyX})`)
- .selectAll('text')
- .data(rankData)
- .enter()
- .append('text')
- .attr('class', 'chart-label-text')
- .attr('x', 4)
- .attr('y', d => {
- return (d.rank * barHeight) + (d.rank + 1) * margin - 12
- })
- .text(d => {
- if (this.chartInfo.param.text !== 'all' && this.chartInfo.param.text !== 'legend') {
- return ''
- } else {
- return d.alias
- }
- })
- })
},
// 处理label
diff --git a/nezha-fronted/src/components/chart/chart/chartSankey.vue b/nezha-fronted/src/components/chart/chart/chartSankey.vue
index 89b587d19..dd25a5391 100644
--- a/nezha-fronted/src/components/chart/chart/chartSankey.vue
+++ b/nezha-fronted/src/components/chart/chart/chartSankey.vue
@@ -136,181 +136,179 @@ export default {
},
drawSankeyChart () {
- this.$nextTick(() => {
- this.dispose()
- // 获取svg宽高 初始化画布
- const svgDom = document.getElementById(`sankey-svg-${this.chartId}`)
- if (!svgDom) {
- return false
- }
- const width = svgDom.getBoundingClientRect().width
- const height = svgDom.getBoundingClientRect().height
- const margin1 = 100
- const margin2 = 50
- this.svg = d3.select(`#sankey-svg-${this.chartId}`)
- const chart = this.svg.append('g').attr('transform', `translate(${margin2}, ${margin2})`)
+ this.dispose()
+ // 获取svg宽高 初始化画布
+ const svgDom = document.getElementById(`sankey-svg-${this.chartId}`)
+ if (!svgDom) {
+ return false
+ }
+ const width = svgDom.getBoundingClientRect().width
+ const height = svgDom.getBoundingClientRect().height
+ const margin1 = 100
+ const margin2 = 50
+ this.svg = d3.select(`#sankey-svg-${this.chartId}`)
+ const chart = this.svg.append('g').attr('transform', `translate(${margin2}, ${margin2})`)
- // 创建桑基图生成器
- const sankey = d3Sankey
- .sankey()
- .nodeWidth(20)
- .nodePadding(20)
- .size([width - 2 * margin1, height - 2 * margin2])
- .nodeId((d) => d.node)
- const nodesData = lodash.cloneDeep(this.nodesData)
- const linksData = lodash.cloneDeep(this.linksData)
+ // 创建桑基图生成器
+ const sankey = d3Sankey
+ .sankey()
+ .nodeWidth(20)
+ .nodePadding(20)
+ .size([width - 2 * margin1, height - 2 * margin2])
+ .nodeId((d) => d.node)
+ const nodesData = lodash.cloneDeep(this.nodesData)
+ const linksData = lodash.cloneDeep(this.linksData)
- // 判断数据是否全部为0
- let allZero = false
- if (linksData.every(item => item.value == 0)) {
- linksData.forEach(item => {
- item.value = 100 // 目的是显示图表 与值大小无关
- })
- allZero = true
- }
-
- const { nodes, links } = sankey({
- nodes: nodesData,
- links: linksData
+ // 判断数据是否全部为0
+ let allZero = false
+ if (linksData.every(item => item.value == 0)) {
+ linksData.forEach(item => {
+ item.value = 100 // 目的是显示图表 与值大小无关
})
+ allZero = true
+ }
- // 设置节点颜色
- nodes.forEach((item, index) => {
- if (index >= 20) {
- const colorRandom = randomcolor()
- this.colorList.push(colorRandom)
- }
- const mapping = this.selectMapping(item.value, this.chartInfo.param.valueMapping, this.chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
- item.mapping = mapping
- item.background = mapping ? mapping.color.bac : this.colorList[index]
- const decimals = this.chartInfo.param.decimals || 2
- item.showValue = chartDataFormat.getUnit(this.chartInfo.unit ? this.chartInfo.unit : 2).compute(!allZero ? item.value : 0, null, -1, decimals)
- })
-
- // 创建一个连线绘制组,绑定连线数据(links)
- chart
- .append('g')
- .attr('fill', 'none')
- .selectAll()
- .data(links)
- .join('path')
- .attr('linkNodes', (d) => { // 设置与当前连线相连的节点(必须以字母开头)
- return 'i-' + d.source.index + ' ' + 'i-' + d.target.index
- })
- .attr('d', d3Sankey.sankeyLinkHorizontal())
- .attr('stroke', (d) => {
- return d.source.background
- })
- .attr('stroke-width', (d) => d.width || 1)
- .style('stroke-opacity', '0.5')
- .attr('cursor', 'pointer')
- .style('transition', 'all 0.3s')
-
- // 创建一个节点绘制组,绑定节点数据(nodes)。
- chart
- .append('g')
- .selectAll()
- .data(nodes)
- .join('g')
- .attr('class', 'node')
- .attr('linkNodes', (d) => { // 设置与当前节点相连的节点(必须以字母开头)
- let nodeStr = ''
- d.targetLinks.forEach(link => {
- nodeStr += 'i-' + link.source.index + ' '
- })
- nodeStr += 'i-' + d.index
- d.sourceLinks.forEach(link => {
- nodeStr += ' ' + 'i-' + link.target.index
- })
-
- return nodeStr
- })
- .attr('index', (d) => { // 设置标识(必须以字母开头)
- return 'i-' + d.index
- })
- .append('rect')
- .attr('fill', (d, i) => {
- return d.background
- })
- .attr('x', (d) => d.x0)
- .attr('y', (d) => d.y0)
- .attr('height', (d) => d.y1 - d.y0 || 2)
- .attr('width', (d) => d.x1 - d.x0)
- .attr('cursor', 'pointer')
- .style('transition', 'all 0.3s')
-
- // 节点添加文字
- chart
- .selectAll('.node')
- .append('foreignObject')
- // .attr('width', 20)
- .attr('height', function (d) { return d.y1 - d.y0 })
- .attr('x', function (d) { return d.x0 + 30 })
- .attr('y', function (d) { return d.y0 })
- .style('overflow', 'visible')
- .style('cursor', 'pointer')
- .style('transition', 'all 0.3s')
- .html((d) => {
- return this.sankeyFormatterLabel(d)
- })
-
- // 划过连线
- chart.selectAll('path')
- .on('mouseover', (e, d) => {
- chart.selectAll('.node, path').style('fill-opacity', '0.1').style('stroke-opacity', '0.1')
- chart.selectAll('.node').selectAll('foreignObject').style('opacity', '0.1')
- const hoverNodes = d3.select(e.target).style('stroke-opacity', '0.8').attr('linkNodes').split(' ')
- hoverNodes.forEach((index) => {
- chart.selectAll('[index=' + index + ']').style('fill-opacity', '1').selectAll('foreignObject').style('opacity', '1')
- })
- // 显示悬浮框
- this.tooltip.title = d.source.node + ' ——> ' + d.target.node
- this.tooltip.value = d.showValue
- this.tooltip.mapping = ''
- this.tooltip.show = true
- this.setPosition(e)
- })
- .on('mousemove', (e) => {
- if (this.tooltip.show) {
- this.setPosition(e)
- }
- })
- .on('mouseleave', () => {
- chart.selectAll('.node, path').style('fill-opacity', '1').style('stroke-opacity', '0.5')
- chart.selectAll('.node').selectAll('foreignObject').style('opacity', '1')
- // 隐藏悬浮框
- this.tooltip.show = false
- })
-
- // 划过节点
- chart.selectAll('.node')
- .on('mouseover', (e, d) => {
- chart.selectAll('.node, path').style('fill-opacity', '0.1').style('stroke-opacity', '0.1')
- chart.selectAll('.node').selectAll('foreignObject').style('opacity', '0.1')
- chart.selectAll('[linkNodes~=' + 'i-' + d.index + ']')
- .style('fill-opacity', '1')
- .style('stroke-opacity', '0.8')
- .selectAll('foreignObject')
- .style('opacity', '1')
- // 显示悬浮框
- this.tooltip.title = d.node
- this.tooltip.value = d.showValue
- this.tooltip.mapping = d.mapping
- this.tooltip.show = true
- this.setPosition(e)
- })
- .on('mousemove', (e) => {
- if (this.tooltip.show) {
- this.setPosition(e)
- }
- })
- .on('mouseleave', () => {
- chart.selectAll('.node, path').style('fill-opacity', '1').style('stroke-opacity', '0.5')
- chart.selectAll('.node').selectAll('foreignObject').style('opacity', '1')
- // 隐藏悬浮框
- this.tooltip.show = false
- })
+ const { nodes, links } = sankey({
+ nodes: nodesData,
+ links: linksData
})
+
+ // 设置节点颜色
+ nodes.forEach((item, index) => {
+ if (index >= 20) {
+ const colorRandom = randomcolor()
+ this.colorList.push(colorRandom)
+ }
+ const mapping = this.selectMapping(item.value, this.chartInfo.param.valueMapping, this.chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
+ item.mapping = mapping
+ item.background = mapping ? mapping.color.bac : this.colorList[index]
+ const decimals = this.chartInfo.param.decimals || 2
+ item.showValue = chartDataFormat.getUnit(this.chartInfo.unit ? this.chartInfo.unit : 2).compute(!allZero ? item.value : 0, null, -1, decimals)
+ })
+
+ // 创建一个连线绘制组,绑定连线数据(links)
+ chart
+ .append('g')
+ .attr('fill', 'none')
+ .selectAll()
+ .data(links)
+ .join('path')
+ .attr('linkNodes', (d) => { // 设置与当前连线相连的节点(必须以字母开头)
+ return 'i-' + d.source.index + ' ' + 'i-' + d.target.index
+ })
+ .attr('d', d3Sankey.sankeyLinkHorizontal())
+ .attr('stroke', (d) => {
+ return d.source.background
+ })
+ .attr('stroke-width', (d) => d.width || 1)
+ .style('stroke-opacity', '0.5')
+ .attr('cursor', 'pointer')
+ .style('transition', 'all 0.3s')
+
+ // 创建一个节点绘制组,绑定节点数据(nodes)。
+ chart
+ .append('g')
+ .selectAll()
+ .data(nodes)
+ .join('g')
+ .attr('class', 'node')
+ .attr('linkNodes', (d) => { // 设置与当前节点相连的节点(必须以字母开头)
+ let nodeStr = ''
+ d.targetLinks.forEach(link => {
+ nodeStr += 'i-' + link.source.index + ' '
+ })
+ nodeStr += 'i-' + d.index
+ d.sourceLinks.forEach(link => {
+ nodeStr += ' ' + 'i-' + link.target.index
+ })
+
+ return nodeStr
+ })
+ .attr('index', (d) => { // 设置标识(必须以字母开头)
+ return 'i-' + d.index
+ })
+ .append('rect')
+ .attr('fill', (d, i) => {
+ return d.background
+ })
+ .attr('x', (d) => d.x0)
+ .attr('y', (d) => d.y0)
+ .attr('height', (d) => d.y1 - d.y0 || 2)
+ .attr('width', (d) => d.x1 - d.x0)
+ .attr('cursor', 'pointer')
+ .style('transition', 'all 0.3s')
+
+ // 节点添加文字
+ chart
+ .selectAll('.node')
+ .append('foreignObject')
+ // .attr('width', 20)
+ .attr('height', function (d) { return d.y1 - d.y0 })
+ .attr('x', function (d) { return d.x0 + 30 })
+ .attr('y', function (d) { return d.y0 })
+ .style('overflow', 'visible')
+ .style('cursor', 'pointer')
+ .style('transition', 'all 0.3s')
+ .html((d) => {
+ return this.sankeyFormatterLabel(d)
+ })
+
+ // 划过连线
+ chart.selectAll('path')
+ .on('mouseover', (e, d) => {
+ chart.selectAll('.node, path').style('fill-opacity', '0.1').style('stroke-opacity', '0.1')
+ chart.selectAll('.node').selectAll('foreignObject').style('opacity', '0.1')
+ const hoverNodes = d3.select(e.target).style('stroke-opacity', '0.8').attr('linkNodes').split(' ')
+ hoverNodes.forEach((index) => {
+ chart.selectAll('[index=' + index + ']').style('fill-opacity', '1').selectAll('foreignObject').style('opacity', '1')
+ })
+ // 显示悬浮框
+ this.tooltip.title = d.source.node + ' ——> ' + d.target.node
+ this.tooltip.value = d.showValue
+ this.tooltip.mapping = ''
+ this.tooltip.show = true
+ this.setPosition(e)
+ })
+ .on('mousemove', (e) => {
+ if (this.tooltip.show) {
+ this.setPosition(e)
+ }
+ })
+ .on('mouseleave', () => {
+ chart.selectAll('.node, path').style('fill-opacity', '1').style('stroke-opacity', '0.5')
+ chart.selectAll('.node').selectAll('foreignObject').style('opacity', '1')
+ // 隐藏悬浮框
+ this.tooltip.show = false
+ })
+
+ // 划过节点
+ chart.selectAll('.node')
+ .on('mouseover', (e, d) => {
+ chart.selectAll('.node, path').style('fill-opacity', '0.1').style('stroke-opacity', '0.1')
+ chart.selectAll('.node').selectAll('foreignObject').style('opacity', '0.1')
+ chart.selectAll('[linkNodes~=' + 'i-' + d.index + ']')
+ .style('fill-opacity', '1')
+ .style('stroke-opacity', '0.8')
+ .selectAll('foreignObject')
+ .style('opacity', '1')
+ // 显示悬浮框
+ this.tooltip.title = d.node
+ this.tooltip.value = d.showValue
+ this.tooltip.mapping = d.mapping
+ this.tooltip.show = true
+ this.setPosition(e)
+ })
+ .on('mousemove', (e) => {
+ if (this.tooltip.show) {
+ this.setPosition(e)
+ }
+ })
+ .on('mouseleave', () => {
+ chart.selectAll('.node, path').style('fill-opacity', '1').style('stroke-opacity', '0.5')
+ chart.selectAll('.node').selectAll('foreignObject').style('opacity', '1')
+ // 隐藏悬浮框
+ this.tooltip.show = false
+ })
},
setPosition (e) {
diff --git a/nezha-fronted/src/components/chart/chart/d3-funnel/d3-funnel/Colorizer.js b/nezha-fronted/src/components/chart/chart/d3-funnel/d3-funnel/Colorizer.js
deleted file mode 100644
index c6c0db49a..000000000
--- a/nezha-fronted/src/components/chart/chart/d3-funnel/d3-funnel/Colorizer.js
+++ /dev/null
@@ -1,177 +0,0 @@
-class Colorizer {
- /**
- * @return {void}
- */
- constructor () {
- this.hexExpression = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i
- this.instanceId = null
- this.labelFill = null
- this.scale = null
- }
-
- /**
- * @param {string} instanceId
- *
- * @return {void}
- */
- setInstanceId (instanceId) {
- this.instanceId = instanceId
- }
-
- /**
- * @param {string} fill
- *
- * @return {void}
- */
- setLabelFill (fill) {
- this.labelFill = fill
- }
-
- /**
- * @param {function|Array} scale
- *
- * @return {void}
- */
- setScale (scale) {
- this.scale = scale
- }
-
- /**
- * Given a raw data block, return an appropriate color for the block.
- *
- * @param {string} fill
- * @param {Number} index
- * @param {string} fillType
- *
- * @return {Object}
- */
- getBlockFill (fill, index, fillType) {
- const raw = this.getBlockRawFill(fill, index)
-
- return {
- raw,
- actual: this.getBlockActualFill(raw, index, fillType)
- }
- }
-
- /**
- * Return the raw hex color for the block.
- *
- * @param {string} fill
- * @param {Number} index
- *
- * @return {string}
- */
- getBlockRawFill (fill, index) {
- // Use the block's color, if set and valid
- if (this.hexExpression.test(fill)) {
- return fill
- }
-
- // Otherwise, attempt to use the array scale
- if (Array.isArray(this.scale)) {
- return this.scale[index]
- }
-
- // Finally, use a functional scale
- return this.scale(index)
- }
-
- /**
- * Return the actual background for the block.
- *
- * @param {string} raw
- * @param {Number} index
- * @param {string} fillType
- *
- * @return {string}
- */
- getBlockActualFill (raw, index, fillType) {
- if (fillType === 'solid') {
- return raw
- }
-
- return `url(#${this.getGradientId(index)})`
- }
-
- /**
- * Return the gradient ID for the given index.
- *
- * @param {Number} index
- *
- * @return {string}
- */
- getGradientId (index) {
- return `${this.instanceId}-gradient-${index}`
- }
-
- /**
- * Given a raw data block, return an appropriate label color.
- *
- * @param {string} labelFill
- *
- * @return {string}
- */
- getLabelColor (labelFill) {
- return this.hexExpression.test(labelFill) ? labelFill : this.labelFill
- }
-
- /**
- * Shade a color to the given percentage.
- *
- * @param {string} color A hex color.
- * @param {number} shade The shade adjustment. Can be positive or negative.
- *
- * @return {string}
- */
- shade (color, shade) {
- const { R, G, B } = this.hexToRgb(color)
- const t = shade < 0 ? 0 : 255
- const p = shade < 0 ? shade * -1 : shade
-
- const converted = 0x1000000 +
- ((Math.round((t - R) * p) + R) * 0x10000) +
- ((Math.round((t - G) * p) + G) * 0x100) +
- (Math.round((t - B) * p) + B)
-
- return `#${converted.toString(16).slice(1)}`
- }
-
- /**
- * Convert a hex color to an RGB object.
- *
- * @param {string} color
- *
- * @returns {{R: Number, G: number, B: number}}
- */
- hexToRgb (color) {
- let hex = color.slice(1)
-
- if (hex.length === 3) {
- hex = this.expandHex(hex)
- }
-
- const f = parseInt(hex, 16)
-
- /* eslint-disable no-bitwise */
- const R = f >> 16
- const G = (f >> 8) & 0x00FF
- const B = f & 0x0000FF
- /* eslint-enable */
-
- return { R, G, B }
- }
-
- /**
- * Expands a three character hex code to six characters.
- *
- * @param {string} hex
- *
- * @return {string}
- */
- expandHex (hex) {
- return hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
- }
-}
-
-export default Colorizer
diff --git a/nezha-fronted/src/components/chart/chart/d3-funnel/d3-funnel/D3Funnel.js b/nezha-fronted/src/components/chart/chart/d3-funnel/d3-funnel/D3Funnel.js
deleted file mode 100644
index e4b849453..000000000
--- a/nezha-fronted/src/components/chart/chart/d3-funnel/d3-funnel/D3Funnel.js
+++ /dev/null
@@ -1,1157 +0,0 @@
-import { easeLinear } from 'd3-ease'
-import { range } from 'd3-array'
-import { scaleOrdinal } from 'd3-scale'
-import { schemeCategory10 } from 'd3-scale-chromatic'
-import { select } from 'd3-selection'
-import 'd3-transition'
-import { nanoid } from 'nanoid'
-
-import Colorizer from './Colorizer'
-import Formatter from './Formatter'
-import Navigator from './Navigator'
-import Utils from './Utils'
-
-class D3Funnel {
- static defaults = {
- chart: {
- width: 350,
- height: 400,
- bottomWidth: 1 / 3,
- bottomPinch: 0,
- inverted: false,
- horizontal: false,
- animate: 0,
- curve: {
- enabled: false,
- height: 20,
- shade: -0.4
- },
- totalCount: null,
- customAnimate:true
- },
- block: {
- dynamicHeight: false,
- dynamicSlope: false,
- barOverlay: false,
- fill: {
- scale: scaleOrdinal(schemeCategory10).domain(range(0, 10)),
- type: 'solid'
- },
- minHeight: 0,
- highlight: false
- },
- label: {
- enabled: true,
- fontFamily: null,
- fontSize: '14px',
- fill: '#fff',
- format: '{l}: {f}'
- },
- tooltip: {
- enabled: false,
- format: '{l}: {f}'
- },
- events: {
- click: {
- block: null
- },
- mouseenter: {
- block: null
- },
- mousemove: {
- block: null
- },
- mouseleave: {
- block: null
- }
- }
- };
-
- /**
- * @param {string|HTMLElement} selector A selector for the container element.
- *
- * @return {void}
- */
- constructor (selector) {
- this.container = select(selector).node()
-
- this.colorizer = new Colorizer()
- this.formatter = new Formatter()
- this.navigator = new Navigator()
-
- this.id = null
-
- // Bind event handlers
- this.onMouseOver = this.onMouseOver.bind(this)
- this.onMouseOut = this.onMouseOut.bind(this)
- }
-
- /**
- * Remove the funnel and its events from the DOM.
- *
- * @return {void}
- */
- destroy () {
- const container = select(this.container)
-
- // D3's remove method appears to be sufficient for removing the events
- container.selectAll('svg').remove()
-
- // Remove other elements from container
- container.selectAll('*').remove()
-
- // Remove inner text from container
- container.text('')
- }
-
- /**
- * Draw the chart inside the container with the data and configuration
- * specified. This will remove any previous SVG elements in the container
- * and draw a new funnel chart on top of it.
- *
- * @param {Array} data A list of rows containing a category, a count,
- * and optionally a color (in hex).
- * @param {Object} options An optional configuration object to override
- * defaults. See the docs.
- *
- * @return {void}
- */
- draw (data, options = {}) {
- this.destroy()
-
- this.initialize(data, options)
-
- this.drawOntoDom()
- }
-
- /**
- * Initialize and calculate important variables for drawing the chart.
- *
- * @param {Array} data
- * @param {Object} options
- *
- * @return {void}
- */
- initialize (data, options) {
- this.validateData(data)
-
- const settings = this.getSettings(options)
-
- this.id = `d3-funnel-${nanoid()}`
-
- // Set labels
- this.labelFormatter = this.formatter.getFormatter(settings.label.format)
- this.tooltipFormatter = this.formatter.getFormatter(settings.tooltip.format)
-
- // Set color scales
- this.colorizer.setInstanceId(this.id)
- this.colorizer.setLabelFill(settings.label.fill)
- this.colorizer.setScale(settings.block.fill.scale)
-
- // Initialize funnel chart settings
- this.settings = {
- width: settings.chart.width,
- height: settings.chart.height,
- bottomWidth: settings.chart.width * settings.chart.bottomWidth,
- bottomPinch: settings.chart.bottomPinch,
- isInverted: settings.chart.inverted,
- isCurved: settings.chart.curve.enabled,
- curveHeight: settings.chart.curve.height,
- curveShade: settings.chart.curve.shade,
- addValueOverlay: settings.block.barOverlay,
- animation: settings.chart.animate,
- totalCount: settings.chart.totalCount,
- fillType: settings.block.fill.type,
- hoverEffects: settings.block.highlight,
- dynamicHeight: settings.block.dynamicHeight,
- dynamicSlope: settings.block.dynamicSlope,
- minHeight: settings.block.minHeight,
- label: settings.label,
- tooltip: settings.tooltip,
- onBlockClick: settings.events.click.block,
- onBlockEnter: settings.events.mouseenter.block,
- onBlockMove: settings.events.mousemove.block,
- onBlockLeave: settings.events.mouseleave.block,
- customAnimate:settings.chart.customAnimate //opacity过渡
- }
-
- this.setBlocks(data)
- }
-
- /**
- * @param {Array} data
- *
- * @return void
- */
- validateData (data) {
- if (Array.isArray(data) === false) {
- throw new Error('Data must be an array.')
- }
-
- if (data.length === 0) {
- throw new Error('Data array must contain at least one element.')
- }
-
- if (typeof data[0] !== 'object') {
- throw new Error('Data array elements must be an object.')
- }
-
- if (
- (Array.isArray(data[0]) && data[0].length < 2) ||
- (Array.isArray(data[0]) === false && (
- data[0].label === undefined || data[0].value === undefined
- ))
- ) {
- throw new Error('Data array elements must contain a label and value.')
- }
- }
-
- /**
- * @param {Object} options
- *
- * @return {Object}
- */
- getSettings (options) {
- const containerDimensions = this.getContainerDimensions()
- const defaults = this.getDefaultSettings(containerDimensions)
-
- // Prepare the configuration settings based on the defaults
- let settings = Utils.extend({}, defaults)
-
- // Override default settings with user options
- settings = Utils.extend(settings, options)
-
- // Account for any percentage-based dimensions
- settings.chart = {
- ...settings.chart,
- ...this.castDimensions(settings, containerDimensions)
- }
-
- return settings
- }
-
- /**
- * Return default settings.
- *
- * @param {Object} containerDimensions
- *
- * @return {Object}
- */
- getDefaultSettings (containerDimensions) {
- const settings = D3Funnel.defaults
-
- // Set the default width and height based on the container
- settings.chart = {
- ...settings.chart,
- ...containerDimensions
- }
-
- return settings
- }
-
- /**
- * Get the width/height dimensions of the container.
- *
- * @return {{width: Number, height: Number}}
- */
- getContainerDimensions () {
- const dimensions = {
- width: parseFloat(select(this.container).style('width')),
- height: parseFloat(select(this.container).style('height'))
- };
-
- // Remove container dimensions that resolve to zero
- ['width', 'height'].forEach((direction) => {
- if (dimensions[direction] === 0) {
- delete dimensions[direction]
- }
- })
-
- return dimensions
- }
-
- /**
- * Cast dimensions into tangible or meaningful numbers.
- *
- * @param {Object} chart
- * @param {Object} containerDimensions
- *
- * @return {{width: Number, height: Number}}
- */
- castDimensions ({ chart }, containerDimensions) {
- const dimensions = {}
-
- Object.keys(containerDimensions).forEach((direction) => {
- const chartDimension = chart[direction]
- const containerDimension = containerDimensions[direction]
-
- if (/%$/.test(String(chartDimension))) {
- // Convert string into a percentage of the container
- dimensions[direction] = (parseFloat(chartDimension) / 100) * containerDimension
- } else if (chartDimension <= 0) {
- // If case of non-positive number, set to a usable number
- dimensions[direction] = D3Funnel.defaults.chart[direction]
- } else {
- dimensions[direction] = chartDimension
- }
- })
-
- return dimensions
- }
-
- /**
- * Register the raw data into a standard block format and pre-calculate
- * some values.
- *
- * @param {Array} data
- *
- * @return void
- */
- setBlocks (data) {
- const totalCount = this.getTotalCount(data)
-
- this.blocks = this.standardizeData(data, totalCount)
- }
-
- /**
- * Return the total count of all blocks.
- *
- * @param {Array} data
- *
- * @return {Number}
- */
- getTotalCount (data) {
- if (this.settings.totalCount !== null) {
- return this.settings.totalCount || 0
- }
-
- return data.reduce((a, b) => a + Utils.getRawBlockCount(b), 0)
- }
-
- /**
- * Convert the raw data into a standardized format.
- *
- * @param {Array} data
- * @param {Number} totalCount
- *
- * @return {Array}
- */
- standardizeData (data, totalCount) {
- return data.map((rawBlock, index) => {
- const block = Array.isArray(rawBlock) ? Utils.convertLegacyBlock(rawBlock) : rawBlock
- const ratio = totalCount > 0 ? (block.value / totalCount || 0) : 1 / data.length
-
- return {
- index,
- ratio,
- value: block.value,
- height: this.settings.height * ratio,
- fill: this.colorizer.getBlockFill(
- block.backgroundColor,
- index,
- this.settings.fillType
- ),
- label: {
- enabled: !block.hideLabel,
- raw: block.label,
- formatted: this.formatter.format(block, this.labelFormatter),
- color: this.colorizer.getLabelColor(block.labelColor)
- },
- tooltip: {
- enabled: block.enabled,
- formatted: this.formatter.format(block, this.tooltipFormatter)
- },
- data: Utils.extend({}, block)
- }
- })
- }
-
- /**
- * Draw the chart onto the DOM.
- *
- * @return {void}
- */
- drawOntoDom () {
- // Add the SVG
- this.svg = select(this.container)
- .append('svg')
- .attr('id', this.id)
- .attr('width', this.settings.width)
- .attr('height', this.settings.height);
-
- [this.blockPaths, this.overlayPaths] = this.makePaths()
-
- // Define color gradients
- if (this.settings.fillType === 'gradient') {
- this.defineColorGradients(this.svg)
- }
-
- // Add top oval if curved
- if (this.settings.isCurved) {
- this.drawTopOval(this.svg, this.blockPaths)
- }
-
- // Add each block
- this.drawBlock(0)
- }
-
- /**
- * Create the paths to be used to define the discrete funnel blocks and
- * returns the results in an array.
- *
- * @return {Array, Array}
- */
- makePaths () {
- // Calculate the important fixed positions
- const bottomLeftX = (this.settings.width - this.settings.bottomWidth) / 2
- const centerX = this.settings.width / 2
-
- let paths = []
- let overlayPaths = []
-
- // Calculate change in x, y direction
- this.dx = this.getDx(bottomLeftX)
- this.dy = this.getDy()
-
- // Initialize velocity
- let { dx, dy } = this
-
- // Initialize starting positions
- let prevLeftX = 0
- let prevRightX = this.settings.width
- let prevHeight = 0
-
- // Start from the bottom for inverted
- if (this.settings.isInverted) {
- prevLeftX = bottomLeftX
- prevRightX = this.settings.width - bottomLeftX
- }
-
- // Initialize next positions
- let nextLeftX = 0
- let nextRightX = 0
- let nextHeight = 0
-
- // Move down if there is an initial curve
- if (this.settings.isCurved) {
- prevHeight = this.settings.curveHeight / 2
- }
-
- let totalHeight = this.settings.height
-
- // This is greedy in that the block will have a guaranteed height
- // and the remaining is shared among the ratio, instead of being
- // shared according to the remaining minus the guaranteed
- if (this.settings.minHeight !== 0) {
- totalHeight = this.settings.height - (this.settings.minHeight * this.blocks.length)
- }
-
- let slopeHeight = this.settings.height
-
- // Correct slope height if there are blocks being pinched (and thus
- // requiring a sharper curve)
- if (this.settings.bottomPinch > 0) {
- this.blocks.forEach((block, i) => {
- let height = (totalHeight * block.ratio)
-
- // Add greedy minimum height
- if (this.settings.minHeight !== 0) {
- height += this.settings.minHeight
- }
-
- // Account for any curvature
- if (this.settings.isCurved) {
- height += this.settings.curveHeight / this.blocks.length
- }
-
- if (this.settings.isInverted) {
- if (i < this.settings.bottomPinch) {
- slopeHeight -= height
- }
- } else if (i >= this.blocks.length - this.settings.bottomPinch) {
- slopeHeight -= height
- }
- })
- }
-
- // The slope will determine the x points on each block iteration
- // Given: slope = (y1 - y2) / (x1 - x2)
- // (x1, y1) = (bottomLeftX, height)
- // (x2, y2) = (0, 0)
- const slope = slopeHeight / bottomLeftX
-
- // Create the path definition for each funnel block
- // Remember to loop back to the beginning point for a closed path
- this.blocks.forEach((block, i) => {
- // Make heights proportional to block weight
- if (this.settings.dynamicHeight) {
- // Slice off the height proportional to this block
- dy = totalHeight * block.ratio
-
- // Add greedy minimum height
- if (this.settings.minHeight !== 0) {
- dy += this.settings.minHeight
- }
-
- // Account for any curvature
- if (this.settings.isCurved) {
- dy -= this.settings.curveHeight / this.blocks.length
- }
-
- // Given: y = mx + b
- // Given: b = 0 (when funnel), b = this.settings.height (when pyramid)
- // For funnel, x_i = y_i / slope
- nextLeftX = (prevHeight + dy) / slope
-
- // For pyramid, x_i = y_i - this.settings.height / -slope
- if (this.settings.isInverted) {
- nextLeftX = ((prevHeight + dy) - this.settings.height) / (-1 * slope)
- }
-
- // If bottomWidth is 0, adjust last x position (to circumvent
- // errors associated with rounding)
- if (this.settings.bottomWidth === 0 && i === this.blocks.length - 1) {
- // For funnel, last position is the center
- nextLeftX = this.settings.width / 2
-
- // For pyramid, last position is the origin
- if (this.settings.isInverted) {
- nextLeftX = 0
- }
- }
-
- // If bottomWidth is same as width, stop x velocity
- if (this.settings.bottomWidth === this.settings.width) {
- nextLeftX = prevLeftX
- }
-
- // Prevent NaN or Infinite values (caused by zero heights)
- if (Number.isNaN(nextLeftX) || !Number.isFinite(nextLeftX)) {
- nextLeftX = 0
- }
-
- // Calculate the shift necessary for both x points
- dx = nextLeftX - prevLeftX
-
- if (this.settings.isInverted) {
- dx = prevLeftX - nextLeftX
- }
- }
-
- // Make slope width proportional to change in block value
- if (this.settings.dynamicSlope && !this.settings.isInverted) {
- const nextBlockValue = this.blocks[i + 1]
- ? this.blocks[i + 1].value
- : block.value
-
- const widthRatio = nextBlockValue / block.value
- dx = (1 - widthRatio) * (centerX - prevLeftX)
- }
-
- // Stop velocity for pinched blocks
- if (this.settings.bottomPinch > 0) {
- // Check if we've reached the bottom of the pinch
- // If so, stop changing on x
- if (!this.settings.isInverted) {
- if (i >= this.blocks.length - this.settings.bottomPinch) {
- dx = 0
- }
- // Pinch at the first blocks relating to the bottom pinch
- // Revert back to normal velocity after pinch
- } else {
- // Revert velocity back to the initial if we are using
- // static heights (prevents zero velocity if isInverted
- // and bottomPinch are non trivial and dynamicHeight is
- // false)
- if (!this.settings.dynamicHeight) {
- ({ dx } = this)
- }
-
- dx = i < this.settings.bottomPinch ? 0 : dx
- }
- }
-
- // Calculate the position of next block
- nextLeftX = prevLeftX + dx
- nextRightX = prevRightX - dx
- nextHeight = prevHeight + dy
-
- this.blocks[i].height = dy
-
- // Expand outward if inverted
- if (this.settings.isInverted) {
- nextLeftX = prevLeftX - dx
- nextRightX = prevRightX + dx
- }
-
- const dimensions = {
- centerX,
- prevLeftX,
- prevRightX,
- prevHeight,
- nextLeftX,
- nextRightX,
- nextHeight,
- curveHeight: this.settings.curveHeight,
- ratio: block.ratio
- }
-
- if (this.settings.isCurved) {
- paths = [...paths, this.navigator.makeCurvedPaths(dimensions)]
-
- if (this.settings.addValueOverlay) {
- overlayPaths = [
- ...overlayPaths,
- this.navigator.makeCurvedPaths(dimensions, true)
- ]
- }
- } else {
- paths = [...paths, this.navigator.makeStraightPaths(dimensions)]
-
- if (this.settings.addValueOverlay) {
- overlayPaths = [
- ...overlayPaths,
- this.navigator.makeStraightPaths(dimensions, true)
- ]
- }
- }
-
- // Set the next block's previous position
- prevLeftX = nextLeftX
- prevRightX = nextRightX
- prevHeight = nextHeight
- })
-
- return [paths, overlayPaths]
- }
-
- /**
- * @param {Number} bottomLeftX
- *
- * @return {Number}
- */
- getDx (bottomLeftX) {
- // Will be sharper if there is a pinch
- if (this.settings.bottomPinch > 0) {
- return bottomLeftX / (this.blocks.length - this.settings.bottomPinch)
- }
-
- return bottomLeftX / this.blocks.length
- }
-
- /**
- * @return {Number}
- */
- getDy () {
- // Curved chart needs reserved pixels to account for curvature
- if (this.settings.isCurved) {
- return (this.settings.height - this.settings.curveHeight) / this.blocks.length
- }
-
- return this.settings.height / this.blocks.length
- }
-
- /**
- * Define the linear color gradients.
- *
- * @param {Object} svg
- *
- * @return {void}
- */
- defineColorGradients (svg) {
- const defs = svg.append('defs')
-
- // Create a gradient for each block
- this.blocks.forEach((block, index) => {
- const color = block.fill.raw
- const shade = this.colorizer.shade(color, -0.2)
-
- // Create linear gradient
- const gradient = defs.append('linearGradient')
- .attr('id', this.colorizer.getGradientId(index))
-
- // Define the gradient stops
- const stops = [
- [0, shade],
- [40, color],
- [60, color],
- [100, shade]
- ]
-
- // Add the gradient stops
- stops.forEach((stop) => {
- gradient.append('stop')
- .attr('offset', `${stop[0]}%`)
- .attr('style', `stop-color: ${stop[1]}`)
- })
- })
- }
-
- /**
- * Draw the top oval of a curved funnel.
- *
- * @param {Object} svg
- * @param {Array} blockPaths
- *
- * @return {void}
- */
- drawTopOval (svg, blockPaths) {
- const centerX = this.settings.width / 2
-
- // Create path from top-most block
- const paths = blockPaths[0]
- const topCurve = paths[1][1] + (this.settings.curveHeight / 2)
-
- const path = this.navigator.plot([
- ['M', paths[0][0], paths[0][1]],
- ['Q', centerX, topCurve],
- [' ', paths[2][0], paths[2][1]],
- ['M', paths[2][0], this.settings.curveHeight / 2],
- ['Q', centerX, 0],
- [' ', paths[0][0], this.settings.curveHeight / 2]
- ])
-
- // Draw top oval
- svg.append('path')
- .attr('fill', this.colorizer.shade(this.blocks[0].fill.raw, this.settings.curveShade))
- .attr('d', path)
- }
-
- /**
- * Draw the next block in the iteration.
- *
- * @param {int} index
- *
- * @return {void}
- */
- drawBlock (index) {
- if (index === this.blocks.length) {
- return
- }
-
- // Create a group just for this block
- const group = this.svg.append('g')
- const block = this.blocks[index]
-
- // Fetch path element
- const path = this.getBlockPath(group, index)
-
- path.style('cursor', 'pointer')
-
- // Attach data to the element
- this.attachData(path, block)
-
- let overlayPath = null
- let pathColor = block.fill.actual
-
- if (this.settings.addValueOverlay) {
- overlayPath = this.getOverlayPath(group, index)
- this.attachData(overlayPath, block)
-
- // Add data attribute to distinguish between paths
- path.node().setAttribute('pathType', 'background')
- overlayPath.node().setAttribute('pathType', 'foreground')
-
- // Default path becomes background of lighter shade
- pathColor = this.colorizer.shade(block.fill.raw, 0.3)
- }
-
- // Add animation components
- if (this.settings.animation !== 0) {
- path.transition()
- .duration(this.settings.animation)
- .ease(easeLinear)
- .attr('fill', pathColor)
- .attr('d', this.getPathDefinition(index))
- .on('end', () => {
- this.drawBlock(index + 1)
- })
- } else {
- path.attr('fill', pathColor)
- .attr('d', this.getPathDefinition(index))
- .style('opacity', 0)
- .transition('opacity').duration(this.settings.customAnimate ? 600 : 0)
- .style('opacity', 1)
- this.drawBlock(index + 1)
- }
-
- // Add path overlay
- if (this.settings.addValueOverlay) {
- path.attr('stroke', this.blocks[index].fill.raw)
-
- if (this.settings.animation !== 0) {
- overlayPath.transition()
- .duration(this.settings.animation)
- .ease(easeLinear)
- .attr('fill', block.fill.actual)
- .attr('d', this.getOverlayPathDefinition(index))
- } else {
- overlayPath.attr('fill', block.fill.actual)
- .attr('d', this.getOverlayPathDefinition(index))
- }
- }
-
- // Add the hover events
- if (this.settings.hoverEffects) {
- [path, overlayPath].forEach((target) => {
- if (!target) {
- return
- }
-
- target
- .on('mouseover', this.onMouseOver)
- .on('mouseout', this.onMouseOut)
- })
- }
-
- // Add block click event
- if (this.settings.onBlockClick !== null) {
- [path, overlayPath].forEach((target) => {
- if (!target) {
- return
- }
-
- target.style('cursor', 'pointer')
- .on('click', this.settings.onBlockClick)
- })
- }
-
- // 自定义tooltip
- [path, overlayPath].forEach((target) => {
- if (!target) {
- return
- }
- target
- .on('mouseenter', this.settings.onBlockEnter)
- .on('mousemove', this.settings.onBlockMove)
- .on('mouseleave', this.settings.onBlockLeave)
- })
-
- // Add tooltips
- if (this.settings.tooltip.enabled) {
- [path, overlayPath].forEach((target) => {
- if (!target) {
- return
- }
-
- target.node().addEventListener('mouseout', () => {
- if (this.tooltip) {
- this.container.removeChild(this.tooltip)
- this.tooltip = null
- }
- })
- target.node().addEventListener('mousemove', (e) => {
- if (!this.tooltip) {
- this.tooltip = document.createElement('div')
- this.tooltip.setAttribute('class', 'd3-funnel-tooltip')
- this.container.appendChild(this.tooltip)
- }
-
- this.tooltip.innerText = block.tooltip.formatted
-
- const width = this.tooltip.offsetWidth
- const height = this.tooltip.offsetHeight
- const rect = this.container.getBoundingClientRect()
- const heightOffset = height + 5
- const containerY = rect.y + window.scrollY
- const isAbove = e.pageY - heightOffset < containerY
- const top = isAbove ? e.pageY + 5 : e.pageY - heightOffset
-
- const styles = [
- 'display: inline-block',
- 'position: absolute',
- `left: ${e.pageX - (width / 2)}px`,
- `top: ${top}px`,
- `border: 1px solid ${block.fill.raw}`,
- 'background: rgb(255,255,255,0.75)',
- 'padding: 5px 15px',
- 'color: #000',
- 'font-size: 14px',
- 'font-weight: bold',
- 'text-align: center',
- 'cursor: default',
- 'pointer-events: none'
- ]
- this.tooltip.setAttribute('style', styles.join(';'))
- })
- })
- }
-
- if (this.settings.label.enabled && block.label.enabled) {
- this.addBlockLabel(group, index)
- }
- }
-
- /**
- * @param {Object} group
- * @param {int} index
- *
- * @return {Object}
- */
- getBlockPath (group, index) {
- const path = group.append('path')
-
- if (this.settings.animation !== 0) {
- this.addBeforeTransition(path, index, false)
- }
-
- return path
- }
-
- /**
- * @param {Object} group
- * @param {int} index
- *
- * @return {Object}
- */
- getOverlayPath (group, index) {
- const path = group.append('path')
-
- if (this.settings.animation !== 0) {
- this.addBeforeTransition(path, index, true)
- }
-
- return path
- }
-
- /**
- * Set the attributes of a path element before its animation.
- *
- * @param {Object} path
- * @param {int} index
- * @param {boolean} isOverlay
- *
- * @return {void}
- */
- addBeforeTransition (path, index, isOverlay) {
- const paths = isOverlay ? this.overlayPaths[index] : this.blockPaths[index]
-
- let beforePath = ''
- let beforeFill = ''
-
- // Construct the top of the trapezoid and leave the other elements
- // hovering around to expand downward on animation
- if (!this.settings.isCurved) {
- beforePath = this.navigator.plot([
- ['M', paths[0][0], paths[0][1]],
- ['L', paths[1][0], paths[1][1]],
- ['L', paths[1][0], paths[1][1]],
- ['L', paths[0][0], paths[0][1]]
- ])
- } else {
- beforePath = this.navigator.plot([
- ['M', paths[0][0], paths[0][1]],
- ['Q', paths[1][0], paths[1][1]],
- [' ', paths[2][0], paths[2][1]],
- ['L', paths[2][0], paths[2][1]],
- ['M', paths[2][0], paths[2][1]],
- ['Q', paths[1][0], paths[1][1]],
- [' ', paths[0][0], paths[0][1]]
- ])
- }
-
- // Use previous fill color, if available
- if (this.settings.fillType === 'solid' && index > 0) {
- beforeFill = this.blocks[index - 1].fill.actual
- // Otherwise use current background
- } else {
- beforeFill = this.blocks[index].fill.actual
- }
-
- path.attr('d', beforePath)
- .attr('fill', beforeFill)
- }
-
- /**
- * Attach data to the target element. Also attach the current node to the
- * data object.
- *
- * @param {Object} element
- * @param {Object} data
- *
- * @return {void}
- */
- attachData (element, data) {
- const nodeData = {
- ...data,
- node: element.node()
- }
-
- element.data([nodeData])
- }
-
- /**
- * @param {int} index
- *
- * @return {string}
- */
- getPathDefinition (index) {
- const commands = []
-
- this.blockPaths[index].forEach((command) => {
- commands.push([command[2], command[0], command[1]])
- })
-
- return this.navigator.plot(commands)
- }
-
- /**
- * @param {int} index
- *
- * @return {string}
- */
- getOverlayPathDefinition (index) {
- const commands = []
-
- this.overlayPaths[index].forEach((command) => {
- commands.push([command[2], command[0], command[1]])
- })
-
- return this.navigator.plot(commands)
- }
-
- /**
- * @param {Object} event
- * @param {Object} data
- *
- * @return {void}
- */
- onMouseOver (event, data) {
- const children = event.target.parentElement.childNodes;
-
- // Highlight all paths within one block
- [...children].forEach((node) => {
- if (node.nodeName.toLowerCase() === 'path') {
- const type = node.getAttribute('pathType') || ''
- if (type === 'foreground') {
- select(node).attr('fill', this.colorizer.shade(data.fill.raw, -0.5))
- } else {
- select(node).transition('fill').attr('fill', this.colorizer.shade(data.fill.raw, 0.2))
- }
- }
- })
- }
-
- /**
- * @param {Object} event
- * @param {Object} data
- *
- * @return {void}
- */
- onMouseOut (event, data) {
- const children = event.target.parentElement.childNodes;
-
- // Restore original color for all paths of a block
- [...children].forEach((node) => {
- if (node.nodeName.toLowerCase() === 'path') {
- const type = node.getAttribute('pathType') || ''
- if (type === 'background') {
- const backgroundColor = this.colorizer.shade(data.fill.raw, 0.3)
- select(node).attr('fill', backgroundColor)
- } else {
- select(node).transition('fill').attr('fill', data.fill.actual)
- }
- }
- })
- }
-
- /**
- * @param {Object} group
- * @param {int} index
- *
- * @return {void}
- */
- addBlockLabel (group, index) {
- const paths = this.blockPaths[index]
-
- const formattedLabel = this.blocks[index].label.formatted
- // const fill = this.blocks[index].label.color
-
- // Center the text
- // const x = this.settings.width / 2
- // const y = this.getTextY(paths)
-
- // const text = group.append('text')
- // .attr('x', x)
- // .attr('y', y)
- // .attr('fill', fill)
- // .attr('font-size', this.settings.label.fontSize)
- // .attr('text-anchor', 'middle')
- // .attr('dominant-baseline', 'middle')
- // .attr('pointer-events', 'none')
-
- // // Add font-family, if exists
- // if (this.settings.label.fontFamily !== null) {
- // text.attr('font-family', this.settings.label.fontFamily)
- // }
-
- // this.addLabelLines(text, formattedLabel, x)
-
- // 修改源码使用foreignObject支持HTML 显示图标
- const foreignObject = group.append('foreignObject')
- const block = this.blocks[index]
- this.attachData(foreignObject, block)
-
- foreignObject.attr('width', (d) => {
- return paths[2][0] - paths[3][0]
- })
- .attr('x', paths[3][0])
- .attr('y', paths[0][1])
- .attr('height', (d) => {
- return d.height
- })
- .html(d=>{
- return d.height>32?formattedLabel:''
- })
- .attr('pointer-events', 'none')
- .style('opacity', 0)
- .transition('opacity').duration(this.settings.customAnimate ? 800 : 0)
- .style('opacity', 1)
- .style('cursor', 'pointer')
- }
-
- /**
- * Add