|
@@ -234,16 +234,20 @@ export default {
|
|
|
this.startSimulation()
|
|
this.startSimulation()
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- startSimulation: function() {
|
|
|
|
|
|
|
+startSimulation: function() {
|
|
|
if (this.isSimulating) return
|
|
if (this.isSimulating) return
|
|
|
this.isSimulating = true
|
|
this.isSimulating = true
|
|
|
- this.simulationTick()
|
|
|
|
|
|
|
+ // 延迟一帧启动,确保DOM已更新
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ setTimeout(function() {
|
|
|
|
|
+ self.simulationTick()
|
|
|
|
|
+ }, 50)
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
stopSimulation: function() {
|
|
stopSimulation: function() {
|
|
|
this.isSimulating = false
|
|
this.isSimulating = false
|
|
|
if (this.animFrameId) {
|
|
if (this.animFrameId) {
|
|
|
- cancelAnimationFrame(this.animFrameId)
|
|
|
|
|
|
|
+ clearTimeout(this.animFrameId)
|
|
|
this.animFrameId = null
|
|
this.animFrameId = null
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -329,10 +333,10 @@ export default {
|
|
|
// 6. 渲染
|
|
// 6. 渲染
|
|
|
this.render()
|
|
this.render()
|
|
|
|
|
|
|
|
- // 7. 下一帧
|
|
|
|
|
- this.animFrameId = requestAnimationFrame(function() {
|
|
|
|
|
|
|
+ // 7. 下一帧(使用 setTimeout 代替 requestAnimationFrame)
|
|
|
|
|
+ this.animFrameId = setTimeout(function() {
|
|
|
self.simulationTick()
|
|
self.simulationTick()
|
|
|
- })
|
|
|
|
|
|
|
+ }, 33)
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// ===== 渲染 =====
|
|
// ===== 渲染 =====
|
|
@@ -344,9 +348,17 @@ export default {
|
|
|
.fields({ node: true, size: true })
|
|
.fields({ node: true, size: true })
|
|
|
.exec(function(res) {
|
|
.exec(function(res) {
|
|
|
if (!res || !res[0] || !res[0].node) {
|
|
if (!res || !res[0] || !res[0].node) {
|
|
|
- self.renderLegacy()
|
|
|
|
|
|
|
+ // Canvas 节点尚未就绪,延迟重试
|
|
|
|
|
+ if (self._retryCount === undefined) self._retryCount = 0
|
|
|
|
|
+ if (self._retryCount < 5) {
|
|
|
|
|
+ self._retryCount++
|
|
|
|
|
+ setTimeout(function() {
|
|
|
|
|
+ self.render()
|
|
|
|
|
+ }, 200)
|
|
|
|
|
+ }
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ self._retryCount = 0
|
|
|
var canvas = res[0].node
|
|
var canvas = res[0].node
|
|
|
var ctx = canvas.getContext('2d')
|
|
var ctx = canvas.getContext('2d')
|
|
|
canvas.width = self.canvasWidth * self.dpr
|
|
canvas.width = self.canvasWidth * self.dpr
|
|
@@ -431,23 +443,36 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- renderLegacy: function() {
|
|
|
|
|
|
|
+renderLegacy: function() {
|
|
|
var ctx = uni.createCanvasContext('forceGraphCanvas', this)
|
|
var ctx = uni.createCanvasContext('forceGraphCanvas', this)
|
|
|
- var self = this
|
|
|
|
|
|
|
+ if (!ctx) return
|
|
|
|
|
+
|
|
|
|
|
+ // hex转rgba辅助
|
|
|
|
|
+ function hexToRgba(hex, alpha) {
|
|
|
|
|
+ if (hex && hex.length === 7) {
|
|
|
|
|
+ var r = parseInt(hex.slice(1, 3), 16)
|
|
|
|
|
+ var g = parseInt(hex.slice(3, 5), 16)
|
|
|
|
|
+ var b = parseInt(hex.slice(5, 7), 16)
|
|
|
|
|
+ return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'
|
|
|
|
|
+ }
|
|
|
|
|
+ return hex || '#FF6B9D'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
|
|
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
|
|
|
|
|
+
|
|
|
var edges = this.edges
|
|
var edges = this.edges
|
|
|
for (var i = 0; i < edges.length; i++) {
|
|
for (var i = 0; i < edges.length; i++) {
|
|
|
var edge = edges[i]
|
|
var edge = edges[i]
|
|
|
- var trust = edge.trust || 75
|
|
|
|
|
var comm = edge.communication || 50
|
|
var comm = edge.communication || 50
|
|
|
- var hue = 120 * trust / 100
|
|
|
|
|
- ctx.setStrokeStyle('hsla(' + hue + ', 75%, 45%, 0.6)')
|
|
|
|
|
- ctx.setLineWidth(1 + (comm / 100) * 4)
|
|
|
|
|
|
|
+ var hue = 120 * (edge.trust || 75) / 100
|
|
|
|
|
+ ctx.strokeStyle = 'hsla(' + hue + ', 75%, 45%, 0.6)'
|
|
|
|
|
+ ctx.lineWidth = 1 + (comm / 100) * 4
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
ctx.moveTo(edge.source.x, edge.source.y)
|
|
ctx.moveTo(edge.source.x, edge.source.y)
|
|
|
ctx.lineTo(edge.target.x, edge.target.y)
|
|
ctx.lineTo(edge.target.x, edge.target.y)
|
|
|
ctx.stroke()
|
|
ctx.stroke()
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
var nodes = this.simNodes
|
|
var nodes = this.simNodes
|
|
|
for (var j = 0; j < nodes.length; j++) {
|
|
for (var j = 0; j < nodes.length; j++) {
|
|
|
var node = nodes[j]
|
|
var node = nodes[j]
|
|
@@ -456,43 +481,43 @@ export default {
|
|
|
if (node.isSelf) {
|
|
if (node.isSelf) {
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI)
|
|
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI)
|
|
|
- ctx.setFillStyle('#FFFFFF')
|
|
|
|
|
|
|
+ ctx.fillStyle = '#FFFFFF'
|
|
|
ctx.fill()
|
|
ctx.fill()
|
|
|
- ctx.setStrokeStyle(this.themeColor)
|
|
|
|
|
- ctx.setLineWidth(3)
|
|
|
|
|
|
|
+ ctx.strokeStyle = this.themeColor
|
|
|
|
|
+ ctx.lineWidth = 3
|
|
|
ctx.stroke()
|
|
ctx.stroke()
|
|
|
// inner ring
|
|
// inner ring
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
ctx.arc(node.x, node.y, r * 0.7, 0, 2 * Math.PI)
|
|
ctx.arc(node.x, node.y, r * 0.7, 0, 2 * Math.PI)
|
|
|
- ctx.setStrokeStyle(this.themeColor + '88')
|
|
|
|
|
- ctx.setLineWidth(2)
|
|
|
|
|
|
|
+ ctx.strokeStyle = hexToRgba(this.themeColor, 0.53)
|
|
|
|
|
+ ctx.lineWidth = 2
|
|
|
ctx.stroke()
|
|
ctx.stroke()
|
|
|
} else if (node.relationshipType === 'spouse') {
|
|
} else if (node.relationshipType === 'spouse') {
|
|
|
this.drawHeartLegacy(ctx, node.x, node.y, r)
|
|
this.drawHeartLegacy(ctx, node.x, node.y, r)
|
|
|
- ctx.setFillStyle(this.themeColor + '22')
|
|
|
|
|
|
|
+ ctx.fillStyle = hexToRgba(this.themeColor, 0.13)
|
|
|
ctx.fill()
|
|
ctx.fill()
|
|
|
} else if (node.relationshipType === 'parent') {
|
|
} else if (node.relationshipType === 'parent') {
|
|
|
this.drawRoundedRectLegacy(ctx, node.x, node.y, r * 2, r * 2, r * 0.3)
|
|
this.drawRoundedRectLegacy(ctx, node.x, node.y, r * 2, r * 2, r * 0.3)
|
|
|
- ctx.setFillStyle(this.themeColor + '22')
|
|
|
|
|
|
|
+ ctx.fillStyle = hexToRgba(this.themeColor, 0.13)
|
|
|
ctx.fill()
|
|
ctx.fill()
|
|
|
} else if (node.relationshipType === 'sibling') {
|
|
} else if (node.relationshipType === 'sibling') {
|
|
|
this.drawPolygonLegacy(ctx, node.x, node.y, r, 8)
|
|
this.drawPolygonLegacy(ctx, node.x, node.y, r, 8)
|
|
|
- ctx.setFillStyle(this.themeColor + '22')
|
|
|
|
|
|
|
+ ctx.fillStyle = hexToRgba(this.themeColor, 0.13)
|
|
|
ctx.fill()
|
|
ctx.fill()
|
|
|
} else {
|
|
} else {
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI)
|
|
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI)
|
|
|
- ctx.setFillStyle(this.themeColor + '22')
|
|
|
|
|
|
|
+ ctx.fillStyle = hexToRgba(this.themeColor, 0.13)
|
|
|
ctx.fill()
|
|
ctx.fill()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ctx.setFillStyle(node.isSelf ? this.themeColor : '#4A5568')
|
|
|
|
|
- ctx.setFont('bold ' + (r * 0.9) + 'px sans-serif')
|
|
|
|
|
- ctx.setTextAlign('center')
|
|
|
|
|
- ctx.setTextBaseline('middle')
|
|
|
|
|
|
|
+ ctx.fillStyle = node.isSelf ? this.themeColor : '#4A5568'
|
|
|
|
|
+ ctx.font = 'bold ' + (r * 0.9) + 'px sans-serif'
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ ctx.textBaseline = 'middle'
|
|
|
ctx.fillText(node.displayName, node.x, node.y)
|
|
ctx.fillText(node.displayName, node.x, node.y)
|
|
|
- ctx.setFillStyle(node.isSelf ? this.themeColor : '#94A3B8')
|
|
|
|
|
- ctx.setFont((r * 0.4) + 'px sans-serif')
|
|
|
|
|
|
|
+ ctx.fillStyle = node.isSelf ? this.themeColor : '#94A3B8'
|
|
|
|
|
+ ctx.font = (r * 0.4) + 'px sans-serif'
|
|
|
ctx.fillText(node.nickname.substring(0, 3), node.x, node.y + r + 10)
|
|
ctx.fillText(node.nickname.substring(0, 3), node.x, node.y + r + 10)
|
|
|
}
|
|
}
|
|
|
ctx.draw()
|
|
ctx.draw()
|