All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.org.jetbrains.letsPlot.awt.plot.FigureToAwt.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2023. JetBrains s.r.o.
 * Use of this source code is governed by the MIT license that can be found in the LICENSE file.
 */

package org.jetbrains.letsPlot.awt.plot

import org.jetbrains.letsPlot.awt.util.AwtEventUtil
import org.jetbrains.letsPlot.commons.event.MouseEventSpec
import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.commons.registration.Disposable
import org.jetbrains.letsPlot.commons.registration.DisposingHub
import org.jetbrains.letsPlot.core.interact.event.ToolEventDispatcher
import org.jetbrains.letsPlot.core.plot.builder.FigureBuildInfo
import org.jetbrains.letsPlot.core.plot.builder.GeomLayer
import org.jetbrains.letsPlot.core.plot.builder.PlotContainer
import org.jetbrains.letsPlot.core.plot.builder.PlotSvgRoot
import org.jetbrains.letsPlot.core.plot.builder.subPlots.CompositeFigureSvgRoot
import org.jetbrains.letsPlot.core.plot.livemap.CursorServiceConfig
import org.jetbrains.letsPlot.core.plot.livemap.LiveMapProviderUtil
import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement
import java.awt.Rectangle
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.JComponent

internal class FigureToAwt(
    private val buildInfo: FigureBuildInfo,
    private val svgComponentFactory: (svg: SvgSvgElement) -> JComponent,
    private val executor: (() -> Unit) -> Unit
) {

    fun eval(): JComponent {

        val buildInfo = buildInfo.layoutedByOuterSize()

        buildInfo.injectLiveMapProvider { tiles: List>, spec: Map ->
            val cursorServiceConfig = CursorServiceConfig()
            LiveMapProviderUtil.injectLiveMapProvider(tiles, spec, cursorServiceConfig)
            cursorServiceConfig
        }

        val svgRoot = buildInfo.createSvgRoot()
        return if (svgRoot is CompositeFigureSvgRoot) {
            processCompositeFigure(svgRoot)
        } else {
            processPlotFigure(svgRoot as PlotSvgRoot)
        }
    }

    private fun processCompositeFigure(
        svgRoot: CompositeFigureSvgRoot,
    ): JComponent {

        svgRoot.ensureContentBuilt()

        // JPanel
        val rootJPanel = DisposableJPanel(null)
        rootJPanel.registerDisposable(
            object : Disposable {
                override fun dispose() {
                    svgRoot.clearContent()
                }
            }
        )

        rootJPanel.border = null
//        rootJPanel.background = Colors.parseColor(Defaults.BACKDROP_COLOR).let {
//            Color(
//                it.red,
//                it.green,
//                it.blue,
//                it.alpha
//            )
//        }
        rootJPanel.isOpaque = false

        fun toJBounds(from: DoubleRectangle): Rectangle {
            return Rectangle(
                from.origin.x.toInt(),
                from.origin.y.toInt(),
                from.dimension.x.toInt(),
                from.dimension.y.toInt()
            )
        }

//        val rootFigureBounds = toJBounds(svgRoot.bounds)
        val rootJComponentBounds = toJBounds(
            DoubleRectangle(DoubleVector.ZERO, svgRoot.bounds.dimension)
        )
        val rootFigureDim = rootJComponentBounds.size
        rootJPanel.preferredSize = rootFigureDim
        rootJPanel.minimumSize = rootFigureDim
        rootJPanel.maximumSize = rootFigureDim

        val rootJComponent: JComponent = svgComponentFactory(svgRoot.svg)
//        rootJComponent.bounds = rootFigureBounds
        rootJComponent.bounds = rootJComponentBounds
        rootJPanel.add(rootJComponent)

        //
        // Sub-plots
        //

        val elementJComponents = ArrayList()
        for (element in svgRoot.elements) {
            if (element is PlotSvgRoot) {
                val comp = processPlotFigure(element)
                comp.bounds = toJBounds(element.bounds)
                elementJComponents.add(comp)
            } else {
                val comp = processCompositeFigure(element as CompositeFigureSvgRoot)
                comp.bounds = toJBounds(element.bounds)
                elementJComponents.add(comp)
            }
        }

        elementJComponents.forEach {
//            rootJPanel.add(it)   // Do not!!!

            // Do not add everithing to root panel.
            // Instead, build components tree: rootPanel -> rootComp -> [subComp->[subSubComp,...], ...].
            // Otherwise JavaFX will not properly propogate mouse events.
            rootJComponent.add(it)
        }

        return rootJPanel
    }

    private fun processPlotFigure(
        svgRoot: PlotSvgRoot,
    ): JComponent {
        val plotContainer = PlotContainer(svgRoot)
        val plotComponent = buildSinglePlotComponent(plotContainer, svgComponentFactory, executor)

        return if (svgRoot.isLiveMap) {
            AwtLiveMapPanel(
//                plotContainer,
                svgRoot.liveMapFigures,
                plotComponent,
                executor,
                svgRoot.liveMapCursorServiceConfig as CursorServiceConfig
            )

        } else {
            plotComponent
        }
    }


    companion object {
        private fun buildSinglePlotComponent(
            plotContainer: PlotContainer,
            svgComponentFactory: (svg: SvgSvgElement) -> JComponent,
            executor: (() -> Unit) -> Unit
        ): JComponent {
            val svg = plotContainer.svg

            val plotComponent: JComponent = svgComponentFactory(svg)
            (plotComponent as DisposingHub).registerDisposable(plotContainer)
            plotComponent.putClientProperty(ToolEventDispatcher::class, plotContainer.toolEventDispatcher)
            (plotComponent as DisposingHub).registerDisposable(object : Disposable {
                override fun dispose() {
                    plotComponent.putClientProperty(ToolEventDispatcher::class, null)
                }
            })

            plotComponent.addMouseMotionListener(object : MouseAdapter() {
                private var lastEvent: MouseEvent? = null

                override fun mouseMoved(e: MouseEvent) {
                    super.mouseMoved(e)

                    // For some reason AWT sends two events with the same coord for one mouse move.
                    lastEvent?.let {
                        if (it.id == e.id && it.point == e.point) {
                            return
                        }
                    }

                    lastEvent = e

                    executor {
                        plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_MOVED, AwtEventUtil.translate(e))
                    }
                }

                override fun mouseDragged(e: MouseEvent) {
                    super.mouseDragged(e)
                    executor {
                        plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_DRAGGED, AwtEventUtil.translate(e))
                    }
                }
            })

            plotComponent.addMouseListener(object : MouseAdapter() {
                override fun mouseExited(e: MouseEvent) {
                    super.mouseExited(e)
                    executor {
                        plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_LEFT, AwtEventUtil.translate(e))
                    }
                }

                override fun mouseClicked(e: MouseEvent) {
                    super.mouseClicked(e)
                    val event = if (e.clickCount % 2 == 1) {
                        MouseEventSpec.MOUSE_CLICKED
                    } else {
                        MouseEventSpec.MOUSE_DOUBLE_CLICKED
                    }

                    executor {
                        plotContainer.mouseEventPeer.dispatch(event, AwtEventUtil.translate(e))
                    }
                }

                override fun mousePressed(e: MouseEvent) {
                    super.mousePressed(e)
                    executor {
                        plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_PRESSED, AwtEventUtil.translate(e))
                    }
                }

                override fun mouseReleased(e: MouseEvent) {
                    super.mouseReleased(e)
                    executor {
                        plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_RELEASED, AwtEventUtil.translate(e))
                    }
                }

                override fun mouseEntered(e: MouseEvent) {
                    super.mouseEntered(e)
                    executor {
                        plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_ENTERED, AwtEventUtil.translate(e))
                    }
                }
            })

            plotComponent.addMouseWheelListener { e ->
                executor {
                    plotContainer.mouseEventPeer.dispatch(MouseEventSpec.MOUSE_WHEEL_ROTATED, AwtEventUtil.translate(e))
                }
            }

            return plotComponent
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy