Fixed layouting, added another example
This commit is contained in:
63
src/main/kotlin/com/jaytux/altgraph/examples/SimpleRegDep.kt
Normal file
63
src/main/kotlin/com/jaytux/altgraph/examples/SimpleRegDep.kt
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package com.jaytux.altgraph.examples
|
||||||
|
|
||||||
|
import com.jaytux.altgraph.core.BaseGraph
|
||||||
|
import com.jaytux.altgraph.core.IGraph
|
||||||
|
import com.jaytux.altgraph.layout.PseudoForestLayout
|
||||||
|
import com.jaytux.altgraph.swing.DefaultVertexComponent
|
||||||
|
import com.jaytux.altgraph.swing.GraphPane
|
||||||
|
import com.jaytux.altgraph.swing.QuadraticEdge
|
||||||
|
import java.awt.Color
|
||||||
|
import javax.swing.JFrame
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
|
data class Edge(val id: Int, val isWrite: Boolean)
|
||||||
|
fun getRegGraph(): IGraph<String, Edge> {
|
||||||
|
val graph = BaseGraph<String, Edge>()
|
||||||
|
val roots = listOf(647, 645, 643, 530, 519, 512, 513, 522, 523, 631, 632, 633, 655).sorted()
|
||||||
|
val others = listOf(533, 550, 547, 552, 637, 638, 635, 634, 548, 659, 657, 636, 639, 557, 642, 640, 644, 646, 648, 650, 652, 653, 654, 656, 658).sorted()
|
||||||
|
|
||||||
|
val regs = roots.associateWith { graph.addVertex("%reg$it", true) } + others.associateWith { graph.addVertex("%reg$it", false) }
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
val normal = listOf(
|
||||||
|
647 to 648, 645 to 646, 519 to 533, 519 to 557, 519 to 639, 512 to 550, 512 to 547, 513 to 552, 522 to 659, 522 to 657, 523 to 637, 631 to 638, 631 to 635,
|
||||||
|
632 to 634, 633 to 634, 633 to 636, 547 to 548, 637 to 659, 637 to 557, 637 to 639, 637 to 657, 635 to 636, 639 to 642, 639 to 640, 639 to 644, 646 to 648,
|
||||||
|
648 to 650, 639 to 648, 639 to 650, 639 to 653, 650 to 652, 650 to 652, 650 to 656, 650 to 654, 652 to 653, 654 to 656, 655 to 656, 650 to 658, 557 to 642,
|
||||||
|
557 to 654, 644 to 646, 657 to 658
|
||||||
|
).map { it to false }
|
||||||
|
val writes = listOf(
|
||||||
|
656 to 643, 550 to 519, 548 to 513, 552 to 522, 658 to 522, 637 to 522, 636 to 631, 634 to 631
|
||||||
|
).map { it to true }
|
||||||
|
(normal + writes).forEach { (p, isWrite) ->
|
||||||
|
val (from, to) = p
|
||||||
|
graph.connect(regs[from]!!, regs[to]!!, Edge(count++, isWrite))
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRegPane(graph: IGraph<String, Edge>): GraphPane<String, Edge> = GraphPane(graph) { pane, graph ->
|
||||||
|
PseudoForestLayout(graph, 10.0f, 20.0f, 10.0f, { it.isWrite }) { v ->
|
||||||
|
(pane.getComponentFor(v) as DefaultVertexComponent).vertexSize()
|
||||||
|
}
|
||||||
|
}.also { pane ->
|
||||||
|
val edge = QuadraticEdge<Edge>(
|
||||||
|
color = { if(it.isWrite) Color.ORANGE.darker() else Color.BLACK }
|
||||||
|
)
|
||||||
|
pane.setEdgeRenderer(edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val graph = getRegGraph()
|
||||||
|
val pane = getRegPane(graph)
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
val frame = JFrame("Simple Register Dependency Graph")
|
||||||
|
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
||||||
|
frame.setSize(800, 600)
|
||||||
|
frame.add(pane)
|
||||||
|
frame.pack()
|
||||||
|
frame.setLocationRelativeTo(null)
|
||||||
|
frame.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ import kotlin.math.max
|
|||||||
* @property horizontalMargin the horizontal margin between vertices in the same layer
|
* @property horizontalMargin the horizontal margin between vertices in the same layer
|
||||||
* @property disjoinXMargin the horizontal margin between disjoint subgraphs
|
* @property disjoinXMargin the horizontal margin between disjoint subgraphs
|
||||||
* @property interLayer the vertical margin between layers
|
* @property interLayer the vertical margin between layers
|
||||||
|
* @property ignoreInLayout a function that returns true if an edge should be ignored during layout (but are not inherently back-edges)
|
||||||
* @property vertexSize a function that returns the size of a vertex, including its label offset
|
* @property vertexSize a function that returns the size of a vertex, including its label offset
|
||||||
*
|
*
|
||||||
* @see ILayout
|
* @see ILayout
|
||||||
@ -42,6 +43,7 @@ class PseudoForestLayout<V, E>(
|
|||||||
var horizontalMargin: Float,
|
var horizontalMargin: Float,
|
||||||
var disjoinXMargin: Float,
|
var disjoinXMargin: Float,
|
||||||
var interLayer: Float,
|
var interLayer: Float,
|
||||||
|
val ignoreInLayout: (E) -> Boolean = { false },
|
||||||
vertexSize: (V) -> VertexSize
|
vertexSize: (V) -> VertexSize
|
||||||
) : ILayout<V, E>
|
) : ILayout<V, E>
|
||||||
{
|
{
|
||||||
@ -117,53 +119,48 @@ class PseudoForestLayout<V, E>(
|
|||||||
*/
|
*/
|
||||||
fun setVertexSize(vertexSize: (V) -> VertexSize) { locked { _vertexSize = vertexSize } }
|
fun setVertexSize(vertexSize: (V) -> VertexSize) { locked { _vertexSize = vertexSize } }
|
||||||
|
|
||||||
// Either a vertex, or a dummy node to break up multi-layer-spanning edges
|
private class Conn<V> private constructor(var from: Vert<V>?, var to: Vert<V>?, private val _id: Int) {
|
||||||
private data class Connector<V>(
|
constructor(from: Vert<V>?, to: Vert<V>?) : this(from, to, _nextId++) {}
|
||||||
var from: LayeredVertex<V>,
|
|
||||||
var to: LayeredVertex<V>
|
|
||||||
)
|
|
||||||
private data class LayeredVertex<V>(
|
|
||||||
val x: SumType<V, Connector<V>>
|
|
||||||
) {
|
|
||||||
constructor(x: V): this(x.sum1())
|
|
||||||
constructor(x: Connector<V>): this(x.sum2())
|
|
||||||
|
|
||||||
fun same(other: LayeredVertex<V>) = x.fold({ it1 ->
|
override fun equals(other: Any?): Boolean = other is Conn<*> && other._id == _id
|
||||||
|
override fun hashCode(): Int = _id.hashCode()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var _nextId = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private class Vert<V>(val x: SumType<V, Conn<V>>) {
|
||||||
|
constructor(x: V): this(x.sum1())
|
||||||
|
constructor(x: Conn<V>): this(x.sum2())
|
||||||
|
|
||||||
|
fun same(other: Vert<V>) = x.fold({ it1 ->
|
||||||
other.x.fold({ it2 -> it1 == it2 }) { false }
|
other.x.fold({ it2 -> it1 == it2 }) { false }
|
||||||
}) { it1 ->
|
}) { it1 ->
|
||||||
other.x.fold({ false }) { it2 -> it1.from == it2.from && it1.to == it2.to }
|
other.x.fold({ false }) { it2 -> it1 == it2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// true is this is a direct parent of the other vertex/connector
|
fun directParentOf(other: Vert<V>, graph: IGraph<V, *>): Boolean = x.fold({ it1 ->
|
||||||
fun directParentOf(other: LayeredVertex<V>, graph: IGraph<V, *>): Boolean =
|
other.x.fold({ it2 -> graph.xToY(it1, it2) != null }) { it2 ->
|
||||||
x.fold({ xx ->
|
it2.from == this
|
||||||
// x is a vertex
|
|
||||||
other.x.fold({
|
|
||||||
// other is a vertex
|
|
||||||
graph.xToY(xx, it) != null
|
|
||||||
}) {
|
|
||||||
// other is a connector
|
|
||||||
it.from.same(this)
|
|
||||||
}
|
}
|
||||||
}) { xx ->
|
}) { it1 ->
|
||||||
// x is a connector -> we need to connect to other
|
it1.to!!.same(other)
|
||||||
xx.to.same(other)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private data class PreConnector<V>(
|
|
||||||
var x: SumType<LayeredVertex<V>, PreConnector<V>>?,
|
private fun buildChain(from: V, to: V, layerF: Int, layerT: Int): List<Vert<V>> {
|
||||||
var y: SumType<LayeredVertex<V>, PreConnector<V>>?
|
val first = Vert(from)
|
||||||
)
|
val last = Vert(to)
|
||||||
private fun realVertex(v: V) = LayeredVertex(v.sum1())
|
val chain = List(layerT - layerF - 1) { Vert(Conn<V>(null, null)) }
|
||||||
private fun buildChain(from: V, to: V, layerF: Int, layerT: Int): List<LayeredVertex<V>> {
|
|
||||||
val chain = mutableListOf(LayeredVertex(Connector(LayeredVertex(from), LayeredVertex(to))))
|
chain.forEachIndexed { i, v ->
|
||||||
while(chain.size < layerT - layerF - 1) {
|
if(i == 0) (v.x.asT2).from = first
|
||||||
val last = chain.last() // last is always Connector<V>, and last.to is always V (== to)
|
else (v.x.asT2).from = chain[i - 1]
|
||||||
val lastX = (last.x as SumType.SumT2<Connector<V>>).value
|
|
||||||
val next = LayeredVertex(Connector(last, lastX.to))
|
if(i == chain.size - 1) (v.x.asT2).to = last
|
||||||
lastX.to = next // reconnect
|
else (v.x.asT2).to = chain[i + 1]
|
||||||
chain += next
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return chain
|
return chain
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +187,11 @@ class PseudoForestLayout<V, E>(
|
|||||||
println(" - Visiting $vertex (layer $layer), path=$onPath, deps=$dep")
|
println(" - Visiting $vertex (layer $layer), path=$onPath, deps=$dep")
|
||||||
|
|
||||||
val succLayer = layer + 1
|
val succLayer = layer + 1
|
||||||
_graph.successors(vertex).forEach { (succ, _) ->
|
_graph.successors(vertex).forEach { (succ, edge) ->
|
||||||
|
if(ignoreInLayout(edge)) {
|
||||||
|
println(" - Ignoring edge $edge for layout")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
if (succ in onPath) return@forEach
|
if (succ in onPath) return@forEach
|
||||||
dep += succ
|
dep += succ
|
||||||
|
|
||||||
@ -278,7 +279,7 @@ class PseudoForestLayout<V, E>(
|
|||||||
// Put each vertex in a list by layer
|
// Put each vertex in a list by layer
|
||||||
val layered = List(layerCount) { layer ->
|
val layered = List(layerCount) { layer ->
|
||||||
sub.mapNotNull {
|
sub.mapNotNull {
|
||||||
if(layers[it]?.first == layer) realVertex(it)
|
if(layers[it]?.first == layer) Vert(it)
|
||||||
else null
|
else null
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
}
|
}
|
||||||
@ -317,7 +318,7 @@ class PseudoForestLayout<V, E>(
|
|||||||
// Layer-by-layer, assign x slots (not yet positions)
|
// Layer-by-layer, assign x slots (not yet positions)
|
||||||
for(i in 1 until layered.size) {
|
for(i in 1 until layered.size) {
|
||||||
// Barycenter heuristic: average of parents' slots
|
// Barycenter heuristic: average of parents' slots
|
||||||
val heuristic = { v: LayeredVertex<V> ->
|
val heuristic = { v: Vert<V> ->
|
||||||
val parents = layered[i - 1].mapIndexedNotNull { idx, p -> if(p.directParentOf(v, _graph)) idx.toFloat() else null }
|
val parents = layered[i - 1].mapIndexedNotNull { idx, p -> if(p.directParentOf(v, _graph)) idx.toFloat() else null }
|
||||||
parents.sum() / parents.size
|
parents.sum() / parents.size
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ sealed class SumType<out T1, out T2> {
|
|||||||
*/
|
*/
|
||||||
class SumT1<out T1>(val value: T1) : SumType<T1, Nothing>() {
|
class SumT1<out T1>(val value: T1) : SumType<T1, Nothing>() {
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
(other is SumT1<*> && value == other.value) || value == other
|
(other is SumT1<*> && value == other.value) //|| value == other
|
||||||
override fun hashCode(): Int = value.hashCode()
|
override fun hashCode(): Int = value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ sealed class SumType<out T1, out T2> {
|
|||||||
*/
|
*/
|
||||||
class SumT2<out T2>(val value: T2) : SumType<Nothing, T2>() {
|
class SumT2<out T2>(val value: T2) : SumType<Nothing, T2>() {
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
(other is SumT2<*> && value == other.value) || value == other
|
(other is SumT2<*> && value == other.value) //|| value == other
|
||||||
override fun hashCode(): Int = value.hashCode()
|
override fun hashCode(): Int = value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +59,18 @@ sealed class SumType<out T1, out T2> {
|
|||||||
is SumT2 -> onT2(value)
|
is SumT2 -> onT2(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the contained value as a `T1`, or throws if the sum type holds a `T2`.
|
||||||
|
*/
|
||||||
|
val asT1: T1
|
||||||
|
get() = fold({ it }) { throw IllegalStateException("Not a T1: $this") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the contained value as a `T2`, or throws if the sum type holds a `T1`.
|
||||||
|
*/
|
||||||
|
val asT2: T2
|
||||||
|
get() = fold({ throw IllegalStateException("Not a T2: $this") }) { it }
|
||||||
|
|
||||||
override fun toString(): String = fold({ it.toString() }) { it.toString() }
|
override fun toString(): String = fold({ it.toString() }) { it.toString() }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -13,12 +13,12 @@ import kotlin.math.sqrt
|
|||||||
class QuadraticEdge<E>(
|
class QuadraticEdge<E>(
|
||||||
var delta: Float = 0.2f,
|
var delta: Float = 0.2f,
|
||||||
var arrowLen: Int = 10,
|
var arrowLen: Int = 10,
|
||||||
var color: Color = Color.BLACK,
|
var color: (E) -> Color = { Color.BLACK },
|
||||||
var arrowAngle: Double = Math.PI / 6.0
|
var arrowAngle: Double = Math.PI / 6.0
|
||||||
) : IEdgeRenderer<E> {
|
) : IEdgeRenderer<E> {
|
||||||
override fun drawEdge(g: Graphics2D, from: Point, to: Point, meta: E, offsetFrom: Float, offsetTo: Float) {
|
override fun drawEdge(g: Graphics2D, from: Point, to: Point, meta: E, offsetFrom: Float, offsetTo: Float) {
|
||||||
val g2 = g.create() as Graphics2D
|
val g2 = g.create() as Graphics2D
|
||||||
g2.color = color
|
g2.color = color(meta)
|
||||||
|
|
||||||
val len = from.distance(to).toFloat()
|
val len = from.distance(to).toFloat()
|
||||||
val xLen = delta * len
|
val xLen = delta * len
|
||||||
|
Reference in New Issue
Block a user