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 1 commit
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
Prev Previous commit
Next Next commit
DOM events refactoring: Implemented pseudo-EventTargets defined on a …
…real Element with a specified bounding box. This allows handling mouse events for both single and composite plots using a single Element.
  • Loading branch information
IKupriyanov-HORIS committed Jun 17, 2024
commit 4f49c5512ec68e775e2d5a0ce806f80cef59c697
1,936 changes: 1,783 additions & 153 deletions docs/dev/notebooks/interactive_tools.ipynb

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions js-package/src/jsMain/kotlin/FigureModelJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/

import org.jetbrains.letsPlot.commons.event.MouseEventSource
import org.jetbrains.letsPlot.commons.logging.PortableLogging
import org.jetbrains.letsPlot.commons.registration.Registration
import org.jetbrains.letsPlot.core.interact.event.ToolEventDispatcher
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 @@ -50,7 +50,7 @@ class FigureModelJs internal constructor(
monolithicParameters.width,
monolithicParameters.height,
monolithicParameters.parentElement,
monolithicParameters.mouseEventSource,
monolithicParameters.eventTarget,
monolithicParameters.options
)

Expand Down Expand Up @@ -91,6 +91,6 @@ internal class MonolithicParameters(
val width: Double,
val height: Double,
val parentElement: HTMLElement,
val mouseEventSource: MouseEventSource,
val eventTarget: Element,
val options: Map<String, Any>
)
59 changes: 36 additions & 23 deletions js-package/src/jsMain/kotlin/FigureToHtml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.dom.createElement
import org.jetbrains.letsPlot.commons.event.MouseEventSource
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 Down Expand Up @@ -37,7 +37,7 @@ import org.w3c.dom.svg.SVGSVGElement
internal class FigureToHtml(
private val buildInfo: FigureBuildInfo,
private val containerElement: HTMLElement,
private val mouseEventSource: MouseEventSource
private val eventTarget: Element
) {

private val parentElement: HTMLElement = if (buildInfo.isComposite) {
Expand Down Expand Up @@ -71,13 +71,14 @@ internal class FigureToHtml(
svgRoot,
origin = null, // The topmost SVG
parentElement = parentElement,
mouseEventSource = mouseEventSource
eventTarget = eventTarget
)
} else {
processPlotFigure(
svgRoot as PlotSvgRoot,
svgRoot = svgRoot as PlotSvgRoot,
parentElement = parentElement,
eventMapper = mouseEventSource
eventTarget = eventTarget,
eventArea = buildInfo.bounds
)
}

Expand All @@ -104,11 +105,12 @@ internal class FigureToHtml(
private fun processPlotFigure(
svgRoot: PlotSvgRoot,
parentElement: HTMLElement,
eventMapper: MouseEventSource
eventTarget: Element,
eventArea: DoubleRectangle
): ToolEventDispatcher {

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

// Livemap cursor pointer
Expand All @@ -126,7 +128,7 @@ internal class FigureToHtml(
svgRoot: CompositeFigureSvgRoot,
origin: DoubleVector?,
parentElement: HTMLElement,
mouseEventSource: MouseEventSource
eventTarget: Element,
): ToolEventDispatcher {
svgRoot.ensureContentBuilt()

Expand Down Expand Up @@ -162,11 +164,12 @@ internal class FigureToHtml(
processPlotFigure(
svgRoot = figureSvgRoot,
parentElement = figureContainer,
eventMapper = mouseEventSource
eventTarget = eventTarget,
eventArea = figureSvgRoot.bounds.add(origin)
)
} else {
figureSvgRoot as CompositeFigureSvgRoot
processCompositeFigure(figureSvgRoot, elementOrigin, parentElement, mouseEventSource)
processCompositeFigure(figureSvgRoot, elementOrigin, parentElement, eventTarget)
}
}

Expand Down Expand Up @@ -199,9 +202,10 @@ internal class FigureToHtml(
private fun buildPlotFigureSVG(
plotContainer: PlotContainer,
parentElement: Element,
mouseEventSource: MouseEventSource
eventTarget: Element,
eventArea: DoubleRectangle
): SVGSVGElement {

val disposer = CompositeRegistration()
val svg: SVGSVGElement = mapSvgToSVG(plotContainer.svg)

if (plotContainer.isLiveMap) {
Expand All @@ -210,7 +214,10 @@ internal class FigureToHtml(
}
}

plotContainer.mouseEventPeer.addEventSource(mouseEventSource)
val plotMouseEventMapper = DomMouseEventMapper(eventTarget, eventArea)
disposer.add(Registration.from(plotMouseEventMapper))

plotContainer.mouseEventPeer.addEventSource(plotMouseEventMapper)

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

val canvasMouseEventMapper = DomMouseEventMapper(
eventTarget,
DoubleRectangle(
eventArea.origin.add(bounds.origin.toDoubleVector()),
bounds.dimension.toDoubleVector()
)
)
disposer.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 @@ -243,10 +251,15 @@ internal class FigureToHtml(
liveMapDiv.onDisconnect(liveMapReg::dispose)
}

// TODO: temporary solution. Dispose in FigureToHtml.Result.figureRegistration instead.
svg.onDisconnect {
println("Plot disconnected")
disposer.dispose()
}
return svg
}

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

import kotlinx.browser.document
import org.jetbrains.letsPlot.commons.event.MouseEventSource
import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.commons.logging.PortableLogging
import org.jetbrains.letsPlot.core.platf.dom.DomMouseEventMapper
import org.jetbrains.letsPlot.core.plot.builder.FigureBuildInfo
import org.jetbrains.letsPlot.core.spec.FailureHandler
import org.jetbrains.letsPlot.core.spec.config.PlotConfig
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.HTMLDivElement
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 @@ -55,11 +50,10 @@ fun buildPlotFromRawSpecs(
emptyMap()
}

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

parentElement.appendChild(figureContainer)
buildPlotFromProcessedSpecsIntern(processedSpec, width, height, figureContainer, mouseEventSource, options)
parentElement.appendChild(persistentDiv)
buildPlotFromProcessedSpecsIntern(processedSpec, width, height, persistentDiv, persistentDiv, options)
} catch (e: RuntimeException) {
handleException(e, parentElement)
null
Expand Down Expand Up @@ -93,11 +87,11 @@ fun buildPlotFromProcessedSpecs(
emptyMap()
}

val figureContainer = document.createElement("div") as HTMLDivElement
parentElement.appendChild(figureContainer)
val mouseEventSource = DomMouseEventMapper(figureContainer)
val persistentDiv = document.createElement("div") as HTMLDivElement

buildPlotFromProcessedSpecsIntern(processedSpec, width, height, figureContainer, mouseEventSource, options)
parentElement.appendChild(persistentDiv)

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

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

Expand All @@ -179,7 +173,7 @@ internal fun buildPlotFromProcessedSpecsIntern(
fun buildGGBunchComponent(
plotInfos: List<FigureBuildInfo>,
parentElement: HTMLElement,
mouseEventSource: MouseEventSource
eventTarget: Element
) {
val bunchBounds = plotInfos.map { it.bounds }
.fold(DoubleRectangle(DoubleVector.ZERO, DoubleVector.ZERO)) { acc, bounds ->
Expand All @@ -199,7 +193,7 @@ fun buildGGBunchComponent(
FigureToHtml(
buildInfo = plotInfo,
containerElement = itemContainerElement,
mouseEventSource = mouseEventSource
eventTarget = eventTarget
).eval()

}
Expand Down