feat(plot): configure one or two marker pairs per axis
This commit is contained in:
@@ -10,6 +10,8 @@ enum class TrendMarker(val title: String, val horizontal: Boolean) {
|
|||||||
data class TrendMarkers(
|
data class TrendMarkers(
|
||||||
val xEnabled: Boolean = true,
|
val xEnabled: Boolean = true,
|
||||||
val yEnabled: Boolean = false,
|
val yEnabled: Boolean = false,
|
||||||
|
val xPairs: Int = 1,
|
||||||
|
val yPairs: Int = 1,
|
||||||
val selected: TrendMarker = TrendMarker.A,
|
val selected: TrendMarker = TrendMarker.A,
|
||||||
val a: Double? = null,
|
val a: Double? = null,
|
||||||
val b: Double? = null,
|
val b: Double? = null,
|
||||||
@@ -20,6 +22,9 @@ data class TrendMarkers(
|
|||||||
val g: Double? = null,
|
val g: Double? = null,
|
||||||
val h: 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) {
|
fun value(marker: TrendMarker): Double? = when (marker) {
|
||||||
TrendMarker.A -> a; TrendMarker.B -> b; TrendMarker.C -> c; TrendMarker.D -> d
|
TrendMarker.A -> a; TrendMarker.B -> b; TrendMarker.C -> c; TrendMarker.D -> d
|
||||||
TrendMarker.E -> e; TrendMarker.F -> f; TrendMarker.G -> g; TrendMarker.H -> h
|
TrendMarker.E -> e; TrendMarker.F -> f; TrendMarker.G -> g; TrendMarker.H -> h
|
||||||
@@ -49,5 +54,10 @@ data class TrendMarkers(
|
|||||||
}.sortedWith(compareBy<Pair<TrendMarker, Double>> { it.second }
|
}.sortedWith(compareBy<Pair<TrendMarker, Double>> { it.second }
|
||||||
.thenBy { if (it.first == selected) 0 else 1 }).firstOrNull()?.first
|
.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ class PlotContractTest {
|
|||||||
@Test fun markerCoordinatesSurviveZoomAndDragUsesGestureStart() {
|
@Test fun markerCoordinatesSurviveZoomAndDragUsesGestureStart() {
|
||||||
val full = PlotBounds(1000.0, 2000.0, -10.0, 10.0)
|
val full = PlotBounds(1000.0, 2000.0, -10.0, 10.0)
|
||||||
val markers = TrendMarkers().positioned(full)
|
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)
|
val zoomed = PlotBounds(1250.0, 1750.0, -5.0, 5.0)
|
||||||
assertEquals(markers, markers.positioned(zoomed))
|
assertEquals(markers, markers.positioned(zoomed))
|
||||||
val dragged = markers.drag(TrendMarker.A, 50.0, 500.0, zoomed)
|
val dragged = markers.drag(TrendMarker.A, 50.0, 500.0, zoomed)
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ class Bounds:
|
|||||||
class Markers:
|
class Markers:
|
||||||
x_enabled: bool = True
|
x_enabled: bool = True
|
||||||
y_enabled: bool = False
|
y_enabled: bool = False
|
||||||
|
x_pairs: int = 1
|
||||||
|
y_pairs: int = 1
|
||||||
selected: Marker = Marker.A
|
selected: Marker = Marker.A
|
||||||
a: Optional[float] = None
|
a: Optional[float] = None
|
||||||
b: Optional[float] = None
|
b: Optional[float] = None
|
||||||
@@ -112,11 +114,21 @@ class Markers:
|
|||||||
g: Optional[float] = None
|
g: Optional[float] = None
|
||||||
h: 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]:
|
def value(self, marker: Marker) -> Optional[float]:
|
||||||
return getattr(self, marker.name.lower())
|
return getattr(self, marker.name.lower())
|
||||||
|
|
||||||
def enabled(self, marker: Marker) -> bool:
|
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":
|
def move(self, marker: Marker, value: float) -> "Markers":
|
||||||
return replace(self, **{marker.name.lower(): value})
|
return replace(self, **{marker.name.lower(): value})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import math
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import unittest
|
import unittest
|
||||||
|
from dataclasses import replace
|
||||||
|
|
||||||
from protocan.plot import Bounds, Marker, Markers, PlotMath, Viewport
|
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):
|
def test_markers_stay_in_data_coordinates_and_cross(self):
|
||||||
bounds = Bounds(1000, 2000, -10, 10)
|
bounds = Bounds(1000, 2000, -10, 10)
|
||||||
markers = Markers().positioned(self.core, bounds)
|
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))
|
zoomed = bounds.visible(self.core, Viewport(.25, .25, .5, .5))
|
||||||
self.assertEqual(markers, markers.positioned(self.core, zoomed))
|
self.assertEqual(markers, markers.positioned(self.core, zoomed))
|
||||||
moved = markers.drag(self.core, Marker.A, 50, 500, zoomed)
|
moved = markers.drag(self.core, Marker.A, 50, 500, zoomed)
|
||||||
|
|||||||
Reference in New Issue
Block a user