From d9275eff6429f0b5cabbd956e398c3eabe2f28e8 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Sat, 17 Aug 2024 15:23:25 +0300 Subject: [PATCH 01/60] feat: add Dijkstra algorithm --- src/main/kotlin/model/algorithm/Dijkstra.kt | 88 +++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/main/kotlin/model/algorithm/Dijkstra.kt diff --git a/src/main/kotlin/model/algorithm/Dijkstra.kt b/src/main/kotlin/model/algorithm/Dijkstra.kt new file mode 100644 index 0000000..87b24f2 --- /dev/null +++ b/src/main/kotlin/model/algorithm/Dijkstra.kt @@ -0,0 +1,88 @@ +package model.algorithm + +import model.graph.* + +class Dijkstra(private val graph: WeightedGraph) { + + fun findShortestPath(startKey: Int, endKey: Int): List? { + val startVertex = graph.vertices.find { it.key == startKey } ?: return null + val endVertex = graph.vertices.find { it.key == endKey } ?: return null + + // Инициализация расстояний и предшественников + val distances = mutableMapOf().withDefault { Long.MAX_VALUE } + val previous = mutableMapOf() + val visited = mutableSetOf() + + distances[startVertex] = 0 + + val priorityQueue = java.util.PriorityQueue(compareBy { distances.getValue(it) }) + priorityQueue.add(startVertex) + + while (priorityQueue.isNotEmpty()) { + val currentVertex = priorityQueue.poll() + if (currentVertex == endVertex) break + + visited.add(currentVertex) + + val edges = graph.adjacencyList[currentVertex] ?: continue + for (edge in edges) { + val neighbor = edge.second + if (neighbor in visited) continue + + val newDist = distances.getValue(currentVertex) + edge.weight + if (newDist < distances.getValue(neighbor)) { + distances[neighbor] = newDist + previous[neighbor] = currentVertex + priorityQueue.add(neighbor) + } + } + } + + // Восстановление пути + val path = mutableListOf() + var currentVertex: Vertex? = endVertex + + while (currentVertex != null && currentVertex != startVertex) { + val prevVertex = previous[currentVertex] ?: break + val edge = graph.adjacencyList[prevVertex]?.find { it.second == currentVertex } + if (edge != null) { + path.add(edge) + } + currentVertex = prevVertex + } + + return if (path.isEmpty()) null else path.reversed() + } +} +fun main(){ + val graph = WeightedGraph() + graph.addVertex(1) + graph.addVertex(2) + graph.addVertex(3) + graph.addVertex(4) + graph.addVertex(5) + graph.addVertex(6) + graph.addVertex(7) + graph.addVertex(8) + graph.addVertex(9) + + graph.addEdge(1, 2, 4) + graph.addEdge(2, 5, 2) + graph.addEdge(1, 3, 3) + graph.addEdge(3, 5, 1) + graph.addEdge(1, 4, 2) + graph.addEdge(4, 5, 3) + graph.addEdge(5, 6, 5) + graph.addEdge(6, 7, 8) + graph.addEdge(6, 8, 9) + graph.addEdge(7, 9, 1) + graph.addEdge(8, 9, 2) + + val result = Dijkstra(graph).findShortestPath(1, 9) + if (result != null) { + for (i in result){ + println(listOf(i.first, i.second, i.weight)) + } + } + +} From a78b38dc20b50f67d89e72a7e726fd85b150d70f Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Sat, 17 Aug 2024 15:30:06 +0300 Subject: [PATCH 02/60] refactor: removed the unnecessary part in the file --- src/main/kotlin/model/algorithm/Dijkstra.kt | 34 --------------------- 1 file changed, 34 deletions(-) diff --git a/src/main/kotlin/model/algorithm/Dijkstra.kt b/src/main/kotlin/model/algorithm/Dijkstra.kt index 87b24f2..cb297cf 100644 --- a/src/main/kotlin/model/algorithm/Dijkstra.kt +++ b/src/main/kotlin/model/algorithm/Dijkstra.kt @@ -8,7 +8,6 @@ class Dijkstra(private val graph: WeightedGraph) { val startVertex = graph.vertices.find { it.key == startKey } ?: return null val endVertex = graph.vertices.find { it.key == endKey } ?: return null - // Инициализация расстояний и предшественников val distances = mutableMapOf().withDefault { Long.MAX_VALUE } val previous = mutableMapOf() val visited = mutableSetOf() @@ -38,7 +37,6 @@ class Dijkstra(private val graph: WeightedGraph) { } } - // Восстановление пути val path = mutableListOf() var currentVertex: Vertex? = endVertex @@ -54,35 +52,3 @@ class Dijkstra(private val graph: WeightedGraph) { return if (path.isEmpty()) null else path.reversed() } } -fun main(){ - val graph = WeightedGraph() - graph.addVertex(1) - graph.addVertex(2) - graph.addVertex(3) - graph.addVertex(4) - graph.addVertex(5) - graph.addVertex(6) - graph.addVertex(7) - graph.addVertex(8) - graph.addVertex(9) - - graph.addEdge(1, 2, 4) - graph.addEdge(2, 5, 2) - graph.addEdge(1, 3, 3) - graph.addEdge(3, 5, 1) - graph.addEdge(1, 4, 2) - graph.addEdge(4, 5, 3) - graph.addEdge(5, 6, 5) - graph.addEdge(6, 7, 8) - graph.addEdge(6, 8, 9) - graph.addEdge(7, 9, 1) - graph.addEdge(8, 9, 2) - - val result = Dijkstra(graph).findShortestPath(1, 9) - if (result != null) { - for (i in result){ - println(listOf(i.first, i.second, i.weight)) - } - } - -} From 0c98d1b52491584a466102adc4ee349f7f2eb1e8 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Sat, 17 Aug 2024 16:01:47 +0300 Subject: [PATCH 03/60] fix: fixed the algorithm class attribute --- src/main/kotlin/model/algorithm/Dijkstra.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/model/algorithm/Dijkstra.kt b/src/main/kotlin/model/algorithm/Dijkstra.kt index cb297cf..33e0ad1 100644 --- a/src/main/kotlin/model/algorithm/Dijkstra.kt +++ b/src/main/kotlin/model/algorithm/Dijkstra.kt @@ -2,7 +2,7 @@ package model.algorithm import model.graph.* -class Dijkstra(private val graph: WeightedGraph) { +class Dijkstra(private val graph: Graph) { fun findShortestPath(startKey: Int, endKey: Int): List? { val startVertex = graph.vertices.find { it.key == startKey } ?: return null From 2b9809a58f60d56c93ee8dbbf1df4bdfa5583538 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Sun, 18 Aug 2024 00:18:57 +0300 Subject: [PATCH 04/60] =?UTF-8?q?refactor:=20=D1=81hanged=20the=20output?= =?UTF-8?q?=20of=20the=20algorithm=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/model/algorithm/Dijkstra.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/model/algorithm/Dijkstra.kt b/src/main/kotlin/model/algorithm/Dijkstra.kt index 33e0ad1..a1f3c95 100644 --- a/src/main/kotlin/model/algorithm/Dijkstra.kt +++ b/src/main/kotlin/model/algorithm/Dijkstra.kt @@ -1,10 +1,11 @@ package model.algorithm import model.graph.* +import java.util.concurrent.TransferQueue class Dijkstra(private val graph: Graph) { - fun findShortestPath(startKey: Int, endKey: Int): List? { + fun findShortestPath(startKey: Int, endKey: Int): List>? { val startVertex = graph.vertices.find { it.key == startKey } ?: return null val endVertex = graph.vertices.find { it.key == endKey } ?: return null @@ -37,14 +38,14 @@ class Dijkstra(private val graph: Graph) { } } - val path = mutableListOf() + val path = mutableListOf>() var currentVertex: Vertex? = endVertex while (currentVertex != null && currentVertex != startVertex) { val prevVertex = previous[currentVertex] ?: break val edge = graph.adjacencyList[prevVertex]?.find { it.second == currentVertex } if (edge != null) { - path.add(edge) + path.add(Triple(edge.first.key, edge.second.key, edge.weight)) } currentVertex = prevVertex } From 265af6f2be26669d27177d9f39a4835d926f950f Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Sun, 18 Aug 2024 01:12:01 +0300 Subject: [PATCH 05/60] feat: add test for Dijkstra algorithm --- .../kotlin/model/algorithm/DijkstraTest.kt | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/test/kotlin/model/algorithm/DijkstraTest.kt diff --git a/src/test/kotlin/model/algorithm/DijkstraTest.kt b/src/test/kotlin/model/algorithm/DijkstraTest.kt new file mode 100644 index 0000000..3ae07d3 --- /dev/null +++ b/src/test/kotlin/model/algorithm/DijkstraTest.kt @@ -0,0 +1,125 @@ +package model.algorithm + +import model.graph.Graph +import model.graph.WeightedGraph +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class DijkstraTest { + lateinit var graph: Graph + + @Nested + inner class `Undirected graph`{ + @BeforeEach + fun setup(){ + graph = WeightedGraph() + } + + @Test + fun `base test`(){ + for (i in 1..5) { + graph.addVertex(i) + } + graph.addEdge(1, 2, 2) + graph.addEdge(2, 5, 4) + graph.addEdge(1, 4, 4) + graph.addEdge(4, 2, 1) + graph.addEdge(1, 3, 3) + graph.addEdge(4, 5, 1) + graph.addEdge(3, 5, 5) + + val path = Dijkstra(graph).findShortestPath(1, 5) + + assertNotNull(path) + assertEquals>>(path ?: emptyList(), + listOf(Triple(1, 2, 2), + Triple(2, 4, 1), + Triple(4, 5, 1))) + } + + @Test + fun `test with identical paths`(){ + for (i in 1..5) { + graph.addVertex(i) + } + graph.addEdge(1, 2, 1) + graph.addEdge(1, 3, 1) + graph.addEdge(1, 4, 2) + graph.addEdge(2, 5, 1) + graph.addEdge(3, 5, 1) + graph.addEdge(4, 5, 2) + + val path = Dijkstra(graph).findShortestPath(1, 5) + + assertNotNull(path) + assertEquals>>(path ?: emptyList(), + listOf(Triple(1, 2, 1), + Triple(2, 5, 1))) + } + + @Test + fun `test this bridges`(){ + for (i in 1..9) { + graph.addVertex(i) + } + graph.addEdge(1, 2, 4) + graph.addEdge(2, 5, 2) + graph.addEdge(1, 3, 3) + graph.addEdge(3, 5, 1) + graph.addEdge(1, 4, 2) + graph.addEdge(4, 5, 3) + graph.addEdge(5, 6, 5) + graph.addEdge(6, 7, 8) + graph.addEdge(6, 8, 9) + graph.addEdge(7, 9, 1) + graph.addEdge(8, 9, 2) + + val path = Dijkstra(graph).findShortestPath(1, 9) + + assertNotNull(path) + assertEquals>>(path ?: emptyList(), + listOf(Triple(1, 3, 3), + Triple(3, 5, 1), + Triple(5, 6, 5), + Triple(6, 7, 8), + Triple(7, 9, 1))) + } + + @Test + fun `test with a nonexistent path`(){ + for (i in 1..9) { + graph.addVertex(i) + } + graph.addEdge(1, 1, 4) + graph.addEdge(2, 5, 2) + graph.addEdge(1, 3, 3) + graph.addEdge(3, 5, 1) + graph.addEdge(1, 4, 2) + graph.addEdge(4, 5, 3) + graph.addEdge(5, 5, 5) + graph.addEdge(6, 7, 8) + graph.addEdge(6, 8, 9) + graph.addEdge(7, 9, 1) + graph.addEdge(8, 9, 2) + + val path = Dijkstra(graph).findShortestPath(1, 9) + + assertEquals(path,null) + } + + @Test + fun `test with one vertex`(){ + for (i in 1..1) { + graph.addVertex(i) + } + graph.addEdge(1, 1, 4) + + val path = Dijkstra(graph).findShortestPath(1, 1) + + assertEquals(path, null) + } + } +} \ No newline at end of file From f261912b7602886b64bc599385e1adab86d60bab Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sun, 18 Aug 2024 22:36:55 +0300 Subject: [PATCH 06/60] fix: delete unused graph view --- src/main/kotlin/view/graph/EdgeView.kt | 30 ---------------- .../kotlin/view/graph/UndirectedGraphView.kt | 24 ------------- src/main/kotlin/view/graph/VertexView.kt | 35 ------------------- 3 files changed, 89 deletions(-) delete mode 100644 src/main/kotlin/view/graph/EdgeView.kt delete mode 100644 src/main/kotlin/view/graph/UndirectedGraphView.kt delete mode 100644 src/main/kotlin/view/graph/VertexView.kt diff --git a/src/main/kotlin/view/graph/EdgeView.kt b/src/main/kotlin/view/graph/EdgeView.kt deleted file mode 100644 index c59e73c..0000000 --- a/src/main/kotlin/view/graph/EdgeView.kt +++ /dev/null @@ -1,30 +0,0 @@ -package view.graph - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import viewModel.graph.EdgeViewModel - -@Composable -fun EdgeView( - viewModel: EdgeViewModel -) { - Canvas(Modifier.fillMaxSize()) { - drawLine( - start = Offset( - viewModel.first.x.dp.toPx() + viewModel.first.radius.toPx(), - viewModel.first.y.dp.toPx() + viewModel.first.radius.toPx(), - ), - end = Offset( - viewModel.second.x.dp.toPx() + viewModel.second.radius.toPx(), - viewModel.second.y.dp.toPx() + viewModel.second.radius.toPx(), - ), - color = Color.Black, - strokeWidth = 1f.dp.toPx() - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/view/graph/UndirectedGraphView.kt b/src/main/kotlin/view/graph/UndirectedGraphView.kt deleted file mode 100644 index 727c007..0000000 --- a/src/main/kotlin/view/graph/UndirectedGraphView.kt +++ /dev/null @@ -1,24 +0,0 @@ -package view.graph - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import viewModel.graph.UndirectedViewModel - -@Composable -fun UndirectedGraphView( - viewModel: UndirectedViewModel -) { - Box(Modifier.fillMaxSize()) { - viewModel.vertices.forEach { v -> - VertexView(v) - } - - viewModel.vertices.forEach { v -> - viewModel.adjacencyList[v]?.forEach { e -> - EdgeView(e) - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt deleted file mode 100644 index 357fa27..0000000 --- a/src/main/kotlin/view/graph/VertexView.kt +++ /dev/null @@ -1,35 +0,0 @@ -package view.graph - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import viewModel.graph.VertexViewModel - -@Composable -fun VertexView( - viewModel: VertexViewModel, - modifier: Modifier = Modifier -) { - Box( - modifier = modifier - .size(viewModel.radius * 2) - .offset(viewModel.x.dp, viewModel.y.dp) - .background(color = viewModel.color, shape = CircleShape) - ) { - if (viewModel.labelVisibility) { - Text( - modifier = Modifier - .align(Alignment.Center) - .offset(0f.dp, -viewModel.radius - 10f.dp), - text = viewModel.label - ) - } - } -} \ No newline at end of file From 17188911cecc9acad5a6da0fac41a3622a754ac5 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 19 Aug 2024 18:12:39 +0300 Subject: [PATCH 07/60] feat: added a dependency for SQLite --- build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1b146b7..72f8d30 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,6 +28,8 @@ dependencies { testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.1") + implementation("org.xerial:sqlite-jdbc:3.41.2.2") + } compose.desktop { From 8fd8afef572bc57895de3a60a31795373b1cb8ca Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 19 Aug 2024 19:15:42 +0300 Subject: [PATCH 08/60] refactor: added the nameGraph parameter to the loadGraph function --- src/main/kotlin/model/reader/Reader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/model/reader/Reader.kt b/src/main/kotlin/model/reader/Reader.kt index 2078dad..c037565 100644 --- a/src/main/kotlin/model/reader/Reader.kt +++ b/src/main/kotlin/model/reader/Reader.kt @@ -4,5 +4,5 @@ import model.graph.Graph interface Reader { fun saveGraph(graph: Graph, filepath: String): Unit - fun loadGraph(filepath: String): Graph + fun loadGraph(filepath: String, nameGraph: String): Graph } \ No newline at end of file From 994c6210a00a63e8a4a889aff42c00953d122353 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 20 Aug 2024 13:09:48 +0300 Subject: [PATCH 09/60] refactor: added the nameGraph parameter to the saveGraph function --- src/main/kotlin/model/reader/Reader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/model/reader/Reader.kt b/src/main/kotlin/model/reader/Reader.kt index c037565..9d41a66 100644 --- a/src/main/kotlin/model/reader/Reader.kt +++ b/src/main/kotlin/model/reader/Reader.kt @@ -3,6 +3,6 @@ package model.reader import model.graph.Graph interface Reader { - fun saveGraph(graph: Graph, filepath: String): Unit + fun saveGraph(graph: Graph, filepath: String, nameGraph: String): Unit fun loadGraph(filepath: String, nameGraph: String): Graph } \ No newline at end of file From 7fe14896971452242dd4a0a97d64408e3bb72ee7 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 20 Aug 2024 16:53:02 +0300 Subject: [PATCH 10/60] refactor: seperate logic, add new viewModels --- .../kotlin/{view => components}/MyText.kt | 2 +- src/main/kotlin/view/SettingsView.kt | 83 ------------------- 2 files changed, 1 insertion(+), 84 deletions(-) rename src/main/kotlin/{view => components}/MyText.kt (96%) delete mode 100644 src/main/kotlin/view/SettingsView.kt diff --git a/src/main/kotlin/view/MyText.kt b/src/main/kotlin/components/MyText.kt similarity index 96% rename from src/main/kotlin/view/MyText.kt rename to src/main/kotlin/components/MyText.kt index b3229a6..a8f4d86 100644 --- a/src/main/kotlin/view/MyText.kt +++ b/src/main/kotlin/components/MyText.kt @@ -1,4 +1,4 @@ -package view +package components import androidx.compose.material.Text import androidx.compose.runtime.Composable diff --git a/src/main/kotlin/view/SettingsView.kt b/src/main/kotlin/view/SettingsView.kt deleted file mode 100644 index 72d90ed..0000000 --- a/src/main/kotlin/view/SettingsView.kt +++ /dev/null @@ -1,83 +0,0 @@ -package view - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.platform.Font -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.zIndex - - -@Composable -fun MySlider(text: String, state: MutableState, range: ClosedFloatingPointRange = (0f..1f)) { - Row( - Modifier.padding(start = 5f.dp, end = 5f.dp), - verticalAlignment = Alignment.CenterVertically - ) { - MyText(text = text) - Slider( - modifier = Modifier.padding(0f.dp), - value = state.value, onValueChange = { change -> state.value = change }, - colors = SliderDefaults.colors( - thumbColor = Color.White, - activeTrackColor = Color.White, - inactiveTrackColor = Color.White, - ), - valueRange = range - ) - } -} - - -@Composable -fun SettingsView(onColorChange: (Color) -> Unit, onSizeChange: (Float) -> Unit, onOrientatedChange: () -> Unit) { - val redSlider = remember { mutableStateOf(1f / (0xFF / 0x8F)) } - val greenSlider = remember { mutableStateOf(0f) } - val blueSlider = remember { mutableStateOf(1f) } - val sizeSlider = remember { mutableStateOf(35f) } - val orientatedCheckBox = remember { mutableStateOf(false) } - - onColorChange(Color(red = redSlider.value, green = greenSlider.value, blue = blueSlider.value)) - onSizeChange(sizeSlider.value) - - Box(Modifier.fillMaxSize().padding(top = 80f.dp, end = 20f.dp).zIndex(10f), contentAlignment = Alignment.TopEnd) { - Box( - Modifier.size(270f.dp, 320f.dp).background(Color(0xFF3D3D3D), RoundedCornerShape(10)) - ) { - Column { - Row(Modifier.fillMaxWidth().padding(top = 10f.dp), horizontalArrangement = Arrangement.Center) { - MyText("Node") - } - Row(Modifier.fillMaxWidth().padding(top = 10f.dp, start = 20f.dp)) { - Column { - MyText("Color:") - MySlider("R: ", redSlider) - MySlider("G: ", greenSlider) - MySlider("B: ", blueSlider) - MySlider("Size: ", sizeSlider, (5f..80f)) - } - } - - Row( - Modifier.fillMaxWidth().padding(start = 20f.dp), - verticalAlignment = Alignment.CenterVertically - ) { - MyText("Orientated") - Checkbox(orientatedCheckBox.value, onCheckedChange = { - onOrientatedChange() - orientatedCheckBox.value = !orientatedCheckBox.value - }) - } - } - } - } -} \ No newline at end of file From 19a5a47fa8b2a32c618887a858efc654215aabff Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 20 Aug 2024 16:53:08 +0300 Subject: [PATCH 11/60] refactor: seperate logic, add new viewModels --- src/main/kotlin/Main.kt | 8 +- src/main/kotlin/components/MySlider.kt | 32 +++++ src/main/kotlin/view/HeaderView.kt | 11 +- src/main/kotlin/view/MainView.kt | 77 +---------- src/main/kotlin/view/SettingsView.kt | 60 +++++++++ src/main/kotlin/view/canvas/CanvasView.kt | 24 ++++ src/main/kotlin/view/canvas/EdgeCanvasView.kt | 18 ++- .../kotlin/view/canvas/VertexCanvasView.kt | 4 +- src/main/kotlin/viewModel/MainViewModel.kt | 15 +++ .../kotlin/viewModel/SettingsViewModel.kt | 10 ++ .../viewModel/canvas/CanvasViewModel.kt | 124 +++++++++++++++--- .../viewModel/canvas/EdgeCanvasViewModel.kt | 3 +- .../viewModel/canvas/VertexCanvasViewModel.kt | 67 ++++++++-- .../viewModel/graph/UndirectedViewModel.kt | 2 +- 14 files changed, 335 insertions(+), 120 deletions(-) create mode 100644 src/main/kotlin/components/MySlider.kt create mode 100644 src/main/kotlin/view/SettingsView.kt create mode 100644 src/main/kotlin/viewModel/MainViewModel.kt create mode 100644 src/main/kotlin/viewModel/SettingsViewModel.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index c56ee92..b74ef14 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -12,10 +12,11 @@ import model.graph.UndirectedGraph import view.HeaderView import view.MainView import view.MenuView +import viewModel.MainViewModel import viewModel.graph.UndirectedViewModel -val AMOUNT_NODES = 16 -val EDGE_CHANGE = 5f +val AMOUNT_NODES = 2 +val EDGE_CHANGE = 100 val graph = UndirectedGraph().apply { for (i in (0 until AMOUNT_NODES)) { @@ -34,6 +35,7 @@ val graph = UndirectedGraph().apply { val groups = Clustering(graph).calculate() val ranks = PageRank(graph).computePageRank(3) val undirectedViewModel = UndirectedViewModel(graph, false, groups, ranks) +val mainViewModel = MainViewModel(graph) fun main() = application { var isOpen by remember { mutableStateOf(true) } @@ -60,7 +62,7 @@ fun main() = application { isMaximized, { windowState.isMinimized = !windowState.isMinimized }) } MainView( - undirectedViewModel, + mainViewModel, ) } } diff --git a/src/main/kotlin/components/MySlider.kt b/src/main/kotlin/components/MySlider.kt new file mode 100644 index 0000000..82c4dde --- /dev/null +++ b/src/main/kotlin/components/MySlider.kt @@ -0,0 +1,32 @@ +package components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Slider +import androidx.compose.material.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun MySlider(text: String, state: MutableState, range: ClosedFloatingPointRange = (0f..1f)) { + Row( + Modifier.padding(start = 5f.dp, end = 5f.dp), + verticalAlignment = Alignment.CenterVertically + ) { + MyText(text = text) + Slider( + modifier = Modifier.padding(0f.dp), + value = state.value, onValueChange = { change -> state.value = change }, + colors = SliderDefaults.colors( + thumbColor = Color.White, + activeTrackColor = Color.White, + inactiveTrackColor = Color.White, + ), + valueRange = range + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/HeaderView.kt b/src/main/kotlin/view/HeaderView.kt index 2420b8c..2b9500c 100644 --- a/src/main/kotlin/view/HeaderView.kt +++ b/src/main/kotlin/view/HeaderView.kt @@ -4,26 +4,17 @@ import Config import androidx.compose.foundation.* import androidx.compose.foundation.gestures.onDrag import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon -import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Done -import androidx.compose.material.icons.filled.Warning import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.key.Key.Companion.R import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max -import androidx.compose.ui.unit.sp +import components.MyText @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index 7f33048..ce6e645 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -1,54 +1,25 @@ package view import Config -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.animateOffsetAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.PointerMatcher -import androidx.compose.foundation.border -import androidx.compose.foundation.gestures.detectDragGestures -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.* -import androidx.compose.foundation.onClick import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.awtEventOrNull -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.* -import androidx.compose.ui.layout.* import androidx.compose.ui.unit.dp import view.canvas.CanvasView -import viewModel.canvas.CanvasViewModel -import viewModel.graph.UndirectedViewModel +import viewModel.MainViewModel val HEADER_HEIGHT = Config.headerHeight val MENU_WIDTH = Config.menuWidth @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable -fun MainView(undirectedViewModel: UndirectedViewModel) { - var zoom by remember { mutableFloatStateOf(1f) } - val zoomAnimate by animateFloatAsState(zoom, tween(200, 0, LinearOutSlowInEasing)) - var center by remember { mutableStateOf(Offset(0f, 0f)) } - val centerAnimate by animateOffsetAsState(center, tween(200, 0, LinearOutSlowInEasing)) - var canvasSize by remember { mutableStateOf(Offset(400f, 400f)) } - - var isOrientated by remember { mutableStateOf(false) } +fun MainView(mainViewModel: MainViewModel) { var isClustering by remember { mutableStateOf(false) } var isRanked by remember { mutableStateOf(false) } var isNodeCreatingMode by remember { mutableStateOf(false) } - undirectedViewModel.clustering = isClustering - undirectedViewModel.ranked = isRanked - - val canvasViewModel = - CanvasViewModel(undirectedViewModel, zoomAnimate, centerAnimate, canvasSize, isOrientated) - Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { MenuView( isNodeCreatingMode, @@ -57,48 +28,12 @@ fun MainView(undirectedViewModel: UndirectedViewModel) { { isClustering = !isClustering }, isRanked, { isRanked = !isRanked }) + CanvasView( - canvasViewModel, - Modifier - .fillMaxSize() - .onPointerEvent(PointerEventType.Scroll) { - if (it.changes.first().scrollDelta.y > 0) { - zoom -= zoom / 8 - } else { - zoom += zoom / 8 - - val awtEvent = it.awtEventOrNull - if (awtEvent != null) { - val xPosition = awtEvent.x.toFloat() - MENU_WIDTH - val yPosition = awtEvent.y.toFloat() - HEADER_HEIGHT - val pointerVector = (Offset(xPosition, yPosition) - (canvasSize / 2f)) * (1 / zoom) - center += pointerVector * 0.15f - } - } - }.pointerInput(Unit) { - detectDragGestures( - matcher = PointerMatcher.Primary - ) { - center -= it * (1 / zoom) - } - }.pointerInput(Unit) { - detectTapGestures { - if (isNodeCreatingMode) { - canvasViewModel.createVertex(it - (canvasSize / 2f), center, zoom) - zoom += 0.000001f // костыль для рекомпозиции - } - } - }.pointerHoverIcon(PointerIcon.Hand) - .onSizeChanged { - canvasSize = Offset(it.width.toFloat(), it.height.toFloat()) - } - .clipToBounds() + mainViewModel.canvasViewModel, + Modifier.fillMaxSize() ) } - SettingsView( - undirectedViewModel::onColorChange, - undirectedViewModel::onSizeChange, - { isOrientated = !isOrientated } - ) + SettingsView(mainViewModel.settingsViewModel) } \ No newline at end of file diff --git a/src/main/kotlin/view/SettingsView.kt b/src/main/kotlin/view/SettingsView.kt new file mode 100644 index 0000000..43b9126 --- /dev/null +++ b/src/main/kotlin/view/SettingsView.kt @@ -0,0 +1,60 @@ +package view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import components.MySlider +import components.MyText +import viewModel.SettingsViewModel + + +@Composable +fun SettingsView(viewModel: SettingsViewModel) { + val redSlider = remember { mutableStateOf(1f / (0xFF / 0x8F)) } + val greenSlider = remember { mutableStateOf(0f) } + val blueSlider = remember { mutableStateOf(1f) } + val sizeSlider = remember { mutableStateOf(35f) } + val orientatedCheckBox = remember { mutableStateOf(false) } + + viewModel.onColorChange(Color(red = redSlider.value, green = greenSlider.value, blue = blueSlider.value)) + viewModel.onSizeChange(sizeSlider.value) + + Box(Modifier.fillMaxSize().padding(top = 80f.dp, end = 20f.dp).zIndex(10f), contentAlignment = Alignment.TopEnd) { + Box( + Modifier.size(270f.dp, 320f.dp).background(Color(0xFF3D3D3D), RoundedCornerShape(10)) + ) { + Column { + Row(Modifier.fillMaxWidth().padding(top = 10f.dp), horizontalArrangement = Arrangement.Center) { + MyText("Node") + } + Row(Modifier.fillMaxWidth().padding(top = 10f.dp, start = 20f.dp)) { + Column { + MyText("Color:") + MySlider("R: ", redSlider) + MySlider("G: ", greenSlider) + MySlider("B: ", blueSlider) + MySlider("Size: ", sizeSlider, (5f..80f)) + } + } + + Row( + Modifier.fillMaxWidth().padding(start = 20f.dp), + verticalAlignment = Alignment.CenterVertically + ) { + MyText("Orientated") + Checkbox(orientatedCheckBox.value, onCheckedChange = { + viewModel.onOrientatedChange(it) + orientatedCheckBox.value = !orientatedCheckBox.value + }) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/canvas/CanvasView.kt b/src/main/kotlin/view/canvas/CanvasView.kt index 018df83..e57d6ab 100644 --- a/src/main/kotlin/view/canvas/CanvasView.kt +++ b/src/main/kotlin/view/canvas/CanvasView.kt @@ -1,19 +1,43 @@ package view.canvas +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.layout.onSizeChanged import viewModel.canvas.CanvasViewModel +import java.util.* +@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable fun CanvasView( viewModel: CanvasViewModel, modifier: Modifier = Modifier ) { + println("[${Date()}] render canvas") Box( modifier = modifier.background(Color(0xFF242424)) + .onPointerEvent(PointerEventType.Scroll, onEvent = viewModel.onScroll) + .pointerInput(Unit, viewModel.onDrag) +// .pointerInput(Unit) { +// detectTapGestures { +// if (isNodeCreatingMode) { +// canvasViewModel.createVertex(it - (canvasSize / 2f), center, zoom) +// zoom += 0.000001f // костыль для рекомпозиции +// } +// } +// } + .pointerHoverIcon(PointerIcon.Hand) + .onSizeChanged { + viewModel.canvasSize = Offset(it.width.toFloat(), it.height.toFloat()) + } + .clipToBounds() ) { viewModel.edges.forEach { EdgeCanvasView(it) diff --git a/src/main/kotlin/view/canvas/EdgeCanvasView.kt b/src/main/kotlin/view/canvas/EdgeCanvasView.kt index 181227c..c561ab5 100644 --- a/src/main/kotlin/view/canvas/EdgeCanvasView.kt +++ b/src/main/kotlin/view/canvas/EdgeCanvasView.kt @@ -8,17 +8,29 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import viewModel.canvas.EdgeCanvasViewModel +import java.util.* @Composable fun EdgeCanvasView( viewModel: EdgeCanvasViewModel, modifier: Modifier = Modifier ) { + println("[${Date()}] render edge") + Canvas(Modifier.fillMaxSize()) { + println("[${Date()}] render canvas inside edge") + // something hard thing for drawing edge from border of node, not from center - val firstCenter = viewModel.first.offset + Offset(viewModel.first.radius.value, viewModel.first.radius.value) + val firstCenter = + viewModel.first.offset + Offset( + viewModel.first.radius.value, + viewModel.first.radius.value + ) val secondCenter = - viewModel.second.offset + Offset(viewModel.second.radius.value, viewModel.second.radius.value) + viewModel.second.offset + Offset( + viewModel.second.radius.value, + viewModel.second.radius.value + ) val vector = (secondCenter - firstCenter) val vectorNorm = vector / vector.getDistance() @@ -37,7 +49,7 @@ fun EdgeCanvasView( ) } - if (viewModel.showOrientation) { + if (viewModel.showOrientation.value) { drawLine( start = end, end = end - rotateVector(radiusVectorSecond * 0.8f, 30.0), diff --git a/src/main/kotlin/view/canvas/VertexCanvasView.kt b/src/main/kotlin/view/canvas/VertexCanvasView.kt index ea33d78..4be14f9 100644 --- a/src/main/kotlin/view/canvas/VertexCanvasView.kt +++ b/src/main/kotlin/view/canvas/VertexCanvasView.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import view.MyText +import components.MyText import viewModel.canvas.VertexCanvasViewModel @OptIn(ExperimentalFoundationApi::class) @@ -31,6 +31,6 @@ fun VertexCanvasView( .onDrag(onDrag = viewModel::onDrag), contentAlignment = Alignment.Center ) { - MyText(viewModel.viewModel.label, viewModel.textSize.value) + MyText(viewModel.vertexViewModel.label, viewModel.textSize.value) } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/MainViewModel.kt b/src/main/kotlin/viewModel/MainViewModel.kt new file mode 100644 index 0000000..db1edaa --- /dev/null +++ b/src/main/kotlin/viewModel/MainViewModel.kt @@ -0,0 +1,15 @@ +package viewModel + +import model.graph.UndirectedGraph +import viewModel.canvas.CanvasViewModel + +class MainViewModel( + graph: UndirectedGraph +) { + val canvasViewModel = CanvasViewModel(graph) + val settingsViewModel = SettingsViewModel( + canvasViewModel::onColorChange, + canvasViewModel::onSizeChange, + canvasViewModel::onOrientatedChange + ) +} \ No newline at end of file diff --git a/src/main/kotlin/viewModel/SettingsViewModel.kt b/src/main/kotlin/viewModel/SettingsViewModel.kt new file mode 100644 index 0000000..a6b6eea --- /dev/null +++ b/src/main/kotlin/viewModel/SettingsViewModel.kt @@ -0,0 +1,10 @@ +package viewModel + +import androidx.compose.ui.graphics.Color + +class SettingsViewModel( + val onColorChange: (Color) -> Unit, + val onSizeChange: (Float) -> Unit, + val onOrientatedChange: (Boolean) -> Unit +) { +} \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 0e7696d..a9c2dfb 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -1,19 +1,62 @@ package viewModel.canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.PointerMatcher +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.awt.awtEventOrNull import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.AwaitPointerEventScope +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerInputScope +import model.graph.UndirectedGraph +import view.HEADER_HEIGHT +import view.MENU_WIDTH import viewModel.graph.UndirectedViewModel -import viewModel.graph.VertexViewModel -import kotlin.math.abs class CanvasViewModel( - val graphViewModel: UndirectedViewModel, - var zoom: Float, - var center: Offset, - var canvasSize: Offset, - var isOrientated: Boolean + val graph: UndirectedGraph, ) { + private val graphViewModel = UndirectedViewModel(graph, true) + + private val _zoom = mutableStateOf(1f) + var zoom + get() = _zoom.value + set(value) { + _zoom.value = value + updateVertexes() + } + + private val _center = mutableStateOf(Offset(0f, 0f)) + var center + get() = _center.value + set(value) { + _center.value = value + updateVertexes() + } + + private val _canvasSize = mutableStateOf(Offset(400f, 400f)) + var canvasSize + get() = _canvasSize.value + set(value) { + _canvasSize.value = value + updateVertexes() + } + + private val _isOrientated = mutableStateOf(false) + var isOrientated + get() = _isOrientated.value + set(value) { + _isOrientated.value = value + } + private val _vertices = graphViewModel.vertices.associateWith { v -> - VertexCanvasViewModel(v, zoom, center, canvasSize) + VertexCanvasViewModel(v, _zoom, _center, _canvasSize) }.toMutableMap() private val _edges = graphViewModel.adjacencyList.map { it.value }.flatten().map { @@ -22,7 +65,7 @@ class CanvasViewModel( val vertex2 = _vertices[it.second] ?: throw IllegalStateException("There is no VertexCanvasViewModel for ${it.second}") - EdgeCanvasViewModel(vertex1, vertex2, it.color, it.strokeWidth, zoom, showOrientation = isOrientated) + EdgeCanvasViewModel(vertex1, vertex2, it.color, it.strokeWidth, zoom, _isOrientated) } val vertices @@ -31,18 +74,65 @@ class CanvasViewModel( val edges get() = _edges - fun getViews(): Collection { - if (Config.optimizeCanvas) { - return _vertices.filter { abs(it.value.offset.x) < canvasSize.x && abs(it.value.offset.y) < canvasSize.y }.values + private fun updateVertexes() { + vertices.forEach { it.updateVertex() } + } + + private fun updateEdges() { + + } + + val onScroll: AwaitPointerEventScope.(PointerEvent) -> Unit = { + if (it.changes.first().scrollDelta.y > 0) { + zoom -= zoom / 8 + } else { + zoom += zoom / 8 + + val awtEvent = it.awtEventOrNull + if (awtEvent != null) { + val xPosition = awtEvent.x.toFloat() - MENU_WIDTH + val yPosition = awtEvent.y.toFloat() - HEADER_HEIGHT + val pointerVector = + (Offset(xPosition, yPosition) - (canvasSize / 2f)) * (1 / zoom) + center += pointerVector * 0.15f + } } + } - return _vertices.values + @OptIn(ExperimentalFoundationApi::class) + val onDrag: suspend PointerInputScope.() -> Unit = { + detectDragGestures( + matcher = PointerMatcher.Primary + ) { + center -= it * (1 / zoom) + } } - fun createVertex(offset: Offset, center: Offset, zoom: Float) { - val coordinates = offset * (1 / zoom) + center - val viewModel = graphViewModel.createVertex(coordinates) ?: return + fun onColorChange(color: Color) { + graphViewModel.onColorChange(color) + } + + fun onSizeChange(newSize: Float) { + graphViewModel.onSizeChange(newSize) + updateVertexes() + } - _vertices[viewModel] = VertexCanvasViewModel(viewModel, zoom, center, canvasSize) + fun onOrientatedChange(isOrientated: Boolean) { + this.isOrientated = isOrientated } + +// fun getViews(): Collection { +// if (Config.optimizeCanvas) { +// return _vertices.filter { abs(it.value.offset.x) < canvasSize.x && abs(it.value.offset.y) < canvasSize.y }.values +// } +// +// return _vertices.values +// } + +// fun createVertex(offset: Offset, center: Offset, zoom: Float) { +// val coordinates = offset * (1 / zoom) + center +// val viewModel = graphViewModel.createVertex(coordinates) ?: return +// +// _vertices[viewModel] = VertexCanvasViewModel(viewModel, _zoom, center, canvasSize) +// } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt index 61028c6..ab0fdda 100644 --- a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt @@ -1,5 +1,6 @@ package viewModel.canvas +import androidx.compose.runtime.MutableState import androidx.compose.ui.graphics.Color class EdgeCanvasViewModel( @@ -8,7 +9,7 @@ class EdgeCanvasViewModel( val color: Color, strokeWidth: Float, zoom: Float, - val showOrientation: Boolean = true + val showOrientation: MutableState ) { val strokeWidth = strokeWidth * zoom } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index ba727e4..872cd05 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -1,26 +1,69 @@ package viewModel.canvas +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.dp import viewModel.graph.VertexViewModel class VertexCanvasViewModel( - val viewModel: VertexViewModel, - private val zoom: Float, - center: Offset, - canvasSize: Offset + val vertexViewModel: VertexViewModel, + private val zoom: MutableState, + private val center: MutableState, + private val canvasSize: MutableState ) { - val offset = Offset( - (canvasSize.x / 2) + ((viewModel.x - center.x) * zoom), - (canvasSize.y / 2) + ((viewModel.y - center.y) * zoom) + private val _offset = mutableStateOf( + calculateOffset() ) + var offset + get() = _offset.value + set(value) { + _offset.value = value + } - val radius = viewModel.radius * zoom - val color = viewModel.color - val strokeWidth = (8f * zoom) - val textSize = (viewModel.radius * 0.6f * zoom) + private val _radius = mutableStateOf(calculateRadius()) + var radius + get() = _radius.value + set(value) { + _radius.value = value + } + + var color + get() = vertexViewModel.color + set(value) { + vertexViewModel.color = value + } + + private val _strokeWidth = mutableStateOf(calculateStrokeWidth()) + var strokeWidth + get() = _strokeWidth.value + set(value) { + _strokeWidth.value = value + } + + private val _textSize = mutableStateOf(calculateTextSize()) + var textSize + get() = _textSize.value + set(value) { + _textSize.value = value + } fun onDrag(it: Offset): Unit { - viewModel.onDrag(it * (1f / zoom)) + vertexViewModel.onDrag(it * (1f / zoom.value)) + } + + fun updateVertex() { + offset = calculateOffset() + radius = calculateRadius() + textSize = calculateTextSize() } + + private fun calculateOffset() = Offset( + (canvasSize.value.x / 2) + ((vertexViewModel.x - center.value.x) * zoom.value), + (canvasSize.value.y / 2) + ((vertexViewModel.y - center.value.y) * zoom.value) + ) + + private fun calculateRadius() = vertexViewModel.radius * zoom.value + private fun calculateStrokeWidth() = 8f * zoom.value + private fun calculateTextSize() = vertexViewModel.radius * 0.6f * zoom.value } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index 476cb8d..f198c97 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -12,7 +12,7 @@ class UndirectedViewModel( private val graph: UndirectedGraph, val showVerticesLabels: Boolean, val groups: HashMap = hashMapOf(), - val ranks: List> + val ranks: List> = listOf() ) { private val _vertices = hashMapOf() private val _adjacencyList = hashMapOf>() From 9ecc1dfa21d489e0cc4c7d87744dd931d18f5fc7 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 20 Aug 2024 23:08:48 +0300 Subject: [PATCH 12/60] feat: created a function to save graph (saveGraph) --- src/main/kotlin/model/reader/SQLiteReader.kt | 101 +++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/main/kotlin/model/reader/SQLiteReader.kt diff --git a/src/main/kotlin/model/reader/SQLiteReader.kt b/src/main/kotlin/model/reader/SQLiteReader.kt new file mode 100644 index 0000000..f3e922c --- /dev/null +++ b/src/main/kotlin/model/reader/SQLiteReader.kt @@ -0,0 +1,101 @@ +package model.reader + +import model.graph.Graph +import model.graph.Vertex + +import java.sql.Connection +import java.sql.DriverManager +import java.sql.PreparedStatement + +class SQLiteReader: Reader { + + private fun createTable(connection: Connection) { + val statement = connection.createStatement() + val createTableVertex = """ + CREATE TABLE IF NOT EXISTS vertex ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + vertex_key INTEGER, + graph_id INTEGER, + FOREIGN KEY (graph_id) REFERENCES graph(graph_id) + ) + """ + val createTableEdge = """ + CREATE TABLE IF NOT EXISTS edge ( + start_vertex_id INTEGER, + end_vertex_id INTEGER, + weight INTEGER, + FOREIGN KEY (end_vertex_id) REFERENCES graph(graph_id), + FOREIGN KEY (start_vertex_id) REFERENCES graph(graph_id) + ) + """ + val createTableGraph = """ + CREATE TABLE IF NOT EXISTS graph ( + graph_id INTEGER PRIMARY KEY AUTOINCREMENT, + graph_name TEXT NOT NULL + ) + """ + statement.execute(createTableGraph) + statement.execute(createTableVertex) + statement.execute(createTableEdge) + statement.close() + } + + private fun insertGraph(connect: Connection, graph: Graph, nameGraph: String){ + val insertName = "INSERT INTO graph (graph_name) VALUES (?)" + val insertNameStmt: PreparedStatement = connect.prepareStatement(insertName) + insertNameStmt.setString(1, nameGraph) + insertNameStmt.executeUpdate() + + val graphId = insertNameStmt.generatedKeys.getInt(1) + insertNameStmt.close() + + val insertVertexSql = "INSERT INTO vertex (vertex_key, graph_id) VALUES (?, ?)" + val insertVertexStmt: PreparedStatement = connect.prepareStatement(insertVertexSql) + + val vertexIdMap = mutableMapOf() + + for (vertex in graph.vertices){ + insertVertexStmt.setInt(1, vertex.key) + insertVertexStmt.setInt(2, graphId) + + insertVertexStmt.executeUpdate() + + val vertexIdResult = insertVertexStmt.generatedKeys + if (vertexIdResult.next()) { + val vertexId = vertexIdResult.getInt(1) + vertexIdMap[vertex] = vertexId + } + } + insertVertexStmt.close() + + val insertEdgeSql = "INSERT INTO edge (start_vertex_id, end_vertex_id, weight) VALUES (?, ?, ?)" + val insertEdgeStmt: PreparedStatement = connect.prepareStatement(insertEdgeSql) + + for ((vertex, edges) in graph.adjacencyList) { + val startVertexId = vertexIdMap[vertex] ?: throw Exception("Vertex not found in vertexIdMap") + for (edge in edges) { + val endVertexId = vertexIdMap[edge.second] ?: throw Exception("End vertex not found in vertexIdMap") + insertEdgeStmt.setInt(1, startVertexId) + insertEdgeStmt.setInt(2, endVertexId) + insertEdgeStmt.setLong(3, edge.weight) + insertEdgeStmt.executeUpdate() + } + } + insertEdgeStmt.close() + } + + override fun saveGraph(graph: Graph, filepath: String, nameGraph: String) { + //Сконектились с базой + val connection = DriverManager.getConnection("jdbc:sqlite:$filepath") + //Создали таблицы и связи между ними + createTable(connection) + //Сохранили граф по полочкам:) + insertGraph(connection, graph, nameGraph) + } + + override fun loadGraph(filepath: String, nameGraph: String): Graph { + TODO("Load graph") + } +} + + From 9b7bcd5666f30a7191bfa429ab737c215796da1c Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Wed, 21 Aug 2024 00:19:48 +0300 Subject: [PATCH 13/60] feat: created function loadGraph (loadGraph) --- src/main/kotlin/model/reader/SQLiteReader.kt | 87 +++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/model/reader/SQLiteReader.kt b/src/main/kotlin/model/reader/SQLiteReader.kt index f3e922c..0dae36a 100644 --- a/src/main/kotlin/model/reader/SQLiteReader.kt +++ b/src/main/kotlin/model/reader/SQLiteReader.kt @@ -2,6 +2,7 @@ package model.reader import model.graph.Graph import model.graph.Vertex +import model.graph.WeightedGraph import java.sql.Connection import java.sql.DriverManager @@ -31,7 +32,7 @@ class SQLiteReader: Reader { val createTableGraph = """ CREATE TABLE IF NOT EXISTS graph ( graph_id INTEGER PRIMARY KEY AUTOINCREMENT, - graph_name TEXT NOT NULL + graph_name TEXT NOT NULL UNIQUE ) """ statement.execute(createTableGraph) @@ -84,17 +85,97 @@ class SQLiteReader: Reader { insertEdgeStmt.close() } + private fun connect(filepath: String): Connection = DriverManager.getConnection("jdbc:sqlite:$filepath") + + override fun saveGraph(graph: Graph, filepath: String, nameGraph: String) { + //Сконектились с базой - val connection = DriverManager.getConnection("jdbc:sqlite:$filepath") + val connection = connect(filepath) + //Создали таблицы и связи между ними createTable(connection) + //Сохранили граф по полочкам:) insertGraph(connection, graph, nameGraph) } override fun loadGraph(filepath: String, nameGraph: String): Graph { - TODO("Load graph") + + //Сконектились с базой + val connection = connect(filepath) + + val graph: Graph = WeightedGraph() + + //Сделали запрос на получение id графа + val graphStmt = connection.prepareStatement( + "SELECT graph_id FROM graph WHERE graph_name = ?" + ) + + graphStmt.setString(1, nameGraph) + val graphResultSet = graphStmt.executeQuery() + + if (!graphResultSet.next()) { + throw IllegalArgumentException("Graph with name $nameGraph not found") + } + + val graphId = graphResultSet.getInt("id") + graphResultSet.close() + graphStmt.close() + + //Сделали запрос на получение id и ключа вершины + val vertexStmt = connection.prepareStatement( + "SELECT id, vertex_key FROM vertex WHERE graph_id = ?" + ) + + vertexStmt.setInt(1, graphId) + val vertexResultSet = vertexStmt.executeQuery() + + // Нужна для нахождение вершин ребра через их id + val vertexMap = mutableMapOf() + + while (vertexResultSet.next()){ + val vertexId = vertexResultSet.getInt("id") + val vertexKey = vertexResultSet.getInt("vertex_key") + val vertex = graph.addVertex(vertexKey) + + if (vertex != null){ + vertexMap[vertexId] = vertex + } + } + + vertexResultSet.close() + vertexStmt.close() + + /* + Сделали запрос на получение id начальной и конечной вершины, а также веса, ребра, + через id вершины полученной от graph_id + */ + val edgeStmt = connection.prepareStatement( + "SELECT start_vertex_id, end_vertex_id, weight FROM edge WHERE start_vertex_id" + + " IN (SELECT id FROM vertex WHERE graph_id = ?)" + ) + + edgeStmt.setInt(1, graphId) + val edgeResultSet = edgeStmt.executeQuery() + + while (edgeResultSet.next()) { + val startVertexId = edgeResultSet.getInt("start_vertex_id") + val endVertexId = edgeResultSet.getInt("end_vertex_id") + val weight = edgeResultSet.getLong("weight") + + val startVertex = vertexMap[startVertexId] + val endVertex = vertexMap[endVertexId] + + if (startVertex != null && endVertex != null) { + graph.addEdge(startVertex.key, endVertex.key, weight) + } + } + edgeResultSet.close() + edgeStmt.close() + + connection.close() + return graph } } From a7014f7ca64581f7afda78e4acb2a632e40adfe1 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Fri, 23 Aug 2024 15:13:58 +0300 Subject: [PATCH 14/60] refactor: delete prints --- src/main/kotlin/view/canvas/CanvasView.kt | 2 -- src/main/kotlin/view/canvas/EdgeCanvasView.kt | 5 ----- 2 files changed, 7 deletions(-) diff --git a/src/main/kotlin/view/canvas/CanvasView.kt b/src/main/kotlin/view/canvas/CanvasView.kt index e57d6ab..a37752f 100644 --- a/src/main/kotlin/view/canvas/CanvasView.kt +++ b/src/main/kotlin/view/canvas/CanvasView.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.* import androidx.compose.ui.layout.onSizeChanged import viewModel.canvas.CanvasViewModel -import java.util.* @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable @@ -20,7 +19,6 @@ fun CanvasView( viewModel: CanvasViewModel, modifier: Modifier = Modifier ) { - println("[${Date()}] render canvas") Box( modifier = modifier.background(Color(0xFF242424)) .onPointerEvent(PointerEventType.Scroll, onEvent = viewModel.onScroll) diff --git a/src/main/kotlin/view/canvas/EdgeCanvasView.kt b/src/main/kotlin/view/canvas/EdgeCanvasView.kt index c561ab5..ae3da58 100644 --- a/src/main/kotlin/view/canvas/EdgeCanvasView.kt +++ b/src/main/kotlin/view/canvas/EdgeCanvasView.kt @@ -5,21 +5,16 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import viewModel.canvas.EdgeCanvasViewModel -import java.util.* @Composable fun EdgeCanvasView( viewModel: EdgeCanvasViewModel, modifier: Modifier = Modifier ) { - println("[${Date()}] render edge") Canvas(Modifier.fillMaxSize()) { - println("[${Date()}] render canvas inside edge") - // something hard thing for drawing edge from border of node, not from center val firstCenter = viewModel.first.offset + Offset( From 26fdbf58a2bd64be91e42b19aeb3005fc393051e Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 23 Aug 2024 21:03:30 +0300 Subject: [PATCH 15/60] refactor: made the DirectedGraph class open --- src/main/kotlin/model/graph/DirectedGraph.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt index 4d4e043..938f9eb 100644 --- a/src/main/kotlin/model/graph/DirectedGraph.kt +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -1,6 +1,6 @@ package model.graph -class DirectedGraph : UndirectedGraph() { +open class DirectedGraph : UndirectedGraph() { override fun addEdge(first: Int, second: Int, weight: Long): Edge? { if (first == second) return null From 344e7acd6f496a74476d84cad0c1480e2083058d Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 23 Aug 2024 21:05:50 +0300 Subject: [PATCH 16/60] feat: add WeightedDirectedGraph class --- .../model/graph/WeightedDirectedGraph.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/kotlin/model/graph/WeightedDirectedGraph.kt diff --git a/src/main/kotlin/model/graph/WeightedDirectedGraph.kt b/src/main/kotlin/model/graph/WeightedDirectedGraph.kt new file mode 100644 index 0000000..cc767de --- /dev/null +++ b/src/main/kotlin/model/graph/WeightedDirectedGraph.kt @@ -0,0 +1,24 @@ +package model.graph + +class WeightedDirectedGraph: DirectedGraph() { + override fun addEdge(first: Int, second: Int, weight: Long): Edge? { + if (first == second) return null + + val vertex1 = _vertices[first] ?: return null + val vertex2 = _vertices[second] ?: return null + + // edge already exists + if (_adjacencyList[vertex1]?.find { it.second.key == second } != null) return null + + _adjacencyList[vertex1]?.add(WeightedDirectedEdge(vertex1, vertex2, weight)) + + + return _adjacencyList[vertex1]?.last() + } + + private data class WeightedDirectedEdge( + override val first: Vertex, + override val second: Vertex, + override var weight: Long + ) : Edge +} \ No newline at end of file From 0cc068e4841909c1eed538268ab1d8081831c785 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 23 Aug 2024 21:07:07 +0300 Subject: [PATCH 17/60] feat: add WeightedDirectedGraphTest class --- .../model/graph/WeightedDirectedGraphTest.kt | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/test/kotlin/model/graph/WeightedDirectedGraphTest.kt diff --git a/src/test/kotlin/model/graph/WeightedDirectedGraphTest.kt b/src/test/kotlin/model/graph/WeightedDirectedGraphTest.kt new file mode 100644 index 0000000..95b9361 --- /dev/null +++ b/src/test/kotlin/model/graph/WeightedDirectedGraphTest.kt @@ -0,0 +1,89 @@ +package model.graph + +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class WeightedDirectedGraphTest { + + @Nested + inner class addEdge { + private val graph = WeightedDirectedGraph() + + @Test + fun `Not linked vertices`() { + assertNotNull(graph.addVertex(2)) + assertNotNull(graph.addVertex(1)) + + val edge = graph.addEdge(1, 2) + + assertNotNull(edge) + + } + + @Test + fun `First vertex == second vertex`() { + assertNotNull(graph.addVertex(1)) + + val edge = graph.addEdge(1, 1) + + assertNull(edge) + } + + @Test + fun `First edge == second edge`() { + assertNotNull(graph.addVertex(1)) + assertNotNull(graph.addVertex(2)) + + val edge1 = graph.addEdge(1, 2) + val edge2 = graph.addEdge(1, 2) + + assertNotNull(edge1) + assertNull(edge2) + } + + @Test + fun `Already linked vertices`() { + assertNotNull(graph.addVertex(1)) + assertNotNull(graph.addVertex(2)) + + val edge = graph.addEdge(1, 2, 5) + + assertNotNull(edge) + + assertEquals(edge.weight, 5) + } + + @Test + fun `Base graph test`() { + assertNotNull(graph.addVertex(1)) + assertNotNull(graph.addVertex(2)) + assertNotNull(graph.addVertex(3)) + + val edge1 = graph.addEdge(1, 2, 5) + val edge2 = graph.addEdge(2, 3, 7) + val edge3 = graph.addEdge(3, 1, 9) + + assertNotNull(edge1) + assertNotNull(edge2) + assertNotNull(edge3) + + assertEquals(edge1.weight, 5) + assertEquals(edge2.weight, 7) + assertEquals(edge3.weight, 9) + } + + @Test + fun `Edge with non existing vertex`() { + val vertex = graph.addVertex(1) + + assertNull(graph.addEdge(2, 1)) + assertNull(graph.addEdge(1, 2)) + assertNull(graph.addEdge(3, 4)) + + assertEquals(graph.adjacencyList[vertex]?.size, 0) + } + } +} \ No newline at end of file From 7a0289e6a6c52541ad57d73ae5df238a7a04d385 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 23 Aug 2024 21:09:36 +0300 Subject: [PATCH 18/60] feat: added to the loadGraph and saveGraph functions to take into account the graph type --- src/main/kotlin/model/reader/SQLiteReader.kt | 40 ++++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/model/reader/SQLiteReader.kt b/src/main/kotlin/model/reader/SQLiteReader.kt index 0dae36a..46b0b82 100644 --- a/src/main/kotlin/model/reader/SQLiteReader.kt +++ b/src/main/kotlin/model/reader/SQLiteReader.kt @@ -1,8 +1,6 @@ package model.reader -import model.graph.Graph -import model.graph.Vertex -import model.graph.WeightedGraph +import model.graph.* import java.sql.Connection import java.sql.DriverManager @@ -32,7 +30,8 @@ class SQLiteReader: Reader { val createTableGraph = """ CREATE TABLE IF NOT EXISTS graph ( graph_id INTEGER PRIMARY KEY AUTOINCREMENT, - graph_name TEXT NOT NULL UNIQUE + graph_name TEXT NOT NULL UNIQUE, + graph_type_flag INTEGER ) """ statement.execute(createTableGraph) @@ -42,9 +41,24 @@ class SQLiteReader: Reader { } private fun insertGraph(connect: Connection, graph: Graph, nameGraph: String){ - val insertName = "INSERT INTO graph (graph_name) VALUES (?)" + + val insertName = "INSERT INTO graph (graph_name, graph_type_flag) VALUES (?, ?)" val insertNameStmt: PreparedStatement = connect.prepareStatement(insertName) insertNameStmt.setString(1, nameGraph) + + if (graph is WeightedGraph){ + insertNameStmt.setInt(2, 1) + } + if (graph is UndirectedGraph){ + insertNameStmt.setInt(2, 2) + } + if (graph is DirectedGraph){ + insertNameStmt.setInt(2, 3) + } + if (graph is WeightedDirectedGraph){ + insertNameStmt.setInt(2, 4) + } + insertNameStmt.executeUpdate() val graphId = insertNameStmt.generatedKeys.getInt(1) @@ -105,11 +119,11 @@ class SQLiteReader: Reader { //Сконектились с базой val connection = connect(filepath) - val graph: Graph = WeightedGraph() + val graph: Graph //Сделали запрос на получение id графа val graphStmt = connection.prepareStatement( - "SELECT graph_id FROM graph WHERE graph_name = ?" + "SELECT graph_id, graph_type_flag FROM graph WHERE graph_name = ?" ) graphStmt.setString(1, nameGraph) @@ -119,7 +133,17 @@ class SQLiteReader: Reader { throw IllegalArgumentException("Graph with name $nameGraph not found") } - val graphId = graphResultSet.getInt("id") + val graphId = graphResultSet.getInt("graph_id") + val graphType = graphResultSet.getInt("graph_type_flag") + + graph = when (graphType) { + 1 -> WeightedGraph() + 2 -> UndirectedGraph() + 3 -> DirectedGraph() + 4 -> WeightedDirectedGraph() + else -> throw IllegalArgumentException("Unknown graph type: $graphType") + } + graphResultSet.close() graphStmt.close() From 35a8b879f3776af22d4adb295a1ea46e384d4269 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 23 Aug 2024 21:10:49 +0300 Subject: [PATCH 19/60] feat: added a directory with the trial database --- src/main/kotlin/model/reader/database/graph.db | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/kotlin/model/reader/database/graph.db diff --git a/src/main/kotlin/model/reader/database/graph.db b/src/main/kotlin/model/reader/database/graph.db new file mode 100644 index 0000000..e69de29 From e49ac6125d7cbc67279d3e7d39f595bdea100ee2 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 2 Sep 2024 13:48:17 +0300 Subject: [PATCH 20/60] feat: add algorithms icon --- src/main/resources/Algorithm.svg | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/resources/Algorithm.svg diff --git a/src/main/resources/Algorithm.svg b/src/main/resources/Algorithm.svg new file mode 100644 index 0000000..6e1333e --- /dev/null +++ b/src/main/resources/Algorithm.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + From ecb9694a69f08c743896b8a0ebf0dd5a4b7f46c2 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 2 Sep 2024 22:28:58 +0300 Subject: [PATCH 21/60] feat: implemented algorithms icon in GUI --- src/main/kotlin/view/MainView.kt | 6 +++++- src/main/kotlin/view/MenuView.kt | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index ce6e645..2571d3a 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -19,6 +19,7 @@ fun MainView(mainViewModel: MainViewModel) { var isClustering by remember { mutableStateOf(false) } var isRanked by remember { mutableStateOf(false) } var isNodeCreatingMode by remember { mutableStateOf(false) } + var isAlgorithmMenuOpen by remember { mutableStateOf(false)} Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { MenuView( @@ -27,7 +28,10 @@ fun MainView(mainViewModel: MainViewModel) { isClustering, { isClustering = !isClustering }, isRanked, - { isRanked = !isRanked }) + { isRanked = !isRanked }, + isAlgorithmMenuOpen, + { isAlgorithmMenuOpen = !isAlgorithmMenuOpen }) + CanvasView( mainViewModel.canvasViewModel, diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 7488d7f..ad005da 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -31,7 +31,9 @@ fun MenuView( isClustering: Boolean, onClusteringChange: () -> Unit, isRanked: Boolean, - onRankedChange: () -> Unit + onRankedChange: () -> Unit, + isAlgorithmMenuOpen: Boolean, + onAlgorithmMenuOpenChange: () -> Unit ) { Column( Modifier.fillMaxHeight().width(Config.menuWidth.dp).background(color = Color(0xFF3D3D3D)), @@ -50,6 +52,9 @@ fun MenuView( MenuIcon("PageRank.svg", "Analysis graph", Modifier.glow(isRanked)) { onRankedChange() } + MenuIcon("Algorithm.svg", "Algorithms...", Modifier.glow(isAlgorithmMenuOpen)){ + onAlgorithmMenuOpenChange() + } } } From ac54f5db478505ef3dbe96060b83c20bb9f42844 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Thu, 5 Sep 2024 20:00:54 +0300 Subject: [PATCH 22/60] feat: add image with description for button --- src/main/resources/AddEdge.svg | 6 ++++++ src/main/resources/AddNode.svg | 6 ++++++ src/main/resources/Algorithms....svg | 5 +++++ src/main/resources/AnalysisGraph.svg | 6 ++++++ src/main/resources/ClusterD.svg | 6 ++++++ 5 files changed, 29 insertions(+) create mode 100644 src/main/resources/AddEdge.svg create mode 100644 src/main/resources/AddNode.svg create mode 100644 src/main/resources/Algorithms....svg create mode 100644 src/main/resources/AnalysisGraph.svg create mode 100644 src/main/resources/ClusterD.svg diff --git a/src/main/resources/AddEdge.svg b/src/main/resources/AddEdge.svg new file mode 100644 index 0000000..ac56564 --- /dev/null +++ b/src/main/resources/AddEdge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/AddNode.svg b/src/main/resources/AddNode.svg new file mode 100644 index 0000000..eeda9bc --- /dev/null +++ b/src/main/resources/AddNode.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/Algorithms....svg b/src/main/resources/Algorithms....svg new file mode 100644 index 0000000..2b76310 --- /dev/null +++ b/src/main/resources/Algorithms....svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/AnalysisGraph.svg b/src/main/resources/AnalysisGraph.svg new file mode 100644 index 0000000..461e240 --- /dev/null +++ b/src/main/resources/AnalysisGraph.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/ClusterD.svg b/src/main/resources/ClusterD.svg new file mode 100644 index 0000000..abac7e0 --- /dev/null +++ b/src/main/resources/ClusterD.svg @@ -0,0 +1,6 @@ + + + + + + From d9ba9bfc31c3d1f5a65157f3cf046c52331ec238 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Thu, 5 Sep 2024 20:05:00 +0300 Subject: [PATCH 23/60] feat: implement description display --- src/main/kotlin/view/MenuView.kt | 74 ++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index ad005da..219caec 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -3,25 +3,83 @@ package view import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State +import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerMoveFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import kotlin.math.roundToInt -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) @Composable fun MenuIcon(name: String, description: String, modifier: Modifier = Modifier, onClick: () -> Unit = {}) { + var isHovered by remember { mutableStateOf(false) } + val popupOffset: Offset = Offset(80f, -180f) + var iconPosition by remember { mutableStateOf(IntOffset.Zero) } + var iconSize by remember { mutableStateOf(IntSize.Zero) } + Image( painter = painterResource(name), contentDescription = description, modifier = modifier .onClick(onClick = onClick) + .pointerMoveFilter( + onEnter = { + isHovered = true + true + }, + onExit = { + isHovered = false + false + } + ) + .onGloballyPositioned { layoutCoordinates -> + // Получаем положение и размер иконки + iconPosition = layoutCoordinates.positionInRoot().run { + IntOffset(x.roundToInt(), y.roundToInt()) + } + iconSize = layoutCoordinates.size + } ) Spacer(Modifier.height(10f.dp)) + if (isHovered){ + Popup( + offset = with(LocalDensity.current) { + IntOffset( + (iconPosition.x + popupOffset.x.toDp().roundToPx()), + (iconPosition.y + iconSize.height + popupOffset.y.toDp().roundToPx()) + ) + }, + alignment = Alignment.TopStart, + properties = PopupProperties(focusable = false) + ) { + DisplayDescription(description) + } + } +} + +@Composable +fun DisplayDescription(name : String) { + Image( + painter = painterResource(name), + contentDescription = "Descript", + modifier = Modifier.size(350f.dp), + contentScale = ContentScale.Fit + ) } @Composable @@ -41,18 +99,18 @@ fun MenuView( ) { Spacer(Modifier.height(25f.dp)) MenuIcon( - "Nodes.svg", "Add Node", Modifier.glow(isNodeCreating) + "Nodes.svg", "AddNode.svg", Modifier.glow(isNodeCreating) ) { onNodeCreatingChange() } - MenuIcon("Ribs.svg", "Add Edge", modifier = Modifier.alpha(0.2f)) - MenuIcon("Clustering.svg", "Clustering", Modifier.glow(isClustering)) { + MenuIcon("Ribs.svg", "AddEdge.svg", modifier = Modifier.alpha(0.2f)) + MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glow(isClustering)) { onClusteringChange() } - MenuIcon("PageRank.svg", "Analysis graph", Modifier.glow(isRanked)) { + MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glow(isRanked)) { onRankedChange() } - MenuIcon("Algorithm.svg", "Algorithms...", Modifier.glow(isAlgorithmMenuOpen)){ + MenuIcon("Algorithm.svg", "Algorithms....svg", Modifier.glow(isAlgorithmMenuOpen)){ onAlgorithmMenuOpenChange() } } From 15c7c1fe0b22f2b613e297716d23fea9f4f7b9eb Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 6 Sep 2024 23:22:21 +0300 Subject: [PATCH 24/60] refactor: delete comment --- src/main/kotlin/view/MenuView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 219caec..8540627 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -48,7 +48,6 @@ fun MenuIcon(name: String, description: String, modifier: Modifier = Modifier, o } ) .onGloballyPositioned { layoutCoordinates -> - // Получаем положение и размер иконки iconPosition = layoutCoordinates.positionInRoot().run { IntOffset(x.roundToInt(), y.roundToInt()) } From ce1d985f869b2480f94012ab36fb834804438f4a Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 6 Sep 2024 23:23:52 +0300 Subject: [PATCH 25/60] feat: added icons for the algorithm menu --- src/main/resources/Bellman-Ford.svg | 5 +++++ src/main/resources/Dijkstra.svg | 5 +++++ src/main/resources/DownMenuAlgorithm.svg | 4 ++++ src/main/resources/FindBridge.svg | 5 +++++ src/main/resources/FindCycle.svg | 5 +++++ src/main/resources/IslandTree.svg | 5 +++++ src/main/resources/StrongConnectivityComponent.svg | 5 +++++ 7 files changed, 34 insertions(+) create mode 100644 src/main/resources/Bellman-Ford.svg create mode 100644 src/main/resources/Dijkstra.svg create mode 100644 src/main/resources/DownMenuAlgorithm.svg create mode 100644 src/main/resources/FindBridge.svg create mode 100644 src/main/resources/FindCycle.svg create mode 100644 src/main/resources/IslandTree.svg create mode 100644 src/main/resources/StrongConnectivityComponent.svg diff --git a/src/main/resources/Bellman-Ford.svg b/src/main/resources/Bellman-Ford.svg new file mode 100644 index 0000000..701e743 --- /dev/null +++ b/src/main/resources/Bellman-Ford.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/Dijkstra.svg b/src/main/resources/Dijkstra.svg new file mode 100644 index 0000000..5013f28 --- /dev/null +++ b/src/main/resources/Dijkstra.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/DownMenuAlgorithm.svg b/src/main/resources/DownMenuAlgorithm.svg new file mode 100644 index 0000000..413215b --- /dev/null +++ b/src/main/resources/DownMenuAlgorithm.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/FindBridge.svg b/src/main/resources/FindBridge.svg new file mode 100644 index 0000000..c222ef8 --- /dev/null +++ b/src/main/resources/FindBridge.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/FindCycle.svg b/src/main/resources/FindCycle.svg new file mode 100644 index 0000000..a1dfe0f --- /dev/null +++ b/src/main/resources/FindCycle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/IslandTree.svg b/src/main/resources/IslandTree.svg new file mode 100644 index 0000000..23dfdbc --- /dev/null +++ b/src/main/resources/IslandTree.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/StrongConnectivityComponent.svg b/src/main/resources/StrongConnectivityComponent.svg new file mode 100644 index 0000000..a214d9d --- /dev/null +++ b/src/main/resources/StrongConnectivityComponent.svg @@ -0,0 +1,5 @@ + + + + + From 5d325c6ce80df5d49e27e678fce4ba68d53c9ca8 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Fri, 6 Sep 2024 23:25:01 +0300 Subject: [PATCH 26/60] feat: made a menu of algorithms --- src/main/kotlin/view/MainView.kt | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index 2571d3a..195bdc0 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -2,17 +2,90 @@ package view import Config import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties import view.canvas.CanvasView import viewModel.MainViewModel val HEADER_HEIGHT = Config.headerHeight val MENU_WIDTH = Config.menuWidth +@Composable +fun DisplayAlgorithmMenu(name : String) { + + val imageResources = listOf( + "FindBridge.svg", + "Dijkstra.svg", + "Bellman-Ford.svg", + "IslandTree.svg", + "StrongConnectivityComponent.svg", + "FindCycle.svg" + ) + Box( + modifier = Modifier.padding(top = 240.dp, start = 80.dp) + ) { + + // Изображение + Image( + painter = painterResource(name), + contentDescription = "Padded Image", + modifier = Modifier.size(452.dp), + contentScale = ContentScale.Fit + ) + + LazyColumn( + modifier = Modifier + .size(450.dp, 300.dp) + .background(Color.Transparent) + .padding(top = 150.dp, start = 30.dp) + ) { + items(imageResources) { image -> + ImageButton( + imageResourceId = image, + onClick = { + } + ) + } + } + } +} + +@Composable +fun ImageButton(imageResourceId: String, onClick: () -> Unit) { + Box( + modifier = Modifier + .size(440.dp, 60.dp) + .padding(1.dp) + .clickable { onClick() } + .background(Color(0x00)) + ) { + Image( + painter = painterResource(imageResourceId), + contentDescription = "Button Image", + modifier = Modifier.size(445.dp, 59.dp), + contentScale = ContentScale.Crop + ) + } +} + @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable fun MainView(mainViewModel: MainViewModel) { @@ -39,5 +112,9 @@ fun MainView(mainViewModel: MainViewModel) { ) } + if (isAlgorithmMenuOpen){ + DisplayAlgorithmMenu("DownMenuAlgorithm.svg") + } + SettingsView(mainViewModel.settingsViewModel) } \ No newline at end of file From a846928322eaa2ce3d47c5a5ba69d51f4f82229b Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 9 Sep 2024 23:23:39 +0300 Subject: [PATCH 27/60] feat: add icon for file menu --- src/main/resources/DataBase.svg | 4 ++++ src/main/resources/DataBaseLoad.svg | 4 ++++ src/main/resources/JSON.svg | 4 ++++ src/main/resources/JSONLoad.svg | 4 ++++ src/main/resources/Neo4j.svg | 4 ++++ src/main/resources/Neo4jLoad.svg | 4 ++++ 6 files changed, 24 insertions(+) create mode 100644 src/main/resources/DataBase.svg create mode 100644 src/main/resources/DataBaseLoad.svg create mode 100644 src/main/resources/JSON.svg create mode 100644 src/main/resources/JSONLoad.svg create mode 100644 src/main/resources/Neo4j.svg create mode 100644 src/main/resources/Neo4jLoad.svg diff --git a/src/main/resources/DataBase.svg b/src/main/resources/DataBase.svg new file mode 100644 index 0000000..8fc91f9 --- /dev/null +++ b/src/main/resources/DataBase.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/DataBaseLoad.svg b/src/main/resources/DataBaseLoad.svg new file mode 100644 index 0000000..7108c31 --- /dev/null +++ b/src/main/resources/DataBaseLoad.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/JSON.svg b/src/main/resources/JSON.svg new file mode 100644 index 0000000..5c13ffc --- /dev/null +++ b/src/main/resources/JSON.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/JSONLoad.svg b/src/main/resources/JSONLoad.svg new file mode 100644 index 0000000..e22b409 --- /dev/null +++ b/src/main/resources/JSONLoad.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/Neo4j.svg b/src/main/resources/Neo4j.svg new file mode 100644 index 0000000..3586484 --- /dev/null +++ b/src/main/resources/Neo4j.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/Neo4jLoad.svg b/src/main/resources/Neo4jLoad.svg new file mode 100644 index 0000000..43fc524 --- /dev/null +++ b/src/main/resources/Neo4jLoad.svg @@ -0,0 +1,4 @@ + + + + From 1a2bf8be83bfe3d41115c6bb861b35d15c3ef368 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 9 Sep 2024 23:25:03 +0300 Subject: [PATCH 28/60] feat: implemented file menu view --- src/main/kotlin/view/HeaderView.kt | 79 +++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/view/HeaderView.kt b/src/main/kotlin/view/HeaderView.kt index 2b9500c..2de5ff1 100644 --- a/src/main/kotlin/view/HeaderView.kt +++ b/src/main/kotlin/view/HeaderView.kt @@ -4,16 +4,24 @@ import Config import androidx.compose.foundation.* import androidx.compose.foundation.gestures.onDrag import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items + import androidx.compose.material.Icon +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.SemanticsActions.OnClick import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties import components.MyText @OptIn(ExperimentalFoundationApi::class) @@ -84,10 +92,68 @@ private fun Logo() { ) } +@Composable +fun ImageButtonFile(imageResourceId: String, onClick: () -> Unit) { + Box( + modifier = Modifier + .size(300.dp, 50.dp) + .padding(0.dp) + .clickable { onClick() } + .background(Color(0x00)) + ) { + Image( + painter = painterResource(imageResourceId), + contentDescription = "Button Image", + Modifier.size(400.dp, 50.dp), + contentScale = ContentScale.Crop + ) + } +} + +@Composable +private fun FileMenu(){ + val imageResources = listOf( + "DataBase.svg", + "JSON.svg", + "Neo4j.svg", + "DataBaseLoad.svg", + "JSONLoad.svg", + "Neo4jLoad.svg" + ) + + Box( + Modifier + .size(300.dp, 300.dp) + .shadow( + elevation = 5f.dp, + spotColor = Color.Black + ) + .background(Color(0xFF3D3D3D)), + + ) { + LazyColumn( + modifier = Modifier + .size(300.dp, 300.dp) + + ) { + items(imageResources) { image -> + ImageButtonFile( + imageResourceId = image, + onClick = { + } + ) + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) @Composable private fun FileButton() { + var isImageVisible by remember { mutableStateOf(false) } Box( Modifier + .clickable{ isImageVisible = !isImageVisible } .size(Config.headerHeight.dp) .shadow( elevation = 5f.dp, @@ -96,5 +162,16 @@ private fun FileButton() { contentAlignment = Alignment.Center ) { MyText("File", 16f) + } + if (isImageVisible) { + Box( + Modifier.padding(top = 33.dp) + ) { + Popup ( + properties = PopupProperties(focusable = false) + ) { + FileMenu() + } + } } } \ No newline at end of file From 1a2e48ff2df01e843a7e4237eb68e18433a13a65 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Mon, 9 Sep 2024 23:28:49 +0300 Subject: [PATCH 29/60] refactor: deleted unnecessary imports --- src/main/kotlin/view/HeaderView.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/kotlin/view/HeaderView.kt b/src/main/kotlin/view/HeaderView.kt index 2de5ff1..8ca2327 100644 --- a/src/main/kotlin/view/HeaderView.kt +++ b/src/main/kotlin/view/HeaderView.kt @@ -6,9 +6,7 @@ import androidx.compose.foundation.gestures.onDrag import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items - import androidx.compose.material.Icon -import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.* @@ -18,7 +16,6 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.SemanticsActions.OnClick import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties From b88cddf60909582e50112e78b52488e003f3ac89 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 10 Sep 2024 16:11:31 +0300 Subject: [PATCH 30/60] fix: stroke width now scales with zoom --- src/main/kotlin/viewModel/canvas/CanvasViewModel.kt | 3 ++- src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt | 6 +++++- src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index a9c2dfb..b526376 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -30,6 +30,7 @@ class CanvasViewModel( set(value) { _zoom.value = value updateVertexes() + updateEdges() } private val _center = mutableStateOf(Offset(0f, 0f)) @@ -79,7 +80,7 @@ class CanvasViewModel( } private fun updateEdges() { - + edges.forEach { it.updateEdge(zoom) } } val onScroll: AwaitPointerEventScope.(PointerEvent) -> Unit = { diff --git a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt index ab0fdda..3df074b 100644 --- a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt @@ -11,5 +11,9 @@ class EdgeCanvasViewModel( zoom: Float, val showOrientation: MutableState ) { - val strokeWidth = strokeWidth * zoom + var strokeWidth = strokeWidth * (zoom) + + fun updateEdge(zoom: Float) { + strokeWidth = 8f * zoom + } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index 872cd05..45ae177 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -55,6 +55,7 @@ class VertexCanvasViewModel( fun updateVertex() { offset = calculateOffset() radius = calculateRadius() + strokeWidth = calculateStrokeWidth() textSize = calculateTextSize() } From 5e6ee3f27c7f0697b0903d00cd2a1fe2e7399aca Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 10 Sep 2024 16:41:35 +0300 Subject: [PATCH 31/60] fix: now clustering and pagerank buttons work --- src/main/kotlin/Main.kt | 4 +- src/main/kotlin/view/MainView.kt | 13 +----- src/main/kotlin/view/MenuView.kt | 21 ++++----- src/main/kotlin/view/canvas/CanvasView.kt | 14 +++--- src/main/kotlin/viewModel/MainViewModel.kt | 11 +++++ src/main/kotlin/viewModel/MenuViewModel.kt | 39 ++++++++++++++++ .../viewModel/canvas/CanvasViewModel.kt | 46 ++++++++++++++----- .../viewModel/graph/UndirectedViewModel.kt | 9 +++- 8 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 src/main/kotlin/viewModel/MenuViewModel.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index b74ef14..bb96c56 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -15,8 +15,8 @@ import view.MenuView import viewModel.MainViewModel import viewModel.graph.UndirectedViewModel -val AMOUNT_NODES = 2 -val EDGE_CHANGE = 100 +val AMOUNT_NODES = 16 +val EDGE_CHANGE = 5.0 val graph = UndirectedGraph().apply { for (i in (0 until AMOUNT_NODES)) { diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index ce6e645..abb1927 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -16,19 +16,8 @@ val MENU_WIDTH = Config.menuWidth @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable fun MainView(mainViewModel: MainViewModel) { - var isClustering by remember { mutableStateOf(false) } - var isRanked by remember { mutableStateOf(false) } - var isNodeCreatingMode by remember { mutableStateOf(false) } - Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { - MenuView( - isNodeCreatingMode, - { isNodeCreatingMode = !isNodeCreatingMode }, - isClustering, - { isClustering = !isClustering }, - isRanked, - { isRanked = !isRanked }) - + MenuView(mainViewModel.menuViewModel) CanvasView( mainViewModel.canvasViewModel, Modifier.fillMaxSize() diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 7488d7f..7c45107 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -4,13 +4,13 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import viewModel.MenuViewModel @OptIn(ExperimentalFoundationApi::class) @Composable @@ -26,12 +26,7 @@ fun MenuIcon(name: String, description: String, modifier: Modifier = Modifier, o @Composable fun MenuView( - isNodeCreating: Boolean, - onNodeCreatingChange: () -> Unit, - isClustering: Boolean, - onClusteringChange: () -> Unit, - isRanked: Boolean, - onRankedChange: () -> Unit + viewModel: MenuViewModel ) { Column( Modifier.fillMaxHeight().width(Config.menuWidth.dp).background(color = Color(0xFF3D3D3D)), @@ -39,16 +34,16 @@ fun MenuView( ) { Spacer(Modifier.height(25f.dp)) MenuIcon( - "Nodes.svg", "Add Node", Modifier.glow(isNodeCreating) + "Nodes.svg", "Add Node", Modifier.glow(viewModel.isNodeCreating) ) { - onNodeCreatingChange() + viewModel.onNodeCreatingChange() } MenuIcon("Ribs.svg", "Add Edge", modifier = Modifier.alpha(0.2f)) - MenuIcon("Clustering.svg", "Clustering", Modifier.glow(isClustering)) { - onClusteringChange() + MenuIcon("Clustering.svg", "Clustering", Modifier.glow(viewModel.isClustering)) { + viewModel.onClusteringChange() } - MenuIcon("PageRank.svg", "Analysis graph", Modifier.glow(isRanked)) { - onRankedChange() + MenuIcon("PageRank.svg", "Analysis graph", Modifier.glow(viewModel.isRanked)) { + viewModel.onRankedChange() } } } diff --git a/src/main/kotlin/view/canvas/CanvasView.kt b/src/main/kotlin/view/canvas/CanvasView.kt index a37752f..a0f369b 100644 --- a/src/main/kotlin/view/canvas/CanvasView.kt +++ b/src/main/kotlin/view/canvas/CanvasView.kt @@ -2,6 +2,7 @@ package view.canvas import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi @@ -23,14 +24,11 @@ fun CanvasView( modifier = modifier.background(Color(0xFF242424)) .onPointerEvent(PointerEventType.Scroll, onEvent = viewModel.onScroll) .pointerInput(Unit, viewModel.onDrag) -// .pointerInput(Unit) { -// detectTapGestures { -// if (isNodeCreatingMode) { -// canvasViewModel.createVertex(it - (canvasSize / 2f), center, zoom) -// zoom += 0.000001f // костыль для рекомпозиции -// } -// } -// } + .pointerInput(Unit) { + detectTapGestures { + viewModel.createNode(it) + } + } .pointerHoverIcon(PointerIcon.Hand) .onSizeChanged { viewModel.canvasSize = Offset(it.width.toFloat(), it.height.toFloat()) diff --git a/src/main/kotlin/viewModel/MainViewModel.kt b/src/main/kotlin/viewModel/MainViewModel.kt index db1edaa..0c3676f 100644 --- a/src/main/kotlin/viewModel/MainViewModel.kt +++ b/src/main/kotlin/viewModel/MainViewModel.kt @@ -1,15 +1,26 @@ package viewModel +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import model.graph.UndirectedGraph import viewModel.canvas.CanvasViewModel class MainViewModel( graph: UndirectedGraph ) { + var isClustering = mutableStateOf(false) + var isRanked = mutableStateOf(false) + var isNodeCreatingMode = mutableStateOf(false) + val canvasViewModel = CanvasViewModel(graph) + val settingsViewModel = SettingsViewModel( canvasViewModel::onColorChange, canvasViewModel::onSizeChange, canvasViewModel::onOrientatedChange ) + + val menuViewModel = MenuViewModel(canvasViewModel) } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/MenuViewModel.kt b/src/main/kotlin/viewModel/MenuViewModel.kt new file mode 100644 index 0000000..16fa9ae --- /dev/null +++ b/src/main/kotlin/viewModel/MenuViewModel.kt @@ -0,0 +1,39 @@ +package viewModel + +import androidx.compose.runtime.mutableStateOf +import viewModel.canvas.CanvasViewModel + +class MenuViewModel( + val canvasViewModel: CanvasViewModel +) { + var isNodeCreating + get() = canvasViewModel.isNodeCreatingMode + set(value) { + canvasViewModel.isNodeCreatingMode = value + } + + fun onNodeCreatingChange() { + isNodeCreating = !isNodeCreating + } + + var isClustering + get() = canvasViewModel.isClustering + set(value) { + canvasViewModel.isClustering = value + } + + fun onClusteringChange() { + isClustering = !isClustering + } + + var isRanked + get() = canvasViewModel.isRanked + set(value) { + canvasViewModel.isRanked = value + } + + fun onRankedChange() { + isRanked = !isRanked + } + +} \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index b526376..103a0b4 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -2,18 +2,16 @@ package viewModel.canvas import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.PointerMatcher -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectDragGestures -import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.awt.awtEventOrNull import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.layout.LayoutCoordinates import model.graph.UndirectedGraph import view.HEADER_HEIGHT import view.MENU_WIDTH @@ -23,6 +21,39 @@ class CanvasViewModel( val graph: UndirectedGraph, ) { private val graphViewModel = UndirectedViewModel(graph, true) + val verticesSize = mutableStateOf(0) + + var isClustering + get() = graphViewModel.clustering + set(value) { + graphViewModel.clustering = value + } + + var isRanked + get() = graphViewModel.ranked + set(value) { + graphViewModel.ranked = value + } + + val _isNodeCreatingMode = mutableStateOf(false) + var isNodeCreatingMode + get() = _isNodeCreatingMode.value + set(value) { + _isNodeCreatingMode.value = value + } + + fun createNode(offset: Offset) { + if (isNodeCreatingMode) { + val coordinates = offset * (1 / zoom) + center + val viewModel = graphViewModel.createVertex(coordinates) ?: return + + zoom += 0.000001f // костыль для рекомпозиции + _vertices[viewModel] = VertexCanvasViewModel(viewModel, _zoom, _center, _canvasSize) + updateVertexes() + println(_vertices.size) + } + } + private val _zoom = mutableStateOf(1f) var zoom @@ -129,11 +160,4 @@ class CanvasViewModel( // // return _vertices.values // } - -// fun createVertex(offset: Offset, center: Offset, zoom: Float) { -// val coordinates = offset * (1 / zoom) + center -// val viewModel = graphViewModel.createVertex(coordinates) ?: return -// -// _vertices[viewModel] = VertexCanvasViewModel(viewModel, _zoom, center, canvasSize) -// } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index f198c97..f4783a7 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -5,14 +5,16 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.unit.dp +import model.algorithm.Clustering +import model.algorithm.PageRank import model.graph.UndirectedGraph import model.graph.Vertex class UndirectedViewModel( private val graph: UndirectedGraph, val showVerticesLabels: Boolean, - val groups: HashMap = hashMapOf(), - val ranks: List> = listOf() + var groups: HashMap = hashMapOf(), + var ranks: List> = listOf() ) { private val _vertices = hashMapOf() private val _adjacencyList = hashMapOf>() @@ -38,6 +40,7 @@ class UndirectedViewModel( get() = _clustering.value set(value) { _clustering.value = value + groups = Clustering(graph).calculate() updateColor() } @@ -45,6 +48,7 @@ class UndirectedViewModel( get() = _ranked.value set(value) { _ranked.value = value + ranks = PageRank(graph).computePageRank(3) updateSizes() } @@ -94,6 +98,7 @@ class UndirectedViewModel( _vertices.forEach { it.value.radius = size.dp } + updateSizes() } fun createVertex(coordinates: Offset): VertexViewModel? { From 762fad9d6d8d1ffcf71aa0c4251eb2e93cb1a84e Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sat, 14 Sep 2024 20:28:43 +0300 Subject: [PATCH 32/60] fix: now adding nodes button works --- .../kotlin/viewModel/canvas/CanvasViewModel.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 103a0b4..f8d6211 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -11,17 +11,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerInputScope -import androidx.compose.ui.layout.LayoutCoordinates import model.graph.UndirectedGraph import view.HEADER_HEIGHT import view.MENU_WIDTH import viewModel.graph.UndirectedViewModel +import viewModel.graph.VertexViewModel class CanvasViewModel( val graph: UndirectedGraph, ) { private val graphViewModel = UndirectedViewModel(graph, true) - val verticesSize = mutableStateOf(0) var isClustering get() = graphViewModel.clustering @@ -44,13 +43,12 @@ class CanvasViewModel( fun createNode(offset: Offset) { if (isNodeCreatingMode) { - val coordinates = offset * (1 / zoom) + center + val coordinates = (offset - (canvasSize / 2.0F)) * (1 / zoom) + center + println(offset - (canvasSize / 2.0F)) val viewModel = graphViewModel.createVertex(coordinates) ?: return - zoom += 0.000001f // костыль для рекомпозиции _vertices[viewModel] = VertexCanvasViewModel(viewModel, _zoom, _center, _canvasSize) updateVertexes() - println(_vertices.size) } } @@ -87,9 +85,13 @@ class CanvasViewModel( _isOrientated.value = value } - private val _vertices = graphViewModel.vertices.associateWith { v -> - VertexCanvasViewModel(v, _zoom, _center, _canvasSize) - }.toMutableMap() + private val _vertices = mutableStateMapOf() + + init { + graphViewModel.vertices.forEach { v -> + _vertices[v] = VertexCanvasViewModel(v, _zoom, _center, _canvasSize) + } + } private val _edges = graphViewModel.adjacencyList.map { it.value }.flatten().map { val vertex1 = From a25c617f64a493f050187a29ebbe3f4e1bc205a8 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sat, 14 Sep 2024 20:35:22 +0300 Subject: [PATCH 33/60] refactor: delete unused variables in main file --- src/main/kotlin/Main.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index bb96c56..f54609b 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -32,17 +32,12 @@ val graph = UndirectedGraph().apply { } } -val groups = Clustering(graph).calculate() -val ranks = PageRank(graph).computePageRank(3) -val undirectedViewModel = UndirectedViewModel(graph, false, groups, ranks) val mainViewModel = MainViewModel(graph) fun main() = application { var isOpen by remember { mutableStateOf(true) } var isMaximized by remember { mutableStateOf(true) } - var isMinimize by remember { mutableStateOf(false) } - var position: WindowPosition by remember { mutableStateOf(WindowPosition.PlatformDefault) } - var headerName by remember { mutableStateOf("Dimabase.db") } + val headerName by remember { mutableStateOf("Dimabase.db") } val windowState = WindowState( placement = if (isMaximized) WindowPlacement.Maximized else WindowPlacement.Floating, From 0f3d03a373ee529aef6d30591a7c9cfd10a1ad8a Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sat, 14 Sep 2024 20:36:25 +0300 Subject: [PATCH 34/60] refactor: optimize imports --- src/main/kotlin/Config.kt | 1 - src/main/kotlin/Main.kt | 11 ++++------- src/main/kotlin/model/algorithm/BellmanFord.kt | 1 - src/main/kotlin/model/algorithm/Dijkstra.kt | 4 ++-- src/main/kotlin/model/algorithm/FindBridges.kt | 4 +++- src/main/kotlin/view/MainView.kt | 6 ++++-- src/main/kotlin/view/MenuView.kt | 1 + src/main/kotlin/view/SettingsView.kt | 6 ++++-- src/main/kotlin/viewModel/MainViewModel.kt | 3 --- src/main/kotlin/viewModel/MenuViewModel.kt | 1 - .../kotlin/viewModel/canvas/VertexCanvasViewModel.kt | 1 - .../kotlin/viewModel/graph/UndirectedViewModel.kt | 1 - 12 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/Config.kt b/src/main/kotlin/Config.kt index 870b698..39b77dc 100644 --- a/src/main/kotlin/Config.kt +++ b/src/main/kotlin/Config.kt @@ -1,5 +1,4 @@ import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp object Config { val headerHeight = 40f diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index f54609b..b4355e3 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -3,17 +3,14 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.* -import model.algorithm.Clustering -import model.algorithm.PageRank +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPlacement +import androidx.compose.ui.window.WindowState +import androidx.compose.ui.window.application import model.graph.UndirectedGraph import view.HeaderView import view.MainView -import view.MenuView import viewModel.MainViewModel -import viewModel.graph.UndirectedViewModel val AMOUNT_NODES = 16 val EDGE_CHANGE = 5.0 diff --git a/src/main/kotlin/model/algorithm/BellmanFord.kt b/src/main/kotlin/model/algorithm/BellmanFord.kt index 67d72e2..ef5079d 100644 --- a/src/main/kotlin/model/algorithm/BellmanFord.kt +++ b/src/main/kotlin/model/algorithm/BellmanFord.kt @@ -3,7 +3,6 @@ package model.algorithm import model.graph.Edge import model.graph.Graph import model.graph.Vertex -import model.graph.WeightedGraph class BellmanFord(private val graph: Graph) { val parentMap = HashMap() diff --git a/src/main/kotlin/model/algorithm/Dijkstra.kt b/src/main/kotlin/model/algorithm/Dijkstra.kt index a1f3c95..2dbded8 100644 --- a/src/main/kotlin/model/algorithm/Dijkstra.kt +++ b/src/main/kotlin/model/algorithm/Dijkstra.kt @@ -1,7 +1,7 @@ package model.algorithm -import model.graph.* -import java.util.concurrent.TransferQueue +import model.graph.Graph +import model.graph.Vertex class Dijkstra(private val graph: Graph) { diff --git a/src/main/kotlin/model/algorithm/FindBridges.kt b/src/main/kotlin/model/algorithm/FindBridges.kt index 686b00e..afef70b 100644 --- a/src/main/kotlin/model/algorithm/FindBridges.kt +++ b/src/main/kotlin/model/algorithm/FindBridges.kt @@ -1,7 +1,9 @@ package model.algorithm -import model.graph.* +import model.graph.Edge +import model.graph.Graph +import model.graph.Vertex class FindBridges( diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index abb1927..77b251e 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -2,8 +2,10 @@ package view import Config import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 7c45107..1b9e8a7 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -1,5 +1,6 @@ package view +import Config import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape diff --git a/src/main/kotlin/view/SettingsView.kt b/src/main/kotlin/view/SettingsView.kt index 43b9126..5d454d1 100644 --- a/src/main/kotlin/view/SettingsView.kt +++ b/src/main/kotlin/view/SettingsView.kt @@ -3,8 +3,10 @@ package view import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material.Checkbox +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color diff --git a/src/main/kotlin/viewModel/MainViewModel.kt b/src/main/kotlin/viewModel/MainViewModel.kt index 0c3676f..ae66cc7 100644 --- a/src/main/kotlin/viewModel/MainViewModel.kt +++ b/src/main/kotlin/viewModel/MainViewModel.kt @@ -1,9 +1,6 @@ package viewModel -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import model.graph.UndirectedGraph import viewModel.canvas.CanvasViewModel diff --git a/src/main/kotlin/viewModel/MenuViewModel.kt b/src/main/kotlin/viewModel/MenuViewModel.kt index 16fa9ae..638b5d9 100644 --- a/src/main/kotlin/viewModel/MenuViewModel.kt +++ b/src/main/kotlin/viewModel/MenuViewModel.kt @@ -1,6 +1,5 @@ package viewModel -import androidx.compose.runtime.mutableStateOf import viewModel.canvas.CanvasViewModel class MenuViewModel( diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index 45ae177..836a3c5 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -3,7 +3,6 @@ package viewModel.canvas import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.unit.dp import viewModel.graph.VertexViewModel class VertexCanvasViewModel( diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index f4783a7..ce010e1 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -3,7 +3,6 @@ package viewModel.graph import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.unit.dp import model.algorithm.Clustering import model.algorithm.PageRank From d61ab86842754b9ee5d37a2a4662354366e71977 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sat, 14 Sep 2024 20:37:09 +0300 Subject: [PATCH 35/60] refactor: delete unused variables --- src/main/kotlin/Config.kt | 1 - src/main/kotlin/view/MainView.kt | 3 --- src/main/kotlin/viewModel/MainViewModel.kt | 5 ----- 3 files changed, 9 deletions(-) diff --git a/src/main/kotlin/Config.kt b/src/main/kotlin/Config.kt index 39b77dc..f0b4354 100644 --- a/src/main/kotlin/Config.kt +++ b/src/main/kotlin/Config.kt @@ -3,7 +3,6 @@ import androidx.compose.ui.graphics.Color object Config { val headerHeight = 40f val menuWidth = 80f - val optimizeCanvas = false object Edge { val color = Color(0xFF00E0FF) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index 77b251e..1ee53c1 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -1,12 +1,10 @@ package view import Config -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import view.canvas.CanvasView @@ -15,7 +13,6 @@ import viewModel.MainViewModel val HEADER_HEIGHT = Config.headerHeight val MENU_WIDTH = Config.menuWidth -@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable fun MainView(mainViewModel: MainViewModel) { Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { diff --git a/src/main/kotlin/viewModel/MainViewModel.kt b/src/main/kotlin/viewModel/MainViewModel.kt index ae66cc7..b6ea42f 100644 --- a/src/main/kotlin/viewModel/MainViewModel.kt +++ b/src/main/kotlin/viewModel/MainViewModel.kt @@ -1,16 +1,11 @@ package viewModel -import androidx.compose.runtime.mutableStateOf import model.graph.UndirectedGraph import viewModel.canvas.CanvasViewModel class MainViewModel( graph: UndirectedGraph ) { - var isClustering = mutableStateOf(false) - var isRanked = mutableStateOf(false) - var isNodeCreatingMode = mutableStateOf(false) - val canvasViewModel = CanvasViewModel(graph) val settingsViewModel = SettingsViewModel( From c492c56b4600116a9b11f4248291fdf8e6e80c56 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sat, 14 Sep 2024 20:46:14 +0300 Subject: [PATCH 36/60] refactor: seperate box style in SettingsView --- src/main/kotlin/view/SettingsView.kt | 55 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/view/SettingsView.kt b/src/main/kotlin/view/SettingsView.kt index 5d454d1..755d200 100644 --- a/src/main/kotlin/view/SettingsView.kt +++ b/src/main/kotlin/view/SettingsView.kt @@ -16,7 +16,6 @@ import components.MySlider import components.MyText import viewModel.SettingsViewModel - @Composable fun SettingsView(viewModel: SettingsViewModel) { val redSlider = remember { mutableStateOf(1f / (0xFF / 0x8F)) } @@ -28,34 +27,42 @@ fun SettingsView(viewModel: SettingsViewModel) { viewModel.onColorChange(Color(red = redSlider.value, green = greenSlider.value, blue = blueSlider.value)) viewModel.onSizeChange(sizeSlider.value) + SettingsContainer { + Row(Modifier.fillMaxWidth().padding(top = 10f.dp), horizontalArrangement = Arrangement.Center) { + MyText("Node") + } + + Row(Modifier.fillMaxWidth().padding(top = 10f.dp, start = 20f.dp)) { + Column { + MyText("Color:") + MySlider("R: ", redSlider) + MySlider("G: ", greenSlider) + MySlider("B: ", blueSlider) + MySlider("Size: ", sizeSlider, (5f..80f)) + } + } + + Row( + Modifier.fillMaxWidth().padding(start = 20f.dp), + verticalAlignment = Alignment.CenterVertically + ) { + MyText("Orientated") + Checkbox(orientatedCheckBox.value, onCheckedChange = { + viewModel.onOrientatedChange(it) + orientatedCheckBox.value = !orientatedCheckBox.value + }) + } + } +} + +@Composable +fun SettingsContainer(content: @Composable () -> Unit) { Box(Modifier.fillMaxSize().padding(top = 80f.dp, end = 20f.dp).zIndex(10f), contentAlignment = Alignment.TopEnd) { Box( Modifier.size(270f.dp, 320f.dp).background(Color(0xFF3D3D3D), RoundedCornerShape(10)) ) { Column { - Row(Modifier.fillMaxWidth().padding(top = 10f.dp), horizontalArrangement = Arrangement.Center) { - MyText("Node") - } - Row(Modifier.fillMaxWidth().padding(top = 10f.dp, start = 20f.dp)) { - Column { - MyText("Color:") - MySlider("R: ", redSlider) - MySlider("G: ", greenSlider) - MySlider("B: ", blueSlider) - MySlider("Size: ", sizeSlider, (5f..80f)) - } - } - - Row( - Modifier.fillMaxWidth().padding(start = 20f.dp), - verticalAlignment = Alignment.CenterVertically - ) { - MyText("Orientated") - Checkbox(orientatedCheckBox.value, onCheckedChange = { - viewModel.onOrientatedChange(it) - orientatedCheckBox.value = !orientatedCheckBox.value - }) - } + content() } } } From ab4358a317594fdac6ba9694583c6e06ceeefa12 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Thu, 19 Sep 2024 15:35:50 +0300 Subject: [PATCH 37/60] refactor: refactor menu, change variables initilization --- src/main/kotlin/view/MenuView.kt | 27 ++++++------- src/main/kotlin/viewModel/MenuViewModel.kt | 46 +++------------------- 2 files changed, 19 insertions(+), 54 deletions(-) diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 49914c9..3a5befc 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -86,26 +86,25 @@ fun DisplayDescription(name: String) { fun MenuView( viewModel: MenuViewModel ) { + var isNodeCreating by viewModel::isNodeCreating + var isClustering by viewModel::isClustering + var isRanked by viewModel::isRanked + var isAlgorithmMenuOpen by viewModel::isAlgorithmMenuOpen + Column( Modifier.fillMaxHeight().width(Config.menuWidth.dp).background(color = Color(0xFF3D3D3D)), horizontalAlignment = Alignment.CenterHorizontally, ) { Spacer(Modifier.height(25f.dp)) - MenuIcon( - "Nodes.svg", "AddNode.svg", Modifier.glow(viewModel.isNodeCreating) - ) { - viewModel.onNodeCreatingChange() - } + MenuIcon("Nodes.svg", "AddNode.svg", Modifier.glow(isNodeCreating)) { isNodeCreating = !isNodeCreating } MenuIcon("Ribs.svg", "AddEdge.svg", modifier = Modifier.alpha(0.2f)) - MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glow(viewModel.isClustering)) { - viewModel.onClusteringChange() - } - MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glow(viewModel.isRanked)) { - viewModel.onRankedChange() - } - MenuIcon("Algorithm.svg", "Algorithms....svg", Modifier.glow(viewModel.isAlgorithmMenuOpen)) { - viewModel.onAlgorithmMenuOpenChange() - } + MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glow(isClustering)) { isClustering = !isClustering } + MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glow(viewModel.isRanked)) { isRanked = !isRanked } + MenuIcon( + "Algorithm.svg", + "Algorithms....svg", + Modifier.glow(viewModel.isAlgorithmMenuOpen) + ) { isAlgorithmMenuOpen = !isAlgorithmMenuOpen } } } diff --git a/src/main/kotlin/viewModel/MenuViewModel.kt b/src/main/kotlin/viewModel/MenuViewModel.kt index 8279cbf..de14aca 100644 --- a/src/main/kotlin/viewModel/MenuViewModel.kt +++ b/src/main/kotlin/viewModel/MenuViewModel.kt @@ -1,49 +1,15 @@ package viewModel +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import viewModel.canvas.CanvasViewModel class MenuViewModel( val canvasViewModel: CanvasViewModel ) { - var isNodeCreating - get() = canvasViewModel.isNodeCreatingMode - set(value) { - canvasViewModel.isNodeCreatingMode = value - } - - fun onNodeCreatingChange() { - isNodeCreating = !isNodeCreating - } - - var isClustering - get() = canvasViewModel.isClustering - set(value) { - canvasViewModel.isClustering = value - } - - fun onClusteringChange() { - isClustering = !isClustering - } - - var isRanked - get() = canvasViewModel.isRanked - set(value) { - canvasViewModel.isRanked = value - } - - fun onRankedChange() { - isRanked = !isRanked - } - - val _isAlgorithmMenuOpen = mutableStateOf(false) - var isAlgorithmMenuOpen - get() = _isAlgorithmMenuOpen.value - set(value) { - _isAlgorithmMenuOpen.value = value - } - - fun onAlgorithmMenuOpenChange() { - isAlgorithmMenuOpen = !isAlgorithmMenuOpen - } + var isNodeCreating by canvasViewModel::isNodeCreatingMode + var isClustering by canvasViewModel::isClustering + var isRanked by canvasViewModel::isRanked + var isAlgorithmMenuOpen by mutableStateOf(false) } \ No newline at end of file From 4384fb96a77a52b5bc099d7250b4c1d9252faf8d Mon Sep 17 00:00:00 2001 From: Homka122 Date: Thu, 19 Sep 2024 16:02:04 +0300 Subject: [PATCH 38/60] refactor: refactor vertecies' state initilization --- .../viewModel/canvas/CanvasViewModel.kt | 9 ---- .../viewModel/canvas/VertexCanvasViewModel.kt | 54 ++++--------------- .../kotlin/viewModel/graph/VertexViewModel.kt | 33 +++--------- 3 files changed, 15 insertions(+), 81 deletions(-) diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index f8d6211..8d0c80a 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -48,7 +48,6 @@ class CanvasViewModel( val viewModel = graphViewModel.createVertex(coordinates) ?: return _vertices[viewModel] = VertexCanvasViewModel(viewModel, _zoom, _center, _canvasSize) - updateVertexes() } } @@ -58,7 +57,6 @@ class CanvasViewModel( get() = _zoom.value set(value) { _zoom.value = value - updateVertexes() updateEdges() } @@ -67,7 +65,6 @@ class CanvasViewModel( get() = _center.value set(value) { _center.value = value - updateVertexes() } private val _canvasSize = mutableStateOf(Offset(400f, 400f)) @@ -75,7 +72,6 @@ class CanvasViewModel( get() = _canvasSize.value set(value) { _canvasSize.value = value - updateVertexes() } private val _isOrientated = mutableStateOf(false) @@ -108,10 +104,6 @@ class CanvasViewModel( val edges get() = _edges - private fun updateVertexes() { - vertices.forEach { it.updateVertex() } - } - private fun updateEdges() { edges.forEach { it.updateEdge(zoom) } } @@ -148,7 +140,6 @@ class CanvasViewModel( fun onSizeChange(newSize: Float) { graphViewModel.onSizeChange(newSize) - updateVertexes() } fun onOrientatedChange(isOrientated: Boolean) { diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index 836a3c5..da6cfc2 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -11,59 +11,23 @@ class VertexCanvasViewModel( private val center: MutableState, private val canvasSize: MutableState ) { - private val _offset = mutableStateOf( - calculateOffset() - ) - var offset - get() = _offset.value - set(value) { - _offset.value = value - } - - private val _radius = mutableStateOf(calculateRadius()) - var radius - get() = _radius.value - set(value) { - _radius.value = value - } - - var color - get() = vertexViewModel.color - set(value) { - vertexViewModel.color = value - } - - private val _strokeWidth = mutableStateOf(calculateStrokeWidth()) - var strokeWidth - get() = _strokeWidth.value - set(value) { - _strokeWidth.value = value - } + var color by vertexViewModel::color - private val _textSize = mutableStateOf(calculateTextSize()) - var textSize - get() = _textSize.value - set(value) { - _textSize.value = value - } + val strokeWidth + get() = 8f * zoom.value + val radius + get() = vertexViewModel.radius * zoom.value + val offset + get() = calculateOffset() + val textSize + get() = vertexViewModel.radius * 0.6f * zoom.value fun onDrag(it: Offset): Unit { vertexViewModel.onDrag(it * (1f / zoom.value)) } - fun updateVertex() { - offset = calculateOffset() - radius = calculateRadius() - strokeWidth = calculateStrokeWidth() - textSize = calculateTextSize() - } - private fun calculateOffset() = Offset( (canvasSize.value.x / 2) + ((vertexViewModel.x - center.value.x) * zoom.value), (canvasSize.value.y / 2) + ((vertexViewModel.y - center.value.y) * zoom.value) ) - - private fun calculateRadius() = vertexViewModel.radius * zoom.value - private fun calculateStrokeWidth() = 8f * zoom.value - private fun calculateTextSize() = vertexViewModel.radius * 0.6f * zoom.value } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/graph/VertexViewModel.kt b/src/main/kotlin/viewModel/graph/VertexViewModel.kt index 2806f7f..cdde6bb 100644 --- a/src/main/kotlin/viewModel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewModel/graph/VertexViewModel.kt @@ -1,6 +1,8 @@ package viewModel.graph +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp @@ -15,33 +17,10 @@ class VertexViewModel( color: Color = Color.Black, radius: Dp = 8f.dp, ) { - private val _x = mutableStateOf(x) - private val _y = mutableStateOf(y) - private val _color = mutableStateOf(color) - private val _radius = mutableStateOf(radius) - - var x: Float - get() = _x.value - set(value) { - _x.value = value - } - - var y: Float - get() = _y.value - set(value) { - _y.value = value - } - - var color: Color - get() = _color.value - set(value) { - _color.value = value - } - var radius: Dp - get() = _radius.value - set(value) { - _radius.value = value - } + var x by mutableStateOf(x) + var y by mutableStateOf(y) + var color by mutableStateOf(color) + var radius by mutableStateOf(radius) val label get() = vertex.key.toString() From 17ff642c37e1886a26d6638b96b5185319646232 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Thu, 19 Sep 2024 17:07:39 +0300 Subject: [PATCH 39/60] refactor: refactor canvas, edge, vertex state's initialization --- src/main/kotlin/view/canvas/EdgeCanvasView.kt | 2 +- .../viewModel/canvas/CanvasViewModel.kt | 78 ++++--------------- .../viewModel/canvas/EdgeCanvasViewModel.kt | 14 ++-- .../viewModel/canvas/VertexCanvasViewModel.kt | 22 +++--- .../kotlin/viewModel/graph/EdgeViewModel.kt | 17 +--- .../viewModel/graph/UndirectedViewModel.kt | 10 +-- 6 files changed, 40 insertions(+), 103 deletions(-) diff --git a/src/main/kotlin/view/canvas/EdgeCanvasView.kt b/src/main/kotlin/view/canvas/EdgeCanvasView.kt index ae3da58..e4679a1 100644 --- a/src/main/kotlin/view/canvas/EdgeCanvasView.kt +++ b/src/main/kotlin/view/canvas/EdgeCanvasView.kt @@ -44,7 +44,7 @@ fun EdgeCanvasView( ) } - if (viewModel.showOrientation.value) { + if (viewModel.showOrientation) { drawLine( start = end, end = end - rotateVector(radiusVectorSecond * 0.8f, 30.0), diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 8d0c80a..e75c0a4 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -3,8 +3,10 @@ package viewModel.canvas import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.PointerMatcher import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.awt.awtEventOrNull import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color @@ -22,24 +24,16 @@ class CanvasViewModel( ) { private val graphViewModel = UndirectedViewModel(graph, true) - var isClustering - get() = graphViewModel.clustering - set(value) { - graphViewModel.clustering = value - } - - var isRanked - get() = graphViewModel.ranked - set(value) { - graphViewModel.ranked = value - } + var isClustering by graphViewModel::clustering + var isRanked by graphViewModel::ranked - val _isNodeCreatingMode = mutableStateOf(false) - var isNodeCreatingMode - get() = _isNodeCreatingMode.value - set(value) { - _isNodeCreatingMode.value = value - } + var isNodeCreatingMode by mutableStateOf(false) + var zoom by mutableStateOf(1f) + var center by mutableStateOf(Offset(0f, 0f)) + var canvasSize by mutableStateOf(Offset(400f, 400f)) + var isOrientated by mutableStateOf(false) + + private val _vertices = mutableStateMapOf() fun createNode(offset: Offset) { if (isNodeCreatingMode) { @@ -47,45 +41,13 @@ class CanvasViewModel( println(offset - (canvasSize / 2.0F)) val viewModel = graphViewModel.createVertex(coordinates) ?: return - _vertices[viewModel] = VertexCanvasViewModel(viewModel, _zoom, _center, _canvasSize) + _vertices[viewModel] = VertexCanvasViewModel(viewModel, this) } } - - private val _zoom = mutableStateOf(1f) - var zoom - get() = _zoom.value - set(value) { - _zoom.value = value - updateEdges() - } - - private val _center = mutableStateOf(Offset(0f, 0f)) - var center - get() = _center.value - set(value) { - _center.value = value - } - - private val _canvasSize = mutableStateOf(Offset(400f, 400f)) - var canvasSize - get() = _canvasSize.value - set(value) { - _canvasSize.value = value - } - - private val _isOrientated = mutableStateOf(false) - var isOrientated - get() = _isOrientated.value - set(value) { - _isOrientated.value = value - } - - private val _vertices = mutableStateMapOf() - init { graphViewModel.vertices.forEach { v -> - _vertices[v] = VertexCanvasViewModel(v, _zoom, _center, _canvasSize) + _vertices[v] = VertexCanvasViewModel(v, this) } } @@ -95,7 +57,7 @@ class CanvasViewModel( val vertex2 = _vertices[it.second] ?: throw IllegalStateException("There is no VertexCanvasViewModel for ${it.second}") - EdgeCanvasViewModel(vertex1, vertex2, it.color, it.strokeWidth, zoom, _isOrientated) + EdgeCanvasViewModel(vertex1, vertex2, it.color, it, this) } val vertices @@ -104,10 +66,6 @@ class CanvasViewModel( val edges get() = _edges - private fun updateEdges() { - edges.forEach { it.updateEdge(zoom) } - } - val onScroll: AwaitPointerEventScope.(PointerEvent) -> Unit = { if (it.changes.first().scrollDelta.y > 0) { zoom -= zoom / 8 @@ -145,12 +103,4 @@ class CanvasViewModel( fun onOrientatedChange(isOrientated: Boolean) { this.isOrientated = isOrientated } - -// fun getViews(): Collection { -// if (Config.optimizeCanvas) { -// return _vertices.filter { abs(it.value.offset.x) < canvasSize.x && abs(it.value.offset.y) < canvasSize.y }.values -// } -// -// return _vertices.values -// } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt index 3df074b..b94acdb 100644 --- a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt @@ -1,19 +1,17 @@ package viewModel.canvas -import androidx.compose.runtime.MutableState import androidx.compose.ui.graphics.Color +import viewModel.graph.EdgeViewModel class EdgeCanvasViewModel( val first: VertexCanvasViewModel, val second: VertexCanvasViewModel, val color: Color, - strokeWidth: Float, - zoom: Float, - val showOrientation: MutableState + val edgeViewModel: EdgeViewModel, + private val canvasViewModel: CanvasViewModel, ) { - var strokeWidth = strokeWidth * (zoom) + var showOrientation by canvasViewModel::isOrientated - fun updateEdge(zoom: Float) { - strokeWidth = 8f * zoom - } + val strokeWidth + get() = edgeViewModel.strokeWidth * canvasViewModel.zoom } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index da6cfc2..3fbbd96 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -1,33 +1,33 @@ package viewModel.canvas -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.geometry.Offset import viewModel.graph.VertexViewModel class VertexCanvasViewModel( val vertexViewModel: VertexViewModel, - private val zoom: MutableState, - private val center: MutableState, - private val canvasSize: MutableState + private val canvasViewModel: CanvasViewModel, ) { var color by vertexViewModel::color + private var zoom by canvasViewModel::zoom + private var center by canvasViewModel::center + private var canvasSize by canvasViewModel::canvasSize val strokeWidth - get() = 8f * zoom.value + get() = 8f * zoom val radius - get() = vertexViewModel.radius * zoom.value + get() = vertexViewModel.radius * zoom val offset get() = calculateOffset() + val textSize - get() = vertexViewModel.radius * 0.6f * zoom.value + get() = vertexViewModel.radius * 0.6f * zoom fun onDrag(it: Offset): Unit { - vertexViewModel.onDrag(it * (1f / zoom.value)) + vertexViewModel.onDrag(it * (1f / zoom)) } private fun calculateOffset() = Offset( - (canvasSize.value.x / 2) + ((vertexViewModel.x - center.value.x) * zoom.value), - (canvasSize.value.y / 2) + ((vertexViewModel.y - center.value.y) * zoom.value) + (canvasSize.x / 2) + ((vertexViewModel.x - center.x) * zoom), + (canvasSize.y / 2) + ((vertexViewModel.y - center.y) * zoom) ) } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/graph/EdgeViewModel.kt b/src/main/kotlin/viewModel/graph/EdgeViewModel.kt index cca76b4..3b3027b 100644 --- a/src/main/kotlin/viewModel/graph/EdgeViewModel.kt +++ b/src/main/kotlin/viewModel/graph/EdgeViewModel.kt @@ -2,7 +2,9 @@ package viewModel.graph import Config import androidx.compose.runtime.State +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color import model.graph.Edge @@ -22,17 +24,6 @@ class EdgeViewModel( edge.weight = value } - private var _color = mutableStateOf(color) - var color - get() = _color.value - set(value) { - _color.value = value - } - - private var _strokeWidth = mutableStateOf(strokeWidth) - var strokeWidth - get() = _strokeWidth.value - set(value) { - _strokeWidth.value = value - } + var color by mutableStateOf(color) + var strokeWidth by mutableStateOf(strokeWidth) } diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index ce010e1..d24069e 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -1,6 +1,8 @@ package viewModel.graph +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @@ -18,16 +20,12 @@ class UndirectedViewModel( private val _vertices = hashMapOf() private val _adjacencyList = hashMapOf>() private val groupColors = hashMapOf(0 to Color.Black) + private val _color = mutableStateOf(Color.Black) - private val _size = mutableStateOf(10f) private val _clustering = mutableStateOf(false) private val _ranked = mutableStateOf(false) - private var size - get() = _size.value - set(value) { - _size.value = value - } + private var size by mutableStateOf(10f) val vertices get() = _vertices.values From 52d519766fda38a03caa777826ff33a0fb6f11e1 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Sun, 22 Sep 2024 20:07:03 +0300 Subject: [PATCH 40/60] refactor: refactor modelView of canvas and vertex --- .../kotlin/view/canvas/VertexCanvasView.kt | 2 +- .../viewModel/canvas/CanvasViewModel.kt | 3 +-- .../viewModel/canvas/VertexCanvasViewModel.kt | 20 +++++++++---------- .../viewModel/graph/UndirectedViewModel.kt | 4 +--- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/view/canvas/VertexCanvasView.kt b/src/main/kotlin/view/canvas/VertexCanvasView.kt index 4be14f9..9c39f6c 100644 --- a/src/main/kotlin/view/canvas/VertexCanvasView.kt +++ b/src/main/kotlin/view/canvas/VertexCanvasView.kt @@ -31,6 +31,6 @@ fun VertexCanvasView( .onDrag(onDrag = viewModel::onDrag), contentAlignment = Alignment.Center ) { - MyText(viewModel.vertexViewModel.label, viewModel.textSize.value) + MyText(viewModel.label, viewModel.textSize.value) } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index e75c0a4..669751c 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -32,13 +32,12 @@ class CanvasViewModel( var center by mutableStateOf(Offset(0f, 0f)) var canvasSize by mutableStateOf(Offset(400f, 400f)) var isOrientated by mutableStateOf(false) - + private val _vertices = mutableStateMapOf() fun createNode(offset: Offset) { if (isNodeCreatingMode) { val coordinates = (offset - (canvasSize / 2.0F)) * (1 / zoom) + center - println(offset - (canvasSize / 2.0F)) val viewModel = graphViewModel.createVertex(coordinates) ?: return _vertices[viewModel] = VertexCanvasViewModel(viewModel, this) diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index 3fbbd96..b4f993a 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -4,30 +4,28 @@ import androidx.compose.ui.geometry.Offset import viewModel.graph.VertexViewModel class VertexCanvasViewModel( - val vertexViewModel: VertexViewModel, + private val vertexViewModel: VertexViewModel, private val canvasViewModel: CanvasViewModel, ) { - var color by vertexViewModel::color - private var zoom by canvasViewModel::zoom - private var center by canvasViewModel::center - private var canvasSize by canvasViewModel::canvasSize + val color by vertexViewModel::color + val label by vertexViewModel::label val strokeWidth - get() = 8f * zoom + get() = 8f * canvasViewModel.zoom val radius - get() = vertexViewModel.radius * zoom + get() = vertexViewModel.radius * canvasViewModel.zoom val offset get() = calculateOffset() val textSize - get() = vertexViewModel.radius * 0.6f * zoom + get() = vertexViewModel.radius * 0.6f * canvasViewModel.zoom fun onDrag(it: Offset): Unit { - vertexViewModel.onDrag(it * (1f / zoom)) + vertexViewModel.onDrag(it * (1f / canvasViewModel.zoom)) } private fun calculateOffset() = Offset( - (canvasSize.x / 2) + ((vertexViewModel.x - center.x) * zoom), - (canvasSize.y / 2) + ((vertexViewModel.y - center.y) * zoom) + (canvasViewModel.canvasSize.x / 2) + ((vertexViewModel.x - canvasViewModel.center.x) * canvasViewModel.zoom), + (canvasViewModel.canvasSize.y / 2) + ((vertexViewModel.y - canvasViewModel.center.y) * canvasViewModel.zoom) ) } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index d24069e..d7a26a3 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -99,9 +99,7 @@ class UndirectedViewModel( } fun createVertex(coordinates: Offset): VertexViewModel? { - val vertex = graph.addVertex(graph.vertices.last().key + 1) - - if (vertex == null) return null + val vertex = graph.addVertex(graph.vertices.last().key + 1) ?: return null val viewModel = VertexViewModel( showVerticesLabels, From 6e5978a6f781c839726ffc63ebc5475ad6538d12 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Mon, 23 Sep 2024 18:22:56 +0300 Subject: [PATCH 41/60] feat: add Neo4jReader --- build.gradle.kts | 6 +- src/main/kotlin/model/reader/Neo4jReader.kt | 106 ++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/model/reader/Neo4jReader.kt diff --git a/build.gradle.kts b/build.gradle.kts index 72f8d30..7032eb0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.1") implementation("org.xerial:sqlite-jdbc:3.41.2.2") - + implementation("org.neo4j.driver", "neo4j-java-driver", "5.6.0") } compose.desktop { @@ -49,9 +49,9 @@ tasks.test { finalizedBy("jacocoTestReport") } -tasks.jacocoTestReport{ +tasks.jacocoTestReport { dependsOn(tasks.test) - reports{ + reports { xml.required.set(true) html.required.set(true) } diff --git a/src/main/kotlin/model/reader/Neo4jReader.kt b/src/main/kotlin/model/reader/Neo4jReader.kt new file mode 100644 index 0000000..0baf0c7 --- /dev/null +++ b/src/main/kotlin/model/reader/Neo4jReader.kt @@ -0,0 +1,106 @@ +package model.reader + +import model.graph.Edge +import model.graph.Graph +import model.graph.UndirectedGraph +import model.graph.Vertex +import org.neo4j.driver.AuthTokens +import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.Transaction + +class Neo4jReader(uri: String, user: String, password: String) : Reader { + + private val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) + private val session = driver.session() + + private fun createNode(node: Vertex, graphName: String, txInput: Transaction?) { + val tx = txInput ?: session.beginTransaction() + + tx.run( + "MERGE (n:Node {graphName: \$graphName, key: \$key})", + mapOf("key" to node.key, "graphName" to graphName) + ) + + if (txInput == null) { + tx.commit() + tx.close() + } + } + + private fun createEdge(edge: Edge, nameGraph: String, txInput: Transaction?) { + val tx = txInput ?: session.beginTransaction() + + tx.run( + "MERGE (v1:Node {graphName: \$graphName, key: \$key1})" + + "MERGE (v2:Node {graphName: \$graphName, key: \$key2})" + + "MERGE (v1)-[:DIRECTED_TO {weight: \$weight}]->(v2)", + mapOf( + "key1" to edge.first.key, + "key2" to edge.second.key, + "weight" to edge.weight, + "graphName" to nameGraph + ) + ) + + if (txInput == null) { + tx.commit() + tx.close() + } + } + + private fun deleteGraph(graphName: String, txInput: Transaction?) { + val tx = txInput ?: session.beginTransaction() + + tx.run( + "MATCH (n:Node {graphName: \$graphName}) DETACH DELETE n", + mapOf( + "graphName" to graphName + ) + ) + + if (txInput == null) { + tx.commit() + tx.close() + } + } + + override fun saveGraph(graph: Graph, filepath: String, nameGraph: String) { + val transaction = session.beginTransaction() + + deleteGraph(nameGraph, transaction) + + graph.vertices.forEach { v -> + createNode(v, nameGraph, transaction) + + graph.adjacencyList[v]?.forEach { e -> + createEdge(e, nameGraph, transaction) + } + } + + transaction.commit() + } + + override fun loadGraph(filepath: String, nameGraph: String): Graph { + val graph = UndirectedGraph() + + session.executeRead { tx -> + tx.run("MATCH (n:Node {graphName: \$graphName}) return n", mapOf("graphName" to nameGraph)) + .forEach { v -> graph.addVertex((v.get("n").get("key").asInt())) } + + tx.run( + "MATCH p=(v1: Node {graphName: \$graphName})-[r]-(v2: Node {graphName: \$graphName}) return v1, v2, r", + mapOf("graphName" to nameGraph) + ).forEach { v -> + val values = v.values() + println(v) + graph.addEdge( + values[0].get("key").asInt(), + values[1].get("key").asInt(), + values[2].get("weight").asLong() + ) + } + } + + return graph + } +} \ No newline at end of file From 7aaa4db554417e7cf729a1bfb45b89114a49f615 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Mon, 23 Sep 2024 18:42:14 +0300 Subject: [PATCH 42/60] refactor: delete println in the method --- src/main/kotlin/model/reader/Neo4jReader.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/model/reader/Neo4jReader.kt b/src/main/kotlin/model/reader/Neo4jReader.kt index 0baf0c7..42e4ca4 100644 --- a/src/main/kotlin/model/reader/Neo4jReader.kt +++ b/src/main/kotlin/model/reader/Neo4jReader.kt @@ -92,7 +92,6 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { mapOf("graphName" to nameGraph) ).forEach { v -> val values = v.values() - println(v) graph.addEdge( values[0].get("key").asInt(), values[1].get("key").asInt(), From 7a01dc500c781c8398c77cb94812b9b194ea4336 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Mon, 23 Sep 2024 19:05:53 +0300 Subject: [PATCH 43/60] fix: close transaction after exiting from method --- src/main/kotlin/model/reader/Neo4jReader.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/model/reader/Neo4jReader.kt b/src/main/kotlin/model/reader/Neo4jReader.kt index 42e4ca4..e42cef7 100644 --- a/src/main/kotlin/model/reader/Neo4jReader.kt +++ b/src/main/kotlin/model/reader/Neo4jReader.kt @@ -78,6 +78,7 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { } transaction.commit() + transaction.close() } override fun loadGraph(filepath: String, nameGraph: String): Graph { From 6da6362993b0d5571022cb5caa9ed9127d7602d6 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Mon, 23 Sep 2024 23:14:14 +0300 Subject: [PATCH 44/60] feat: add type to graph --- src/main/kotlin/model/reader/Neo4jReader.kt | 39 ++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/model/reader/Neo4jReader.kt b/src/main/kotlin/model/reader/Neo4jReader.kt index e42cef7..1b94402 100644 --- a/src/main/kotlin/model/reader/Neo4jReader.kt +++ b/src/main/kotlin/model/reader/Neo4jReader.kt @@ -1,9 +1,6 @@ package model.reader -import model.graph.Edge -import model.graph.Graph -import model.graph.UndirectedGraph -import model.graph.Vertex +import model.graph.* import org.neo4j.driver.AuthTokens import org.neo4j.driver.GraphDatabase import org.neo4j.driver.Transaction @@ -57,6 +54,12 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { "graphName" to graphName ) ) + tx.run( + "MATCH (g:Graph {graphName: \$graphName}) DETACH DELETE g", + mapOf( + "graphName" to graphName + ) + ) if (txInput == null) { tx.commit() @@ -69,6 +72,21 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { deleteGraph(nameGraph, transaction) + val graphType: String = when (graph) { + is WeightedDirectedGraph -> "WeightedUndirected" + is WeightedGraph -> "Weighted" + is DirectedGraph -> "Directed" + else -> "Undirected" + } + + transaction.run( + "MERGE (g:Graph {graphName: \$graphName, type: \$graphType})", + mapOf( + "graphName" to nameGraph, + "graphType" to graphType + ) + ) + graph.vertices.forEach { v -> createNode(v, nameGraph, transaction) @@ -82,9 +100,20 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { } override fun loadGraph(filepath: String, nameGraph: String): Graph { - val graph = UndirectedGraph() + var graph: Graph = UndirectedGraph() session.executeRead { tx -> + val graphType = + tx.run("MATCH (g:Graph {graphName: \$graphName}) return g", mapOf("graphName" to nameGraph)).single() + .get("g").get("type").asString() + + graph = when (graphType) { + "Undirected" -> UndirectedGraph() + "Directed" -> DirectedGraph() + "Weighted" -> WeightedGraph() + else -> WeightedDirectedGraph() + } + tx.run("MATCH (n:Node {graphName: \$graphName}) return n", mapOf("graphName" to nameGraph)) .forEach { v -> graph.addVertex((v.get("n").get("key").asInt())) } From 2587a9e98a7696bcbccf55101a8a9da3a2ac2db0 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Mon, 23 Sep 2024 23:21:17 +0300 Subject: [PATCH 45/60] docs: add comments to Neo4jReader --- src/main/kotlin/model/reader/Neo4jReader.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/kotlin/model/reader/Neo4jReader.kt b/src/main/kotlin/model/reader/Neo4jReader.kt index 1b94402..a37808e 100644 --- a/src/main/kotlin/model/reader/Neo4jReader.kt +++ b/src/main/kotlin/model/reader/Neo4jReader.kt @@ -67,6 +67,9 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { } } + /** + * Save graph to Neo4j Database + */ override fun saveGraph(graph: Graph, filepath: String, nameGraph: String) { val transaction = session.beginTransaction() @@ -99,6 +102,12 @@ class Neo4jReader(uri: String, user: String, password: String) : Reader { transaction.close() } + /** + * Load graph to Neo4j Database + * + * @return the loaded graph + * @throws NoSuchRecordException if there is no graph with given graph name + */ override fun loadGraph(filepath: String, nameGraph: String): Graph { var graph: Graph = UndirectedGraph() From 439d948b89e1313d245cbef0a89034de109474c1 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Mon, 23 Sep 2024 23:27:59 +0300 Subject: [PATCH 46/60] feat: add tests for Neo4jReader --- .../kotlin/model/reader/Neo4jReaderTest.kt | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/test/kotlin/model/reader/Neo4jReaderTest.kt diff --git a/src/test/kotlin/model/reader/Neo4jReaderTest.kt b/src/test/kotlin/model/reader/Neo4jReaderTest.kt new file mode 100644 index 0000000..85992ef --- /dev/null +++ b/src/test/kotlin/model/reader/Neo4jReaderTest.kt @@ -0,0 +1,129 @@ +package model.reader + +import model.algorithm.Dijkstra +import model.graph.Graph +import model.graph.UndirectedGraph +import model.graph.Vertex +import model.graph.WeightedGraph +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.neo4j.driver.exceptions.NoSuchRecordException +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class Neo4jReaderTest { + lateinit var testGraph: Graph + val neo4jReader = Neo4jReader("bolt://localhost:7687", "neo4j", "qwertyui") + val testGraphName = "testGraph" + + private fun isSameNodes(graph1: Graph, graph2: Graph): Boolean = + graph1.vertices.sortedBy { v -> v.key } == graph2.vertices.sortedBy { v -> v.key } + + private fun isSameEdges(graph1: Graph, graph2: Graph): Boolean { + listOf(graph1, graph2).forEach { graph -> + graph.vertices.forEach { v -> + val graph1EdgeNodesWithWeights = + graph1.adjacencyList[v]?.map { edge -> Pair(edge.second, edge.weight) } + ?.sortedBy { node -> node.first.key } + + val graph2EdgeNodesWithWeights = + graph2.adjacencyList[v]?.map { edge -> Pair(edge.second, edge.weight) } + ?.sortedBy { node -> node.first.key } + + if (graph1EdgeNodesWithWeights != graph2EdgeNodesWithWeights) return false + } + } + + return true + } + + @Nested + inner class `Save and load graph` { + @BeforeEach + fun setup() { + testGraph = WeightedGraph() + } + + @Test + fun `save and load empty graph one time`() { + neo4jReader.saveGraph(testGraph, "", testGraphName) + val graph = neo4jReader.loadGraph("", testGraphName) + + assertEquals(graph.vertices.size, testGraph.vertices.size) + assertEquals(graph.adjacencyList.values.size, testGraph.adjacencyList.size) + } + + @Test + fun `save and load empty graph 100 times in a row`() { + var graph: Graph = testGraph + + for (i in 1..100) { + neo4jReader.saveGraph(testGraph, "", testGraphName) + graph = neo4jReader.loadGraph("", testGraphName) + } + + assertEquals(graph.vertices.size, testGraph.vertices.size) + assertEquals(graph.adjacencyList.values.size, testGraph.adjacencyList.size) + } + + @Test + fun `save and load non-empty graph one time`() { + for (i in 1..5) { + testGraph.addVertex(i) + } + testGraph.addEdge(1, 2, 2) + testGraph.addEdge(2, 5, 4) + testGraph.addEdge(1, 4, 4) + testGraph.addEdge(4, 2, 1) + testGraph.addEdge(1, 3, 3) + testGraph.addEdge(4, 5, 1) + testGraph.addEdge(3, 5, 5) + + neo4jReader.saveGraph(testGraph, "", testGraphName) + val graph = neo4jReader.loadGraph("", testGraphName) + + assertEquals(graph.vertices.size, testGraph.vertices.size) + assertEquals(graph.adjacencyList.size, testGraph.adjacencyList.size) + + assertTrue(isSameNodes(graph, testGraph)) + assertTrue(isSameEdges(graph, testGraph)) + } + + @Test + fun `save and load non-empty graph 100 times in a row`() { + for (i in 1..5) { + testGraph.addVertex(i) + } + testGraph.addEdge(1, 2, 2) + testGraph.addEdge(2, 5, 4) + testGraph.addEdge(1, 4, 4) + testGraph.addEdge(4, 2, 1) + testGraph.addEdge(1, 3, 3) + testGraph.addEdge(4, 5, 1) + testGraph.addEdge(3, 5, 5) + + var graph = testGraph + for (i in 1..100) { + neo4jReader.saveGraph(testGraph, "", testGraphName) + graph = neo4jReader.loadGraph("", testGraphName) + + } + assertEquals(graph.vertices.size, testGraph.vertices.size) + assertEquals(graph.adjacencyList.size, testGraph.adjacencyList.size) + + assertTrue(isSameNodes(graph, testGraph)) + assertTrue(isSameEdges(graph, testGraph)) + } + + @Test + fun `load graph that don't exist in DB`() { + try { + neo4jReader.loadGraph("", "Homka") + } catch (_: NoSuchRecordException) { + + } + } + } +} \ No newline at end of file From ffd989b81f374e60df1b692a4b5f54c0ec26daaf Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 00:26:32 +0300 Subject: [PATCH 47/60] feat: add methods for change edge's color --- .../viewModel/canvas/CanvasViewModel.kt | 33 +++++++++++++++---- .../viewModel/canvas/EdgeCanvasViewModel.kt | 3 +- .../kotlin/viewModel/graph/EdgeViewModel.kt | 2 +- .../viewModel/graph/UndirectedViewModel.kt | 30 +++++++++++++++++ 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 669751c..537f4fb 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -13,9 +13,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerInputScope +import model.graph.Edge import model.graph.UndirectedGraph import view.HEADER_HEIGHT import view.MENU_WIDTH +import viewModel.graph.EdgeViewModel import viewModel.graph.UndirectedViewModel import viewModel.graph.VertexViewModel @@ -35,6 +37,10 @@ class CanvasViewModel( private val _vertices = mutableStateMapOf() + private fun getVertex(vm: VertexViewModel): VertexCanvasViewModel { + return _vertices[vm] ?: throw IllegalArgumentException("There is no VertexCanvasViewModel for $vm") + } + fun createNode(offset: Offset) { if (isNodeCreatingMode) { val coordinates = (offset - (canvasSize / 2.0F)) * (1 / zoom) + center @@ -50,20 +56,22 @@ class CanvasViewModel( } } - private val _edges = graphViewModel.adjacencyList.map { it.value }.flatten().map { - val vertex1 = - _vertices[it.first] ?: throw IllegalStateException("There is no VertexCanvasViewModel for ${it.first}") - val vertex2 = - _vertices[it.second] ?: throw IllegalStateException("There is no VertexCanvasViewModel for ${it.second}") + private val _edges = graphViewModel.adjacencyList.mapValues { + it.value.map { edgeViewModel -> + val vertex1 = getVertex(edgeViewModel.first) + val vertex2 = getVertex(edgeViewModel.second) - EdgeCanvasViewModel(vertex1, vertex2, it.color, it, this) + EdgeCanvasViewModel(vertex1, vertex2, edgeViewModel, this) + } + }.mapKeys { + getVertex(it.key) } val vertices get() = _vertices.values val edges - get() = _edges + get() = _edges.values.flatten() val onScroll: AwaitPointerEventScope.(PointerEvent) -> Unit = { if (it.changes.first().scrollDelta.y > 0) { @@ -102,4 +110,15 @@ class CanvasViewModel( fun onOrientatedChange(isOrientated: Boolean) { this.isOrientated = isOrientated } + + /* + * Change edges' color + * */ + fun changeEdgesColor(edges: List>) { + graphViewModel.changeEdgesColor(edges) + } + + fun resetEdgesColorToDefault() { + graphViewModel.resetEdgesColorToDefault() + } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt index b94acdb..372a79b 100644 --- a/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/EdgeCanvasViewModel.kt @@ -1,16 +1,15 @@ package viewModel.canvas -import androidx.compose.ui.graphics.Color import viewModel.graph.EdgeViewModel class EdgeCanvasViewModel( val first: VertexCanvasViewModel, val second: VertexCanvasViewModel, - val color: Color, val edgeViewModel: EdgeViewModel, private val canvasViewModel: CanvasViewModel, ) { var showOrientation by canvasViewModel::isOrientated + var color by edgeViewModel::color val strokeWidth get() = edgeViewModel.strokeWidth * canvasViewModel.zoom diff --git a/src/main/kotlin/viewModel/graph/EdgeViewModel.kt b/src/main/kotlin/viewModel/graph/EdgeViewModel.kt index 3b3027b..0ce2395 100644 --- a/src/main/kotlin/viewModel/graph/EdgeViewModel.kt +++ b/src/main/kotlin/viewModel/graph/EdgeViewModel.kt @@ -11,7 +11,7 @@ import model.graph.Edge class EdgeViewModel( val first: VertexViewModel, val second: VertexViewModel, - private val edge: Edge, + val edge: Edge, private val _weightVisibility: State, color: Color = Config.Edge.color, strokeWidth: Float = Config.Edge.strokeWidth diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index d7a26a3..5b63bff 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -1,5 +1,6 @@ package viewModel.graph +import Config import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -8,6 +9,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import model.algorithm.Clustering import model.algorithm.PageRank +import model.graph.Edge import model.graph.UndirectedGraph import model.graph.Vertex @@ -115,6 +117,34 @@ class UndirectedViewModel( return viewModel } + /* + * Change edges' color + * */ + fun changeEdgesColor(edges: List>) { + edges.forEach { p -> + val edge = p.first + val color = p.second + + val vertex1 = _vertices[edge.first] ?: return + val vertex2 = _vertices[edge.second] ?: return + + val edgeViewModelList1 = _adjacencyList[vertex1] ?: return + val edgeViewModel1 = edgeViewModelList1.find { it.second == vertex2 } ?: return + edgeViewModel1.color = color + + val edgeViewModelList2 = _adjacencyList[vertex2] ?: return + val edgeViewModel2 = edgeViewModelList2.find { it.second == vertex1 } ?: return + edgeViewModel2.color = color + } + } + + /* + * Reset current color on all edges to default in Config + * */ + fun resetEdgesColorToDefault() { + adjacencyList.values.flatten().forEach { it.color = Config.Edge.color } + } + init { graph.vertices.forEachIndexed { i, vertex -> val group = groups.getOrDefault(vertex, 0) From 2209f0f3271cd11ab27bf6b6eb49523e516c5390 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 01:21:37 +0300 Subject: [PATCH 48/60] feat: add creating edge button --- src/main/kotlin/view/MenuView.kt | 3 +- src/main/kotlin/view/canvas/CanvasView.kt | 7 ++- .../kotlin/view/canvas/VertexCanvasView.kt | 8 ++- src/main/kotlin/viewModel/MenuViewModel.kt | 1 + .../viewModel/canvas/CanvasViewModel.kt | 54 +++++++++++++++---- .../viewModel/canvas/VertexCanvasViewModel.kt | 8 ++- .../viewModel/graph/UndirectedViewModel.kt | 12 +++++ .../kotlin/viewModel/graph/VertexViewModel.kt | 4 ++ 8 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 3a5befc..cb6af38 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -87,6 +87,7 @@ fun MenuView( viewModel: MenuViewModel ) { var isNodeCreating by viewModel::isNodeCreating + var isEdgeCreating by viewModel::isEdgeCreating var isClustering by viewModel::isClustering var isRanked by viewModel::isRanked var isAlgorithmMenuOpen by viewModel::isAlgorithmMenuOpen @@ -97,7 +98,7 @@ fun MenuView( ) { Spacer(Modifier.height(25f.dp)) MenuIcon("Nodes.svg", "AddNode.svg", Modifier.glow(isNodeCreating)) { isNodeCreating = !isNodeCreating } - MenuIcon("Ribs.svg", "AddEdge.svg", modifier = Modifier.alpha(0.2f)) + MenuIcon("Ribs.svg", "AddEdge.svg", Modifier.glow(isEdgeCreating)) { isEdgeCreating = !isEdgeCreating } MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glow(isClustering)) { isClustering = !isClustering } MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glow(viewModel.isRanked)) { isRanked = !isRanked } MenuIcon( diff --git a/src/main/kotlin/view/canvas/CanvasView.kt b/src/main/kotlin/view/canvas/CanvasView.kt index a0f369b..a2c7207 100644 --- a/src/main/kotlin/view/canvas/CanvasView.kt +++ b/src/main/kotlin/view/canvas/CanvasView.kt @@ -35,8 +35,11 @@ fun CanvasView( } .clipToBounds() ) { - viewModel.edges.forEach { - EdgeCanvasView(it) + // for rerender when update + if (viewModel.edgesCount > 0) { + viewModel.edges.flatten().forEach { + EdgeCanvasView(it) + } } viewModel.vertices.forEach { diff --git a/src/main/kotlin/view/canvas/VertexCanvasView.kt b/src/main/kotlin/view/canvas/VertexCanvasView.kt index 9c39f6c..34cf53a 100644 --- a/src/main/kotlin/view/canvas/VertexCanvasView.kt +++ b/src/main/kotlin/view/canvas/VertexCanvasView.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.gestures.onDrag import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size +import androidx.compose.foundation.onClick import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -26,8 +27,13 @@ fun VertexCanvasView( modifier .size(viewModel.radius * 2) .offset(viewModel.offset.x.dp, viewModel.offset.y.dp) - .border(color = viewModel.color, width = viewModel.strokeWidth.dp, shape = CircleShape) + .border( + color = if (viewModel.canvasViewModel.pickedNodeForEdgeCreating != viewModel) viewModel.color else Color.Green, + width = viewModel.strokeWidth.dp, + shape = CircleShape + ) .background(color = Color(0xFF242424), shape = CircleShape) + .onClick { viewModel.onClickWhenEdgeCreating() } .onDrag(onDrag = viewModel::onDrag), contentAlignment = Alignment.Center ) { diff --git a/src/main/kotlin/viewModel/MenuViewModel.kt b/src/main/kotlin/viewModel/MenuViewModel.kt index de14aca..d548045 100644 --- a/src/main/kotlin/viewModel/MenuViewModel.kt +++ b/src/main/kotlin/viewModel/MenuViewModel.kt @@ -9,6 +9,7 @@ class MenuViewModel( val canvasViewModel: CanvasViewModel ) { var isNodeCreating by canvasViewModel::isNodeCreatingMode + var isEdgeCreating by canvasViewModel::isEdgeCreatingMode var isClustering by canvasViewModel::isClustering var isRanked by canvasViewModel::isRanked var isAlgorithmMenuOpen by mutableStateOf(false) diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 537f4fb..3807027 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.awt.awtEventOrNull import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent @@ -29,13 +30,18 @@ class CanvasViewModel( var isClustering by graphViewModel::clustering var isRanked by graphViewModel::ranked + var isEdgeCreatingMode by mutableStateOf(false) + var pickedNodeForEdgeCreating by mutableStateOf(null) + var isNodeCreatingMode by mutableStateOf(false) + var edgesCount by mutableStateOf(0) var zoom by mutableStateOf(1f) var center by mutableStateOf(Offset(0f, 0f)) var canvasSize by mutableStateOf(Offset(400f, 400f)) var isOrientated by mutableStateOf(false) private val _vertices = mutableStateMapOf() + private val _edges = mutableStateMapOf>() private fun getVertex(vm: VertexViewModel): VertexCanvasViewModel { return _vertices[vm] ?: throw IllegalArgumentException("There is no VertexCanvasViewModel for $vm") @@ -54,24 +60,24 @@ class CanvasViewModel( graphViewModel.vertices.forEach { v -> _vertices[v] = VertexCanvasViewModel(v, this) } - } - private val _edges = graphViewModel.adjacencyList.mapValues { - it.value.map { edgeViewModel -> - val vertex1 = getVertex(edgeViewModel.first) - val vertex2 = getVertex(edgeViewModel.second) + graphViewModel.adjacencyList.forEach { + _edges[getVertex(it.key)] = ArrayList(it.value.map { edgeViewModel -> + val vertex1 = getVertex(edgeViewModel.first) + val vertex2 = getVertex(edgeViewModel.second) - EdgeCanvasViewModel(vertex1, vertex2, edgeViewModel, this) + EdgeCanvasViewModel(vertex1, vertex2, edgeViewModel, this) + }.toList()) } - }.mapKeys { - getVertex(it.key) + + edgesCount = _edges.values.flatten().size } val vertices get() = _vertices.values val edges - get() = _edges.values.flatten() + get() = _edges.values val onScroll: AwaitPointerEventScope.(PointerEvent) -> Unit = { if (it.changes.first().scrollDelta.y > 0) { @@ -121,4 +127,34 @@ class CanvasViewModel( fun resetEdgesColorToDefault() { graphViewModel.resetEdgesColorToDefault() } + + fun createEdge(first: VertexCanvasViewModel, second: VertexCanvasViewModel) { + val edgesVM = graphViewModel.createEdge(first.vertexViewModel, second.vertexViewModel) + val firstCanvasEdgeList = _edges[first] ?: return + val secondCanvasEdgeList = _edges[second] ?: return + + if (edgesVM != null) { + firstCanvasEdgeList.add(EdgeCanvasViewModel(first, second, edgesVM.first, this)) + secondCanvasEdgeList.add(EdgeCanvasViewModel(second, first, edgesVM.second, this)) + } + + edgesCount++ + } + + fun onClickNodeEdgeCreating(vm: VertexCanvasViewModel) { + if (!isEdgeCreatingMode) return + + if (pickedNodeForEdgeCreating == vm) { + pickedNodeForEdgeCreating = null + return + } + + if (pickedNodeForEdgeCreating == null) { + pickedNodeForEdgeCreating = vm + return + } + + createEdge(pickedNodeForEdgeCreating ?: return, vm) + pickedNodeForEdgeCreating = null + } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index b4f993a..b0831db 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -4,8 +4,8 @@ import androidx.compose.ui.geometry.Offset import viewModel.graph.VertexViewModel class VertexCanvasViewModel( - private val vertexViewModel: VertexViewModel, - private val canvasViewModel: CanvasViewModel, + val vertexViewModel: VertexViewModel, + val canvasViewModel: CanvasViewModel, ) { val color by vertexViewModel::color val label by vertexViewModel::label @@ -24,6 +24,10 @@ class VertexCanvasViewModel( vertexViewModel.onDrag(it * (1f / canvasViewModel.zoom)) } + fun onClickWhenEdgeCreating() { + canvasViewModel.onClickNodeEdgeCreating(this) + } + private fun calculateOffset() = Offset( (canvasViewModel.canvasSize.x / 2) + ((vertexViewModel.x - canvasViewModel.center.x) * canvasViewModel.zoom), (canvasViewModel.canvasSize.y / 2) + ((vertexViewModel.y - canvasViewModel.center.y) * canvasViewModel.zoom) diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index 5b63bff..d778dda 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -51,6 +51,18 @@ class UndirectedViewModel( updateSizes() } + fun createEdge(first: VertexViewModel, second: VertexViewModel): Pair? { + val edge = graph.addEdge(first.getKey(), second.getKey()) ?: return null + + val firstEdge = EdgeViewModel(first, second, edge, mutableStateOf(false)) + val secondEdge = EdgeViewModel(second, first, edge, mutableStateOf(false)) + + _adjacencyList[first]?.add(EdgeViewModel(first, second, edge, mutableStateOf(false))) + _adjacencyList[second]?.add(EdgeViewModel(second, first, edge, mutableStateOf(false))) + + return Pair(firstEdge, secondEdge) + } + private fun getColor(group: Int): Color { if (clustering) { val color = groupColors[group] diff --git a/src/main/kotlin/viewModel/graph/VertexViewModel.kt b/src/main/kotlin/viewModel/graph/VertexViewModel.kt index cdde6bb..749f50a 100644 --- a/src/main/kotlin/viewModel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewModel/graph/VertexViewModel.kt @@ -28,6 +28,10 @@ class VertexViewModel( val labelVisibility get() = _labelVisible + fun getKey(): Int { + return vertex.key + } + fun onDrag(it: Offset): Unit { x += it.x y += it.y From f0a4cffd1345c55696bb68353545b5a991921700 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 05:30:10 +0300 Subject: [PATCH 49/60] feat: linked the bridges algorithm to the button --- src/main/kotlin/view/MainView.kt | 53 ++++++++++++++----- src/main/kotlin/view/MenuView.kt | 10 ++-- src/main/kotlin/viewModel/MenuViewModel.kt | 3 +- .../viewModel/canvas/CanvasViewModel.kt | 3 +- .../viewModel/graph/UndirectedViewModel.kt | 25 ++++++++- 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index 19c8b76..c27ed15 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -1,26 +1,28 @@ package view import Config -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import view.canvas.CanvasView import viewModel.MainViewModel +import viewModel.MenuViewModel val HEADER_HEIGHT = Config.headerHeight val MENU_WIDTH = Config.menuWidth @Composable -fun DisplayAlgorithmMenu(name: String) { +fun DisplayAlgorithmMenu(name: String, viewModel: MenuViewModel, onClick: () -> Unit = {}) { val imageResources = listOf( "FindBridge.svg", @@ -52,34 +54,49 @@ fun DisplayAlgorithmMenu(name: String) { ImageButton( imageResourceId = image, onClick = { - } + if(image == "FindBridge.svg"){ + onClick() + } + }, viewModel ) } } } } +@OptIn(ExperimentalFoundationApi::class) @Composable -fun ImageButton(imageResourceId: String, onClick: () -> Unit) { +fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuViewModel) { Box( modifier = Modifier .size(440.dp, 60.dp) .padding(1.dp) - .clickable { onClick() } .background(Color(0x00)) ) { - Image( - painter = painterResource(imageResourceId), - contentDescription = "Button Image", - modifier = Modifier.size(445.dp, 59.dp), - contentScale = ContentScale.Crop - ) + if (imageResourceId == "FindBridge.svg") { + Image( + painter = painterResource(imageResourceId), + contentDescription = "Button Image", + modifier = Modifier.glowRec(viewModel.isFinded).size(445.dp, 59.dp).onClick(onClick = onClick), + contentScale = ContentScale.Crop + ) + } + else { + Image( + painter = painterResource(imageResourceId), + contentDescription = "Button Image", + modifier = Modifier.alpha(0.2f).size(445.dp, 59.dp).onClick(onClick = onClick), + contentScale = ContentScale.Crop + ) + } } } @Composable fun MainView(mainViewModel: MainViewModel) { + var isBridgeFinded by mainViewModel.menuViewModel::isFinded + Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { MenuView(mainViewModel.menuViewModel) @@ -90,8 +107,16 @@ fun MainView(mainViewModel: MainViewModel) { } if (mainViewModel.menuViewModel.isAlgorithmMenuOpen) { - DisplayAlgorithmMenu("DownMenuAlgorithm.svg") + DisplayAlgorithmMenu("DownMenuAlgorithm.svg", + mainViewModel.menuViewModel + ){ isBridgeFinded = !isBridgeFinded } } SettingsView(mainViewModel.settingsViewModel) +} + +fun Modifier.glowRec(flag: Boolean): Modifier { + if (!flag) return Modifier + + return Modifier.border(1f.dp, color = Color(0xFFFF00FF), shape = RectangleShape) } \ No newline at end of file diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 3a5befc..9fd5643 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -96,19 +96,19 @@ fun MenuView( horizontalAlignment = Alignment.CenterHorizontally, ) { Spacer(Modifier.height(25f.dp)) - MenuIcon("Nodes.svg", "AddNode.svg", Modifier.glow(isNodeCreating)) { isNodeCreating = !isNodeCreating } + MenuIcon("Nodes.svg", "AddNode.svg", Modifier.glowCircle(isNodeCreating)) { isNodeCreating = !isNodeCreating } MenuIcon("Ribs.svg", "AddEdge.svg", modifier = Modifier.alpha(0.2f)) - MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glow(isClustering)) { isClustering = !isClustering } - MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glow(viewModel.isRanked)) { isRanked = !isRanked } + MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glowCircle(isClustering)) { isClustering = !isClustering } + MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glowCircle(viewModel.isRanked)) { isRanked = !isRanked } MenuIcon( "Algorithm.svg", "Algorithms....svg", - Modifier.glow(viewModel.isAlgorithmMenuOpen) + Modifier.glowCircle(viewModel.isAlgorithmMenuOpen) ) { isAlgorithmMenuOpen = !isAlgorithmMenuOpen } } } -fun Modifier.glow(flag: Boolean): Modifier { +fun Modifier.glowCircle(flag: Boolean): Modifier { if (!flag) return Modifier return Modifier.border(4f.dp, color = Color(0xFFFF00FF), shape = CircleShape) diff --git a/src/main/kotlin/viewModel/MenuViewModel.kt b/src/main/kotlin/viewModel/MenuViewModel.kt index de14aca..697e010 100644 --- a/src/main/kotlin/viewModel/MenuViewModel.kt +++ b/src/main/kotlin/viewModel/MenuViewModel.kt @@ -11,5 +11,6 @@ class MenuViewModel( var isNodeCreating by canvasViewModel::isNodeCreatingMode var isClustering by canvasViewModel::isClustering var isRanked by canvasViewModel::isRanked + var isFinded by canvasViewModel::isFinded var isAlgorithmMenuOpen by mutableStateOf(false) -} \ No newline at end of file +} diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 537f4fb..415330b 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -28,6 +28,7 @@ class CanvasViewModel( var isClustering by graphViewModel::clustering var isRanked by graphViewModel::ranked + var isFinded by graphViewModel::bridgeFinded var isNodeCreatingMode by mutableStateOf(false) var zoom by mutableStateOf(1f) @@ -114,7 +115,7 @@ class CanvasViewModel( /* * Change edges' color * */ - fun changeEdgesColor(edges: List>) { + fun changeEdgesColor(edges: MutableList>) { graphViewModel.changeEdgesColor(edges) } diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index 5b63bff..bd42282 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import model.algorithm.Clustering +import model.algorithm.FindBridges import model.algorithm.PageRank import model.graph.Edge import model.graph.UndirectedGraph @@ -17,15 +18,18 @@ class UndirectedViewModel( private val graph: UndirectedGraph, val showVerticesLabels: Boolean, var groups: HashMap = hashMapOf(), - var ranks: List> = listOf() + var ranks: List> = listOf(), + var bridges: List = listOf() ) { private val _vertices = hashMapOf() private val _adjacencyList = hashMapOf>() private val groupColors = hashMapOf(0 to Color.Black) + private val BridgesWthColor = mutableListOf>() private val _color = mutableStateOf(Color.Black) private val _clustering = mutableStateOf(false) private val _ranked = mutableStateOf(false) + private val _bridgeFinded = mutableStateOf(false) private var size by mutableStateOf(10f) @@ -48,8 +52,25 @@ class UndirectedViewModel( set(value) { _ranked.value = value ranks = PageRank(graph).computePageRank(3) + println("хуй") updateSizes() } + + var bridgeFinded + get() = _bridgeFinded.value + set(value) { + _bridgeFinded.value = value + bridges = FindBridges(graph).findBridges() + bridges.forEach { + BridgesWthColor.add(it to Color.Red) + } + if (bridgeFinded) { + changeEdgesColor(BridgesWthColor) + } + else{ + resetEdgesColorToDefault() + } + } private fun getColor(group: Int): Color { if (clustering) { @@ -120,7 +141,7 @@ class UndirectedViewModel( /* * Change edges' color * */ - fun changeEdgesColor(edges: List>) { + fun changeEdgesColor(edges: MutableList>) { edges.forEach { p -> val edge = p.first val color = p.second From 0d8789b680b838e3534844fbbd4ed4811eb26dbc Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 11:48:30 +0300 Subject: [PATCH 50/60] refactor: got rid of println and fixed branch conflict --- src/main/kotlin/view/MenuView.kt | 10 +++++----- src/main/kotlin/viewModel/graph/UndirectedViewModel.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/view/MenuView.kt b/src/main/kotlin/view/MenuView.kt index 9fd5643..3a5befc 100644 --- a/src/main/kotlin/view/MenuView.kt +++ b/src/main/kotlin/view/MenuView.kt @@ -96,19 +96,19 @@ fun MenuView( horizontalAlignment = Alignment.CenterHorizontally, ) { Spacer(Modifier.height(25f.dp)) - MenuIcon("Nodes.svg", "AddNode.svg", Modifier.glowCircle(isNodeCreating)) { isNodeCreating = !isNodeCreating } + MenuIcon("Nodes.svg", "AddNode.svg", Modifier.glow(isNodeCreating)) { isNodeCreating = !isNodeCreating } MenuIcon("Ribs.svg", "AddEdge.svg", modifier = Modifier.alpha(0.2f)) - MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glowCircle(isClustering)) { isClustering = !isClustering } - MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glowCircle(viewModel.isRanked)) { isRanked = !isRanked } + MenuIcon("Clustering.svg", "ClusterD.svg", Modifier.glow(isClustering)) { isClustering = !isClustering } + MenuIcon("PageRank.svg", "AnalysisGraph.svg", Modifier.glow(viewModel.isRanked)) { isRanked = !isRanked } MenuIcon( "Algorithm.svg", "Algorithms....svg", - Modifier.glowCircle(viewModel.isAlgorithmMenuOpen) + Modifier.glow(viewModel.isAlgorithmMenuOpen) ) { isAlgorithmMenuOpen = !isAlgorithmMenuOpen } } } -fun Modifier.glowCircle(flag: Boolean): Modifier { +fun Modifier.glow(flag: Boolean): Modifier { if (!flag) return Modifier return Modifier.border(4f.dp, color = Color(0xFFFF00FF), shape = CircleShape) diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index bd42282..27e7ea0 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -52,7 +52,7 @@ class UndirectedViewModel( set(value) { _ranked.value = value ranks = PageRank(graph).computePageRank(3) - println("хуй") + updateSizes() } From 1014e627c37c9bcb21b1d04ccec7f574c3a83cbc Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 12:17:28 +0300 Subject: [PATCH 51/60] fix: disable tests that interact with database --- src/test/kotlin/model/reader/Neo4jReaderTest.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/kotlin/model/reader/Neo4jReaderTest.kt b/src/test/kotlin/model/reader/Neo4jReaderTest.kt index 85992ef..b9b28b8 100644 --- a/src/test/kotlin/model/reader/Neo4jReaderTest.kt +++ b/src/test/kotlin/model/reader/Neo4jReaderTest.kt @@ -13,6 +13,10 @@ import org.neo4j.driver.exceptions.NoSuchRecordException import kotlin.test.assertEquals import kotlin.test.assertTrue +// Unfortunately tests don't work without local database, and mocking database it useless while you check work with DB +// TODO: use this tests for integrate tests +const val IS_ENABLED = false + class Neo4jReaderTest { lateinit var testGraph: Graph val neo4jReader = Neo4jReader("bolt://localhost:7687", "neo4j", "qwertyui") @@ -48,6 +52,8 @@ class Neo4jReaderTest { @Test fun `save and load empty graph one time`() { + if (!IS_ENABLED) return + neo4jReader.saveGraph(testGraph, "", testGraphName) val graph = neo4jReader.loadGraph("", testGraphName) @@ -57,6 +63,8 @@ class Neo4jReaderTest { @Test fun `save and load empty graph 100 times in a row`() { + if (!IS_ENABLED) return + var graph: Graph = testGraph for (i in 1..100) { @@ -70,6 +78,8 @@ class Neo4jReaderTest { @Test fun `save and load non-empty graph one time`() { + if (!IS_ENABLED) return + for (i in 1..5) { testGraph.addVertex(i) } @@ -93,6 +103,8 @@ class Neo4jReaderTest { @Test fun `save and load non-empty graph 100 times in a row`() { + if (!IS_ENABLED) return + for (i in 1..5) { testGraph.addVertex(i) } @@ -119,6 +131,8 @@ class Neo4jReaderTest { @Test fun `load graph that don't exist in DB`() { + if (!IS_ENABLED) return + try { neo4jReader.loadGraph("", "Homka") } catch (_: NoSuchRecordException) { From f99a20984d63cbb776ea9c75948d55b5b1f48d9a Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 12:42:15 +0300 Subject: [PATCH 52/60] refactor: added some changes after the review --- src/main/kotlin/view/MainView.kt | 39 ++++++++++++-------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index c27ed15..2d61e5b 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -51,14 +51,7 @@ fun DisplayAlgorithmMenu(name: String, viewModel: MenuViewModel, onClick: () -> .padding(top = 150.dp, start = 30.dp) ) { items(imageResources) { image -> - ImageButton( - imageResourceId = image, - onClick = { - if(image == "FindBridge.svg"){ - onClick() - } - }, viewModel - ) + ImageButton(imageResourceId = image, onClick = onClick, viewModel) } } } @@ -73,22 +66,17 @@ fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuVie .padding(1.dp) .background(Color(0x00)) ) { - if (imageResourceId == "FindBridge.svg") { - Image( - painter = painterResource(imageResourceId), - contentDescription = "Button Image", - modifier = Modifier.glowRec(viewModel.isFinded).size(445.dp, 59.dp).onClick(onClick = onClick), - contentScale = ContentScale.Crop - ) - } - else { - Image( - painter = painterResource(imageResourceId), - contentDescription = "Button Image", - modifier = Modifier.alpha(0.2f).size(445.dp, 59.dp).onClick(onClick = onClick), - contentScale = ContentScale.Crop - ) + val modifier = when (imageResourceId) { + "FindBridge.svg" -> Modifier.glowRec(viewModel.isFinded).onClick(onClick = onClick) + else -> Modifier.alpha(0.2f) } + + Image( + painter = painterResource(imageResourceId), + contentDescription = "Button Image", + modifier = modifier.size(445.dp, 59.dp), + contentScale = ContentScale.Crop + ) } } @@ -107,9 +95,10 @@ fun MainView(mainViewModel: MainViewModel) { } if (mainViewModel.menuViewModel.isAlgorithmMenuOpen) { - DisplayAlgorithmMenu("DownMenuAlgorithm.svg", + DisplayAlgorithmMenu( + "DownMenuAlgorithm.svg", mainViewModel.menuViewModel - ){ isBridgeFinded = !isBridgeFinded } + ) { isBridgeFinded = !isBridgeFinded } } SettingsView(mainViewModel.settingsViewModel) From 4a5893bd67b8e2f5fbf554eb2a9323e785cfd938 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 14:24:15 +0300 Subject: [PATCH 53/60] rafactor: made the algorithm list function more simple to connect the button and the algorithm --- src/main/kotlin/view/MainView.kt | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index 2d61e5b..d401d9e 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -23,14 +23,15 @@ val MENU_WIDTH = Config.menuWidth @Composable fun DisplayAlgorithmMenu(name: String, viewModel: MenuViewModel, onClick: () -> Unit = {}) { + var isBridgeFinded by viewModel::isFinded val imageResources = listOf( - "FindBridge.svg", - "Dijkstra.svg", - "Bellman-Ford.svg", - "IslandTree.svg", - "StrongConnectivityComponent.svg", - "FindCycle.svg" + mapOf("icon" to "FindBridge.svg", "onClick" to { isBridgeFinded = !isBridgeFinded }), + mapOf("icon" to "Dijkstra.svg", "onClick" to {}), + mapOf("icon" to "Bellman-Ford.svg", "onClick" to {}), + mapOf("icon" to "IslandTree.svg", "onClick" to {}), + mapOf("icon" to "StrongConnectivityComponent.svg", "onClick" to {}), + mapOf("icon" to "FindCycle.svg", "onClick" to {}) ) Box( modifier = Modifier.padding(top = 240.dp, start = 80.dp) @@ -50,8 +51,10 @@ fun DisplayAlgorithmMenu(name: String, viewModel: MenuViewModel, onClick: () -> .background(Color.Transparent) .padding(top = 150.dp, start = 30.dp) ) { - items(imageResources) { image -> - ImageButton(imageResourceId = image, onClick = onClick, viewModel) + items(imageResources) { button -> + val icon = button["icon"] as String + val onClickAction = button["onClick"] as? () -> Unit ?: {} + ImageButton(imageResourceId = icon, onClick = onClickAction, viewModel) } } } @@ -67,14 +70,14 @@ fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuVie .background(Color(0x00)) ) { val modifier = when (imageResourceId) { - "FindBridge.svg" -> Modifier.glowRec(viewModel.isFinded).onClick(onClick = onClick) + "FindBridge.svg" -> Modifier.glowRec(viewModel.isFinded) else -> Modifier.alpha(0.2f) } Image( painter = painterResource(imageResourceId), contentDescription = "Button Image", - modifier = modifier.size(445.dp, 59.dp), + modifier = modifier.size(445.dp, 59.dp).onClick(onClick = onClick), contentScale = ContentScale.Crop ) } @@ -82,9 +85,7 @@ fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuVie @Composable fun MainView(mainViewModel: MainViewModel) { - var isBridgeFinded by mainViewModel.menuViewModel::isFinded - Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { MenuView(mainViewModel.menuViewModel) From 54f6fb4122ce6cb2818e38ed14690414963ec94e Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 14:52:54 +0300 Subject: [PATCH 54/60] refactor: change map on data class --- src/main/kotlin/view/MainView.kt | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index d401d9e..b8fa8da 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -22,22 +21,23 @@ val HEADER_HEIGHT = Config.headerHeight val MENU_WIDTH = Config.menuWidth @Composable -fun DisplayAlgorithmMenu(name: String, viewModel: MenuViewModel, onClick: () -> Unit = {}) { +fun displayAlgorithmMenu(name: String, viewModel: MenuViewModel) { var isBridgeFinded by viewModel::isFinded + data class ImageResource(val icon: String, val onClick: () -> Unit) + val imageResources = listOf( - mapOf("icon" to "FindBridge.svg", "onClick" to { isBridgeFinded = !isBridgeFinded }), - mapOf("icon" to "Dijkstra.svg", "onClick" to {}), - mapOf("icon" to "Bellman-Ford.svg", "onClick" to {}), - mapOf("icon" to "IslandTree.svg", "onClick" to {}), - mapOf("icon" to "StrongConnectivityComponent.svg", "onClick" to {}), - mapOf("icon" to "FindCycle.svg", "onClick" to {}) + ImageResource("FindBridge.svg") { isBridgeFinded = !isBridgeFinded }, + ImageResource("Dijkstra.svg") {}, + ImageResource("Bellman-Ford.svg") {}, + ImageResource("IslandTree.svg") {}, + ImageResource("StrongConnectivityComponent.svg") {}, + ImageResource("FindCycle.svg") {} ) + Box( modifier = Modifier.padding(top = 240.dp, start = 80.dp) ) { - - // Изображение Image( painter = painterResource(name), contentDescription = "Padded Image", @@ -51,10 +51,8 @@ fun DisplayAlgorithmMenu(name: String, viewModel: MenuViewModel, onClick: () -> .background(Color.Transparent) .padding(top = 150.dp, start = 30.dp) ) { - items(imageResources) { button -> - val icon = button["icon"] as String - val onClickAction = button["onClick"] as? () -> Unit ?: {} - ImageButton(imageResourceId = icon, onClick = onClickAction, viewModel) + items(imageResources) { imageResource -> + ImageButton(imageResourceId = imageResource.icon, onClick = imageResource.onClick, viewModel) } } } @@ -85,7 +83,7 @@ fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuVie @Composable fun MainView(mainViewModel: MainViewModel) { - var isBridgeFinded by mainViewModel.menuViewModel::isFinded + Row(Modifier.offset(0f.dp, Config.headerHeight.dp)) { MenuView(mainViewModel.menuViewModel) @@ -96,10 +94,10 @@ fun MainView(mainViewModel: MainViewModel) { } if (mainViewModel.menuViewModel.isAlgorithmMenuOpen) { - DisplayAlgorithmMenu( + displayAlgorithmMenu( "DownMenuAlgorithm.svg", mainViewModel.menuViewModel - ) { isBridgeFinded = !isBridgeFinded } + ) } SettingsView(mainViewModel.settingsViewModel) From bacb10436085f16779c0ea01bc7d06e70f5244ff Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 15:59:48 +0300 Subject: [PATCH 55/60] feat: add findEdge to Graph interface and implementation --- src/main/kotlin/model/graph/Graph.kt | 1 + src/main/kotlin/model/graph/UndirectedGraph.kt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index e2e2888..0829c09 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -9,4 +9,5 @@ interface Graph { fun updateVertex(key: Int, newKey: Int): Vertex? fun addEdge(first: Int, second: Int, weight: Long = 1): Edge? fun removeEdge(first: Int, second: Int): Edge? + fun getEdge(first: Int, second: Int): Edge? } \ No newline at end of file diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt index 9f80745..0a98769 100644 --- a/src/main/kotlin/model/graph/UndirectedGraph.kt +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -73,6 +73,12 @@ open class UndirectedGraph : Graph { fun getEdges(vertex: Vertex) = _adjacencyList[vertex] + override fun getEdge(first: Int, second: Int): Edge? { + val firstVertex = findVertex(first) ?: return null + + return getEdges(firstVertex)?.find { it.second.key == second } + } + private data class UndirectedVertex(override var key: Int) : Vertex private data class UndirectedEdge(override val first: Vertex, override val second: Vertex) : Edge { From 40c7086f9db9e52430714a3a7376abeab60a97d2 Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 16:44:00 +0300 Subject: [PATCH 56/60] feat: connected the Dijkstra algorithm button to the algorithm itself] --- src/main/kotlin/Config.kt | 1 + src/main/kotlin/model/algorithm/Dijkstra.kt | 7 ++++ src/main/kotlin/view/MainView.kt | 11 +++++-- .../kotlin/view/canvas/VertexCanvasView.kt | 4 +-- src/main/kotlin/viewModel/MenuViewModel.kt | 3 +- .../viewModel/canvas/CanvasViewModel.kt | 32 +++++++++++++++++++ .../viewModel/canvas/VertexCanvasViewModel.kt | 11 ++++++- .../viewModel/graph/UndirectedViewModel.kt | 12 ++++--- 8 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/Config.kt b/src/main/kotlin/Config.kt index f0b4354..756f857 100644 --- a/src/main/kotlin/Config.kt +++ b/src/main/kotlin/Config.kt @@ -6,6 +6,7 @@ object Config { object Edge { val color = Color(0xFF00E0FF) + val dijkstraColor = Color.Green val strokeWidth = 8f } } \ No newline at end of file diff --git a/src/main/kotlin/model/algorithm/Dijkstra.kt b/src/main/kotlin/model/algorithm/Dijkstra.kt index 2dbded8..fe07ce0 100644 --- a/src/main/kotlin/model/algorithm/Dijkstra.kt +++ b/src/main/kotlin/model/algorithm/Dijkstra.kt @@ -1,9 +1,16 @@ package model.algorithm +import model.graph.Edge import model.graph.Graph import model.graph.Vertex class Dijkstra(private val graph: Graph) { + fun triplesToEdges(list: List>): List { + return list.map { + graph.getEdge(it.first, it.second) + ?: throw Error("There is no edge from ${it.first} node to ${it.second} node") + } + } fun findShortestPath(startKey: Int, endKey: Int): List>? { val startVertex = graph.vertices.find { it.key == startKey } ?: return null diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index b8fa8da..09e1106 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -22,13 +22,17 @@ val MENU_WIDTH = Config.menuWidth @Composable fun displayAlgorithmMenu(name: String, viewModel: MenuViewModel) { - var isBridgeFinded by viewModel::isFinded + var isBridgeFinded by viewModel::isBridgeFinded + var isDijkstraMode by viewModel::isDijkstraMode data class ImageResource(val icon: String, val onClick: () -> Unit) val imageResources = listOf( ImageResource("FindBridge.svg") { isBridgeFinded = !isBridgeFinded }, - ImageResource("Dijkstra.svg") {}, + ImageResource("Dijkstra.svg") { + isDijkstraMode = !isDijkstraMode + if (!isDijkstraMode) viewModel.canvasViewModel.resetEdgesColorToDefault() + }, ImageResource("Bellman-Ford.svg") {}, ImageResource("IslandTree.svg") {}, ImageResource("StrongConnectivityComponent.svg") {}, @@ -68,7 +72,8 @@ fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuVie .background(Color(0x00)) ) { val modifier = when (imageResourceId) { - "FindBridge.svg" -> Modifier.glowRec(viewModel.isFinded) + "FindBridge.svg" -> Modifier.glowRec(viewModel.isBridgeFinded) + "Dijkstra.svg" -> Modifier.glowRec(viewModel.isDijkstraMode) else -> Modifier.alpha(0.2f) } diff --git a/src/main/kotlin/view/canvas/VertexCanvasView.kt b/src/main/kotlin/view/canvas/VertexCanvasView.kt index 34cf53a..82d02bf 100644 --- a/src/main/kotlin/view/canvas/VertexCanvasView.kt +++ b/src/main/kotlin/view/canvas/VertexCanvasView.kt @@ -28,12 +28,12 @@ fun VertexCanvasView( .size(viewModel.radius * 2) .offset(viewModel.offset.x.dp, viewModel.offset.y.dp) .border( - color = if (viewModel.canvasViewModel.pickedNodeForEdgeCreating != viewModel) viewModel.color else Color.Green, + color = if (viewModel.canvasViewModel.pickedNodeForDijkstra != viewModel) viewModel.color else Color.Green, width = viewModel.strokeWidth.dp, shape = CircleShape ) .background(color = Color(0xFF242424), shape = CircleShape) - .onClick { viewModel.onClickWhenEdgeCreating() } + .onClick { viewModel.onClick() } .onDrag(onDrag = viewModel::onDrag), contentAlignment = Alignment.Center ) { diff --git a/src/main/kotlin/viewModel/MenuViewModel.kt b/src/main/kotlin/viewModel/MenuViewModel.kt index 66ad09a..208cfcf 100644 --- a/src/main/kotlin/viewModel/MenuViewModel.kt +++ b/src/main/kotlin/viewModel/MenuViewModel.kt @@ -12,6 +12,7 @@ class MenuViewModel( var isEdgeCreating by canvasViewModel::isEdgeCreatingMode var isClustering by canvasViewModel::isClustering var isRanked by canvasViewModel::isRanked - var isFinded by canvasViewModel::isFinded + var isBridgeFinded by canvasViewModel::isFinded + var isDijkstraMode by canvasViewModel::isDijkstraMode var isAlgorithmMenuOpen by mutableStateOf(false) } diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 8a5b828..a5ba556 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -1,5 +1,6 @@ package viewModel.canvas +import Config import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.PointerMatcher import androidx.compose.foundation.gestures.detectDragGestures @@ -14,6 +15,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerInputScope +import model.algorithm.Dijkstra import model.graph.Edge import model.graph.UndirectedGraph import view.HEADER_HEIGHT @@ -32,7 +34,9 @@ class CanvasViewModel( var isFinded by graphViewModel::bridgeFinded var isEdgeCreatingMode by mutableStateOf(false) + var isDijkstraMode by mutableStateOf(false) var pickedNodeForEdgeCreating by mutableStateOf(null) + var pickedNodeForDijkstra by mutableStateOf(null) var isNodeCreatingMode by mutableStateOf(false) var edgesCount by mutableStateOf(0) @@ -158,4 +162,32 @@ class CanvasViewModel( createEdge(pickedNodeForEdgeCreating ?: return, vm) pickedNodeForEdgeCreating = null } + + fun onClickNodeDijkstraOn(vm: VertexCanvasViewModel) { + if (!isDijkstraMode) { + resetEdgesColorToDefault() + return + } + + if (pickedNodeForDijkstra == vm) { + pickedNodeForDijkstra = null + return + } + + if (pickedNodeForDijkstra == null) { + pickedNodeForDijkstra = vm + return + } + + val firstVertex = + pickedNodeForDijkstra ?: throw IllegalStateException("there is no node in pickedNodeForDijkstra method") + + val dijksta = Dijkstra(graph) + val path = dijksta.findShortestPath(firstVertex.vertexViewModel.getKey(), vm.vertexViewModel.getKey()) ?: return + val edges = dijksta.triplesToEdges(path) + + val PathWthColor = edges.map { it to Config.Edge.dijkstraColor } + changeEdgesColor(PathWthColor.toMutableList()) + pickedNodeForDijkstra = null + } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index b0831db..99b9695 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -24,10 +24,19 @@ class VertexCanvasViewModel( vertexViewModel.onDrag(it * (1f / canvasViewModel.zoom)) } - fun onClickWhenEdgeCreating() { + fun onClick() { + onClickWhenDijkstraOn() + onClickWhenEdgeCreating() + } + + private fun onClickWhenEdgeCreating() { canvasViewModel.onClickNodeEdgeCreating(this) } + private fun onClickWhenDijkstraOn() { + canvasViewModel.onClickNodeDijkstraOn(this) + } + private fun calculateOffset() = Offset( (canvasViewModel.canvasSize.x / 2) + ((vertexViewModel.x - canvasViewModel.center.x) * canvasViewModel.zoom), (canvasViewModel.canvasSize.y / 2) + ((vertexViewModel.y - canvasViewModel.center.y) * canvasViewModel.zoom) diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index c9524f9..08dfaf9 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import model.algorithm.Clustering +import model.algorithm.Dijkstra import model.algorithm.FindBridges import model.algorithm.PageRank import model.graph.Edge @@ -19,17 +20,19 @@ class UndirectedViewModel( val showVerticesLabels: Boolean, var groups: HashMap = hashMapOf(), var ranks: List> = listOf(), - var bridges: List = listOf() + var bridges: List = listOf(), ) { private val _vertices = hashMapOf() private val _adjacencyList = hashMapOf>() private val groupColors = hashMapOf(0 to Color.Black) private val BridgesWthColor = mutableListOf>() + private val PathWthColor = mutableListOf>() private val _color = mutableStateOf(Color.Black) private val _clustering = mutableStateOf(false) private val _ranked = mutableStateOf(false) private val _bridgeFinded = mutableStateOf(false) + private val _shortestPathFinded = mutableStateOf(false) private var size by mutableStateOf(10f) @@ -55,7 +58,7 @@ class UndirectedViewModel( updateSizes() } - + var bridgeFinded get() = _bridgeFinded.value set(value) { @@ -66,12 +69,13 @@ class UndirectedViewModel( } if (bridgeFinded) { changeEdgesColor(BridgesWthColor) - } - else{ + } else { resetEdgesColorToDefault() } } + var shortestPathFinded by mutableStateOf(false) + fun createEdge(first: VertexViewModel, second: VertexViewModel): Pair? { val edge = graph.addEdge(first.getKey(), second.getKey()) ?: return null From 4b0c99d1040ce8683eddd1804f1ae17be2dde5f2 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 16:51:50 +0300 Subject: [PATCH 57/60] feat: add find cycle button --- src/main/kotlin/view/MainView.kt | 5 ++++- src/main/kotlin/view/canvas/VertexCanvasView.kt | 2 +- .../kotlin/viewModel/canvas/CanvasViewModel.kt | 17 +++++++++++++++++ .../viewModel/canvas/VertexCanvasViewModel.kt | 4 ++-- .../viewModel/graph/UndirectedViewModel.kt | 6 +++--- .../kotlin/viewModel/graph/VertexViewModel.kt | 2 +- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index b8fa8da..7c74906 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -32,7 +32,10 @@ fun displayAlgorithmMenu(name: String, viewModel: MenuViewModel) { ImageResource("Bellman-Ford.svg") {}, ImageResource("IslandTree.svg") {}, ImageResource("StrongConnectivityComponent.svg") {}, - ImageResource("FindCycle.svg") {} + ImageResource("FindCycle.svg") { + viewModel.canvasViewModel.isEdgeFindCycleMode = !viewModel.canvasViewModel.isEdgeFindCycleMode + viewModel.canvasViewModel.resetEdgesColorToDefault() + } ) Box( diff --git a/src/main/kotlin/view/canvas/VertexCanvasView.kt b/src/main/kotlin/view/canvas/VertexCanvasView.kt index 34cf53a..d046022 100644 --- a/src/main/kotlin/view/canvas/VertexCanvasView.kt +++ b/src/main/kotlin/view/canvas/VertexCanvasView.kt @@ -33,7 +33,7 @@ fun VertexCanvasView( shape = CircleShape ) .background(color = Color(0xFF242424), shape = CircleShape) - .onClick { viewModel.onClickWhenEdgeCreating() } + .onClick { viewModel.onClick(viewModel) } .onDrag(onDrag = viewModel::onDrag), contentAlignment = Alignment.Center ) { diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index 8a5b828..a933190 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerInputScope +import model.algorithm.FindCycle import model.graph.Edge import model.graph.UndirectedGraph import view.HEADER_HEIGHT @@ -34,6 +35,8 @@ class CanvasViewModel( var isEdgeCreatingMode by mutableStateOf(false) var pickedNodeForEdgeCreating by mutableStateOf(null) + var isEdgeFindCycleMode by mutableStateOf(false) + var isNodeCreatingMode by mutableStateOf(false) var edgesCount by mutableStateOf(0) var zoom by mutableStateOf(1f) @@ -54,6 +57,7 @@ class CanvasViewModel( val viewModel = graphViewModel.createVertex(coordinates) ?: return _vertices[viewModel] = VertexCanvasViewModel(viewModel, this) + _edges[getVertex(viewModel)] = ArrayList() } } @@ -142,6 +146,11 @@ class CanvasViewModel( edgesCount++ } + fun onClick(vm: VertexCanvasViewModel) { + onClickNodeEdgeCreating(vm) + onClickNodeFindCycle(vm) + } + fun onClickNodeEdgeCreating(vm: VertexCanvasViewModel) { if (!isEdgeCreatingMode) return @@ -158,4 +167,12 @@ class CanvasViewModel( createEdge(pickedNodeForEdgeCreating ?: return, vm) pickedNodeForEdgeCreating = null } + + fun onClickNodeFindCycle(vm: VertexCanvasViewModel) { + if (!isEdgeFindCycleMode) return + + changeEdgesColor(FindCycle(graph).calculate(vm.vertexViewModel.vertex).map { Pair(it, Color.Red) } + .toMutableList()) + isEdgeFindCycleMode = false + } } \ No newline at end of file diff --git a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt index b0831db..81bc3bb 100644 --- a/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/VertexCanvasViewModel.kt @@ -24,8 +24,8 @@ class VertexCanvasViewModel( vertexViewModel.onDrag(it * (1f / canvasViewModel.zoom)) } - fun onClickWhenEdgeCreating() { - canvasViewModel.onClickNodeEdgeCreating(this) + fun onClick(vm: VertexCanvasViewModel) { + canvasViewModel.onClick(this) } private fun calculateOffset() = Offset( diff --git a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt index c9524f9..e413838 100644 --- a/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt +++ b/src/main/kotlin/viewModel/graph/UndirectedViewModel.kt @@ -55,7 +55,7 @@ class UndirectedViewModel( updateSizes() } - + var bridgeFinded get() = _bridgeFinded.value set(value) { @@ -66,8 +66,7 @@ class UndirectedViewModel( } if (bridgeFinded) { changeEdgesColor(BridgesWthColor) - } - else{ + } else { resetEdgesColorToDefault() } } @@ -146,6 +145,7 @@ class UndirectedViewModel( ) _vertices[vertex] = viewModel + _adjacencyList[viewModel] = ArrayList() return viewModel } diff --git a/src/main/kotlin/viewModel/graph/VertexViewModel.kt b/src/main/kotlin/viewModel/graph/VertexViewModel.kt index 749f50a..16d6a8b 100644 --- a/src/main/kotlin/viewModel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewModel/graph/VertexViewModel.kt @@ -11,7 +11,7 @@ import model.graph.Vertex class VertexViewModel( private val _labelVisible: Boolean, - private val vertex: Vertex, + val vertex: Vertex, x: Float = 0f, y: Float = 0f, color: Color = Color.Black, From 0bd93741fce8f4507f2038b3a91fe952eb2e31dd Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 16:57:57 +0300 Subject: [PATCH 58/60] fix: now color reset when you click on another node --- src/main/kotlin/model/reader/SQLiteReader.kt | 412 +++++++++--------- src/main/kotlin/view/MainView.kt | 1 + .../viewModel/canvas/CanvasViewModel.kt | 2 +- 3 files changed, 208 insertions(+), 207 deletions(-) diff --git a/src/main/kotlin/model/reader/SQLiteReader.kt b/src/main/kotlin/model/reader/SQLiteReader.kt index 46b0b82..f72838f 100644 --- a/src/main/kotlin/model/reader/SQLiteReader.kt +++ b/src/main/kotlin/model/reader/SQLiteReader.kt @@ -1,206 +1,206 @@ -package model.reader - -import model.graph.* - -import java.sql.Connection -import java.sql.DriverManager -import java.sql.PreparedStatement - -class SQLiteReader: Reader { - - private fun createTable(connection: Connection) { - val statement = connection.createStatement() - val createTableVertex = """ - CREATE TABLE IF NOT EXISTS vertex ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - vertex_key INTEGER, - graph_id INTEGER, - FOREIGN KEY (graph_id) REFERENCES graph(graph_id) - ) - """ - val createTableEdge = """ - CREATE TABLE IF NOT EXISTS edge ( - start_vertex_id INTEGER, - end_vertex_id INTEGER, - weight INTEGER, - FOREIGN KEY (end_vertex_id) REFERENCES graph(graph_id), - FOREIGN KEY (start_vertex_id) REFERENCES graph(graph_id) - ) - """ - val createTableGraph = """ - CREATE TABLE IF NOT EXISTS graph ( - graph_id INTEGER PRIMARY KEY AUTOINCREMENT, - graph_name TEXT NOT NULL UNIQUE, - graph_type_flag INTEGER - ) - """ - statement.execute(createTableGraph) - statement.execute(createTableVertex) - statement.execute(createTableEdge) - statement.close() - } - - private fun insertGraph(connect: Connection, graph: Graph, nameGraph: String){ - - val insertName = "INSERT INTO graph (graph_name, graph_type_flag) VALUES (?, ?)" - val insertNameStmt: PreparedStatement = connect.prepareStatement(insertName) - insertNameStmt.setString(1, nameGraph) - - if (graph is WeightedGraph){ - insertNameStmt.setInt(2, 1) - } - if (graph is UndirectedGraph){ - insertNameStmt.setInt(2, 2) - } - if (graph is DirectedGraph){ - insertNameStmt.setInt(2, 3) - } - if (graph is WeightedDirectedGraph){ - insertNameStmt.setInt(2, 4) - } - - insertNameStmt.executeUpdate() - - val graphId = insertNameStmt.generatedKeys.getInt(1) - insertNameStmt.close() - - val insertVertexSql = "INSERT INTO vertex (vertex_key, graph_id) VALUES (?, ?)" - val insertVertexStmt: PreparedStatement = connect.prepareStatement(insertVertexSql) - - val vertexIdMap = mutableMapOf() - - for (vertex in graph.vertices){ - insertVertexStmt.setInt(1, vertex.key) - insertVertexStmt.setInt(2, graphId) - - insertVertexStmt.executeUpdate() - - val vertexIdResult = insertVertexStmt.generatedKeys - if (vertexIdResult.next()) { - val vertexId = vertexIdResult.getInt(1) - vertexIdMap[vertex] = vertexId - } - } - insertVertexStmt.close() - - val insertEdgeSql = "INSERT INTO edge (start_vertex_id, end_vertex_id, weight) VALUES (?, ?, ?)" - val insertEdgeStmt: PreparedStatement = connect.prepareStatement(insertEdgeSql) - - for ((vertex, edges) in graph.adjacencyList) { - val startVertexId = vertexIdMap[vertex] ?: throw Exception("Vertex not found in vertexIdMap") - for (edge in edges) { - val endVertexId = vertexIdMap[edge.second] ?: throw Exception("End vertex not found in vertexIdMap") - insertEdgeStmt.setInt(1, startVertexId) - insertEdgeStmt.setInt(2, endVertexId) - insertEdgeStmt.setLong(3, edge.weight) - insertEdgeStmt.executeUpdate() - } - } - insertEdgeStmt.close() - } - - private fun connect(filepath: String): Connection = DriverManager.getConnection("jdbc:sqlite:$filepath") - - - override fun saveGraph(graph: Graph, filepath: String, nameGraph: String) { - - //Сконектились с базой - val connection = connect(filepath) - - //Создали таблицы и связи между ними - createTable(connection) - - //Сохранили граф по полочкам:) - insertGraph(connection, graph, nameGraph) - } - - override fun loadGraph(filepath: String, nameGraph: String): Graph { - - //Сконектились с базой - val connection = connect(filepath) - - val graph: Graph - - //Сделали запрос на получение id графа - val graphStmt = connection.prepareStatement( - "SELECT graph_id, graph_type_flag FROM graph WHERE graph_name = ?" - ) - - graphStmt.setString(1, nameGraph) - val graphResultSet = graphStmt.executeQuery() - - if (!graphResultSet.next()) { - throw IllegalArgumentException("Graph with name $nameGraph not found") - } - - val graphId = graphResultSet.getInt("graph_id") - val graphType = graphResultSet.getInt("graph_type_flag") - - graph = when (graphType) { - 1 -> WeightedGraph() - 2 -> UndirectedGraph() - 3 -> DirectedGraph() - 4 -> WeightedDirectedGraph() - else -> throw IllegalArgumentException("Unknown graph type: $graphType") - } - - graphResultSet.close() - graphStmt.close() - - //Сделали запрос на получение id и ключа вершины - val vertexStmt = connection.prepareStatement( - "SELECT id, vertex_key FROM vertex WHERE graph_id = ?" - ) - - vertexStmt.setInt(1, graphId) - val vertexResultSet = vertexStmt.executeQuery() - - // Нужна для нахождение вершин ребра через их id - val vertexMap = mutableMapOf() - - while (vertexResultSet.next()){ - val vertexId = vertexResultSet.getInt("id") - val vertexKey = vertexResultSet.getInt("vertex_key") - val vertex = graph.addVertex(vertexKey) - - if (vertex != null){ - vertexMap[vertexId] = vertex - } - } - - vertexResultSet.close() - vertexStmt.close() - - /* - Сделали запрос на получение id начальной и конечной вершины, а также веса, ребра, - через id вершины полученной от graph_id - */ - val edgeStmt = connection.prepareStatement( - "SELECT start_vertex_id, end_vertex_id, weight FROM edge WHERE start_vertex_id" + - " IN (SELECT id FROM vertex WHERE graph_id = ?)" - ) - - edgeStmt.setInt(1, graphId) - val edgeResultSet = edgeStmt.executeQuery() - - while (edgeResultSet.next()) { - val startVertexId = edgeResultSet.getInt("start_vertex_id") - val endVertexId = edgeResultSet.getInt("end_vertex_id") - val weight = edgeResultSet.getLong("weight") - - val startVertex = vertexMap[startVertexId] - val endVertex = vertexMap[endVertexId] - - if (startVertex != null && endVertex != null) { - graph.addEdge(startVertex.key, endVertex.key, weight) - } - } - edgeResultSet.close() - edgeStmt.close() - - connection.close() - return graph - } -} - - +//package model.reader +// +//import model.graph.* +// +//import java.sql.Connection +//import java.sql.DriverManager +//import java.sql.PreparedStatement +// +//class SQLiteReader: Reader { +// +// private fun createTable(connection: Connection) { +// val statement = connection.createStatement() +// val createTableVertex = """ +// CREATE TABLE IF NOT EXISTS vertex ( +// id INTEGER PRIMARY KEY AUTOINCREMENT, +// vertex_key INTEGER, +// graph_id INTEGER, +// FOREIGN KEY (graph_id) REFERENCES graph(graph_id) +// ) +// """ +// val createTableEdge = """ +// CREATE TABLE IF NOT EXISTS edge ( +// start_vertex_id INTEGER, +// end_vertex_id INTEGER, +// weight INTEGER, +// FOREIGN KEY (end_vertex_id) REFERENCES graph(graph_id), +// FOREIGN KEY (start_vertex_id) REFERENCES graph(graph_id) +// ) +// """ +// val createTableGraph = """ +// CREATE TABLE IF NOT EXISTS graph ( +// graph_id INTEGER PRIMARY KEY AUTOINCREMENT, +// graph_name TEXT NOT NULL UNIQUE, +// graph_type_flag INTEGER +// ) +// """ +// statement.execute(createTableGraph) +// statement.execute(createTableVertex) +// statement.execute(createTableEdge) +// statement.close() +// } +// +// private fun insertGraph(connect: Connection, graph: Graph, nameGraph: String){ +// +// val insertName = "INSERT INTO graph (graph_name, graph_type_flag) VALUES (?, ?)" +// val insertNameStmt: PreparedStatement = connect.prepareStatement(insertName) +// insertNameStmt.setString(1, nameGraph) +// +// if (graph is WeightedGraph){ +// insertNameStmt.setInt(2, 1) +// } +// if (graph is UndirectedGraph){ +// insertNameStmt.setInt(2, 2) +// } +// if (graph is DirectedGraph){ +// insertNameStmt.setInt(2, 3) +// } +// if (graph is WeightedDirectedGraph){ +// insertNameStmt.setInt(2, 4) +// } +// +// insertNameStmt.executeUpdate() +// +// val graphId = insertNameStmt.generatedKeys.getInt(1) +// insertNameStmt.close() +// +// val insertVertexSql = "INSERT INTO vertex (vertex_key, graph_id) VALUES (?, ?)" +// val insertVertexStmt: PreparedStatement = connect.prepareStatement(insertVertexSql) +// +// val vertexIdMap = mutableMapOf() +// +// for (vertex in graph.vertices){ +// insertVertexStmt.setInt(1, vertex.key) +// insertVertexStmt.setInt(2, graphId) +// +// insertVertexStmt.executeUpdate() +// +// val vertexIdResult = insertVertexStmt.generatedKeys +// if (vertexIdResult.next()) { +// val vertexId = vertexIdResult.getInt(1) +// vertexIdMap[vertex] = vertexId +// } +// } +// insertVertexStmt.close() +// +// val insertEdgeSql = "INSERT INTO edge (start_vertex_id, end_vertex_id, weight) VALUES (?, ?, ?)" +// val insertEdgeStmt: PreparedStatement = connect.prepareStatement(insertEdgeSql) +// +// for ((vertex, edges) in graph.adjacencyList) { +// val startVertexId = vertexIdMap[vertex] ?: throw Exception("Vertex not found in vertexIdMap") +// for (edge in edges) { +// val endVertexId = vertexIdMap[edge.second] ?: throw Exception("End vertex not found in vertexIdMap") +// insertEdgeStmt.setInt(1, startVertexId) +// insertEdgeStmt.setInt(2, endVertexId) +// insertEdgeStmt.setLong(3, edge.weight) +// insertEdgeStmt.executeUpdate() +// } +// } +// insertEdgeStmt.close() +// } +// +// private fun connect(filepath: String): Connection = DriverManager.getConnection("jdbc:sqlite:$filepath") +// +// +// override fun saveGraph(graph: Graph, filepath: String, nameGraph: String) { +// +// //Сконектились с базой +// val connection = connect(filepath) +// +// //Создали таблицы и связи между ними +// createTable(connection) +// +// //Сохранили граф по полочкам:) +// insertGraph(connection, graph, nameGraph) +// } +// +// override fun loadGraph(filepath: String, nameGraph: String): Graph { +// +// //Сконектились с базой +// val connection = connect(filepath) +// +// val graph: Graph +// +// //Сделали запрос на получение id графа +// val graphStmt = connection.prepareStatement( +// "SELECT graph_id, graph_type_flag FROM graph WHERE graph_name = ?" +// ) +// +// graphStmt.setString(1, nameGraph) +// val graphResultSet = graphStmt.executeQuery() +// +// if (!graphResultSet.next()) { +// throw IllegalArgumentException("Graph with name $nameGraph not found") +// } +// +// val graphId = graphResultSet.getInt("graph_id") +// val graphType = graphResultSet.getInt("graph_type_flag") +// +// graph = when (graphType) { +// 1 -> WeightedGraph() +// 2 -> UndirectedGraph() +// 3 -> DirectedGraph() +// 4 -> WeightedDirectedGraph() +// else -> throw IllegalArgumentException("Unknown graph type: $graphType") +// } +// +// graphResultSet.close() +// graphStmt.close() +// +// //Сделали запрос на получение id и ключа вершины +// val vertexStmt = connection.prepareStatement( +// "SELECT id, vertex_key FROM vertex WHERE graph_id = ?" +// ) +// +// vertexStmt.setInt(1, graphId) +// val vertexResultSet = vertexStmt.executeQuery() +// +// // Нужна для нахождение вершин ребра через их id +// val vertexMap = mutableMapOf() +// +// while (vertexResultSet.next()){ +// val vertexId = vertexResultSet.getInt("id") +// val vertexKey = vertexResultSet.getInt("vertex_key") +// val vertex = graph.addVertex(vertexKey) +// +// if (vertex != null){ +// vertexMap[vertexId] = vertex +// } +// } +// +// vertexResultSet.close() +// vertexStmt.close() +// +// /* +// Сделали запрос на получение id начальной и конечной вершины, а также веса, ребра, +// через id вершины полученной от graph_id +// */ +// val edgeStmt = connection.prepareStatement( +// "SELECT start_vertex_id, end_vertex_id, weight FROM edge WHERE start_vertex_id" + +// " IN (SELECT id FROM vertex WHERE graph_id = ?)" +// ) +// +// edgeStmt.setInt(1, graphId) +// val edgeResultSet = edgeStmt.executeQuery() +// +// while (edgeResultSet.next()) { +// val startVertexId = edgeResultSet.getInt("start_vertex_id") +// val endVertexId = edgeResultSet.getInt("end_vertex_id") +// val weight = edgeResultSet.getLong("weight") +// +// val startVertex = vertexMap[startVertexId] +// val endVertex = vertexMap[endVertexId] +// +// if (startVertex != null && endVertex != null) { +// graph.addEdge(startVertex.key, endVertex.key, weight) +// } +// } +// edgeResultSet.close() +// edgeStmt.close() +// +// connection.close() +// return graph +// } +//} +// +// diff --git a/src/main/kotlin/view/MainView.kt b/src/main/kotlin/view/MainView.kt index 7c74906..1bc68f0 100644 --- a/src/main/kotlin/view/MainView.kt +++ b/src/main/kotlin/view/MainView.kt @@ -72,6 +72,7 @@ fun ImageButton(imageResourceId: String, onClick: () -> Unit, viewModel: MenuVie ) { val modifier = when (imageResourceId) { "FindBridge.svg" -> Modifier.glowRec(viewModel.isFinded) + "FindCycle.svg" -> Modifier.glowRec(viewModel.canvasViewModel.isEdgeFindCycleMode) else -> Modifier.alpha(0.2f) } diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index a933190..638f233 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -171,8 +171,8 @@ class CanvasViewModel( fun onClickNodeFindCycle(vm: VertexCanvasViewModel) { if (!isEdgeFindCycleMode) return + resetEdgesColorToDefault() changeEdgesColor(FindCycle(graph).calculate(vm.vertexViewModel.vertex).map { Pair(it, Color.Red) } .toMutableList()) - isEdgeFindCycleMode = false } } \ No newline at end of file From e28f8020b6a32ebb701be9a21fb6720c73441eeb Mon Sep 17 00:00:00 2001 From: Demon32123 Date: Tue, 24 Sep 2024 17:02:07 +0300 Subject: [PATCH 59/60] refactor: added reset color of edges in the algorithm before reuse --- src/main/kotlin/viewModel/canvas/CanvasViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt index a5ba556..6650a8f 100644 --- a/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt +++ b/src/main/kotlin/viewModel/canvas/CanvasViewModel.kt @@ -187,6 +187,7 @@ class CanvasViewModel( val edges = dijksta.triplesToEdges(path) val PathWthColor = edges.map { it to Config.Edge.dijkstraColor } + resetEdgesColorToDefault() changeEdgesColor(PathWthColor.toMutableList()) pickedNodeForDijkstra = null } From 6dce6f40861c8d283e69091082adb2c700a13d42 Mon Sep 17 00:00:00 2001 From: Homka122 Date: Tue, 24 Sep 2024 19:02:39 +0300 Subject: [PATCH 60/60] feat: add integrate test --- src/test/kotlin/model/IntegrateTests.kt | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/kotlin/model/IntegrateTests.kt diff --git a/src/test/kotlin/model/IntegrateTests.kt b/src/test/kotlin/model/IntegrateTests.kt new file mode 100644 index 0000000..077c0ae --- /dev/null +++ b/src/test/kotlin/model/IntegrateTests.kt @@ -0,0 +1,63 @@ +package model + +import androidx.compose.ui.geometry.Offset +import model.graph.UndirectedGraph +import org.junit.jupiter.api.Test +import viewModel.MainViewModel +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class IntegrateTests { + @Test + fun scenario1() { + // User launch app, mainViewModel is creating + val AMOUNT_NODES = 16 + val EDGE_CHANGE = 5.0 + + val graph = UndirectedGraph().apply { + for (i in (0 until AMOUNT_NODES)) { + addVertex(i) + } + + for (i in (0 until AMOUNT_NODES)) { + for (j in (0 until AMOUNT_NODES)) { + if (Math.random() < EDGE_CHANGE / 100) { + addEdge(i, j) + } + } + } + } + + val mainViewModel = MainViewModel(graph) + + // User create a few nodes + val oldSize = graph.vertices.size + + mainViewModel.canvasViewModel.isNodeCreatingMode = true + mainViewModel.canvasViewModel.createNode(offset = Offset(100f, 100f)) + mainViewModel.canvasViewModel.createNode(offset = Offset(300f, 100f)) + mainViewModel.canvasViewModel.createNode(offset = Offset(200f, 100f)) + mainViewModel.canvasViewModel.createNode(offset = Offset(100f, 100f)) + + // Graph changed + assertTrue(graph.vertices.size != oldSize) + + mainViewModel.canvasViewModel.isNodeCreatingMode = false + + // User add edge + mainViewModel.canvasViewModel.isEdgeCreatingMode = true + val firstVertex = mainViewModel.canvasViewModel.vertices.find { it.vertexViewModel.getKey() == 17 } + ?: throw Error("There is no vertex with id 17") + val secondVertex = mainViewModel.canvasViewModel.vertices.find { it.vertexViewModel.getKey() == 18 } + ?: throw Error("There is no vertex with id 18") + + // User click on two vertecies + mainViewModel.canvasViewModel.onClick(firstVertex) + mainViewModel.canvasViewModel.onClick(secondVertex) + + // Edge created + val edges = mainViewModel.canvasViewModel.edges.flatten() + val edge = edges.find { it.first == firstVertex && it.second == secondVertex } + assertNotNull(edge) + } +} \ No newline at end of file