Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactivity: wheel tool fix for firefox #1116

Merged
merged 5 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2,445 changes: 2,368 additions & 77 deletions docs/dev/notebooks/interactive_tools.ipynb

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions js-package/src/jsMain/kotlin/FigureModelJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.jetbrains.letsPlot.core.spec.Option.Plot.SPEC_OVERRIDE
import org.jetbrains.letsPlot.platf.w3c.jsObject.dynamicFromAnyQ
import org.jetbrains.letsPlot.platf.w3c.jsObject.dynamicObjectToMap
import org.jetbrains.letsPlot.platf.w3c.jsObject.dynamicToAnyQ
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement

@OptIn(ExperimentalJsExport::class)
Expand Down Expand Up @@ -49,6 +50,7 @@ class FigureModelJs internal constructor(
monolithicParameters.width,
monolithicParameters.height,
monolithicParameters.parentElement,
monolithicParameters.eventTarget,
monolithicParameters.options
)

Expand Down Expand Up @@ -89,5 +91,6 @@ internal class MonolithicParameters(
val width: Double,
val height: Double,
val parentElement: HTMLElement,
val eventTarget: Element,
val options: Map<String, Any>
)
84 changes: 54 additions & 30 deletions js-package/src/jsMain/kotlin/FigureToHtml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import kotlinx.dom.createElement
import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.commons.geometry.Vector
import org.jetbrains.letsPlot.commons.registration.CompositeRegistration
import org.jetbrains.letsPlot.commons.registration.Registration
import org.jetbrains.letsPlot.core.canvasFigure.CanvasFigure
import org.jetbrains.letsPlot.core.interact.event.ToolEventDispatcher
Expand All @@ -35,7 +36,8 @@ import org.w3c.dom.svg.SVGSVGElement

internal class FigureToHtml(
private val buildInfo: FigureBuildInfo,
private val containerElement: HTMLElement
private val containerElement: HTMLElement,
private val eventTarget: Element
) {

private val parentElement: HTMLElement = if (buildInfo.isComposite) {
Expand All @@ -52,6 +54,10 @@ internal class FigureToHtml(
fun eval(): Result {

val buildInfo = buildInfo.layoutedByOuterSize()
containerElement.style.apply {
width = "${buildInfo.layoutInfo.figureSize.x}px"
height = "${buildInfo.layoutInfo.figureSize.y}px"
}

buildInfo.injectLiveMapProvider { tiles: List<List<GeomLayer>>, spec: Map<String, Any> ->
val cursorServiceConfig = CursorServiceConfig()
Expand All @@ -60,20 +66,23 @@ internal class FigureToHtml(
}

val svgRoot = buildInfo.createSvgRoot()
val toolEventDispatcher = if (svgRoot is CompositeFigureSvgRoot) {
val (toolEventDispatcher, eventsRegistration) = if (svgRoot is CompositeFigureSvgRoot) {
processCompositeFigure(
svgRoot,
origin = null, // The topmost SVG
parentElement = parentElement
parentElement = parentElement,
eventTarget = eventTarget
)
} else {
processPlotFigure(
svgRoot as PlotSvgRoot,
parentElement = parentElement
svgRoot = svgRoot as PlotSvgRoot,
parentElement = parentElement,
eventTarget = eventTarget,
eventArea = buildInfo.bounds
)
}

val registration = object : Registration() {
val domCleanupRegistration = object : Registration() {
override fun doRemove() {
while (containerElement.firstChild != null) {
containerElement.removeChild(containerElement.firstChild!!)
Expand All @@ -83,11 +92,14 @@ internal class FigureToHtml(

return Result(
toolEventDispatcher,
registration
CompositeRegistration().add(
eventsRegistration,
domCleanupRegistration
)
)
}

class Result(
data class Result(
val toolEventDispatcher: ToolEventDispatcher,
val figureRegistration: Registration
)
Expand All @@ -96,10 +108,12 @@ internal class FigureToHtml(
private fun processPlotFigure(
svgRoot: PlotSvgRoot,
parentElement: HTMLElement,
): ToolEventDispatcher {
eventTarget: Element,
eventArea: DoubleRectangle
): Pair<ToolEventDispatcher, Registration> {

val plotContainer = PlotContainer(svgRoot)
val rootSVG: SVGSVGElement = buildPlotFigureSVG(plotContainer, parentElement)
val (rootSVG, cleanupRegistration) = buildPlotFigureSVG(plotContainer, parentElement, eventTarget, eventArea)
rootSVG.style.setCursor(CssCursor.CROSSHAIR)

// Livemap cursor pointer
Expand All @@ -110,14 +124,15 @@ internal class FigureToHtml(
}

parentElement.appendChild(rootSVG)
return plotContainer.toolEventDispatcher
return plotContainer.toolEventDispatcher to cleanupRegistration
}

private fun processCompositeFigure(
svgRoot: CompositeFigureSvgRoot,
origin: DoubleVector?,
parentElement: HTMLElement,
): ToolEventDispatcher {
eventTarget: Element,
): Pair<ToolEventDispatcher, Registration> {
svgRoot.ensureContentBuilt()

val rootSvgSvg: SvgSvgElement = svgRoot.svg
Expand Down Expand Up @@ -151,15 +166,17 @@ internal class FigureToHtml(
}
processPlotFigure(
svgRoot = figureSvgRoot,
parentElement = figureContainer
parentElement = figureContainer,
eventTarget = eventTarget,
eventArea = figureSvgRoot.bounds.add(origin)
)
} else {
figureSvgRoot as CompositeFigureSvgRoot
processCompositeFigure(figureSvgRoot, elementOrigin, parentElement)
processCompositeFigure(figureSvgRoot, elementOrigin, parentElement, eventTarget)
}
}

return UnsupportedToolEventDispatcher()
return UnsupportedToolEventDispatcher() to Registration.EMPTY
}

fun setupRootHTMLElement(element: HTMLElement, size: DoubleVector) {
Expand Down Expand Up @@ -187,9 +204,10 @@ internal class FigureToHtml(

private fun buildPlotFigureSVG(
plotContainer: PlotContainer,
parentElement: Element
): SVGSVGElement {

parentElement: Element,
eventTarget: Element,
eventArea: DoubleRectangle,
): Pair<SVGSVGElement, Registration> {
val svg: SVGSVGElement = mapSvgToSVG(plotContainer.svg)

if (plotContainer.isLiveMap) {
Expand All @@ -198,7 +216,12 @@ internal class FigureToHtml(
}
}

plotContainer.mouseEventPeer.addEventSource(DomMouseEventMapper(svg))
val plotMouseEventMapper = DomMouseEventMapper(eventTarget, eventArea)

val eventsRegistration = CompositeRegistration()
eventsRegistration.add(Registration.from(plotMouseEventMapper))

plotContainer.mouseEventPeer.addEventSource(plotMouseEventMapper)

plotContainer.liveMapFigures.forEach { liveMapFigure ->
val bounds = (liveMapFigure as CanvasFigure).bounds().get()
Expand All @@ -211,18 +234,19 @@ internal class FigureToHtml(
setPosition(CssPosition.RELATIVE)
}

val canvasMouseEventMapper = DomMouseEventMapper(
eventTarget,
DoubleRectangle(
eventArea.origin.add(bounds.origin.toDoubleVector()),
bounds.dimension.toDoubleVector()
)
)
eventsRegistration.add(Registration.from(canvasMouseEventMapper))

val canvasControl = DomCanvasControl(
myRootElement = liveMapDiv,
size = Vector(bounds.dimension.x, bounds.dimension.y),
mouseEventSource = DomMouseEventMapper(
eventSource = svg,
bounds = DoubleRectangle.XYWH(
bounds.origin.x,
bounds.origin.y,
bounds.dimension.x,
bounds.dimension.y
)
)
mouseEventSource = canvasMouseEventMapper
)

val liveMapReg = liveMapFigure.mapToCanvas(canvasControl)
Expand All @@ -231,10 +255,10 @@ internal class FigureToHtml(
liveMapDiv.onDisconnect(liveMapReg::dispose)
}

return svg
return svg to eventsRegistration
}

private fun HTMLElement.onDisconnect(onDisconnected: () -> Unit): Int {
private fun Node.onDisconnect(onDisconnected: () -> Unit): Int {
fun checkConnection() {
if (!isConnected) {
onDisconnected()
Expand Down
34 changes: 24 additions & 10 deletions js-package/src/jsMain/kotlin/MonolithicJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

/* root package */

import kotlinx.browser.document
import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.commons.logging.PortableLogging
Expand All @@ -15,9 +16,7 @@ import org.jetbrains.letsPlot.core.util.MonolithicCommon
import org.jetbrains.letsPlot.core.util.MonolithicCommon.PlotsBuildResult.Error
import org.jetbrains.letsPlot.core.util.MonolithicCommon.PlotsBuildResult.Success
import org.jetbrains.letsPlot.platf.w3c.jsObject.dynamicObjectToMap
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLParagraphElement
import org.w3c.dom.get
import org.w3c.dom.*
import sizing.SizingOption
import sizing.SizingPolicy

Expand Down Expand Up @@ -50,7 +49,11 @@ fun buildPlotFromRawSpecs(
} else {
emptyMap()
}
buildPlotFromProcessedSpecsIntern(processedSpec, width, height, parentElement, options)

val persistentDiv = document.createElement("div") as HTMLDivElement

parentElement.appendChild(persistentDiv)
buildPlotFromProcessedSpecsIntern(processedSpec, width, height, persistentDiv, persistentDiv, options)
} catch (e: RuntimeException) {
handleException(e, parentElement)
null
Expand Down Expand Up @@ -83,7 +86,12 @@ fun buildPlotFromProcessedSpecs(
} else {
emptyMap()
}
buildPlotFromProcessedSpecsIntern(processedSpec, width, height, parentElement, options)

val persistentDiv = document.createElement("div") as HTMLDivElement

parentElement.appendChild(persistentDiv)

buildPlotFromProcessedSpecsIntern(processedSpec, width, height, persistentDiv, persistentDiv, options)
} catch (e: RuntimeException) {
handleException(e, parentElement)
null
Expand All @@ -95,6 +103,7 @@ internal fun buildPlotFromProcessedSpecsIntern(
width: Double,
height: Double,
parentElement: HTMLElement,
eventTarget: Element,
options: Map<String, Any>
): FigureModelJs? {

Expand Down Expand Up @@ -145,23 +154,27 @@ internal fun buildPlotFromProcessedSpecsIntern(
val figureModel = if (success.buildInfos.size == 1) {
// a single figure
val buildInfo = success.buildInfos[0]
val result = FigureToHtml(buildInfo, parentElement).eval()
val result = FigureToHtml(buildInfo, parentElement, eventTarget).eval()
FigureModelJs(
plotSpec,
MonolithicParameters(width, height, parentElement, options),
MonolithicParameters(width, height, parentElement, eventTarget, options),
result.toolEventDispatcher,
result.figureRegistration
)
} else {
// a bunch
buildGGBunchComponent(success.buildInfos, parentElement)
buildGGBunchComponent(success.buildInfos, parentElement, eventTarget)
null
}

return figureModel
}

fun buildGGBunchComponent(plotInfos: List<FigureBuildInfo>, parentElement: HTMLElement) {
fun buildGGBunchComponent(
plotInfos: List<FigureBuildInfo>,
parentElement: HTMLElement,
eventTarget: Element
) {
val bunchBounds = plotInfos.map { it.bounds }
.fold(DoubleRectangle(DoubleVector.ZERO, DoubleVector.ZERO)) { acc, bounds ->
acc.union(bounds)
Expand All @@ -179,7 +192,8 @@ fun buildGGBunchComponent(plotInfos: List<FigureBuildInfo>, parentElement: HTMLE

FigureToHtml(
buildInfo = plotInfo,
containerElement = itemContainerElement
containerElement = itemContainerElement,
eventTarget = eventTarget
).eval()

}
Expand Down