Skip to content

Commit

Permalink
Interactivity: wheel tool fix for firefox (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
IKupriyanov-HORIS committed Jun 17, 2024
1 parent b7bb4bc commit 8b2e923
Show file tree
Hide file tree
Showing 7 changed files with 2,560 additions and 185 deletions.
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

0 comments on commit 8b2e923

Please sign in to comment.