I have first defined a graph and a vertex class as follows:
case class Coordinates(val x: Int, val y: Int)
class Vertex(val id: String, val coords: Coordinates) {
def dist(v: Vertex):Double = dist(v.coords.x, v.coords.y)
def dist(x: Int, y: Int):Double = Math.sqrt(
Math.pow(coords.x - x, 2) + Math.pow(coords.y - y, 2))
override def toString = s"V($id)@$coords"
}
class Graph(val vertices: Seq[Vertex], val neighbours: Map[Vertex, Seq[Vertex]]) {
def +(v: Vertex):Graph = new Graph(v +: vertices, neighbours) // adds a vertex to the graph
def ~(v: Vertex, n: Vertex):Graph = { // sets a neighbour relationship between two vertices
val g = new Graph(vertices, neighbours + (v -> (neighbours(v) :+ n)))
new Graph(g.vertices, g.neighbours + (n -> (g.neighbours(n) :+ v)))
}
def closest(x: Int, y: Int): Vertex = vertices.minBy( v => v.dist(x, y))
override def toString = s"G(Vertices: $vertices, Neighbours: $neighbours)"
}
Then comes my Scala version of Dijkstra (would be nice to share your version...)
object Dijkstra {
def run(graph: Graph, source: Vertex, target: Vertex):Seq[Vertex] = {
val neighbours = graph.neighbours
def dj2(u: Vertex, vs: Seq[Vertex], distances: Map[Vertex, Double], queue: Seq[Vertex],
visited: Seq[Vertex], previous: Map[Vertex, Vertex]):
(Map[Vertex, Double], Seq[Vertex], Seq[Vertex], Map[Vertex, Vertex]) = {
if (!vs.isEmpty) {
val v = vs.head
val alt = distances(u) + u.dist(v)
if (alt < distances(v) && !visited.contains(v)) {
dj2(u, vs.tail, distances + (v -> alt), v +: queue, visited, previous + (v -> u))
} else if (!vs.tail.isEmpty) {
dj2(u, vs.tail, distances, queue, visited, previous)
} else (distances, queue, visited, previous)
} else (distances, queue, visited, previous)
} // dj2
def dj1(distances: Map[Vertex, Double], queue: Seq[Vertex], visited: Seq[Vertex], previous: Map[Vertex, Vertex]):Seq[Vertex] = {
if (!queue.isEmpty) {
val u:Vertex = queue.filter(v => {!visited.contains(v)}).min(Ordering.by((v:Vertex) => distances(v)))
if (u == target) {
sequenceSol(previous, target)
} else {
val res = dj2(u, neighbours(u), distances, queue.filterNot(v => v.id == u.id), u +: visited, previous)
dj1(res._1, res._2, res._3, res._4)
}
} else Seq()
} // dj1
dj1(Map(source -> 0.0) withDefaultValue Double.MaxValue, Seq(source), Seq(), Map())
} // dijkstra
def sequenceSol(previous: Map[Vertex, Vertex], to: Vertex): Seq[Vertex] = {
def helper(seq: Seq[Vertex], current: Vertex): Seq[Vertex] =
if (previous.contains(current)) helper(previous(current) +: seq, previous(current)) else seq
helper(Seq(to), to)
}
}
I would like to find a simpler version.. really.
The FXML is a simple file like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<AnchorPane id="AnchorPane" fx:id="masterAnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="org.jts.dijkstra.DijkstraController">
<children>
<BorderPane fx:id="borderPane" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #CCFF99" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<top>
<FlowPane minHeight="21.0" prefHeight="21.0" prefWidth="600.0">
<children>
<TextField fx:id="nbNodes" alignment="CENTER_RIGHT" prefWidth="50.0" promptText="# nodes" text="100" />
<Button fx:id="applyButton" mnemonicParsing="false" onAction="#handleApply" text="Apply" />
</children>
</FlowPane>
</top>
</BorderPane>
</children>
</AnchorPane>
And finally the controller:
package org.jts.dijkstra
import javafx.fxml.{Initializable, FXML}
import javafx.scene.{control => jfxctrl}
import javafx.scene.{layout => jfxlyt}
import javafx.{event => jfxe}
import scalafx.scene.effect.Bloom
import scalafx.scene.paint.Color
import scalafx.scene.{layout => scxlyt}
import scalafx.scene.{control => scxctrl}
import java.net.URL
import javafx.event.EventHandler
import javafx.scene.input.MouseEvent
object Constants {
val nodeSize = 16
val width = 1500
val height = 800
}
class DijkstraController extends Initializable {
@FXML private var nbNodes: jfxctrl.TextField = null
@FXML private var applyButton: jfxctrl.Button = null
@FXML private var borderPane: jfxlyt.BorderPane = null
@FXML private var masterAnchorPane: jfxlyt.AnchorPane = null
private var scxMasterPane: scxlyt.AnchorPane = null
private var scxPane: scxlyt.BorderPane = null
private var scxNbNodes: scxctrl.TextField = null
private var scxApplyButton: scxctrl.Button = null
private var scxCanvas: scalafx.scene.canvas.Canvas = null
private var fromVertex: Option[Vertex] = None
private var toVertex: Option[Vertex] = None
override def initialize(url: URL, rb: java.util.ResourceBundle) {
require(masterAnchorPane != null, "masterAnchorPane must not be null")
scxMasterPane = new scxlyt.AnchorPane(masterAnchorPane)
require(nbNodes != null, "nbNodes must not be null")
scxNbNodes = new scxctrl.TextField(nbNodes)
require(applyButton != null, "applyButton must not be null")
scxApplyButton = new scxctrl.Button(applyButton)
require(borderPane != null, "centerPane must not be null")
scxPane = new scxlyt.BorderPane(borderPane)
scxCanvas = new scalafx.scene.canvas.Canvas(Constants.width, Constants.height)
scxPane.setCenter(scxCanvas)
}
@FXML private def handleApply(event: jfxe.ActionEvent) {
// TODO In Future not in EDT
val maxNodes:Integer = scxNbNodes.getText.toInt
val width = scxCanvas.width.toInt
val height = scxCanvas.height.toInt
println(s"Generating random graph with $maxNodes in ($width, $height)")
val random = new java.util.Random
val vertices = for(i <- 0 until maxNodes) yield {
val x = random.nextInt(width)
val y = random.nextInt(height)
val vertex = new Vertex(s"v-$i", Coordinates(x, y))
vertex
}
println(s"Generated vertices $vertices")
def generateNeighbours(neighbours: Map[Vertex, Seq[Vertex]], nbTimes: Int): Map[Vertex, Seq[Vertex]] = {
if (nbTimes >= maxNodes) neighbours
else {
val v1 = vertices(random.nextInt(maxNodes))
val v2 = vertices(random.nextInt(maxNodes))
val n1 = neighbours + (v1 -> (neighbours(v1) :+ v2))
val n2 = n1 + (v2 -> (n1(v2) :+ v1))
generateNeighbours(n2, nbTimes + 1)
}
}
val neighbours = generateNeighbours(Map() withDefaultValue Seq(), 0)
println(s"Generated neighbours $neighbours")
val graph = new Graph(vertices, neighbours)
drawGraph(graph)
addMouseEventHandler(graph)
}
private def drawGraph(g: Graph, sol: Seq[Vertex] = Seq()) {
val gc = scxCanvas.getGraphicsContext2D
gc.clearRect(0, 0, Constants.width, Constants.height)
gc.setFill(Color.BLACK)
gc.fillRect(0, 0, Constants.width, Constants.height)
gc.setFill(Color.LIGHTYELLOW)
gc.setEffect(new Bloom())
g.vertices.foreach(v => {
gc.fillOval(v.coords.x, v.coords.y, Constants.nodeSize, Constants.nodeSize)
})
gc.setEffect(null)
val offset = Constants.nodeSize / 2
gc.setStroke(Color.LIGHTBLUE)
g.neighbours.foreach(t => {
val from = t._1
t._2.foreach(to => {
gc.strokeLine(from.coords.x + offset, from.coords.y + offset, to.coords.x + offset, to.coords.y + offset)
gc.setLineWidth(1.0)
})
})
if (!sol.isEmpty) {
gc.setFill(Color.RED)
gc.setStroke(Color.GREEN)
gc.setEffect(new Bloom())
val from = sol(0)
val to = sol.last
gc.fillOval(from.coords.x, from.coords.y, Constants.nodeSize, Constants.nodeSize)
gc.fillOval(to.coords.x, to.coords.y, Constants.nodeSize, Constants.nodeSize)
val path = sol.sliding(2, 1)
gc.setLineWidth(5.0)
while(path.hasNext) {
val subPath = path.next
if (subPath.size == 2) {
val f = subPath(0)
val t = subPath(1)
gc.strokeLine(f.coords.x + offset, f.coords.y + offset, t.coords.x + offset, t.coords.y + offset)
}
}
}
}
private def addMouseEventHandler(g: Graph) {
scxCanvas.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler[MouseEvent]() {
override def handle(me: MouseEvent) = {
val closestVertex = g.closest(me.getX.toInt, me.getY.toInt)
//println(s"Original vertex: $closestVertex")
fromVertex = Some(closestVertex)
}
})
scxCanvas.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler[MouseEvent]() {
override def handle(me: MouseEvent) = {
val closestVertex = g.closest(me.getX.toInt, me.getY.toInt)
//println(s"Closest vertex: $closestVertex")
val oldToVertex = toVertex
toVertex = Some(closestVertex)
if (!oldToVertex.equals(toVertex)) runDijkstra(g)
}
})
}
private def runDijkstra(g: Graph) {
(fromVertex, toVertex) match {
case (Some(f), Some(t)) => {
val sol = Dijkstra.run(g, f, t)
drawGraph(g, sol)
println(s"Sol from $f to $t is $sol")
}
case _ =>
}
}
}
I am still trying to grasp ScalaFX - but looks ok so far.
Full source code available on request. Just email me.
No comments:
Post a Comment