在 Prefuse 的 ForceDirectedLayout 中实现自定义力
Implement a custom force in Prefuse's ForceDirectedLayout
我想将自定义力添加到 Prefuse 中的力导向布局。具体来说,我想让 a) 特定节点之间的边非常严格,b) 引入有向边,使源顶点倾向于移动到目标顶点上方。
任何线索如何进行?
可以子class 标准SpringForce
并添加更多约束。以下效果很好:
import prefuse.util.force.{Spring, SpringForce}
object MySpringForce {
private final val pi = math.Pi.toFloat
private final val piH = (math.Pi/2).toFloat
private final val eps = (1 * math.Pi/180).toFloat
}
class MySpringForce extends SpringForce {
import MySpringForce._
private val TORQUE = params.length
private val DISTANCE = TORQUE + 1
params = params ++ Array[Float](5e-5f, -1f)
minValues = minValues ++ Array[Float](0f , -1f)
maxValues = maxValues ++ Array[Float](1e-3f, 500f)
override def getParameterNames: Array[String] =
super.getParameterNames ++ Array("Torque", "Limit")
private def angleBetween(a: Float, b: Float): Float = {
val d = b - a
math.atan2(math.sin(d), math.cos(d)).toFloat
}
override def getForce(s: Spring): Unit = {
val item1 = s.item1
val item2 = s.item2
val length = if (s.length < 0)
params(SpringForce.SPRING_LENGTH) else s.length
val x1 = item1.location(0)
val y1 = item1.location(1)
val x2 = item2.location(0)
val y2 = item2.location(1)
var dx = x2 - x1
var dy = y2 - y1
val r0 = math.sqrt(dx * dx + dy * dy).toFloat
val r1 = if (r0 == 0.0) {
dx = (math.random.toFloat - 0.5f) / 50.0f
dy = (math.random.toFloat - 0.5f) / 50.0f
math.sqrt(dx * dx + dy * dy).toFloat
} else r0
val dist = params(DISTANCE)
val r = if (dist < 0) r1 else math.min(dist, r1)
val d = r - length
val coeff = (if (s.coeff < 0)
params(SpringForce.SPRING_COEFF) else s.coeff) * d / r
item1.force(0) += coeff * dx
item1.force(1) += coeff * dy
item2.force(0) += -coeff * dx
item2.force(1) += -coeff * dy
val ang = math.atan2(dy, dx).toFloat
val da = angleBetween(ang, -piH)
if (math.abs(da) <= eps) return
val af = da / pi * params(TORQUE)
val rH = r/2
val cx = (x1 + x2) / 2
val cy = (y1 + y2) / 2
val cos = math.cos(ang + af).toFloat * rH
val sin = math.sin(ang + af).toFloat * rH
val x1t = cx - cos
val y1t = cy - sin
val x2t = cx + cos
val y2t = cy + sin
item1.force(0) += x1t - x1
item1.force(1) += y1t - y1
item2.force(0) += x2t - x2
item2.force(1) += y2t - y2
}
}
我想将自定义力添加到 Prefuse 中的力导向布局。具体来说,我想让 a) 特定节点之间的边非常严格,b) 引入有向边,使源顶点倾向于移动到目标顶点上方。
任何线索如何进行?
可以子class 标准SpringForce
并添加更多约束。以下效果很好:
import prefuse.util.force.{Spring, SpringForce}
object MySpringForce {
private final val pi = math.Pi.toFloat
private final val piH = (math.Pi/2).toFloat
private final val eps = (1 * math.Pi/180).toFloat
}
class MySpringForce extends SpringForce {
import MySpringForce._
private val TORQUE = params.length
private val DISTANCE = TORQUE + 1
params = params ++ Array[Float](5e-5f, -1f)
minValues = minValues ++ Array[Float](0f , -1f)
maxValues = maxValues ++ Array[Float](1e-3f, 500f)
override def getParameterNames: Array[String] =
super.getParameterNames ++ Array("Torque", "Limit")
private def angleBetween(a: Float, b: Float): Float = {
val d = b - a
math.atan2(math.sin(d), math.cos(d)).toFloat
}
override def getForce(s: Spring): Unit = {
val item1 = s.item1
val item2 = s.item2
val length = if (s.length < 0)
params(SpringForce.SPRING_LENGTH) else s.length
val x1 = item1.location(0)
val y1 = item1.location(1)
val x2 = item2.location(0)
val y2 = item2.location(1)
var dx = x2 - x1
var dy = y2 - y1
val r0 = math.sqrt(dx * dx + dy * dy).toFloat
val r1 = if (r0 == 0.0) {
dx = (math.random.toFloat - 0.5f) / 50.0f
dy = (math.random.toFloat - 0.5f) / 50.0f
math.sqrt(dx * dx + dy * dy).toFloat
} else r0
val dist = params(DISTANCE)
val r = if (dist < 0) r1 else math.min(dist, r1)
val d = r - length
val coeff = (if (s.coeff < 0)
params(SpringForce.SPRING_COEFF) else s.coeff) * d / r
item1.force(0) += coeff * dx
item1.force(1) += coeff * dy
item2.force(0) += -coeff * dx
item2.force(1) += -coeff * dy
val ang = math.atan2(dy, dx).toFloat
val da = angleBetween(ang, -piH)
if (math.abs(da) <= eps) return
val af = da / pi * params(TORQUE)
val rH = r/2
val cx = (x1 + x2) / 2
val cy = (y1 + y2) / 2
val cos = math.cos(ang + af).toFloat * rH
val sin = math.sin(ang + af).toFloat * rH
val x1t = cx - cos
val y1t = cy - sin
val x2t = cx + cos
val y2t = cy + sin
item1.force(0) += x1t - x1
item1.force(1) += y1t - y1
item2.force(0) += x2t - x2
item2.force(1) += y2t - y2
}
}