From f6787163dcd89a239392896b57b6db4d89cb22b9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 4 Sep 2026 12:56:16 +0300 Subject: [PATCH] feat(plot): configure one or two marker pairs per axis --- .../ru/setcorp/setprotocol/trends/TrendMarkers.kt | 12 +++++++++++- .../setcorp/setprotocol/trends/PlotContractTest.kt | 3 +++ python/protocan/plot.py | 14 +++++++++++++- python/tests/test_plot.py | 4 ++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendMarkers.kt b/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendMarkers.kt index a319035..57dabf6 100644 --- a/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendMarkers.kt +++ b/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendMarkers.kt @@ -10,6 +10,8 @@ enum class TrendMarker(val title: String, val horizontal: Boolean) { data class TrendMarkers( val xEnabled: Boolean = true, val yEnabled: Boolean = false, + val xPairs: Int = 1, + val yPairs: Int = 1, val selected: TrendMarker = TrendMarker.A, val a: Double? = null, val b: Double? = null, @@ -20,6 +22,9 @@ data class TrendMarkers( val g: Double? = null, val h: Double? = null, ) { + init { + require(xPairs in 1..2 && yPairs in 1..2) { "Количество пар маркеров должно быть от 1 до 2" } + } fun value(marker: TrendMarker): Double? = when (marker) { TrendMarker.A -> a; TrendMarker.B -> b; TrendMarker.C -> c; TrendMarker.D -> d TrendMarker.E -> e; TrendMarker.F -> f; TrendMarker.G -> g; TrendMarker.H -> h @@ -49,5 +54,10 @@ data class TrendMarkers( }.sortedWith(compareBy> { it.second } .thenBy { if (it.first == selected) 0 else 1 }).firstOrNull()?.first } - fun enabled(marker: TrendMarker): Boolean = if (marker.horizontal) yEnabled else xEnabled + fun enabled(marker: TrendMarker): Boolean = when (marker) { + TrendMarker.A, TrendMarker.B -> xEnabled + TrendMarker.C, TrendMarker.D -> xEnabled && xPairs >= 2 + TrendMarker.E, TrendMarker.F -> yEnabled + TrendMarker.G, TrendMarker.H -> yEnabled && yPairs >= 2 + } } diff --git a/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/trends/PlotContractTest.kt b/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/trends/PlotContractTest.kt index af427fa..803820a 100644 --- a/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/trends/PlotContractTest.kt +++ b/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/trends/PlotContractTest.kt @@ -25,6 +25,9 @@ class PlotContractTest { @Test fun markerCoordinatesSurviveZoomAndDragUsesGestureStart() { val full = PlotBounds(1000.0, 2000.0, -10.0, 10.0) val markers = TrendMarkers().positioned(full) + assertTrue(markers.enabled(TrendMarker.A)) + assertFalse(markers.enabled(TrendMarker.C)) + assertTrue(markers.copy(xPairs = 2).enabled(TrendMarker.C)) val zoomed = PlotBounds(1250.0, 1750.0, -5.0, 5.0) assertEquals(markers, markers.positioned(zoomed)) val dragged = markers.drag(TrendMarker.A, 50.0, 500.0, zoomed) diff --git a/python/protocan/plot.py b/python/protocan/plot.py index 127242b..9661827 100644 --- a/python/protocan/plot.py +++ b/python/protocan/plot.py @@ -102,6 +102,8 @@ class Bounds: class Markers: x_enabled: bool = True y_enabled: bool = False + x_pairs: int = 1 + y_pairs: int = 1 selected: Marker = Marker.A a: Optional[float] = None b: Optional[float] = None @@ -112,11 +114,21 @@ class Markers: g: Optional[float] = None h: Optional[float] = None + def __post_init__(self) -> None: + if self.x_pairs not in (1, 2) or self.y_pairs not in (1, 2): + raise ValueError("Marker pair count must be 1 or 2") + def value(self, marker: Marker) -> Optional[float]: return getattr(self, marker.name.lower()) def enabled(self, marker: Marker) -> bool: - return self.y_enabled if marker.horizontal else self.x_enabled + if marker in (Marker.A, Marker.B): + return self.x_enabled + if marker in (Marker.C, Marker.D): + return self.x_enabled and self.x_pairs >= 2 + if marker in (Marker.E, Marker.F): + return self.y_enabled + return self.y_enabled and self.y_pairs >= 2 def move(self, marker: Marker, value: float) -> "Markers": return replace(self, **{marker.name.lower(): value}) diff --git a/python/tests/test_plot.py b/python/tests/test_plot.py index 4470200..9fe8640 100644 --- a/python/tests/test_plot.py +++ b/python/tests/test_plot.py @@ -5,6 +5,7 @@ import math import os from pathlib import Path import unittest +from dataclasses import replace from protocan.plot import Bounds, Marker, Markers, PlotMath, Viewport @@ -31,6 +32,9 @@ class PlotTests(unittest.TestCase): def test_markers_stay_in_data_coordinates_and_cross(self): bounds = Bounds(1000, 2000, -10, 10) markers = Markers().positioned(self.core, bounds) + self.assertTrue(markers.enabled(Marker.A)) + self.assertFalse(markers.enabled(Marker.C)) + self.assertTrue(replace(markers, x_pairs=2).enabled(Marker.C)) zoomed = bounds.visible(self.core, Viewport(.25, .25, .5, .5)) self.assertEqual(markers, markers.positioned(self.core, zoomed)) moved = markers.drag(self.core, Marker.A, 50, 500, zoomed)