Skip to content

Commit 9cb8ca6

Browse files
committed
Fix pixel density scaling.
1 parent 0b2f017 commit 9cb8ca6

File tree

5 files changed

+30
-10
lines changed

5 files changed

+30
-10
lines changed

demo/plot/compose-desktop/src/main/kotlin/demo/plot/pixeldensity/PixelDensityTestDemo.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ fun main() = application {
6060
// Plot panel that reacts to pixel density changes
6161
PlotPanel(
6262
figure = createTestFigure(),
63-
preserveAspectRatio = false,
63+
preserveAspectRatio = true,
6464
modifier = Modifier.fillMaxSize(),
6565
computationMessagesHandler = { messages ->
6666
messages.forEach { println("Plot: $it") }

lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/PlotPanel.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ actual fun PlotPanel(
6565
.weight(1f) // Take the remaining vertical space
6666
.fillMaxWidth() // Fill available width
6767
.onSizeChanged { newSize ->
68+
// Convert logical pixels (from Compose layout) to physical pixels (plot SVG pixels)
6869
panelSize = DoubleVector(newSize.width / density, newSize.height / density)
6970
}
7071
.background(Color.LightGray)
@@ -104,12 +105,14 @@ actual fun PlotPanel(
104105
val plotWidth = viewModel.svg.width().get() ?: panelSize.x
105106
val plotHeight = viewModel.svg.height().get() ?: panelSize.y
106107

108+
// Calculate centering position in physical pixels
109+
// Both panelSize and plot dimensions are in physical pixels
107110
val position = DoubleVector(
108111
maxOf(0.0, (panelSize.x - plotWidth) / 2.0),
109112
maxOf(0.0, (panelSize.y - plotHeight) / 2.0)
110113
)
111114

112-
plotContainer.updateViewModel(viewModel, position)
115+
plotContainer.updateViewModel(viewModel, position, density.toFloat())
113116
}
114117
}
115118

lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/desktop/PlotContainer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ class PlotContainer : Disposable {
1313
internal val svgView = SvgView()
1414
private var viewModel: ViewModel? = null
1515

16-
fun updateViewModel(viewModel: ViewModel, position: DoubleVector) {
16+
fun updateViewModel(viewModel: ViewModel, position: DoubleVector, pixelDensity: Float) {
1717
this.viewModel = viewModel
1818

1919
svgView.svg = viewModel.svg
2020
svgView.eventDispatcher = viewModel.eventDispatcher
2121
svgView.setPosition(position.x.toFloat(), position.y.toFloat())
22+
svgView.setPixelDensity(pixelDensity)
2223
}
2324

2425
override fun dispose() {

lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/desktop/SvgView.kt

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,20 @@ class SvgView : SkiaSvgView() {
3030
// Position offset for centering the plot
3131
private var offsetX: Float = 0f
3232
private var offsetY: Float = 0f
33+
34+
private var pixelDensity: Float = 1.0f
3335

3436
fun setPosition(x: Float, y: Float) {
37+
// physical pixels (plot SVG pixels)
3538
offsetX = x
3639
offsetY = y
3740
needRedraw()
3841
}
42+
43+
fun setPixelDensity(density: Float) {
44+
pixelDensity = density
45+
needRedraw()
46+
}
3947

4048
override fun needRedraw() {
4149
// Forward redraw requests to the Compose recomposition mechanism.
@@ -50,10 +58,15 @@ class SvgView : SkiaSvgView() {
5058
@Suppress("USELESS_CAST")
5159
val canvas = drawScope.drawContext.canvas.nativeCanvas as org.jetbrains.skia.Canvas
5260

53-
// Apply position offset for centering
61+
// Apply density scaling and position offset
5462
canvas.save()
5563
try {
64+
// Scale from physical pixels (plot SVG pixels) back to logical pixels (canvas pixels)
65+
canvas.scale(pixelDensity, pixelDensity)
66+
67+
// Apply position offset for centering (also in physical pixels, so gets scaled too)
5668
canvas.translate(offsetX, offsetY)
69+
5770
renderIntern(canvas)
5871
} finally {
5972
canvas.restore()
@@ -64,9 +77,11 @@ class SvgView : SkiaSvgView() {
6477
val change = pointerEvent.changes.first()
6578
val position = change.position
6679

67-
// Adjust coordinates to account for the position offset
68-
val adjustedX = (position.x - offsetX).roundToInt()
69-
val adjustedY = (position.y - offsetY).roundToInt()
80+
// Convert logical pixel coordinates to physical pixel coordinates for SVG interaction
81+
// 1. Scale down by density (logical → physical pixels)
82+
// 2. Subtract position offset (which is also in physical pixels)
83+
val adjustedX = ((position.x / pixelDensity) - offsetX).roundToInt()
84+
val adjustedY = ((position.y / pixelDensity) - offsetY).roundToInt()
7085
val vector = Vector(adjustedX, adjustedY)
7186

7287
// Translate PointerEvent to lets-plot MouseEvent
@@ -123,9 +138,9 @@ class SvgView : SkiaSvgView() {
123138
}
124139

125140
fun handleClick(position: Offset, clickCount: Int) {
126-
// Adjust coordinates to account for the position offset
127-
val adjustedX = (position.x - offsetX).roundToInt()
128-
val adjustedY = (position.y - offsetY).roundToInt()
141+
// Convert logical pixel coordinates to physical pixel coordinates for SVG interaction
142+
val adjustedX = ((position.x / pixelDensity) - offsetX).roundToInt()
143+
val adjustedY = ((position.y / pixelDensity) - offsetY).roundToInt()
129144
val vector = Vector(adjustedX, adjustedY)
130145
val mouseEvent = MouseEvent.leftButton(vector)
131146

lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/desktop/SvgViewPanel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fun SvgViewPanel(
5454
modifier = modifier
5555
.pointerHoverIcon(PointerIcon(Cursor(Cursor.CROSSHAIR_CURSOR)))
5656
.onSizeChanged { size ->
57+
// Convert canvas logical pixels (from Compose layout) to physical pixels (plot SVG pixels)
5758
val width = (size.width / density).toInt()
5859
val height = (size.height / density).toInt()
5960
canvasSize = IntSize(width, height)

0 commit comments

Comments
 (0)