Documentation
This commit is contained in:
@ -1,5 +1,19 @@
|
|||||||
package com.jaytux.altgraph.core
|
package com.jaytux.altgraph.core
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mutable directed graph implementation, with arbitrary vertex and edge types. Supports marking vertices as "roots"
|
||||||
|
* during addition.
|
||||||
|
*
|
||||||
|
* Vertices and edges must be unique (no duplicates allowed). Edges are directed, and there can be at most one edge
|
||||||
|
* from a given vertex `A` to another vertex `B`.
|
||||||
|
*
|
||||||
|
* All operations that attempt to violate these constraints will throw a [GraphException].
|
||||||
|
*
|
||||||
|
* @param V The vertex type.
|
||||||
|
* @param E The edge type.
|
||||||
|
* @see [IMutableGraph]
|
||||||
|
* @see [IGraph]
|
||||||
|
*/
|
||||||
open class BaseGraph<V, E> : IMutableGraph<V, E> {
|
open class BaseGraph<V, E> : IMutableGraph<V, E> {
|
||||||
private val _vertices = mutableMapOf<V, Boolean>()
|
private val _vertices = mutableMapOf<V, Boolean>()
|
||||||
// [from] -> {e: exists v s.t. e = (from, v)}
|
// [from] -> {e: exists v s.t. e = (from, v)}
|
||||||
@ -70,6 +84,15 @@ open class BaseGraph<V, E> : IMutableGraph<V, E> {
|
|||||||
if (_existing[from]?.isEmpty() == true) { _existing.remove(from) }
|
if (_existing[from]?.isEmpty() == true) { _existing.remove(from) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an immutable view of this graph.
|
||||||
|
*
|
||||||
|
* Further modifications to the original graph will be reflected in the immutable view.
|
||||||
|
*
|
||||||
|
* This operations is very cheap, as it does not involve any copying of data.
|
||||||
|
*
|
||||||
|
* @return An immutable view of this graph
|
||||||
|
*/
|
||||||
fun immutable(): IGraph<V, E> = BaseGraphImmutable()
|
fun immutable(): IGraph<V, E> = BaseGraphImmutable()
|
||||||
|
|
||||||
override fun successors(vertex: V): Map<V, E> =
|
override fun successors(vertex: V): Map<V, E> =
|
||||||
|
@ -1,42 +1,221 @@
|
|||||||
package com.jaytux.altgraph.core
|
package com.jaytux.altgraph.core
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple, immutable graph interface.
|
||||||
|
*
|
||||||
|
* The graph is directed, and may contain cycles (including self-loops). However, it may not contain parallel edges.
|
||||||
|
*
|
||||||
|
* The supported operations are:
|
||||||
|
* - Querying for vertices and edges;
|
||||||
|
* - Querying for graph roots (these do not have to be roots in the traditional sense, but can be any vertex that
|
||||||
|
* should be treated as a root for layout purposes);
|
||||||
|
* - Querying for successors and predecessors of a vertex;
|
||||||
|
* - Querying for the edge between two vertices, if it exists.
|
||||||
|
*
|
||||||
|
* Importantly, each vertex (`V`) and edge (`E`) should be unique in the graph. Edges should not encode any incidence
|
||||||
|
* information (i.e. their endpoints), but should be distinct objects.
|
||||||
|
*
|
||||||
|
* Mutable graphs should implement [IMutableGraph], which extends this interface with mutation operations.
|
||||||
|
*
|
||||||
|
* @param V the type of vertices in the graph
|
||||||
|
* @param E the type of edges in the graph
|
||||||
|
* @see IMutableGraph
|
||||||
|
* @see BaseGraph
|
||||||
|
*/
|
||||||
interface IGraph<V, E> {
|
interface IGraph<V, E> {
|
||||||
|
/**
|
||||||
|
* Gets all the vertices in the graph.
|
||||||
|
*
|
||||||
|
* @return a set of all vertices in the graph
|
||||||
|
*/
|
||||||
fun vertices(): Set<V>
|
fun vertices(): Set<V>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the edges in the graph, along with their endpoints.
|
||||||
|
*
|
||||||
|
* @return a map from edges to their endpoints (as pairs (from, to))
|
||||||
|
*/
|
||||||
fun edges(): Map<E, Pair<V, V>>
|
fun edges(): Map<E, Pair<V, V>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the root vertices in the graph.
|
||||||
|
*
|
||||||
|
* @return a set of all root vertices in the graph
|
||||||
|
*/
|
||||||
fun roots(): Set<V>
|
fun roots(): Set<V>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the successors of a given vertex, along with the connecting edges.
|
||||||
|
*
|
||||||
|
* A default implementation is provided in the [IGraph] interface, but may be overridden for efficiency.
|
||||||
|
*
|
||||||
|
* @param vertex the vertex whose successors are to be found
|
||||||
|
* @return a map from successor vertices to their outgoing edges
|
||||||
|
* @see predecessors
|
||||||
|
*/
|
||||||
fun successors(vertex: V): Map<V, E> =
|
fun successors(vertex: V): Map<V, E> =
|
||||||
edges().filter { it.value.first == vertex }
|
edges().filter { it.value.first == vertex }
|
||||||
.map { (edge, pair) -> pair.second to edge }.toMap()
|
.map { (edge, pair) -> pair.second to edge }.toMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the predecessors of a given vertex, along with the connecting edges.
|
||||||
|
*
|
||||||
|
* A default implementation is provided in the [IGraph] interface, but may be overridden for efficiency.
|
||||||
|
*
|
||||||
|
* @param vertex the vertex whose predecessors are to be found
|
||||||
|
* @return a map from predecessor vertices to their incoming edges
|
||||||
|
* @see successors
|
||||||
|
*/
|
||||||
fun predecessors(vertex: V): Map<V, E> =
|
fun predecessors(vertex: V): Map<V, E> =
|
||||||
edges().filter { it.value.second == vertex }
|
edges().filter { it.value.second == vertex }
|
||||||
.map { (edge, pair) -> pair.first to edge }.toMap()
|
.map { (edge, pair) -> pair.first to edge }.toMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an edge exists between two vertices, and returns it if so.
|
||||||
|
*
|
||||||
|
* A default implementation is provided in the [IGraph] interface, but may be overridden for efficiency.
|
||||||
|
*
|
||||||
|
* @param x the starting vertex
|
||||||
|
* @param y the ending vertex
|
||||||
|
* @return the edge from `x` to `y`, or `null` if no such edge exists
|
||||||
|
*/
|
||||||
fun xToY(x: V, y: V): E? = edges().entries.firstOrNull { it.value == x to y }?.key
|
fun xToY(x: V, y: V): E? = edges().entries.firstOrNull { it.value == x to y }?.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple, mutable graph interface, extending [IGraph].
|
||||||
|
*
|
||||||
|
* In addition to the operations provided by [IGraph], mutable graphs support:
|
||||||
|
* - Adding and removing vertices;
|
||||||
|
* - Connecting and disconnecting vertices with edges.
|
||||||
|
*
|
||||||
|
* The same restrictions apply as for [IGraph]: the graph is directed, can contain cycles (including self-loops), can't
|
||||||
|
* contain parallel edges, and vertex (`V`) and edge (`E`) objects should be unique in the graph.
|
||||||
|
*
|
||||||
|
* Immutable graphs should implement [IGraph].
|
||||||
|
*
|
||||||
|
* @param V the type of vertices in the graph
|
||||||
|
* @param E the type of edges in the graph
|
||||||
|
* @see IGraph
|
||||||
|
* @see BaseGraph
|
||||||
|
*/
|
||||||
interface IMutableGraph<V, E> : IGraph<V, E> {
|
interface IMutableGraph<V, E> : IGraph<V, E> {
|
||||||
|
/**
|
||||||
|
* Adds a vertex to the graph.
|
||||||
|
*
|
||||||
|
* @param vertex the vertex to add
|
||||||
|
* @param isRoot whether the vertex should be considered a root (by default, `false`)
|
||||||
|
* @return the added vertex
|
||||||
|
* @throws GraphException if the vertex already exists in the graph
|
||||||
|
*/
|
||||||
fun addVertex(vertex: V, isRoot: Boolean = false): V
|
fun addVertex(vertex: V, isRoot: Boolean = false): V
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a vertex from the graph.
|
||||||
|
*
|
||||||
|
* If the vertex has any incident edges (i.e. incoming or outgoing edges), they are also removed.
|
||||||
|
*
|
||||||
|
* @param vertex the vertex to remove
|
||||||
|
* @throws GraphException if the vertex does not exist in the graph
|
||||||
|
*/
|
||||||
fun removeVertex(vertex: V)
|
fun removeVertex(vertex: V)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects two vertices with an edge.
|
||||||
|
*
|
||||||
|
* @param from the starting vertex
|
||||||
|
* @param to the ending vertex
|
||||||
|
* @param edge the edge to add between `from` and `to`
|
||||||
|
* @return the added edge
|
||||||
|
* @throws GraphException if either vertex does not exist in the graph, if the edge already exists in the graph,
|
||||||
|
* or if there is already an edge between `from` and `to`.
|
||||||
|
*/
|
||||||
fun connect(from: V, to: V, edge: E): E
|
fun connect(from: V, to: V, edge: E): E
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects two vertices by removing the edge between them.
|
||||||
|
*
|
||||||
|
* @param from the starting vertex
|
||||||
|
* @param to the ending vertex
|
||||||
|
* @throws GraphException if there is no edge between `from` and `to`
|
||||||
|
*/
|
||||||
fun disconnect(from: V, to: V)
|
fun disconnect(from: V, to: V)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an edge from the graph.
|
||||||
|
*
|
||||||
|
* @param edge the edge to remove
|
||||||
|
* @throws GraphException if the edge does not exist in the graph
|
||||||
|
*/
|
||||||
fun removeEdge(edge: E)
|
fun removeEdge(edge: E)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a graph operation fails.
|
||||||
|
*
|
||||||
|
* @param message the exception message
|
||||||
|
*/
|
||||||
class GraphException(message: String) : RuntimeException(message) {
|
class GraphException(message: String) : RuntimeException(message) {
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Constructs a [GraphException] indicating that a vertex already exists in the graph.
|
||||||
|
*
|
||||||
|
* @param V the type of vertices in the graph
|
||||||
|
* @param vertex the vertex that already exists
|
||||||
|
* @return a [GraphException] with an appropriate message
|
||||||
|
*/
|
||||||
fun <V> vertexAlreadyExists(vertex: V) =
|
fun <V> vertexAlreadyExists(vertex: V) =
|
||||||
GraphException("Vertex '$vertex' already exists in this graph.")
|
GraphException("Vertex '$vertex' already exists in this graph.")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a [GraphException] indicating that an edge already exists in the graph.
|
||||||
|
*
|
||||||
|
* @param E the type of edges in the graph
|
||||||
|
* @param edge the edge that already exists
|
||||||
|
* @return a [GraphException] with an appropriate message
|
||||||
|
*/
|
||||||
fun <E> edgeAlreadyExists(edge: E) =
|
fun <E> edgeAlreadyExists(edge: E) =
|
||||||
GraphException("Edge '$edge' already exists in this graph.")
|
GraphException("Edge '$edge' already exists in this graph.")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a [GraphException] indicating that an edge already exists between two vertices in the graph.
|
||||||
|
*
|
||||||
|
* @param V the type of vertices in the graph
|
||||||
|
* @param from the starting vertex
|
||||||
|
* @param to the ending vertex
|
||||||
|
* @return a [GraphException] with an appropriate message
|
||||||
|
*/
|
||||||
fun <V> edgeBetweenAlreadyExists(from: V, to: V) =
|
fun <V> edgeBetweenAlreadyExists(from: V, to: V) =
|
||||||
GraphException("Edge from '$from' to '$to' already exists in this graph.")
|
GraphException("Edge from '$from' to '$to' already exists in this graph.")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a [GraphException] indicating that a vertex was not found in the graph.
|
||||||
|
*
|
||||||
|
* @param V the type of vertices in the graph
|
||||||
|
* @param vertex the vertex that was not found
|
||||||
|
* @return a [GraphException] with an appropriate message
|
||||||
|
*/
|
||||||
fun <V> vertexNotFound(vertex: V) =
|
fun <V> vertexNotFound(vertex: V) =
|
||||||
GraphException("Vertex '$vertex' not found in this graph.")
|
GraphException("Vertex '$vertex' not found in this graph.")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a [GraphException] indicating that no edge was found between two vertices in the graph.
|
||||||
|
*
|
||||||
|
* @param V the type of vertices in the graph
|
||||||
|
* @param from the starting vertex
|
||||||
|
* @param to the ending vertex
|
||||||
|
* @return a [GraphException] with an appropriate message
|
||||||
|
*/
|
||||||
fun <V> noEdgeFound(from: V, to: V) =
|
fun <V> noEdgeFound(from: V, to: V) =
|
||||||
GraphException("No edge found from '$from' to '$to' in this graph.")
|
GraphException("No edge found from '$from' to '$to' in this graph.")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a [GraphException] indicating that an edge was not found in the graph.
|
||||||
|
*
|
||||||
|
* @param E the type of edges in the graph
|
||||||
|
* @param edge the edge that was not found
|
||||||
|
* @return a [GraphException] with an appropriate message
|
||||||
|
*/
|
||||||
fun <E> edgeNotFound(edge: E) =
|
fun <E> edgeNotFound(edge: E) =
|
||||||
GraphException("Edge '$edge' not found in this graph.")
|
GraphException("Edge '$edge' not found in this graph.")
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,21 @@
|
|||||||
package com.jaytux.altgraph.layout
|
package com.jaytux.altgraph.layout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple 2D size.
|
||||||
|
*
|
||||||
|
* This class is intentionally kept simple to easily allow wrapping library-specific size classes.
|
||||||
|
*
|
||||||
|
* @property width the width
|
||||||
|
* @property height the height
|
||||||
|
*/
|
||||||
data class GSize(var width: Float, var height: Float)
|
data class GSize(var width: Float, var height: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple 2D point.
|
||||||
|
*
|
||||||
|
* This class is intentionally kept simple to easily allow wrapping library-specific point classes.
|
||||||
|
*
|
||||||
|
* @property x the x coordinate
|
||||||
|
* @property y the y coordinate
|
||||||
|
*/
|
||||||
data class GPoint(var x: Float, var y: Float)
|
data class GPoint(var x: Float, var y: Float)
|
@ -2,12 +2,76 @@ package com.jaytux.altgraph.layout
|
|||||||
|
|
||||||
import com.jaytux.altgraph.core.IGraph
|
import com.jaytux.altgraph.core.IGraph
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A layout algorithm for graphs.
|
||||||
|
*
|
||||||
|
* The layout algorithm is responsible for positioning vertices in 2D space.
|
||||||
|
*
|
||||||
|
* The layout algorithm should cache the results of the (expensive) [compute] operation, such that the [location] and
|
||||||
|
* [boundingBox] operation can be performed efficiently.
|
||||||
|
* However, it is not required to invalidate the cache when [setGraph] or other mutating operations are called.
|
||||||
|
*
|
||||||
|
* @param V the vertex type
|
||||||
|
* @param E the edge type
|
||||||
|
*/
|
||||||
interface ILayout<V, E> {
|
interface ILayout<V, E> {
|
||||||
|
/**
|
||||||
|
* Gets the graph to layout.
|
||||||
|
*
|
||||||
|
* @return the graph
|
||||||
|
*/
|
||||||
fun graph(): IGraph<V, E>
|
fun graph(): IGraph<V, E>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the graph to layout.
|
||||||
|
*
|
||||||
|
* @param graph the graph
|
||||||
|
*/
|
||||||
fun setGraph(graph: IGraph<V, E>)
|
fun setGraph(graph: IGraph<V, E>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the layout.
|
||||||
|
*
|
||||||
|
* The computation results should be cached for future calls to [location] and [boundingBox].
|
||||||
|
*/
|
||||||
fun compute()
|
fun compute()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the location of a vertex.
|
||||||
|
*
|
||||||
|
* If the layout has not been computed yet, this should invoke [compute].
|
||||||
|
* However, if the layout has been computed, this should return the cached location.
|
||||||
|
*/
|
||||||
fun location(vertex: V): GPoint
|
fun location(vertex: V): GPoint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freezes a vertex at a specific point.
|
||||||
|
*
|
||||||
|
* Not all layout algorithms support freezing vertices; check the documentation of the specific implementation.
|
||||||
|
* Additionally, the algorithms that support freezing may restrict which vertices can be frozen, or put restrictions
|
||||||
|
* on the position of frozen vertices.
|
||||||
|
*
|
||||||
|
* A frozen vertex will not be moved by subsequent calls to [compute] until it is unfrozen.
|
||||||
|
*
|
||||||
|
* @param vertex the vertex to freeze
|
||||||
|
* @param point the point to freeze the vertex at
|
||||||
|
*/
|
||||||
fun freezeAt(vertex: V, point: GPoint)
|
fun freezeAt(vertex: V, point: GPoint)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unfreezes a vertex, allowing it to be moved by subsequent calls to [compute].
|
||||||
|
*
|
||||||
|
* If an implementation does not support freezing vertices, this method should do nothing.
|
||||||
|
*
|
||||||
|
* @param vertex the vertex to unfreeze
|
||||||
|
*/
|
||||||
fun unfreeze(vertex: V)
|
fun unfreeze(vertex: V)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bounding box of the layout.
|
||||||
|
*
|
||||||
|
* If the layout has not been computed yet, this should invoke [compute].
|
||||||
|
* However, if the layout has been computed, this should return the cached bounding box.
|
||||||
|
*/
|
||||||
fun boundingBox(): GSize
|
fun boundingBox(): GSize
|
||||||
}
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package com.jaytux.altgraph.layout
|
|
||||||
|
|
||||||
data class MutablePair<T1, T2>(var x: T1, var y: T2)
|
|
@ -8,14 +8,56 @@ import kotlin.concurrent.atomics.AtomicBoolean
|
|||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Sugiyama-style layout algorithm for "pseudo-forests" (such as control-flow graphs).
|
||||||
|
*
|
||||||
|
* This algorithm arranges the graph in layers, attempting to minimize edge crossings and distribute nodes evenly.
|
||||||
|
* It treats the graph as a collection of disjoint acyclic graphs, disregarding back-edges during layout.
|
||||||
|
*
|
||||||
|
* This layout algorithm does not support freezing vertices; calling [freezeAt] will throw
|
||||||
|
* [UnsupportedOperationException], and [unfreeze] is a no-op.
|
||||||
|
* Additionally, [setGraph] and [setVertexSize] do not invalidate the cache.
|
||||||
|
*
|
||||||
|
* All mutation operations are synchronized, and thread-safe. However, the layout is not incremental, and doesn't track
|
||||||
|
* graph changes. Any change to the graph or vertex sizes requires a full recomputation of the layout. The synchronized
|
||||||
|
* operations are:
|
||||||
|
* - Changing the graph ([setGraph]), measuring ([setVertexSize]),
|
||||||
|
* - Querying layout-dependent values ([location], [boundingBox]),
|
||||||
|
* - Computing the layout ([compute]).
|
||||||
|
*
|
||||||
|
* Locking is done via a simple spin-lock.
|
||||||
|
*
|
||||||
|
* @param V the vertex type
|
||||||
|
* @param E the edge type
|
||||||
|
* @param graph the graph to layout
|
||||||
|
* @property horizontalMargin the horizontal margin between vertices in the same layer
|
||||||
|
* @property disjoinXMargin the horizontal margin between disjoint subgraphs
|
||||||
|
* @property interLayer the vertical margin between layers
|
||||||
|
* @property vertexSize a function that returns the size of a vertex, including its label offset
|
||||||
|
*
|
||||||
|
* @see ILayout
|
||||||
|
*/
|
||||||
class PseudoForestLayout<V, E>(
|
class PseudoForestLayout<V, E>(
|
||||||
graph: IGraph<V, E>,
|
graph: IGraph<V, E>,
|
||||||
var horizontalMargin: Float,
|
var horizontalMargin: Float,
|
||||||
var disjoinXMargin: Float,
|
var disjoinXMargin: Float,
|
||||||
var interLayer: Float,
|
var interLayer: Float,
|
||||||
vertexSize: (V) -> VertexSize
|
vertexSize: (V) -> VertexSize
|
||||||
) : ILayout<V, E> {
|
) : ILayout<V, E>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A class representing data on the size of a vertex, including its label offset and size.
|
||||||
|
*
|
||||||
|
* @property vertex the size of the vertex itself
|
||||||
|
* @property labelOffset the offset of the label relative to the vertex's center
|
||||||
|
* @property labelSize the size of the label
|
||||||
|
*/
|
||||||
data class VertexSize(val vertex: GSize, val labelOffset: GPoint, val labelSize: GSize) {
|
data class VertexSize(val vertex: GSize, val labelOffset: GPoint, val labelSize: GSize) {
|
||||||
|
/**
|
||||||
|
* Calculates the full size of the vertex including its label and offset.
|
||||||
|
*
|
||||||
|
* @return the full size of the vertex
|
||||||
|
*/
|
||||||
fun fullSize(): GSize { // TODO: check the math here
|
fun fullSize(): GSize { // TODO: check the math here
|
||||||
val minX = 0 + labelOffset.x
|
val minX = 0 + labelOffset.x
|
||||||
val minY = 0 + labelOffset.y
|
val minY = 0 + labelOffset.y
|
||||||
@ -27,6 +69,11 @@ class PseudoForestLayout<V, E>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the center point of the vertex within its bounding box.
|
||||||
|
*
|
||||||
|
* @return the center point of the vertex
|
||||||
|
*/
|
||||||
fun vCenterInBox(): GPoint { // TODO: check the math here
|
fun vCenterInBox(): GPoint { // TODO: check the math here
|
||||||
return GPoint(
|
return GPoint(
|
||||||
x = labelOffset.x + vertex.width / 2,
|
x = labelOffset.x + vertex.width / 2,
|
||||||
@ -60,6 +107,12 @@ class PseudoForestLayout<V, E>(
|
|||||||
|
|
||||||
override fun graph(): IGraph<V, E> = _graph
|
override fun graph(): IGraph<V, E> = _graph
|
||||||
override fun setGraph(graph: IGraph<V, E>) { locked { _graph = graph } }
|
override fun setGraph(graph: IGraph<V, E>) { locked { _graph = graph } }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the vertex measuring function.
|
||||||
|
*
|
||||||
|
* @param vertexSize a function that returns the size of a vertex, including its label offset
|
||||||
|
*/
|
||||||
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
|
// Either a vertex, or a dummy node to break up multi-layer-spanning edges
|
||||||
@ -268,14 +321,16 @@ class PseudoForestLayout<V, E>(
|
|||||||
|
|
||||||
override fun location(vertex: V): GPoint {
|
override fun location(vertex: V): GPoint {
|
||||||
if(vertex !in _graph.vertices()) throw GraphException.vertexNotFound(vertex)
|
if(vertex !in _graph.vertices()) throw GraphException.vertexNotFound(vertex)
|
||||||
return _positions[vertex] ?: run {
|
return locked {
|
||||||
compute()
|
_positions[vertex] ?: run {
|
||||||
_positions[vertex]!!
|
compute()
|
||||||
|
_positions[vertex]!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun freezeAt(vertex: V, point: GPoint) = throw UnsupportedOperationException("PseudoForestLayout does not allow freezing vertices.")
|
override fun freezeAt(vertex: V, point: GPoint) = throw UnsupportedOperationException("PseudoForestLayout does not allow freezing vertices.")
|
||||||
override fun unfreeze(vertex: V) { /* no-op: cannot freeze vertices */ }
|
override fun unfreeze(vertex: V) { /* no-op: cannot freeze vertices */ }
|
||||||
|
|
||||||
override fun boundingBox(): GSize = _boundingBox ?: run { compute(); _boundingBox!! }
|
override fun boundingBox(): GSize = locked { _boundingBox ?: run { compute(); _boundingBox!! } }
|
||||||
}
|
}
|
@ -1,25 +1,78 @@
|
|||||||
package com.jaytux.altgraph.layout
|
package com.jaytux.altgraph.layout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sum type (aka tagged union, variant, discriminated union) of two types.
|
||||||
|
*
|
||||||
|
* At any time, a SumType holds either a `T1` or a `T2` value.
|
||||||
|
*
|
||||||
|
* @param T1 the first type
|
||||||
|
* @param T2 the second type
|
||||||
|
*/
|
||||||
sealed class SumType<out T1, out T2> {
|
sealed class SumType<out T1, out T2> {
|
||||||
|
/**
|
||||||
|
* The first alternative of the sum type.
|
||||||
|
*
|
||||||
|
* This class overrides both [equals] and [hashCode] to refer through to the contained value.
|
||||||
|
* Specifically, equality is defined such that only the following hold:
|
||||||
|
* - `SumT1(x) == SumT1(y)` if and only if `x == y`; or
|
||||||
|
* - `SumT1(x) == y` if and only if `x == y`.
|
||||||
|
*
|
||||||
|
* @param T1 the type of the value
|
||||||
|
* @property value the value
|
||||||
|
*/
|
||||||
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
|
(other is SumT1<*> && value == other.value) || value == other
|
||||||
override fun hashCode(): Int = value.hashCode()
|
override fun hashCode(): Int = value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The second alternative of the sum type.
|
||||||
|
*
|
||||||
|
* This class overrides both [equals] and [hashCode] to refer through to the contained value.
|
||||||
|
* Specifically, equality is defined such that only the following hold:
|
||||||
|
* - `SumT2(x) == SumT2(y)` if and only if `x == y`; or
|
||||||
|
* - `SumT2(x) == y` if and only if `x == y`.
|
||||||
|
*
|
||||||
|
* @param T2 the type of the value
|
||||||
|
* @property value the value
|
||||||
|
*/
|
||||||
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
|
(other is SumT2<*> && value == other.value) || value == other
|
||||||
override fun hashCode(): Int = value.hashCode()
|
override fun hashCode(): Int = value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern matches on the sum type.
|
||||||
|
*
|
||||||
|
* If the sum type holds a `T1`, invokes [onT1] with the contained value and returns its result.
|
||||||
|
* Otherwise, invokes [onT2] with the contained value and returns its result.
|
||||||
|
*
|
||||||
|
* @param R the return type of the pattern match
|
||||||
|
* @param onT1 the function to invoke if the sum type holds a `T1`
|
||||||
|
* @param onT2 the function to invoke if the sum type holds a `T2`
|
||||||
|
* @return the result of invoking either [onT1] or [onT2]
|
||||||
|
*/
|
||||||
fun <R> fold(onT1: (T1) -> R, onT2: (T2) -> R): R = when(this) {
|
fun <R> fold(onT1: (T1) -> R, onT2: (T2) -> R): R = when(this) {
|
||||||
is SumT1 -> onT1(value)
|
is SumT1 -> onT1(value)
|
||||||
is SumT2 -> onT2(value)
|
is SumT2 -> onT2(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Constructs a [SumType] holding a `T1`.
|
||||||
|
*
|
||||||
|
* @receiver the value to hold
|
||||||
|
* @return a [SumType] holding the receiver as a `T1`
|
||||||
|
*/
|
||||||
fun <T> T.sum1(): SumType<T, Nothing> = SumT1(this)
|
fun <T> T.sum1(): SumType<T, Nothing> = SumT1(this)
|
||||||
|
/**
|
||||||
|
* Constructs a [SumType] holding a `T2`.
|
||||||
|
*
|
||||||
|
* @receiver the value to hold
|
||||||
|
* @return a [SumType] holding the receiver as a `T2`
|
||||||
|
*/
|
||||||
fun <T> T.sum2(): SumType<Nothing, T> = SumT2(this)
|
fun <T> T.sum2(): SumType<Nothing, T> = SumT2(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user