Skip to content

Commit f89044c

Browse files
Add hyperlink support in SVG canvas: handle click events to open links
1 parent 6b904e9 commit f89044c

File tree

3 files changed

+42
-2
lines changed

3 files changed

+42
-2
lines changed

platf-awt/src/main/kotlin/org/jetbrains/letsPlot/awt/plot/component/DefaultPlotComponentProviderCanvas.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,20 @@ open class DefaultPlotComponentProviderCanvas(
2424
companion object {
2525
private val LOG = PortableLogging.logger(DefaultPlotComponentProviderCanvas::class)
2626

27+
private fun browseLink(href: String) {
28+
try {
29+
val uri = java.net.URI(href)
30+
java.awt.Desktop.getDesktop().browse(uri)
31+
} catch (e: Exception) {
32+
LOG.info { "Failed to open link: $href (${e.message})" }
33+
}
34+
}
35+
2736
private val SVG_COMPONENT_FACTORY_CANVAS = { svg: SvgSvgElement ->
2837
CanvasPane2().apply {
29-
figure = SvgCanvasFigure2(svg)
38+
figure = SvgCanvasFigure2(svg).apply {
39+
onHrefClick(::browseLink)
40+
}
3041
}
3142
}
3243
}

plot-raster/src/commonMain/kotlin/org/jetbrains/letsPlot/raster/view/PlotCanvasFigure2.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class PlotCanvasFigure2 : CanvasFigure2 {
5050
buildPlotSvg()
5151
}
5252

53+
fun onHrefClick(handler: (href: String) -> Unit) {
54+
plotSvgFigure.onHrefClick(handler)
55+
}
56+
5357
override fun paint(context2d: Context2d) {
5458
plotSvgFigure.paint(context2d)
5559
}
@@ -98,6 +102,7 @@ class PlotCanvasFigure2 : CanvasFigure2 {
98102
)
99103

100104
plotSvgFigure.svgSvgElement = vm.svg
105+
plotSvgFigure.eventPeer.addEventSource(eventPeer)
101106
eventReg = vm.eventDispatcher.addEventSource(eventPeer)
102107

103108
viewModel = vm

plot-raster/src/commonMain/kotlin/org/jetbrains/letsPlot/raster/view/SvgCanvasFigure2.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
package org.jetbrains.letsPlot.raster.view
77

8+
import org.jetbrains.letsPlot.commons.event.MouseEvent
89
import org.jetbrains.letsPlot.commons.event.MouseEventPeer
10+
import org.jetbrains.letsPlot.commons.event.MouseEventSpec
911
import org.jetbrains.letsPlot.commons.geometry.Vector
12+
import org.jetbrains.letsPlot.commons.intern.observable.event.EventHandler
1013
import org.jetbrains.letsPlot.commons.registration.Registration
1114
import org.jetbrains.letsPlot.core.canvas.CanvasPeer
1215
import org.jetbrains.letsPlot.core.canvas.Context2d
@@ -20,6 +23,7 @@ import org.jetbrains.letsPlot.raster.mapping.svg.SvgCanvasPeer
2023
import org.jetbrains.letsPlot.raster.mapping.svg.SvgSvgElementMapper
2124
import org.jetbrains.letsPlot.raster.shape.Container
2225
import org.jetbrains.letsPlot.raster.shape.Element
26+
import org.jetbrains.letsPlot.raster.shape.reversedDepthFirstTraversal
2327
import kotlin.math.ceil
2428

2529
class SvgCanvasFigure2(svg: SvgSvgElement = SvgSvgElement()) : CanvasFigure2 {
@@ -29,6 +33,8 @@ class SvgCanvasFigure2(svg: SvgSvgElement = SvgSvgElement()) : CanvasFigure2 {
2933
return Vector(contentWidth, contentHeight)
3034
}
3135

36+
override val eventPeer: MouseEventPeer = MouseEventPeer()
37+
3238
var svgSvgElement: SvgSvgElement = svg
3339
set(value) {
3440
field = value
@@ -41,6 +47,11 @@ class SvgCanvasFigure2(svg: SvgSvgElement = SvgSvgElement()) : CanvasFigure2 {
4147

4248
internal lateinit var rootMapper: SvgSvgElementMapper // = SvgSvgElementMapper(svgSvgElement, canvasPeer)
4349
private val repaintRequestListeners = mutableListOf<() -> Unit>()
50+
private var onHrefClick: (String) -> Unit = { }
51+
52+
fun onHrefClick(handler: (String) -> Unit) {
53+
onHrefClick = handler
54+
}
4455

4556
override fun mapToCanvas(canvasPeer: CanvasPeer): Registration {
4657
svgCanvasPeer = SvgCanvasPeer(canvasPeer)
@@ -54,7 +65,20 @@ class SvgCanvasFigure2(svg: SvgSvgElement = SvgSvgElement()) : CanvasFigure2 {
5465
}
5566
}
5667

57-
override val eventPeer: MouseEventPeer = MouseEventPeer()
68+
init {
69+
eventPeer.addEventHandler(MouseEventSpec.MOUSE_CLICKED, object : EventHandler<MouseEvent> {
70+
override fun onEvent(event: MouseEvent) {
71+
val coord = event.location.toDoubleVector()
72+
val linkElement = reversedDepthFirstTraversal(rootMapper.target)
73+
.filter { it.href != null }
74+
.filterNot(Element::isMouseTransparent)
75+
.firstOrNull() { coord in it.screenBounds }
76+
77+
val href = linkElement?.href ?: return
78+
onHrefClick(href)
79+
}
80+
})
81+
}
5882

5983
override fun paint(context2d: Context2d) {
6084
renderElement(rootMapper.target, context2d)

0 commit comments

Comments
 (0)