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

geom_spoke() as a layer of geom_livemap() #1049

Merged
merged 2 commits into from
Mar 14, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions docs/f-24b/geom_spoke_on_map.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class SpokeGeom : GeomBase(), WithWidth, WithHeight {
val y = finiteOrNull(p.y()) ?: continue
val spoke = toSpoke(p) ?: continue
val base = DoubleVector(x, y)
val start = getStart(base, spoke)
val end = getEnd(base, spoke)
val start = getStart(base, spoke, pivot)
val end = getEnd(base, spoke, pivot)
val line = svgElementHelper.createLine(start, end, p) ?: continue

root.add(line)
Expand Down Expand Up @@ -80,8 +80,8 @@ class SpokeGeom : GeomBase(), WithWidth, WithHeight {
loc.flip()
}
val spoke = toSpoke(p) ?: return null
val start = getStart(base, spoke)
val end = getEnd(base, spoke)
val start = getStart(base, spoke, pivot)
val end = getEnd(base, spoke, pivot)
return if (spanAxisAes == Aes.X) {
DoubleSpan(start.x, end.x)
} else {
Expand All @@ -93,23 +93,7 @@ class SpokeGeom : GeomBase(), WithWidth, WithHeight {
val angle = finiteOrNull(p.angle()) ?: return null
val radius = finiteOrNull(p.radius()) ?: return null

return DoubleVector(radius * cos(angle), radius * sin(angle))
}

private fun getStart(base: DoubleVector, spoke: DoubleVector): DoubleVector {
return when (pivot) {
Pivot.TAIL -> base
Pivot.MIDDLE -> base.subtract(spoke.mul(0.5))
Pivot.TIP -> base.subtract(spoke)
}
}

private fun getEnd(base: DoubleVector, spoke: DoubleVector): DoubleVector {
return when (pivot) {
Pivot.TAIL -> base.add(spoke)
Pivot.MIDDLE -> base.add(spoke.mul(0.5))
Pivot.TIP -> base
}
return getSpoke(angle, radius)
}

enum class Pivot {
Expand All @@ -119,6 +103,38 @@ class SpokeGeom : GeomBase(), WithWidth, WithHeight {
companion object {
val DEF_PIVOT = Pivot.TAIL

fun createGeometry(
x: Double,
y: Double,
angle: Double,
radius: Double,
pivot: Pivot
): List<DoubleVector> {
val base = DoubleVector(x, y)
val spoke = getSpoke(angle, radius)
return listOf(getStart(base, spoke, pivot), getEnd(base, spoke, pivot))
}

private fun getStart(base: DoubleVector, spoke: DoubleVector, pivot: Pivot): DoubleVector {
return when (pivot) {
Pivot.TAIL -> base
Pivot.MIDDLE -> base.subtract(spoke.mul(0.5))
Pivot.TIP -> base.subtract(spoke)
}
}

private fun getEnd(base: DoubleVector, spoke: DoubleVector, pivot: Pivot): DoubleVector {
return when (pivot) {
Pivot.TAIL -> base.add(spoke)
Pivot.MIDDLE -> base.add(spoke.mul(0.5))
Pivot.TIP -> base
}
}

private fun getSpoke(angle: Double, radius: Double): DoubleVector {
return DoubleVector(radius * cos(angle), radius * sin(angle))
}

const val HANDLES_GROUPS = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.jetbrains.letsPlot.commons.intern.typedGeometry.Vec
import org.jetbrains.letsPlot.commons.intern.typedGeometry.explicitVec
import org.jetbrains.letsPlot.commons.values.Color
import org.jetbrains.letsPlot.core.commons.data.SeriesUtil
import org.jetbrains.letsPlot.core.commons.data.SeriesUtil.finiteOrNull
import org.jetbrains.letsPlot.core.plot.base.Aes
import org.jetbrains.letsPlot.core.plot.base.Aesthetics
import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics
Expand Down Expand Up @@ -67,6 +68,7 @@ internal class DataPointsConverter(
fun toText(geom: Geom) = pointFeatureConverter.text(geom)
fun toPie(geom: PieGeom) = pieConverter(geom)
fun toCurve(geom: CurveGeom) = mySinglePathFeatureConverter.curve(geom)
fun toSpoke(geom: SpokeGeom) = mySinglePathFeatureConverter.spoke(geom)

private abstract class PathFeatureConverterBase(
val aesthetics: Aesthetics
Expand Down Expand Up @@ -230,6 +232,17 @@ internal class DataPointsConverter(
}
}

fun spoke(geom: SpokeGeom): List<DataPointLiveMapAesthetics> {
return process(isClosed = false) {
val x = finiteOrNull(it.x()) ?: return@process emptyList()
val y = finiteOrNull(it.y()) ?: return@process emptyList()
val angle = finiteOrNull(it.angle()) ?: return@process emptyList()
val radius = finiteOrNull(it.radius()) ?: return@process emptyList()

SpokeGeom.createGeometry(x, y, angle, radius, geom.pivot)
}
}

private fun process(
isClosed: Boolean,
dataPointToGeometry: (DataPointAesthetics) -> List<DoubleVector>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsUtil
import org.jetbrains.letsPlot.core.plot.base.geom.PieGeom
import org.jetbrains.letsPlot.core.plot.base.geom.PointGeom
import org.jetbrains.letsPlot.core.plot.base.geom.SegmentGeom
import org.jetbrains.letsPlot.core.plot.base.geom.CurveGeom
import org.jetbrains.letsPlot.core.plot.base.geom.SpokeGeom
import org.jetbrains.letsPlot.core.plot.builder.LayerRendererUtil.LayerRendererData
import org.jetbrains.letsPlot.core.canvas.FontStyle
import org.jetbrains.letsPlot.core.canvas.FontWeight
import org.jetbrains.letsPlot.core.plot.base.geom.CurveGeom
import org.jetbrains.letsPlot.livemap.api.*


Expand All @@ -43,6 +44,7 @@ object LayerConverter {
CURVE -> MapLayerKind.PATH to dataPointsConverter.toCurve(layer.geom as CurveGeom)
RECT -> MapLayerKind.POLYGON to dataPointsConverter.toRect()
TILE, BIN_2D -> MapLayerKind.POLYGON to dataPointsConverter.toTile()
SPOKE -> MapLayerKind.PATH to dataPointsConverter.toSpoke(layer.geom as SpokeGeom)
DENSITY2D, CONTOUR, PATH -> MapLayerKind.PATH to dataPointsConverter.toPath(layer.geom)
TEXT, LABEL -> MapLayerKind.TEXT to dataPointsConverter.toText(layer.geom)
DENSITY2DF, CONTOURF, POLYGON, MAP -> MapLayerKind.POLYGON to dataPointsConverter.toPolygon()
Expand Down
20 changes: 20 additions & 0 deletions python-package/lets_plot/plot/geom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5949,6 +5949,26 @@ def geom_spoke(mapping=None, *, data=None, position=None, show_legend=None, samp
scale_color_gradient(low='#2c7bb6', high='#d7191c') + \\
coord_fixed()

|

.. jupyter-execute::
:linenos:
:emphasize-lines: 13

import numpy as np
from lets_plot import *
LetsPlot.setup_html()
n = 10
a, b = 10, 30
d = (b - a) / (n - 1)
space = np.linspace(a, b, n)
X, Y = np.meshgrid(space, space)
A = np.arctan2(*np.gradient(X * Y, d))
data = dict(x=X.reshape(-1), y=Y.reshape(-1), a=A.reshape(-1))
ggplot(data, aes('x', 'y')) + \\
geom_livemap() + \\
geom_spoke(aes(angle='a', radius='a'))

"""
return _geom('spoke',
mapping=mapping,
Expand Down