关于PIXI方面的资料比较少,在具体实现方面遇到的很多问题并不能在网上找到解答或现成的方案。经过努力看官方文档和不停的调试终于顺利解决所有bug。
这里先将源码po在这里,以后再对细节进行完善。
import React, { Component, PropTypes } from 'react';
import * as d3 from 'd3';
import * as PIXI from 'pixi.js'
import { Tooltip, Switch, Button, Icon } from 'antd';
const ButtonGroup = Button.Group
import data from '../../../static/data/data.json';
export default class D3Force extends Component {
constructor( props ) {
super(props);
this.state = {
}
}
resetZoom(){
let {position, scale} = this.stage
position.x = 0
position.y = 0
scale.x = 1
scale.y = 1
this.reload = true
this.renderer.render(this.stage)
}
allUnlock(){
data.nodes.forEach((node) => {
node.isLocked = false
this.updateNode(node, 0xFFFFFF)
})
}
componentDidMount() {
const width = 920, height = 650, r = 5, minScale = 1/3, maxScale = 10
this.renderer = PIXI.autoDetectRenderer(width, height,
{antialias:true, transparent: false, resolution: 1})
this.stage = new PIXI.Container()
this.transform = d3.zoomIdentity
this.reload = false
this.ix = 0
this.iy = 0
let renderer = this.renderer,
stage = this.stage,
transform = this.transform,
links = new PIXI.Graphics(),
node_x=[], node_y=[]//save the absolute coordinate of nodes
this.canvas.appendChild(renderer.view)
stage.addChild(links)
renderer.view.style.position = "absolute"
renderer.view.style.width = this.canvas.innerWidth + "px"
renderer.view.style.height = this.canvas.innerHeight + "px"
renderer.view.style.display = "block"
renderer.view.style.border = "2px solid white"
renderer.backgroundColor = 0xf3f6fa
let simulation = d3.forceSimulation()
.force("link", d3.forceLink().id((d)=> d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
this.setState({tips:data.nodes[0]})
let nodeColor = (isLocked) => isLocked ? 0x7EC1F3 : 0xC6C7C7
this.tfRender = ()=>{
let {position, scale} = stage
position.x = transform.x
position.y = transform.y
scale.x = transform.k
scale.y = transform.k
renderer.render(stage)
}
let tfRender = this.tfRender
let dragsubject = function() {
let {position, scale} = stage
let {event} = d3
let x = transform.invertX(event.x),
y = transform.invertY(event.y),
dx, dy
for(let i = data.nodes.length-1; i>=0; i--){
let nx = node_x[i], ny = node_y[i]
dx = x - nx
dy = y - ny
if(dx*dx + dy*dy < r*r){
this.ix = nx
this.iy = ny
return simulation.find((event.x-position.x)/scale.x, (event.y-position.y)/scale.y)
}
}
}
let dragstarted = function() {
if(d3.event.subject.isLocked){
return
}
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
let {subject} = d3.event
subject.fx = subject.x
subject.fy = subject.y
tfRender()
}
let dragged = function() {
if(d3.event.subject.isLocked){
return
}
let {subject} = d3.event,
{k} = transform
subject.fx = (d3.event.x + this.ix * (k - 1)) / k
subject.fy = (d3.event.y + this.iy * (k - 1)) / k
tfRender()
}
let dragended = function() {
if(d3.event.subject.isLocked){
return
}
if (!d3.event.active) simulation.alphaTarget(0)
let {subject} = d3.event
subject.fx = null
subject.fy = null
tfRender()
}
let zoomed = function() {
if(this.reload){
this.reload = false
let {transform} = d3.event
transform.x = 0
transform.y = 0
transform.k = 1
}
transform = d3.event.transform
tfRender()
}
let drawNode = function(node, stkClr){
let {gfx} = node
gfx.lineStyle(1.5, stkClr)
.beginFill(nodeColor(node.isLocked))
.drawCircle(0, 0, r)
.endFill()
stage.addChild(gfx)
renderer.render(stage)
}
this.updateNode = function(node, stkClr){
let { x, y, gfx } = node
gfx.clear()
gfx.position = new PIXI.Point(x, y)
drawNode(node, stkClr)
}
let updateNode = this.updateNode
data.nodes.forEach((node) => {
node.gfx = new PIXI.Graphics()
let {gfx} = node
gfx.interactive = true
gfx.buttonMode = true
drawNode(node, 0xFFFFFF)
let tipStyle = new PIXI.TextStyle({
align: "left",
fontSize: 10,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 10,
});
gfx.on("rightclick", ()=>{
let stkclr = (node.isLocked = !node.isLocked) ? 0x000000 : 0xFFFFFF
updateNode(node, stkclr)
})
.on("mouseover",()=>{
if(!node.isLocked) {
updateNode(node, 0x000000)
}
this.rect = new PIXI.Graphics()
this.rect.lineStyle(2, 0x000000, 0.4)
.beginFill(0x000000, 0.8)
.drawRoundedRect(node.x + 2*r, node.y - r - 12, 9*node.id.length, 20, 1)
.endFill()
this.tooltip = new PIXI.Text(node.id, tipStyle)
this.tooltip.x = node.x + 2.5*r
this.tooltip.y = node.y - r - 10
stage.addChild(this.rect)
stage.addChild(this.tooltip)
renderer.render(stage)
})
.on("mouseout",()=>{
if(!node.isLocked) {
updateNode(node, 0xFFFFFF)
}
stage.removeChild(this.rect)
stage.removeChild(this.tooltip)
renderer.render(stage)
})
})
simulation
.nodes(data.nodes)
.on('tick', ticked)
simulation.force('link')
.links(data.links)
d3.select(this.canvas)
.call(d3.drag()
.container(this.canvas)
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
)
.call(d3.zoom()
.scaleExtent([minScale, maxScale])
.on("zoom", zoomed.bind(this))
)
function ticked () {
data.nodes.forEach((node,i) => {
let { x, y, gfx } = node
if(!node.isLocked){//Only update the unlocked nodes
gfx.position = new PIXI.Point(x, y)
node_x[i]=x
node_y[i]=y
}
})
links.alpha = 0.6
links.clear()
data.links.forEach((link,k) => {
let { source, target } = link,
{nodes} = data,
{length} = nodes,
lock_s = false,
lock_t = false
let i,j
for(i = 0; i < length; i++){
if(source.isLocked === true && source.id === nodes[i].id){
lock_s = true;
break;
}
}
for(j = 0; j < length; j++){
if(target.isLocked === true && target.id === nodes[j].id){
lock_t = true;
break;
}
}
links.lineStyle(1, 0x999999)
let node_s = {"x":node_x[i],"y":node_y[i]},
node_t = {"x":node_x[j],"y":node_y[j]}
if(lock_s){
links.moveTo(node_s.x, node_s.y)
}
else{
links.moveTo(source.x, source.y)
}
if(lock_t){
links.lineTo(node_t.x, node_t.y)
}
else{
links.lineTo(target.x, target.y)
}
})
links.endFill()
renderer.render(stage)
}
}
render() {
return (
<div>
<svg width="150px" height="60px">
<circle cx="12" cy="10" r="5" fill="#7EC1F3" />
<text x="25" y="12">Locked Node</text>
<circle cx="12" cy="30" r="6" fill="#118EEA" />
<text x="25" y="32">Hightlight Node</text>
<line x1="7" y1="50" x2="17" y2="50" stroke="#000" />
<text x="25" y="52">New Link</text>
</svg>
<div ref={(canvas)=> {this.canvas=canvas}}
width = {window.innerWidth*0.45}
height = {window.innerHeight*0.9}
>
</div>
<ButtonGroup>
<Tooltip placement="bottom" title="Center">
<Button type="primary" icon="scan" onClick={this.resetZoom.bind(this)} />
</Tooltip>
<Tooltip placement="bottom" title="Unlock">
<Button type="primary" icon="unlock" onClick={this.allUnlock.bind(this)} />
</Tooltip>
</ButtonGroup>
</div>
)
}
}