├── .gitignore ├── .idea └── copyright │ ├── LetsPlot.xml │ └── profiles_settings.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android_demo.gif ├── build.gradle.kts ├── demo ├── android-plot-view │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── demo │ │ │ └── plot │ │ │ └── view │ │ │ ├── DensityPlotDemoActivity.kt │ │ │ ├── PlotGirdDemoActivity.kt │ │ │ ├── ResizingWithFitContainerSizePolicyDemoActivity.kt │ │ │ └── ResizingWithFixedPlotSizePolicyDemoActivity.kt │ │ └── res │ │ ├── drawable │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ └── activity_another.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ └── values │ │ ├── ic_launcher_background.xml │ │ └── strings.xml ├── android-svg-view │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── demo │ │ │ └── svg │ │ │ └── view │ │ │ ├── MainActivity.kt │ │ │ ├── SvgReferenceFixedSize.kt │ │ │ └── SvgReferenceWrapContent.kt │ │ └── res │ │ ├── drawable │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ └── values │ │ ├── ic_launcher_background.xml │ │ └── strings.xml ├── plot │ ├── compose-android-median │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ └── demo │ │ │ │ └── plot │ │ │ │ ├── MainActivity.kt │ │ │ │ └── ui │ │ │ │ ├── DemoDropdownMenu.kt │ │ │ │ └── DemoRadioGroup.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ └── values │ │ │ ├── ic_launcher_background.xml │ │ │ └── strings.xml │ ├── compose-android-min │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ └── demo │ │ │ │ ├── plot │ │ │ │ └── MainActivity.kt │ │ │ │ └── util │ │ │ │ └── SandboxPanel.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ └── values │ │ │ ├── ic_launcher_background.xml │ │ │ └── strings.xml │ ├── compose-android-redraw │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── jniLibs │ │ │ ├── arm64-v8a │ │ │ │ └── libskiko-android-arm64.so │ │ │ └── x86_64 │ │ │ │ └── libskiko-android-x64.so │ │ │ ├── kotlin │ │ │ └── demo │ │ │ │ └── letsPlot │ │ │ │ └── composeAndroidMin │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ └── values │ │ │ ├── ic_launcher_background.xml │ │ │ └── strings.xml │ ├── compose-desktop │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── demo │ │ │ ├── plot │ │ │ ├── median │ │ │ │ ├── AppMain.kt │ │ │ │ └── ui │ │ │ │ │ └── DemoList.kt │ │ │ ├── minimal │ │ │ │ ├── DensityCompose.kt │ │ │ │ ├── MultiplePlotSizeLayoutCompose.kt │ │ │ │ ├── MultiplePlotWeightLayoutCompose.kt │ │ │ │ ├── PieCompose.kt │ │ │ │ ├── PlotGridCompose.kt │ │ │ │ └── Recomposition.kt │ │ │ ├── redraw │ │ │ │ └── FastRedrawApp.kt │ │ │ └── various │ │ │ │ ├── HyperlinkCompose.kt │ │ │ │ ├── StrokeDashCompose.kt │ │ │ │ └── ToolbarCompose.kt │ │ │ └── util │ │ │ └── SandboxPanel.kt │ ├── shared │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ ├── demoData │ │ │ ├── AutoMpg.kt │ │ │ ├── Iris.kt │ │ │ └── Raster.kt │ │ │ └── plotSpec │ │ │ ├── AutoSpec.kt │ │ │ ├── BarPlotSpec.kt │ │ │ ├── CurveSpec.kt │ │ │ ├── DensitySpec.kt │ │ │ ├── FacetWrapSpec.kt │ │ │ ├── HyperlinkSpec.kt │ │ │ ├── LabelSpec.kt │ │ │ ├── MarginalLayersFacetsSpec.kt │ │ │ ├── MarkdownSpec.kt │ │ │ ├── PerfSpec.kt │ │ │ ├── PieSpec.kt │ │ │ ├── PlotDemoSpec.kt │ │ │ ├── PlotGridSpec.kt │ │ │ ├── PolarHeatmapSpec.kt │ │ │ ├── RasterSpec.kt │ │ │ ├── StrokeDashSpec.kt │ │ │ ├── SuperscriptExponentNotationSpec.kt │ │ │ ├── ThemeOptionsSpec.kt │ │ │ ├── TooltipAnchorSpec.kt │ │ │ ├── VariadicPathSpec.kt │ │ │ └── ViolinSpec.kt │ └── swing │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── kotlin │ │ └── demo │ │ ├── plot │ │ ├── defaultViewer │ │ │ └── DensityViewer.kt │ │ └── various │ │ │ ├── BarPlot.kt │ │ │ ├── Curve.kt │ │ │ ├── Density.kt │ │ │ ├── FacetWrap.kt │ │ │ ├── Hyperlink.kt │ │ │ ├── Label.kt │ │ │ ├── MarginalLayersFacets.kt │ │ │ ├── Markdown.kt │ │ │ ├── Pie.kt │ │ │ ├── PlotGrid.kt │ │ │ ├── PolarHeatmap.kt │ │ │ ├── Raster.kt │ │ │ ├── SuperscriptExponentNotation.kt │ │ │ ├── ThemeOptions.kt │ │ │ ├── TooltipAnchor.kt │ │ │ ├── VariadicPath.kt │ │ │ └── Violin.kt │ │ └── util │ │ └── PlotSpecsDemoWindow.kt └── svg │ ├── android-svg-compose │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── demo │ │ │ ├── plot │ │ │ └── MainActivity.kt │ │ │ └── util │ │ │ └── SandboxPanel.kt │ │ └── res │ │ ├── drawable │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ └── values │ │ ├── ic_launcher_background.xml │ │ └── strings.xml │ ├── compose-desktop │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── demo │ │ └── svg │ │ └── AppMain.kt │ ├── shared │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── demo │ │ └── svgModel │ │ ├── ClipPathSvgModel.kt │ │ ├── OpacityDemoModel.kt │ │ ├── ReferenceSvgModel.kt │ │ ├── SvgDsl.kt │ │ └── SvgImageElementModel.kt │ └── swing │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── demo │ └── svg │ ├── ClipPathSvgDemo.kt │ ├── OpacityDemo.kt │ ├── ReferenceSvgDemo.kt │ ├── SuperscriptExponentNotationDemo.kt │ ├── SvgImageElementDemo.kt │ ├── TextJustificationDemo.kt │ ├── TextLabelDemo.kt │ ├── TooltipBoxDemo.kt │ └── utils │ ├── DemoBase.kt │ └── DemoWindow.kt ├── devdocs ├── DEVELOPMENT.md ├── PUBLISHING.md └── RELEASE.md ├── future_changes.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img-2.png ├── img.png ├── lets-plot-compose ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── org │ │ └── jetbrains │ │ └── letsPlot │ │ └── skia │ │ └── compose │ │ ├── PlotPanel.kt │ │ └── android │ │ ├── ColorRect.kt │ │ ├── PlotComponentProvider.kt │ │ └── PlotViewContainer.kt │ ├── commonMain │ └── kotlin │ │ └── org │ │ └── jetbrains │ │ └── letsPlot │ │ └── skia │ │ └── compose │ │ ├── PlotPanel.kt │ │ └── util │ │ └── NaiveLogger.kt │ └── desktopMain │ └── kotlin │ └── org │ └── jetbrains │ └── letsPlot │ └── skia │ └── compose │ ├── PlotFigureModel.kt │ ├── PlotPanel.kt │ ├── PlotToolbar.kt │ └── desktop │ ├── DebouncedRunner.kt │ └── PlotContainer.kt ├── lets-plot-swing-skia ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── jetbrains │ └── letsPlot │ └── skia │ └── swing │ ├── AwtAppEnv.kt │ ├── FigureEx.kt │ ├── PlotComponentProviderSkiaSwing.kt │ ├── PlotPanelSkiaSwing.kt │ └── PlotViewerWindowSkia.kt ├── local.properties.template ├── platf-skia-awt ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── jetbrains │ └── letsPlot │ └── skia │ └── awt │ ├── builderHW │ ├── FigureToSkiaAwt.kt │ ├── MonolithicSkiaAwt.kt │ └── Util.kt │ └── view │ ├── SvgPanel.kt │ └── SvgSkikoViewAwt.kt ├── platf-skia ├── build.gradle.kts └── src │ ├── androidInstrumentedTest │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── org │ │ │ └── jetbrains │ │ │ └── letsPlot │ │ │ └── android │ │ │ └── canvas │ │ │ ├── ContextClipTest.kt │ │ │ ├── ContextPath2dTest.kt │ │ │ ├── ImageComparer.kt │ │ │ └── Utils.kt │ └── resources │ │ ├── arc_transform_after_restore.bmp │ │ ├── bezier_curve_inside_path.bmp │ │ ├── circle_fill.bmp │ │ ├── circle_fill_stroke.bmp │ │ ├── circle_stroke.bmp │ │ ├── clip_after_transform.bmp │ │ ├── clip_and_fill.bmp │ │ ├── clip_before_transform.bmp │ │ ├── clip_path.bmp │ │ ├── clip_restore.bmp │ │ ├── ellipse.bmp │ │ ├── ellipse_inside_path.bmp │ │ ├── ellipse_inside_path_diff.bmp │ │ ├── multi_path_fill.bmp │ │ ├── multi_path_stroke.bmp │ │ ├── nested_translates.bmp │ │ ├── path_transform_on_build.bmp │ │ ├── rotated_ellipse.bmp │ │ ├── rounded_rect_with_curves.bmp │ │ ├── sheared_circular_arc.bmp │ │ ├── sheared_ellipse.bmp │ │ ├── simple_bezier_curve.bmp │ │ ├── simple_clip_path.bmp │ │ ├── text_skew_transform.bmp │ │ ├── zigzag_fill.bmp │ │ └── zigzag_stroke.bmp │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── org │ │ └── jetbrains │ │ └── letsPlot │ │ ├── android │ │ └── canvas │ │ │ ├── AndroidAnimationTimerPeer.kt │ │ │ ├── AndroidCanvas.kt │ │ │ ├── AndroidContext2d.kt │ │ │ ├── AndroidMouseEventMapper.kt │ │ │ ├── CanvasView.kt │ │ │ ├── SizeConverter.kt │ │ │ └── Utils.kt │ │ └── skia │ │ └── android │ │ └── view │ │ └── SvgCanvasView.kt │ ├── commonMain │ └── kotlin │ │ └── org │ │ └── jetbrains │ │ └── letsPlot │ │ └── skia │ │ ├── builderLW │ │ ├── CompositeFigureEventDispatcher.kt │ │ ├── FigureToViewModel.kt │ │ ├── MonolithicSkiaLW.kt │ │ └── ViewModel.kt │ │ ├── mapping │ │ └── svg │ │ │ ├── DebugOptions.kt │ │ │ ├── FontManager.kt │ │ │ ├── SkiaTargetPeer.kt │ │ │ ├── SvgElementMapper.kt │ │ │ ├── SvgGElementMapper.kt │ │ │ ├── SvgImageElementMapper.kt │ │ │ ├── SvgNodeMapper.kt │ │ │ ├── SvgNodeMapperFactory.kt │ │ │ ├── SvgSkiaPeer.kt │ │ │ ├── SvgStyleElementMapper.kt │ │ │ ├── SvgSvgElementMapper.kt │ │ │ ├── SvgTextElementMapper.kt │ │ │ ├── SvgTransformParser.kt │ │ │ ├── SvgUtils.kt │ │ │ └── attr │ │ │ ├── SvgAttrMapping.kt │ │ │ ├── SvgCircleAttrMapping.kt │ │ │ ├── SvgEllipseAttrMapping.kt │ │ │ ├── SvgGAttrMapping.kt │ │ │ ├── SvgImageAttrMapping.kt │ │ │ ├── SvgLineAttrMapping.kt │ │ │ ├── SvgPathAttrMapping.kt │ │ │ ├── SvgRectAttrMapping.kt │ │ │ ├── SvgShapeMapping.kt │ │ │ ├── SvgSvgAttrMapping.kt │ │ │ ├── SvgTSpanElementAttrMapping.kt │ │ │ └── SvgTextElementAttrMapping.kt │ │ ├── shape │ │ ├── Circle.kt │ │ ├── Colors.kt │ │ ├── ComputedProperty.kt │ │ ├── Container.kt │ │ ├── Element.kt │ │ ├── Ellipse.kt │ │ ├── Figure.kt │ │ ├── Group.kt │ │ ├── Image.kt │ │ ├── Line.kt │ │ ├── Node.kt │ │ ├── Pane.kt │ │ ├── Path.kt │ │ ├── Rectangle.kt │ │ ├── TSpan.kt │ │ ├── Text.kt │ │ ├── Util.kt │ │ └── VisualProperty.kt │ │ └── view │ │ ├── SkikoViewEventDispatcher.kt │ │ └── SvgSkikoView.kt │ └── jvmTest │ └── kotlin │ └── org │ └── jetbrains │ └── letsPlot │ └── skia │ └── shape │ ├── HierarchyTest.kt │ ├── PropertiesSynchronizationTest.kt │ ├── SvgComplianceTest.kt │ └── SvgDocUtil.kt └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | # share copyright profiles 3 | !.idea/copyright 4 | 5 | *.iml 6 | *.so 7 | .gradle 8 | .DS_Store 9 | **/build 10 | **/out 11 | demo-android-app/src/main/jniLibs/arm64-v8a/org/* 12 | demo-android-app/src/main/jniLibs/arm64-v8a/META-INF/* 13 | demo-android-app/src/main/jniLibs/x86_64/org/* 14 | demo-android-app/src/main/jniLibs/x86_64/META-INF/* 15 | /kotlin-js-store/yarn.lock 16 | 17 | local.properties 18 | skiko-jni-libs.zip 19 | 20 | .maven-publish-dev-repo/* -------------------------------------------------------------------------------- /.idea/copyright/LetsPlot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JetBrains 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/android_demo.gif -------------------------------------------------------------------------------- /demo/android-plot-view/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("android") 8 | id("com.android.application") 9 | } 10 | 11 | android { 12 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 13 | namespace = "demo.plot.view" 14 | 15 | defaultConfig { 16 | applicationId = "demo.plot.view" 17 | 18 | minSdk = (findProperty("android.minSdk") as String).toInt() 19 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 20 | 21 | versionCode = 1 22 | versionName = "1.0" 23 | 24 | ndk { 25 | abiFilters += listOf("x86_64", "arm64-v8a") 26 | } 27 | } 28 | 29 | buildTypes { 30 | debug { 31 | isDebuggable = true 32 | } 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility = JavaVersion.VERSION_11 37 | targetCompatibility = JavaVersion.VERSION_11 38 | } 39 | 40 | kotlin { 41 | jvmToolchain(11) 42 | } 43 | } 44 | 45 | val letsPlotVersion = extra["letsPlot.version"] as String 46 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 47 | 48 | dependencies { 49 | implementation(project(":platf-skia")) 50 | implementation(project(":demo-plot-shared")) 51 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:${letsPlotKotlinVersion}") 52 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 53 | implementation("org.jetbrains.lets-plot:canvas:${letsPlotVersion}") 54 | implementation("org.jetbrains.lets-plot:plot-raster:$letsPlotVersion") 55 | } 56 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/kotlin/demo/plot/view/DensityPlotDemoActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import org.jetbrains.letsPlot.android.canvas.CanvasView 12 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 13 | import org.jetbrains.letsPlot.intern.toSpec 14 | import org.jetbrains.letsPlot.raster.builder.MonolithicCanvas 15 | import plotSpec.DensitySpec 16 | 17 | class DensityPlotDemoActivity : Activity() { 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | val plotFigure = DensitySpec().createFigure() 22 | 23 | setContentView( 24 | CanvasView(this).apply { 25 | figure = MonolithicCanvas.buildPlotFigureFromRawSpec( 26 | rawSpec = plotFigure.toSpec(), 27 | sizingPolicy = SizingPolicy.fitContainerSize(false), 28 | computationMessagesHandler = {} 29 | ) 30 | setBackgroundColor(Color.GREEN) 31 | } 32 | ) 33 | } 34 | } -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/kotlin/demo/plot/view/PlotGirdDemoActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import org.jetbrains.letsPlot.android.canvas.CanvasView 12 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 13 | import org.jetbrains.letsPlot.intern.toSpec 14 | import org.jetbrains.letsPlot.raster.builder.MonolithicCanvas 15 | import plotSpec.PlotGridSpec 16 | 17 | class PlotGirdDemoActivity : Activity() { 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | val plotFigure = PlotGridSpec().createFigure() 22 | 23 | setContentView( 24 | CanvasView(this).apply { 25 | figure = MonolithicCanvas.buildPlotFigureFromRawSpec( 26 | rawSpec = plotFigure.toSpec(), 27 | sizingPolicy = SizingPolicy.fitContainerSize(false), 28 | computationMessagesHandler = {} 29 | ) 30 | setBackgroundColor(Color.GREEN) 31 | } 32 | ) 33 | } 34 | } -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/kotlin/demo/plot/view/ResizingWithFitContainerSizePolicyDemoActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import demo.plot.view.ResizingWithFixedPlotSizePolicyDemoActivity.Companion.setupResizableCanvas 12 | import org.jetbrains.letsPlot.android.canvas.CanvasView 13 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 14 | import org.jetbrains.letsPlot.intern.toSpec 15 | import org.jetbrains.letsPlot.raster.builder.MonolithicCanvas 16 | import plotSpec.DensitySpec 17 | 18 | class ResizingWithFitContainerSizePolicyDemoActivity: Activity() { 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | val plotFigure = DensitySpec().createFigure() 22 | 23 | val view = CanvasView(this).apply { 24 | figure = MonolithicCanvas.buildPlotFigureFromRawSpec( 25 | rawSpec = plotFigure.toSpec(), 26 | sizingPolicy = SizingPolicy.fitContainerSize(false), 27 | computationMessagesHandler = {} 28 | ) 29 | setBackgroundColor(Color.GREEN) 30 | } 31 | 32 | 33 | 34 | setupResizableCanvas(view) 35 | } 36 | } -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/kotlin/demo/plot/view/ResizingWithFixedPlotSizePolicyDemoActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import android.widget.FrameLayout 12 | import org.jetbrains.letsPlot.android.canvas.CanvasView 13 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 14 | import org.jetbrains.letsPlot.intern.toSpec 15 | import org.jetbrains.letsPlot.raster.builder.MonolithicCanvas 16 | import plotSpec.DensitySpec 17 | import java.util.* 18 | 19 | class ResizingWithFixedPlotSizePolicyDemoActivity: Activity() { 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | val plotFigure = DensitySpec().createFigure() 23 | 24 | val view = CanvasView(this).apply { 25 | figure = MonolithicCanvas.buildPlotFigureFromRawSpec( 26 | rawSpec = plotFigure.toSpec(), 27 | sizingPolicy = SizingPolicy.keepFigureDefaultSize(), 28 | computationMessagesHandler = {} 29 | ) 30 | setBackgroundColor(Color.GREEN) 31 | } 32 | 33 | setupResizableCanvas(view) 34 | } 35 | 36 | companion object { 37 | fun Activity.setupResizableCanvas(view: CanvasView) { 38 | setContentView(view) 39 | val resizeTask = object : TimerTask() { 40 | var width = 500 41 | var height = 500 42 | var dv = 2 43 | 44 | override fun run() { 45 | runOnUiThread { 46 | if (width < 500 || width > 1000) { 47 | dv *= -1 48 | } 49 | 50 | width += dv 51 | height += dv 52 | 53 | view.layoutParams = FrameLayout.LayoutParams(width, height) 54 | } 55 | } 56 | } 57 | Timer().schedule(resizeTask, 0, 10) 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/res/layout/activity_another.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3C3F41 4 | -------------------------------------------------------------------------------- /demo/android-plot-view/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SVG to Canvas Mapping Demo 3 | 4 | -------------------------------------------------------------------------------- /demo/android-svg-view/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("android") 8 | id("com.android.application") 9 | } 10 | 11 | android { 12 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 13 | namespace = "demo.svg.view" 14 | 15 | defaultConfig { 16 | applicationId = "demo.svg.view" 17 | 18 | minSdk = (findProperty("android.minSdk") as String).toInt() 19 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 20 | 21 | versionCode = 1 22 | versionName = "1.0" 23 | } 24 | 25 | buildTypes { 26 | debug { 27 | isDebuggable = true 28 | } 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_11 33 | targetCompatibility = JavaVersion.VERSION_11 34 | } 35 | 36 | kotlin { 37 | jvmToolchain(11) 38 | } 39 | } 40 | 41 | val letsPlotVersion = extra["letsPlot.version"] as String 42 | 43 | dependencies { 44 | implementation(project(":platf-skia")) 45 | implementation(project(":demo-svg-shared")) 46 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 47 | implementation("org.jetbrains.lets-plot:canvas:${letsPlotVersion}") 48 | implementation("org.jetbrains.lets-plot:plot-raster:$letsPlotVersion") 49 | } 50 | -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/kotlin/demo/svg/view/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import android.view.ViewGroup 12 | import android.widget.Button 13 | import android.widget.LinearLayout 14 | import demo.svgModel.ClipPathSvgModel 15 | import demo.svgModel.ReferenceSvgModel 16 | import demo.svgModel.SvgImageElementModel 17 | import org.jetbrains.letsPlot.android.canvas.CanvasView 18 | import org.jetbrains.letsPlot.raster.view.SvgCanvasFigure 19 | 20 | class MainActivity : Activity() { 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | 24 | val layout = LinearLayout(this) 25 | layout.orientation = LinearLayout.VERTICAL 26 | layout.layoutParams = 27 | LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) 28 | setContentView(layout, layout.layoutParams) 29 | 30 | layout.addView(Button(this).apply { 31 | text = "Back" 32 | setOnClickListener { 33 | println("Back button clicked") 34 | } 35 | }) 36 | // Svg pictures A, B, C 37 | layout.addView( 38 | CanvasView(this).apply { 39 | figure = SvgCanvasFigure(ReferenceSvgModel.createModel()) 40 | setBackgroundColor(Color.GREEN) 41 | } 42 | ) 43 | 44 | layout.addView( 45 | CanvasView(this).apply { 46 | figure = SvgCanvasFigure(SvgImageElementModel.createModel()) 47 | setBackgroundColor(Color.RED) 48 | } 49 | ) 50 | 51 | layout.addView( 52 | CanvasView(this).apply { 53 | figure = SvgCanvasFigure(ClipPathSvgModel.createModel()) 54 | setBackgroundColor(Color.BLUE) 55 | } 56 | ) 57 | } 58 | } -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/kotlin/demo/svg/view/SvgReferenceFixedSize.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import android.view.ViewGroup 12 | import demo.svgModel.ReferenceSvgModel 13 | import org.jetbrains.letsPlot.android.canvas.CanvasView 14 | import org.jetbrains.letsPlot.raster.view.SvgCanvasFigure 15 | 16 | class SvgReferenceFixedSize : Activity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | 20 | setContentView( 21 | CanvasView(this).apply { 22 | figure = SvgCanvasFigure(ReferenceSvgModel.createModel()) 23 | setBackgroundColor(Color.BLUE) 24 | }, 25 | ViewGroup.LayoutParams(500, 500) 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/kotlin/demo/svg/view/SvgReferenceWrapContent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg.view 7 | 8 | import android.app.Activity 9 | import android.graphics.Color 10 | import android.os.Bundle 11 | import android.view.ViewGroup 12 | import android.widget.LinearLayout 13 | import demo.svgModel.ReferenceSvgModel 14 | import org.jetbrains.letsPlot.android.canvas.CanvasView 15 | import org.jetbrains.letsPlot.raster.view.SvgCanvasFigure 16 | 17 | class SvgReferenceWrapContent : Activity() { 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | setContentView( 22 | CanvasView(this).apply { 23 | figure = SvgCanvasFigure(ReferenceSvgModel.createModel()) 24 | setBackgroundColor(Color.BLUE) 25 | }, 26 | LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) 27 | ) 28 | } 29 | } -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3C3F41 4 | -------------------------------------------------------------------------------- /demo/android-svg-view/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SVG to Canvas Mapping Demo 3 | 4 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("android") 8 | id("org.jetbrains.compose") 9 | id("com.android.application") 10 | kotlin("plugin.compose") 11 | } 12 | 13 | 14 | android { 15 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 16 | namespace = "demo.plot.CanvasDemo" 17 | 18 | buildFeatures { 19 | compose = true 20 | } 21 | 22 | defaultConfig { 23 | applicationId = "demo.plot.CanvasDemo" 24 | 25 | minSdk = (findProperty("android.minSdk") as String).toInt() 26 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 27 | 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | 32 | buildTypes { 33 | debug { 34 | isDebuggable = true 35 | } 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility = JavaVersion.VERSION_11 40 | targetCompatibility = JavaVersion.VERSION_11 41 | } 42 | 43 | kotlin { 44 | jvmToolchain(11) 45 | } 46 | } 47 | 48 | val composeVersion = extra["compose.version"] as String 49 | val androidxActivityCompose = extra["androidx.activity.compose"] as String 50 | val letsPlotVersion = extra["letsPlot.version"] as String 51 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 52 | 53 | dependencies { 54 | implementation(compose.runtime) 55 | implementation(compose.foundation) 56 | implementation(compose.material) 57 | implementation(compose.ui) 58 | implementation("androidx.activity:activity-compose:$androidxActivityCompose") 59 | 60 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 61 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 62 | implementation("org.jetbrains.lets-plot:canvas:${letsPlotVersion}") 63 | implementation("org.jetbrains.lets-plot:plot-raster:${letsPlotVersion}") 64 | 65 | implementation(project(":lets-plot-compose")) 66 | implementation(project(":demo-plot-shared")) 67 | 68 | implementation("org.slf4j:slf4j-api:2.0.9") 69 | implementation("com.github.tony19:logback-android:3.0.0") 70 | } 71 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/kotlin/demo/plot/ui/DemoDropdownMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.ui 7 | 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.material.* 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.MutableState 12 | import androidx.compose.runtime.mutableStateOf 13 | import androidx.compose.runtime.remember 14 | 15 | 16 | @Suppress("FunctionName") 17 | @Composable 18 | fun DemoDropdownMenu( 19 | options: List, 20 | selectedIndex: MutableState 21 | ) { 22 | val expanded = remember { (mutableStateOf(false)) } 23 | 24 | Box() { 25 | @OptIn(ExperimentalMaterialApi::class) 26 | ExposedDropdownMenuBox( 27 | expanded = expanded.value, 28 | onExpandedChange = { 29 | expanded.value = !expanded.value 30 | }, 31 | // modifier = Modifier.height(40.dp).width(40.dp) 32 | ) { 33 | TextField( 34 | value = options[selectedIndex.value], 35 | onValueChange = {}, 36 | readOnly = true, 37 | trailingIcon = { 38 | @OptIn(ExperimentalMaterialApi::class) 39 | ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded.value) 40 | } 41 | ) 42 | 43 | @OptIn(ExperimentalMaterialApi::class) 44 | ExposedDropdownMenu( 45 | expanded = expanded.value, 46 | onDismissRequest = { expanded.value = false } 47 | ) { 48 | options.forEachIndexed { index, name -> 49 | DropdownMenuItem( 50 | onClick = { 51 | selectedIndex.value = index 52 | expanded.value = false 53 | } 54 | ) { 55 | Text(text = name) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3C3F41 4 | -------------------------------------------------------------------------------- /demo/plot/compose-android-median/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Plot Rendering Demo (median) 3 | 4 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("android") 8 | id("org.jetbrains.compose") 9 | id("com.android.application") 10 | kotlin("plugin.compose") 11 | } 12 | 13 | 14 | android { 15 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 16 | namespace = "demo.plot.CanvasDemo" 17 | 18 | buildFeatures { 19 | compose = true 20 | } 21 | 22 | defaultConfig { 23 | applicationId = "demo.plot.CanvasDemo" 24 | 25 | minSdk = (findProperty("android.minSdk") as String).toInt() 26 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 27 | 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | 32 | buildTypes { 33 | debug { 34 | isDebuggable = true 35 | } 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility = JavaVersion.VERSION_11 40 | targetCompatibility = JavaVersion.VERSION_11 41 | } 42 | 43 | kotlin { 44 | jvmToolchain(11) 45 | } 46 | } 47 | 48 | val composeVersion = extra["compose.version"] as String 49 | val androidxActivityCompose = extra["androidx.activity.compose"] as String 50 | val letsPlotVersion = extra["letsPlot.version"] as String 51 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 52 | 53 | dependencies { 54 | implementation(compose.runtime) 55 | implementation(compose.foundation) 56 | implementation(compose.material) 57 | implementation(compose.ui) 58 | implementation("androidx.activity:activity-compose:$androidxActivityCompose") 59 | 60 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 61 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 62 | implementation("org.jetbrains.lets-plot:canvas:$letsPlotVersion") 63 | implementation("org.jetbrains.lets-plot:plot-raster:$letsPlotVersion") 64 | 65 | implementation(project(":lets-plot-compose")) 66 | implementation(project(":demo-plot-shared")) 67 | 68 | implementation("org.slf4j:slf4j-api:2.0.9") 69 | implementation("com.github.tony19:logback-android:3.0.0") 70 | } 71 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3C3F41 4 | -------------------------------------------------------------------------------- /demo/plot/compose-android-min/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Plot Rendering Demo (min) 3 | 4 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("android") 8 | id("org.jetbrains.compose") 9 | id("com.android.application") 10 | kotlin("plugin.compose") 11 | } 12 | 13 | 14 | android { 15 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 16 | namespace = "demo.letsPlot" 17 | 18 | buildFeatures { 19 | compose = true 20 | } 21 | 22 | defaultConfig { 23 | applicationId = "demo.letsPlot.composeMinDemo" 24 | 25 | minSdk = (findProperty("android.minSdk") as String).toInt() 26 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 27 | 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | 32 | buildTypes { 33 | getByName("release") { 34 | signingConfig = signingConfigs.getByName("debug") 35 | } 36 | debug { 37 | isDebuggable = true 38 | } 39 | } 40 | 41 | compileOptions { 42 | sourceCompatibility = JavaVersion.VERSION_11 43 | targetCompatibility = JavaVersion.VERSION_11 44 | } 45 | 46 | kotlin { 47 | jvmToolchain(11) 48 | } 49 | } 50 | 51 | val composeVersion = extra["compose.version"] as String 52 | val androidxActivityCompose = extra["androidx.activity.compose"] as String 53 | val letsPlotVersion = extra["letsPlot.version"] as String 54 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 55 | 56 | dependencies { 57 | implementation(compose.runtime) 58 | implementation(compose.foundation) 59 | implementation(compose.material) 60 | implementation(compose.ui) 61 | implementation("androidx.activity:activity-compose:$androidxActivityCompose") 62 | 63 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 64 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 65 | 66 | implementation("org.jetbrains.lets-plot:canvas:${letsPlotVersion}") 67 | implementation("org.jetbrains.lets-plot:plot-raster:${letsPlotVersion}") 68 | 69 | implementation(project(":lets-plot-compose")) 70 | implementation(project(":demo-plot-shared")) 71 | 72 | implementation("org.slf4j:slf4j-api:2.0.9") 73 | implementation("com.github.tony19:logback-android:3.0.0") 74 | } 75 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/jniLibs/arm64-v8a/libskiko-android-arm64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/demo/plot/compose-android-redraw/src/main/jniLibs/arm64-v8a/libskiko-android-arm64.so -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/jniLibs/x86_64/libskiko-android-x64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/demo/plot/compose-android-redraw/src/main/jniLibs/x86_64/libskiko-android-x64.so -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3C3F41 4 | -------------------------------------------------------------------------------- /demo/plot/compose-android-redraw/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Plot Redraw Demo 3 | 4 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | // kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22) 3 | kotlin("jvm") 4 | kotlin("plugin.compose") 5 | id("org.jetbrains.compose") 6 | } 7 | 8 | val letsPlotVersion = extra["letsPlot.version"] as String 9 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 10 | 11 | dependencies { 12 | implementation(compose.desktop.currentOs) 13 | 14 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 15 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 16 | implementation("org.jetbrains.lets-plot:platf-awt:$letsPlotVersion") 17 | 18 | implementation(project(":lets-plot-compose")) 19 | implementation(project(":demo-plot-shared")) 20 | 21 | implementation("org.slf4j:slf4j-simple:2.0.9") // Enable logging to console 22 | } 23 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/median/ui/DemoList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.median.ui 7 | 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.foundation.selection.selectable 10 | import androidx.compose.material.RadioButton 11 | import androidx.compose.material.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.MutableState 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.unit.dp 17 | 18 | 19 | @Suppress("FunctionName") 20 | @Composable 21 | fun DemoList( 22 | options: List, 23 | selectedIndex: MutableState 24 | ) { 25 | Box() { 26 | Column( 27 | modifier = Modifier 28 | .width(IntrinsicSize.Max) 29 | ) { 30 | options.forEachIndexed { index, name -> 31 | 32 | Row( 33 | verticalAlignment = Alignment.CenterVertically, 34 | modifier = Modifier 35 | .fillMaxWidth() 36 | .height(24.dp) 37 | .selectable( 38 | selected = selectedIndex.value == index, 39 | onClick = { selectedIndex.value = index } 40 | ) 41 | ) { 42 | RadioButton( 43 | onClick = { selectedIndex.value = index }, 44 | selected = index == selectedIndex.value, 45 | ) 46 | Text(name) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/minimal/DensityCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.minimal 7 | 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material.MaterialTheme 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.window.Window 15 | import androidx.compose.ui.window.application 16 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 17 | import plotSpec.DensitySpec 18 | 19 | fun main() = application { 20 | Window(onCloseRequest = ::exitApplication, title = "Density Plot (Compose Desktop)") { 21 | MaterialTheme { 22 | Column( 23 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 24 | ) { 25 | 26 | PlotPanel( 27 | figure = DensitySpec().createFigure(), 28 | modifier = Modifier.fillMaxSize() 29 | ) { computationMessages -> 30 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/minimal/MultiplePlotSizeLayoutCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.minimal 7 | 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.window.Window 13 | import androidx.compose.ui.window.application 14 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 15 | import plotSpec.DensitySpec 16 | 17 | fun main() = application { 18 | Window(onCloseRequest = ::exitApplication, title = "Multiple Plot Size Layout (Compose Desktop)") { 19 | MaterialTheme { 20 | Column( 21 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 22 | ) { 23 | 24 | PlotPanel( 25 | figure = DensitySpec().createFigure(), 26 | modifier = Modifier.height(100.dp).width(100.dp) 27 | ) { computationMessages -> 28 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 29 | } 30 | 31 | PlotPanel( 32 | figure = DensitySpec().createFigure(), 33 | modifier = Modifier.height(100.dp).width(100.dp) 34 | ) { computationMessages -> 35 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/minimal/MultiplePlotWeightLayoutCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.minimal 7 | 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material.MaterialTheme 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.window.Window 15 | import androidx.compose.ui.window.application 16 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 17 | import plotSpec.DensitySpec 18 | 19 | fun main() = application { 20 | Window(onCloseRequest = ::exitApplication, title = "Multiple Plot Weight Layout (Compose Desktop)") { 21 | MaterialTheme { 22 | Column( 23 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 24 | ) { 25 | 26 | PlotPanel( 27 | figure = DensitySpec().createFigure(), 28 | modifier = Modifier.weight(1f) 29 | ) { computationMessages -> 30 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 31 | } 32 | 33 | PlotPanel( 34 | figure = DensitySpec().createFigure(), 35 | modifier = Modifier.weight(1f) 36 | ) { computationMessages -> 37 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/minimal/PieCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.minimal 7 | 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material.MaterialTheme 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.window.Window 15 | import androidx.compose.ui.window.application 16 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 17 | import plotSpec.PieSpec 18 | 19 | fun main() = application { 20 | Window(onCloseRequest = ::exitApplication, title = "Pie Plot (Compose Desktop)") { 21 | MaterialTheme { 22 | Column( 23 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 24 | ) { 25 | 26 | PlotPanel( 27 | figure = PieSpec().createFigure(), 28 | modifier = Modifier.fillMaxSize() 29 | ) { computationMessages -> 30 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/minimal/PlotGridCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.minimal 7 | 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material.MaterialTheme 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.window.Window 15 | import androidx.compose.ui.window.application 16 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 17 | import plotSpec.PlotGridSpec 18 | 19 | fun main() = application { 20 | Window(onCloseRequest = ::exitApplication, title = "Plot Grid (Compose Desktop)") { 21 | MaterialTheme { 22 | Column( 23 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 24 | ) { 25 | 26 | PlotPanel( 27 | figure = PlotGridSpec().createFigure(), 28 | modifier = Modifier.fillMaxSize() 29 | ) { computationMessages -> 30 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 31 | } 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/minimal/Recomposition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.minimal 7 | 8 | 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.material.Button 13 | import androidx.compose.material.MaterialTheme 14 | import androidx.compose.material.Text 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.setValue 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.unit.dp 21 | import androidx.compose.ui.window.Window 22 | import androidx.compose.ui.window.application 23 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 24 | import plotSpec.DensitySpec 25 | 26 | 27 | // Enable logging to see recompositions: 28 | // org.jetbrains.letsPlot.skia.compose.util.NaiveLoggerKt.ENABLED 29 | fun main() = application { 30 | Window(onCloseRequest = ::exitApplication, title = "Density Plot (Compose Desktop)") { 31 | var counter by remember { mutableStateOf(0) } 32 | 33 | MaterialTheme { 34 | Column( 35 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 36 | ) { 37 | Button(onClick = { counter++ }) { 38 | Text("Click me! (already clicked $counter times)") 39 | } 40 | 41 | PlotPanel( 42 | figure = DensitySpec().createFigure(), 43 | modifier = Modifier.fillMaxSize() 44 | ) { computationMessages -> 45 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/various/HyperlinkCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.window.Window 13 | import androidx.compose.ui.window.application 14 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 15 | import plotSpec.HyperlinkSpec 16 | 17 | @OptIn(ExperimentalLayoutApi::class) 18 | fun main() = application { 19 | Window(onCloseRequest = ::exitApplication, title = "Hyperlink (Compose Desktop)") { 20 | MaterialTheme { 21 | FlowRow( 22 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 23 | ) { 24 | HyperlinkSpec().createFigureList().forEach { figure -> 25 | Column { 26 | PlotPanel( 27 | figure = figure, 28 | modifier = Modifier.size(610.dp) 29 | ) { computationMessages -> 30 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/various/StrokeDashCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.window.Window 13 | import androidx.compose.ui.window.application 14 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 15 | import plotSpec.StrokeDashSpec 16 | 17 | @OptIn(ExperimentalLayoutApi::class) 18 | fun main() = application { 19 | Window(onCloseRequest = ::exitApplication, title = "StrokeDash (Compose Desktop)") { 20 | MaterialTheme { 21 | FlowRow( 22 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 23 | ) { 24 | StrokeDashSpec().createFigureList().forEach { figure -> 25 | Column { 26 | PlotPanel( 27 | figure = figure, 28 | modifier = Modifier.size(610.dp) 29 | ) { computationMessages -> 30 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/plot/various/ToolbarCompose.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.window.Window 13 | import androidx.compose.ui.window.application 14 | import org.jetbrains.letsPlot.interact.ggtb 15 | import org.jetbrains.letsPlot.intern.Plot 16 | import org.jetbrains.letsPlot.skia.compose.PlotPanel 17 | import plotSpec.AutoSpec 18 | 19 | 20 | @OptIn(ExperimentalLayoutApi::class) 21 | fun main() = application { 22 | Window(onCloseRequest = ::exitApplication, title = "ggtb() (Compose Desktop)") { 23 | MaterialTheme { 24 | FlowRow( 25 | modifier = Modifier.fillMaxSize().padding(start = 10.dp, top = 10.dp, end = 10.dp, bottom = 10.dp), 26 | ) { 27 | AutoSpec().createFigureList().forEach { figure -> 28 | Column { 29 | PlotPanel( 30 | figure = (figure as Plot) + ggtb(), 31 | modifier = Modifier.fillMaxSize() 32 | ) { computationMessages -> 33 | computationMessages.forEach { println("[DEMO APP MESSAGE] $it") } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /demo/plot/compose-desktop/src/main/kotlin/demo/util/SandboxPanel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.util 7 | 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.DisposableEffect 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.awt.NoOpUpdate 12 | import androidx.compose.ui.awt.SwingPanel 13 | import androidx.compose.ui.layout.onGloballyPositioned 14 | import androidx.compose.ui.platform.LocalDensity 15 | import java.awt.Color 16 | import java.awt.Component 17 | import java.awt.Rectangle 18 | import javax.swing.JPanel 19 | 20 | @Suppress("FunctionName") 21 | @Composable 22 | fun SandboxPanel( 23 | color: Color, 24 | modifier: Modifier, 25 | 26 | ) { 27 | val provider = SandboxPanelProvider(color) 28 | 29 | val factory: () -> Component = provider.factory 30 | 31 | DisposableEffect(factory) { 32 | onDispose { 33 | println("onDispose $this") 34 | } 35 | } 36 | 37 | val density = LocalDensity.current.density 38 | val modifier1 = modifier.onGloballyPositioned { coordinates -> 39 | val size = coordinates.size 40 | val width = size.width / density 41 | val height = size.height / density 42 | 43 | println("onGloballyPositioned $size density: $density") 44 | provider.onGloballyPositioned(width / 2, height / 2) 45 | } 46 | SwingPanel( 47 | background = androidx.compose.ui.graphics.Color.White, 48 | factory = factory, 49 | modifier = modifier1, 50 | update = NoOpUpdate 51 | ) 52 | } 53 | 54 | private class SandboxPanelProvider(color: Color) { 55 | private val container = JPanel().apply { 56 | isOpaque = true 57 | background = color 58 | layout = null 59 | } 60 | 61 | val factory: () -> Component = { container } 62 | 63 | fun onGloballyPositioned(w: Float, h: Float) { 64 | container.removeAll() // ToDo: dispose 65 | container.add( 66 | JPanel().also { 67 | it.bounds = Rectangle(0, 0, w.toInt(), h.toInt()) 68 | it.isOpaque = true 69 | it.background = Color.YELLOW 70 | } 71 | ) 72 | } 73 | } -------------------------------------------------------------------------------- /demo/plot/shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("jvm") 8 | } 9 | 10 | val letsPlotVersion = extra["letsPlot.version"] as String 11 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 12 | 13 | dependencies { 14 | compileOnly("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 15 | 16 | compileOnly("org.jetbrains.lets-plot:commons:$letsPlotVersion") 17 | compileOnly("org.jetbrains.lets-plot:datamodel:$letsPlotVersion") 18 | 19 | testImplementation(kotlin("test")) 20 | } 21 | -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/demoData/Raster.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demoData 7 | 8 | object Raster { 9 | @Suppress("FunctionName") 10 | fun rasterData_Blue(): Map> { 11 | val x = ArrayList() 12 | val y = ArrayList() 13 | val fill = ArrayList() 14 | val alpha = ArrayList() 15 | 16 | val width = 3 17 | val height = 3 18 | for (col in 0 until width) { 19 | for (row in 0 until height) { 20 | x.add(col.toDouble()) 21 | y.add(row.toDouble()) 22 | fill.add(col.toDouble()) 23 | alpha.add(row.toDouble()) 24 | } 25 | } 26 | 27 | @Suppress("DuplicatedCode") 28 | val map = HashMap>() 29 | map["x"] = x 30 | map["y"] = y 31 | map["fill"] = fill 32 | map["alpha"] = alpha 33 | return map 34 | } 35 | 36 | @Suppress("FunctionName") 37 | fun rasterData_RGB(): Map> { 38 | // R | G | B alpha = 1 39 | // R | G | B alpha = 0.5 40 | // .5 | 1 | .5 <-- gray, alpha 41 | 42 | val fillInt = intArrayOf(0xFF0000, 0xFF00, 0xFF, 0xFF0000, 0xFF00, 0xFF, 0x7F0000, 0x7F00, 0x7F) 43 | 44 | val fill = ArrayList() 45 | for (i in fillInt) { 46 | fill.add(i.toDouble()) 47 | } 48 | 49 | val alpha = listOf(1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5) 50 | val x = listOf(0.0, 1.0, 2.0, 0.0, 1.0, 2.0, 0.0, 1.0, 2.0) 51 | val y = listOf(0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0) 52 | 53 | val map = HashMap>() 54 | map["x"] = x 55 | map["y"] = y 56 | map["fill"] = fill 57 | map["alpha"] = alpha 58 | return map 59 | } 60 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/AutoSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import demoData.AutoMpg 9 | import org.jetbrains.letsPlot.Figure 10 | import org.jetbrains.letsPlot.geom.geomLabel 11 | import org.jetbrains.letsPlot.geom.geomPoint 12 | import org.jetbrains.letsPlot.letsPlot 13 | 14 | class AutoSpec : PlotDemoSpec { 15 | override fun createFigureList(): List
{ 16 | return listOf( 17 | scatter() 18 | ) 19 | } 20 | 21 | fun scatter(): Figure { 22 | return letsPlot(AutoMpg.map()) + geomPoint { 23 | x = "engine horsepower" 24 | y = "miles per gallon" 25 | color = "origin of car" 26 | } + geomLabel( 27 | checkOverlap = true, 28 | ) { 29 | x = "engine horsepower" 30 | y = "miles per gallon" 31 | color = "origin of car" 32 | label = "vehicle name" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/BarPlotSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.asDiscrete 10 | import org.jetbrains.letsPlot.geom.geomBar 11 | import org.jetbrains.letsPlot.letsPlot 12 | import org.jetbrains.letsPlot.scale.scaleFillDiscrete 13 | import org.jetbrains.letsPlot.scale.scaleFillHue 14 | 15 | class BarPlotSpec : PlotDemoSpec { 16 | 17 | override fun createFigureList(): List
{ 18 | val basic = letsPlot(DATA) + 19 | geomBar(alpha = 0.5) { 20 | x = "time" 21 | color = "time" 22 | fill = "time" 23 | } 24 | 25 | // ToDo: this doesn't work the same way as in the same demo in LP. 26 | val fancy = letsPlot(DATA) + 27 | geomBar { 28 | x = "time" 29 | fill = asDiscrete("..count..") 30 | } + scaleFillHue() 31 | 32 | return listOf( 33 | basic, 34 | fancy, 35 | fancyWithWidth(0.5), 36 | fancyWithWidth(5.0), 37 | ) 38 | } 39 | 40 | private fun fancyWithWidth(w: Double): Figure { 41 | return letsPlot(DATA) + 42 | geomBar(width = w) { 43 | x = "time" 44 | fill = "..count.." 45 | } + scaleFillDiscrete() 46 | } 47 | 48 | companion object { 49 | val DATA = mapOf( 50 | "time" to listOf("Lunch", "Lunch", "Dinner", "Dinner", "Dinner") 51 | ) 52 | } 53 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/CurveSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.geom.extras.arrow 10 | import org.jetbrains.letsPlot.geom.geomCurve 11 | import org.jetbrains.letsPlot.intern.Plot 12 | import org.jetbrains.letsPlot.label.ggtitle 13 | import org.jetbrains.letsPlot.letsPlot 14 | import org.jetbrains.letsPlot.scale.xlim 15 | 16 | class CurveSpec : PlotDemoSpec { 17 | 18 | override fun createFigureList(): List
{ 19 | 20 | fun curvePlot(curvature: Double, angle: Int, ncp: Int = 0): Plot { 21 | val data = mapOf( 22 | "x" to listOf(-10), 23 | "y" to listOf(1), 24 | "xend" to listOf(10), 25 | "yend" to listOf(-1), 26 | ) 27 | 28 | return letsPlot(data) { x = "x"; y = "y"; xend = "xend"; yend = "yend" } + 29 | geomCurve(curvature = curvature, angle = angle, ncp = ncp, arrow = arrow(ends = "both")) + 30 | xlim(listOf(-15, 15)) + 31 | ggtitle("curvature = $curvature, angle=$angle, ncp=$ncp") 32 | } 33 | 34 | return listOf( 35 | curvePlot(curvature = 0.5, angle = 0, ncp = 5), 36 | curvePlot(curvature = 0.5, angle = 90, ncp = 1), 37 | curvePlot(curvature = 0.5, angle = 45, ncp = 5), 38 | curvePlot(curvature = -1.0, angle = 45, ncp = 5), 39 | curvePlot(curvature = 0.7, angle = 30, ncp = 5), 40 | curvePlot(curvature = -0.7, angle = 30, ncp = 5), 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/DensitySpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import demoData.AutoMpg 9 | import demoData.Iris 10 | import org.jetbrains.letsPlot.Figure 11 | import org.jetbrains.letsPlot.coord.coordFixed 12 | import org.jetbrains.letsPlot.geom.geomDensity 13 | import org.jetbrains.letsPlot.letsPlot 14 | 15 | class DensitySpec : PlotDemoSpec { 16 | override fun createFigure(): Figure { 17 | val rand = java.util.Random() 18 | val n = 200 19 | val xs = List(n) { rand.nextGaussian() } 20 | val data = mapOf( 21 | "x" to xs, 22 | "w" to xs.map { if (it < 0.0) 2.0 else 0.5 } 23 | ) 24 | 25 | return letsPlot(data) + geomDensity(color = "black", size = 1.2) { 26 | x = "x" 27 | } 28 | } 29 | 30 | override fun createFigureList(): List
{ 31 | val sepalLength = letsPlot(Iris.map()) + 32 | geomDensity(alpha = 0.7) { 33 | x = "sepal length (cm)" 34 | color = "sepal width (cm)" 35 | fill = "target" 36 | } 37 | 38 | val sepalLengthCoordFixed = letsPlot(Iris.map()) + 39 | geomDensity(alpha = 0.7) { 40 | x = "sepal length (cm)" 41 | color = "sepal width (cm)" 42 | fill = "target" 43 | } + coordFixed() 44 | 45 | val withQuantileAes = letsPlot(AutoMpg.map()) + 46 | geomDensity(alpha = 0.7, size = 2) { 47 | x = "miles per gallon" 48 | group = "number of cylinders" 49 | color = "..quantile.." 50 | } 51 | 52 | val withQuantileLines = letsPlot(Iris.map()) + 53 | geomDensity( 54 | color = "black", 55 | quantiles = listOf(0, 0.02, 0.1, 0.5, 0.9, 0.98, 1), 56 | quantileLines = true 57 | ) { 58 | x = "sepal length (cm)" 59 | group = "target" 60 | fill = "..quantile.." 61 | } 62 | 63 | return listOf( 64 | sepalLength, 65 | sepalLengthCoordFixed, 66 | withQuantileAes, 67 | withQuantileLines 68 | ) 69 | } 70 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/FacetWrapSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import demoData.AutoMpg 9 | import org.jetbrains.letsPlot.Figure 10 | import org.jetbrains.letsPlot.facet.facetWrap 11 | import org.jetbrains.letsPlot.geom.geomPoint 12 | import org.jetbrains.letsPlot.intern.Plot 13 | import org.jetbrains.letsPlot.letsPlot 14 | import org.jetbrains.letsPlot.themes.themeGrey 15 | 16 | class FacetWrapSpec : PlotDemoSpec { 17 | 18 | override fun createFigureList(): List
{ 19 | val oneFacetDef = commonSpecs() + facetWrap(facets = "number of cylinders", format = "{d} cyl") 20 | val oneFacet3cols = commonSpecs() + facetWrap(facets = "number of cylinders", ncol = 3, format = "{d} cyl") 21 | val oneFacet4rows = 22 | commonSpecs() + facetWrap(facets = "number of cylinders", ncol = 4, dir = "v", format = "{d} cyl") 23 | val twoFacets = commonSpecs() + facetWrap( 24 | facets = listOf("origin of car", "number of cylinders"), 25 | ncol = 5, 26 | format = listOf(null, "{d} cyl") 27 | ) 28 | val twoFacetsCylindersOrderDesc = commonSpecs() + facetWrap( 29 | facets = listOf("origin of car", "number of cylinders"), 30 | ncol = 5, 31 | order = listOf(null, -1), 32 | format = listOf(null, "{d} cyl") 33 | ) 34 | 35 | return listOf( 36 | oneFacetDef, 37 | oneFacet3cols, 38 | oneFacet4rows, 39 | twoFacets, 40 | twoFacetsCylindersOrderDesc, 41 | ) 42 | } 43 | 44 | private fun commonSpecs(): Plot { 45 | return letsPlot(AutoMpg.map()) { 46 | x = "engine horsepower" 47 | y = "miles per gallon" 48 | color = "origin of car" 49 | } + geomPoint() + themeGrey() 50 | } 51 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/LabelSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.geom.geomLabel 10 | import org.jetbrains.letsPlot.letsPlot 11 | 12 | class LabelSpec : PlotDemoSpec { 13 | override fun createFigureList(): List
{ 14 | val fonts = run { 15 | val families = listOf( 16 | "Arial", 17 | "Calibri", 18 | "Garamond", 19 | "Geneva", 20 | "Georgia", 21 | "Helvetica", 22 | "Lucida Grande", 23 | "Rockwell", 24 | "Times New Roman", 25 | "Verdana", 26 | "sans-serif", 27 | "serif", 28 | "monospace" 29 | ) 30 | letsPlot() + geomLabel { 31 | y = families.indices 32 | label = families 33 | family = families 34 | } 35 | } 36 | 37 | return listOf(fonts) 38 | } 39 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/MarkdownSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import demoData.AutoMpg 9 | import org.jetbrains.letsPlot.Figure 10 | import org.jetbrains.letsPlot.geom.geomPoint 11 | import org.jetbrains.letsPlot.label.labs 12 | import org.jetbrains.letsPlot.letsPlot 13 | import org.jetbrains.letsPlot.scale.scaleColorManual 14 | import org.jetbrains.letsPlot.themes.elementMarkdown 15 | import org.jetbrains.letsPlot.themes.elementText 16 | import org.jetbrains.letsPlot.themes.theme 17 | 18 | class MarkdownSpec : PlotDemoSpec { 19 | override fun createFigureList(): List
{ 20 | return listOf( 21 | mpg() 22 | ) 23 | } 24 | 25 | fun mpg(): Figure { 26 | return letsPlot(AutoMpg.map()) + 27 | geomPoint(size=8) { x="engine displacement (cu. inches)"; y="miles per gallon"; color="number of cylinders" } + 28 | scaleColorManual(listOf("#66c2a5", "#fc8d62", "#8da0cb"), guide="none") + 29 | 30 | // Enable Markdown in all titles 31 | theme(title=elementMarkdown()) + 32 | 33 | // Adjust style of title and subtitle 34 | theme(plotTitle=elementText(size=30, family="Georgia", hjust=0.5), 35 | plotSubtitle=elementText(family="Georgia", hjust=0.5)) + 36 | 37 | labs( 38 | 39 | // Span styling, mixing style and emphasis 40 | title= 41 | """**4**, """ + 42 | """**6** and """ + 43 | """**8** cylinders""", 44 | 45 | // Simple emphasis 46 | subtitle="**City milage** *vs* **displacement**", 47 | 48 | // multiline caption, multiline style span, links 49 | caption="" + 50 | "Powered by Lets-Plot. \n" + 51 | "Visit the issue tracker for feedback." + 52 | "", 53 | 54 | // Axis titles 55 | x="Displacement (***inches***)", 56 | y="Miles per gallon (***cty***)" 57 | ) 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/PerfSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.geom.geomPoint 10 | import org.jetbrains.letsPlot.intern.Plot 11 | import org.jetbrains.letsPlot.letsPlot 12 | import org.jetbrains.letsPlot.sampling.samplingNone 13 | import kotlin.random.Random 14 | 15 | class PerfSpec : PlotDemoSpec { 16 | 17 | private fun points(): Plot { 18 | val rand = Random(12) 19 | val n = 25_000 20 | 21 | val data = mapOf( 22 | "x" to List(n) { rand.nextDouble() }, 23 | "y" to List(n) { rand.nextDouble() }, 24 | "col" to List(n) { rand.nextDouble() }, 25 | ) 26 | 27 | return letsPlot(data) + geomPoint(size = 8, alpha = 0.3, sampling = samplingNone) { 28 | x = "x" 29 | y = "y" 30 | color = "col" 31 | } 32 | } 33 | 34 | override fun createFigureList(): List
{ 35 | return listOf(points()) 36 | } 37 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/PieSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.Stat 10 | import org.jetbrains.letsPlot.geom.geomPie 11 | import org.jetbrains.letsPlot.letsPlot 12 | 13 | class PieSpec : PlotDemoSpec { 14 | override fun createFigureList(): List
{ 15 | return listOf( 16 | simplePie(), 17 | multiPie() 18 | ) 19 | } 20 | 21 | private fun multiPie(): Figure { 22 | 23 | val data = mapOf( 24 | "x" to listOf("a", "a", "a", "a", "a", "b", "b", "c", "c", "c"), 25 | "y" to listOf(1, 1, 1, 1, 1, 2, 2, 1.5, 1.5, 1.5), 26 | "s" to listOf(3, 1, 2, 1, 4, 1, 3, 3, 3, 1), 27 | "n" to listOf("a", "b", "a", "c", "a", "a", "b", "c", "a", "b") 28 | ) 29 | return letsPlot(data) + 30 | geomPie(size = 10, hole=0.3) { 31 | x = "x" 32 | y = "y" 33 | slice = "s" 34 | fill = "n" 35 | } 36 | } 37 | 38 | private fun simplePie(): Figure { 39 | val data = mapOf( 40 | "name" to listOf('a', 'b', 'c', 'd', 'b'), 41 | "value" to listOf(40, 90, 10, 50, 20) 42 | ) 43 | return letsPlot(data) + 44 | geomPie(stat = Stat.identity, size = 0.7, sizeUnit = "x") { 45 | slice = "value" 46 | fill = "name" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/PlotDemoSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | 10 | interface PlotDemoSpec { 11 | fun createFigure(): Figure { 12 | return createFigureList().first() 13 | } 14 | 15 | fun createFigureList(): List
16 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/PolarHeatmapSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.coord.coordPolar 10 | import org.jetbrains.letsPlot.geom.geomTile 11 | import org.jetbrains.letsPlot.ggplot 12 | import org.jetbrains.letsPlot.label.ggtitle 13 | import org.jetbrains.letsPlot.scale.scaleBrewer 14 | import org.jetbrains.letsPlot.themes.elementBlank 15 | import org.jetbrains.letsPlot.themes.theme 16 | import kotlin.math.PI 17 | import kotlin.math.cos 18 | import kotlin.math.sin 19 | 20 | class PolarHeatmapSpec : PlotDemoSpec { 21 | override fun createFigureList(): List
{ 22 | return listOf( 23 | polarHeatmap() 24 | ) 25 | } 26 | 27 | private fun polarHeatmap(): Figure { 28 | val maxR = 100 29 | val stepsR = 4 * maxR 30 | val maxTheta = 2 * PI 31 | val stepsTheta = 200 32 | val dataFunction: (Double, Double) -> Double = { x, y -> sin(x * 7) + cos(y / 11) } 33 | 34 | fun linspace(start: Double, stop: Double, num: Int): List { 35 | if (num <= 0) return emptyList() 36 | if (num == 1) return listOf(start) 37 | val step = (stop - start) / (num - 1) 38 | return List(num) { start + it * step } 39 | } 40 | 41 | fun simpleMeshgrid(xs: List, ys: List): Pair>, List>> { 42 | return Pair( 43 | List(ys.size) { xs }, 44 | ys.map { y -> List(xs.size) { y } } 45 | ) 46 | } 47 | 48 | fun getData(xs: List, ys: List, f: (Double, Double) -> Double): Map> { 49 | val zs = (xs zip ys).map { p -> f(p.first, p.second) } 50 | return mapOf("x" to xs, "y" to ys, "z" to zs) 51 | } 52 | 53 | val (gridR, gridTheta) = simpleMeshgrid( 54 | linspace(0.0, maxR.toDouble(), stepsR), 55 | linspace(0.0, maxTheta.toDouble(), stepsTheta) 56 | ) 57 | 58 | val dataMap = getData(gridTheta.flatten(), gridR.flatten(), dataFunction) 59 | 60 | val p = ggplot(dataMap) + 61 | geomTile(size = 1) { x = "x"; y = "y"; color = "z"; fill = "z" } + 62 | scaleBrewer(listOf("color", "fill"), palette = "Spectral", direction = -1) + 63 | theme(axisTitle = elementBlank()) 64 | 65 | return p + coordPolar() + ggtitle("Cartesian Heatmap") 66 | } 67 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/RasterSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import demoData.Raster.rasterData_Blue 9 | import demoData.Raster.rasterData_RGB 10 | import org.jetbrains.letsPlot.Figure 11 | import org.jetbrains.letsPlot.geom.geomRaster 12 | import org.jetbrains.letsPlot.letsPlot 13 | import org.jetbrains.letsPlot.scale.scaleFillIdentity 14 | 15 | class RasterSpec : PlotDemoSpec { 16 | 17 | override fun createFigureList(): List
{ 18 | return listOf( 19 | rasterPlot(rasterData_Blue(), scaleFillIdentity = false), 20 | rasterPlot(rasterData_RGB(), scaleFillIdentity = true) 21 | ) 22 | } 23 | 24 | private fun rasterPlot(data: Map<*, *>, scaleFillIdentity: Boolean): Figure { 25 | var plot = letsPlot(data) + 26 | geomRaster { 27 | x = "x" 28 | y = "y" 29 | fill = "fill" 30 | alpha = "alpha" 31 | } 32 | 33 | if (scaleFillIdentity) { 34 | plot += scaleFillIdentity() 35 | } 36 | 37 | return plot 38 | } 39 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/StrokeDashSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.geom.geomSegment 10 | import org.jetbrains.letsPlot.ggplot 11 | 12 | class StrokeDashSpec : PlotDemoSpec { 13 | override fun createFigureList(): List
{ 14 | return listOf( 15 | ggplot() 16 | + geomSegment(x=0, y=20, xend = 100, yend=20, linetype = listOf(5, listOf(10, 5))) 17 | + geomSegment(x=0, y=10, xend = 100, yend=10, linetype = listOf(0, listOf(10, 5))) 18 | + geomSegment(x=0, y=0, xend = 100, yend=0), 19 | ) 20 | } 21 | } -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/SuperscriptExponentNotationSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.geom.geomPoint 10 | import org.jetbrains.letsPlot.label.ggtitle 11 | import org.jetbrains.letsPlot.letsPlot 12 | import org.jetbrains.letsPlot.themes.theme 13 | import kotlin.math.pow 14 | 15 | class SuperscriptExponentNotationSpec : PlotDemoSpec { 16 | override fun createFigureList(): List
{ 17 | return listOf( 18 | simple() 19 | ) 20 | } 21 | 22 | private fun simple(): Figure { 23 | val xs: List = (-10..10).map(Int::toDouble).toList() 24 | val f: (Double) -> Double = { it * 10.0.pow(-5) } 25 | val data = mapOf("x" to xs, "y" to xs.map(f)) 26 | return letsPlot(data) { 27 | x = "x"; y = "y" 28 | } + geomPoint() + ggtitle("No transform") + theme(exponentFormat = "pow") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/plot/shared/src/main/kotlin/plotSpec/VariadicPathSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package plotSpec 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.geom.geomPath 10 | import org.jetbrains.letsPlot.label.ggtitle 11 | import org.jetbrains.letsPlot.letsPlot 12 | 13 | class VariadicPathSpec : PlotDemoSpec { 14 | override fun createFigureList(): List
{ 15 | return listOf( 16 | variadicPathPlot() 17 | ) 18 | } 19 | 20 | private fun variadicPathPlot(): Figure { 21 | val data = mapOf( 22 | "x" to listOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 ), 23 | "y" to listOf(0.0, 5.0, 0.0, 10.0, 0.0, 5.0, 0.0, 5.0, 0.0 ), 24 | "g" to listOf(0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0 ), 25 | "c" to listOf(1.0, 17.0, 4.0, 8.0, 3.0, 15.0, 15.0, 2.0, 9.0 ), 26 | "s" to listOf(10.0, 10.0, 10.0, 8.0, 3.0, 9.0, 15.0, 12.0, 9.0), 27 | ) 28 | return letsPlot(data) + geomPath() { x = "x"; y = "y"; size = "s"; color = "c" } + ggtitle("Variadic Path") 29 | } 30 | } -------------------------------------------------------------------------------- /demo/plot/swing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | val osName = System.getProperty("os.name")!! 6 | val hostOs = when { 7 | osName == "Mac OS X" -> "macos" 8 | osName.startsWith("Win") -> "windows" 9 | osName.startsWith("Linux") -> "linux" 10 | else -> error("Unsupported OS: $osName") 11 | } 12 | 13 | var hostArch = when (val osArch = System.getProperty("os.arch")) { 14 | "x86_64", "amd64" -> "x64" 15 | "aarch64" -> "arm64" 16 | else -> error("Unsupported arch: $osArch") 17 | } 18 | 19 | val skikoVersion = extra["skiko.version"] as String 20 | val letsPlotVersion = extra["letsPlot.version"] as String 21 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 22 | 23 | dependencies { 24 | implementation("org.jetbrains.skiko:skiko:$skikoVersion") 25 | implementation("org.jetbrains.skiko:skiko-awt-runtime-$hostOs-$hostArch:$skikoVersion") 26 | 27 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 28 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 29 | implementation("org.jetbrains.lets-plot:platf-awt:$letsPlotVersion") 30 | 31 | implementation(project(":lets-plot-swing-skia")) 32 | implementation(project(":demo-plot-shared")) 33 | 34 | implementation("org.slf4j:slf4j-simple:2.0.9") // Enable logging to console 35 | } 36 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/defaultViewer/DensityViewer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.defaultViewer 7 | 8 | import org.jetbrains.letsPlot.skia.swing.PlotViewerWindowSkia 9 | import plotSpec.DensitySpec 10 | 11 | fun main() { 12 | with(DensitySpec()) { 13 | PlotViewerWindowSkia( 14 | "Density plot", 15 | createFigure(), 16 | null, 17 | preserveAspectRatio = false 18 | ).open() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/BarPlot.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.BarPlotSpec 10 | 11 | fun main() { 12 | with(BarPlotSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Bar-plot", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Curve.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.CurveSpec 10 | 11 | fun main() { 12 | with(CurveSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Curve", 15 | createFigureList(), 16 | maxCol = 2 17 | ).open() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Density.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.DensitySpec 10 | 11 | fun main() { 12 | with(DensitySpec()) { 13 | PlotSpecsDemoWindow( 14 | "Density plot", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/FacetWrap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.FacetWrapSpec 10 | 11 | fun main() { 12 | with(FacetWrapSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Facet Wrap", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Hyperlink.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.HyperlinkSpec 10 | 11 | fun main() { 12 | with(HyperlinkSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Hyperlink", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Label.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.LabelSpec 10 | 11 | fun main() { 12 | with(LabelSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Labels", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/MarginalLayersFacets.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.MarginalLayersFacetsSpec 10 | 11 | fun main() { 12 | with(MarginalLayersFacetsSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Marginal Layers", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Markdown.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.MarkdownSpec 10 | 11 | fun main() { 12 | with(MarkdownSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Markdown", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Pie.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.PieSpec 10 | 11 | fun main() { 12 | with(PieSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Pie-plot", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/PlotGrid.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.PlotGridSpec 10 | 11 | fun main() { 12 | with(PlotGridSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Plot grid", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/PolarHeatmap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.PolarHeatmapSpec 10 | 11 | fun main() { 12 | with(PolarHeatmapSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Polar heatmap", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Raster.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.RasterSpec 10 | 11 | fun main() { 12 | with(RasterSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Raster", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/SuperscriptExponentNotation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.SuperscriptExponentNotationSpec 10 | 11 | fun main() { 12 | with(SuperscriptExponentNotationSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Superscript exponent notation", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/ThemeOptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.ThemeOptionsSpec 10 | 11 | fun main() { 12 | with(ThemeOptionsSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Theme options", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/TooltipAnchor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.TooltipAnchorSpec 10 | 11 | fun main() { 12 | with(TooltipAnchorSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Tooltip Anchor", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/VariadicPath.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.VariadicPathSpec 10 | 11 | fun main() { 12 | with(VariadicPathSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Variadic Path", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/plot/various/Violin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.plot.various 7 | 8 | import demo.util.PlotSpecsDemoWindow 9 | import plotSpec.ViolinSpec 10 | 11 | fun main() { 12 | with(ViolinSpec()) { 13 | PlotSpecsDemoWindow( 14 | "Violin", 15 | createFigureList(), 16 | ).open() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/plot/swing/src/main/kotlin/demo/util/PlotSpecsDemoWindow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.util 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.skia.swing.createComponent 10 | import java.awt.Color 11 | import java.awt.Dimension 12 | import java.awt.GridLayout 13 | import javax.swing.* 14 | import kotlin.math.min 15 | 16 | internal class PlotSpecsDemoWindow( 17 | title: String, 18 | private val figures: List
, 19 | maxCol: Int = 3, 20 | private val plotSize: Dimension? = null, 21 | background: Color = Color.WHITE, 22 | ) : JFrame("$title (Skia Swing)") { 23 | private val rootPanel: JPanel 24 | 25 | init { 26 | defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE 27 | 28 | rootPanel = JPanel() 29 | rootPanel.layout = GridLayout(0, min(maxCol, figures.size)) 30 | rootPanel.background = background 31 | rootPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) 32 | 33 | if (plotSize == null) { 34 | contentPane.add(rootPanel) 35 | } else { 36 | // Fixed plot size 37 | val scrollPane = JScrollPane( 38 | rootPanel, 39 | ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 40 | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED 41 | 42 | ) 43 | contentPane.add(scrollPane) 44 | } 45 | } 46 | 47 | fun open() { 48 | SwingUtilities.invokeLater { 49 | createWindowContent() 50 | 51 | pack() 52 | setLocationRelativeTo(null) // move to the screen center 53 | isVisible = true 54 | } 55 | } 56 | 57 | private fun createWindowContent() { 58 | val preferredSizeFromPlot = (plotSize == null) 59 | val components = figures.map { figure -> 60 | val figureComponent = figure.createComponent( 61 | preferredSizeFromPlot = preferredSizeFromPlot 62 | ) { messages -> 63 | for (message in messages) { 64 | println("[Demo Plot Viewer] $message") 65 | } 66 | } 67 | 68 | plotSize?.let { 69 | figureComponent.preferredSize = it 70 | } 71 | 72 | figureComponent 73 | } 74 | 75 | components.forEach { rootPanel.add(it) } 76 | } 77 | } -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("android") 8 | id("org.jetbrains.compose") 9 | id("com.android.application") 10 | kotlin("plugin.compose") 11 | } 12 | 13 | 14 | android { 15 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 16 | namespace = "demo.plot.CanvasDemo" 17 | 18 | buildFeatures { 19 | compose = true 20 | } 21 | 22 | defaultConfig { 23 | applicationId = "demo.plot.CanvasDemo" 24 | 25 | minSdk = (findProperty("android.minSdk") as String).toInt() 26 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 27 | 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | 32 | buildTypes { 33 | debug { 34 | isDebuggable = true 35 | } 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility = JavaVersion.VERSION_11 40 | targetCompatibility = JavaVersion.VERSION_11 41 | } 42 | 43 | kotlin { 44 | jvmToolchain(11) 45 | } 46 | } 47 | 48 | val composeVersion = extra["compose.version"] as String 49 | val androidxActivityCompose = extra["androidx.activity.compose"] as String 50 | val letsPlotVersion = extra["letsPlot.version"] as String 51 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 52 | 53 | dependencies { 54 | implementation(compose.runtime) 55 | implementation(compose.foundation) 56 | implementation(compose.material) 57 | implementation(compose.ui) 58 | implementation("androidx.activity:activity-compose:$androidxActivityCompose") 59 | 60 | implementation("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 61 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 62 | implementation("org.jetbrains.lets-plot:canvas:$letsPlotVersion") 63 | implementation("org.jetbrains.lets-plot:plot-raster:$letsPlotVersion") 64 | 65 | implementation(project(":lets-plot-compose")) 66 | implementation(project(":demo-plot-shared")) 67 | 68 | implementation("org.slf4j:slf4j-api:2.0.9") 69 | implementation("com.github.tony19:logback-android:3.0.0") 70 | } 71 | -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3C3F41 4 | -------------------------------------------------------------------------------- /demo/svg/android-svg-compose/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Plot Rendering Demo (min) 3 | 4 | -------------------------------------------------------------------------------- /demo/svg/compose-desktop/build.gradle.kts: -------------------------------------------------------------------------------- 1 | //import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | 3 | plugins { 4 | // kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22) 5 | kotlin("jvm") 6 | kotlin("plugin.compose") 7 | id("org.jetbrains.compose") 8 | } 9 | 10 | val letsPlotVersion = extra["letsPlot.version"] as String 11 | 12 | dependencies { 13 | implementation(compose.desktop.currentOs) 14 | 15 | implementation(project(":platf-skia")) 16 | implementation(project(":platf-skia-awt")) 17 | 18 | implementation(project(":demo-svg-shared")) 19 | 20 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 21 | } 22 | -------------------------------------------------------------------------------- /demo/svg/shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("jvm") 8 | } 9 | 10 | val kotlinLoggingVersion = extra["kotlinLogging.version"] as String 11 | val letsPlotVersion = extra["letsPlot.version"] as String 12 | 13 | dependencies { 14 | implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion") 15 | 16 | compileOnly("org.jetbrains.lets-plot:commons:$letsPlotVersion") 17 | compileOnly("org.jetbrains.lets-plot:datamodel:$letsPlotVersion") 18 | 19 | testImplementation(kotlin("test")) 20 | } 21 | -------------------------------------------------------------------------------- /demo/svg/shared/src/main/kotlin/demo/svgModel/OpacityDemoModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svgModel 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgColors 9 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgNode 10 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 11 | 12 | object OpacityDemoModel { 13 | fun createModel() = SvgSvgElement(width = 600.0, height = 400.0).apply { 14 | var i = 0 15 | fun SvgNode.row(text: String, opacity: Double? = null, strokeOpacity: Double? = null, fillOpacity: Double? = null) { 16 | val x = 200.0 17 | val size = 60.0 18 | val strokeWidth = 15.0 19 | val vMargin = 30.0 20 | val hMargin = 10.0 21 | val y = 40 + i * (size + vMargin) 22 | i++ 23 | 24 | rect(x, y, size, size, SvgColors.CRIMSON, SvgColors.STEEL_BLUE, strokeWidth) { 25 | opacity?.let { setAttribute("opacity", it.toString()) } 26 | strokeOpacity?.let { setAttribute("stroke-opacity", it.toString()) } 27 | fillOpacity?.let { setAttribute("fill-opacity", it.toString()) } 28 | } 29 | text(text, x = x + size + hMargin, y = y + size / 2) 30 | } 31 | 32 | g { 33 | row("opacity=0.5", opacity = 0.5) 34 | row("fill/stroke-opacity=0.5", fillOpacity = 0.5, strokeOpacity = 0.5) 35 | row("fill-opacity=0.5", fillOpacity = 0.5) 36 | row("stroke-opacity=0.5", strokeOpacity = 0.5) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /demo/svg/swing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | val skikoVersion = extra["skiko.version"] as String 6 | val letsPlotVersion = extra["letsPlot.version"] as String 7 | 8 | val osName = System.getProperty("os.name")!! 9 | val hostOs = when { 10 | osName == "Mac OS X" -> "macos" 11 | osName.startsWith("Win") -> "windows" 12 | osName.startsWith("Linux") -> "linux" 13 | else -> error("Unsupported OS: $osName") 14 | } 15 | 16 | var hostArch = when (val osArch = System.getProperty("os.arch")) { 17 | "x86_64", "amd64" -> "x64" 18 | "aarch64" -> "arm64" 19 | else -> error("Unsupported arch: $osArch") 20 | } 21 | 22 | val host = "${hostOs}-${hostArch}" 23 | 24 | 25 | dependencies { 26 | implementation("org.jetbrains.skiko:skiko:$skikoVersion") 27 | implementation("org.jetbrains.skiko:skiko-awt-runtime-$hostOs-$hostArch:$skikoVersion") 28 | 29 | implementation(project(":platf-skia")) 30 | implementation(project(":platf-skia-awt")) 31 | 32 | implementation(project(":demo-svg-shared")) 33 | 34 | implementation("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 35 | } 36 | -------------------------------------------------------------------------------- /demo/svg/swing/src/main/kotlin/demo/svg/ClipPathSvgDemo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg 7 | 8 | import demo.svg.utils.DemoWindow 9 | import demo.svgModel.ClipPathSvgModel 10 | 11 | fun main() { 12 | DemoWindow("SVG with clip-path", listOf(ClipPathSvgModel.createModel())).open() 13 | } 14 | -------------------------------------------------------------------------------- /demo/svg/swing/src/main/kotlin/demo/svg/OpacityDemo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg 7 | 8 | import demo.svg.utils.DemoWindow 9 | import demo.svgModel.OpacityDemoModel 10 | 11 | fun main() { 12 | DemoWindow("Opacity Demo", listOf(OpacityDemoModel.createModel())).open() 13 | } 14 | -------------------------------------------------------------------------------- /demo/svg/swing/src/main/kotlin/demo/svg/ReferenceSvgDemo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg 7 | 8 | import demo.svg.utils.DemoWindow 9 | import demo.svgModel.ReferenceSvgModel 10 | 11 | fun main() { 12 | DemoWindow("Reference SVG", listOf(ReferenceSvgModel.createModel())).open() 13 | } 14 | -------------------------------------------------------------------------------- /demo/svg/swing/src/main/kotlin/demo/svg/SvgImageElementDemo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg 7 | 8 | import demo.svg.utils.DemoWindow 9 | import demo.svgModel.SvgImageElementModel 10 | 11 | fun main() { 12 | DemoWindow("SvgImage demo", listOf(SvgImageElementModel.createModel())).open() 13 | } 14 | -------------------------------------------------------------------------------- /demo/svg/swing/src/main/kotlin/demo/svg/utils/DemoBase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg.utils 7 | 8 | import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle 9 | import org.jetbrains.letsPlot.commons.geometry.DoubleVector 10 | import org.jetbrains.letsPlot.core.plot.base.render.svg.GroupComponent 11 | import org.jetbrains.letsPlot.core.plot.builder.presentation.Style 12 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgColors 13 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgCssResource 14 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgRectElement 15 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 16 | 17 | internal abstract class DemoBase( 18 | private val demoInnerSize: DoubleVector 19 | ) { 20 | protected open val cssStyle: String = Style.generateCSS(Style.default(), plotId = null, decorationLayerId = null) 21 | 22 | private val demoOuterSize = demoInnerSize.add(PADDING.mul(2.0)) 23 | 24 | fun createSvgRoots(demoGroups: List): List { 25 | return demoGroups.map { 26 | it.moveTo(PADDING) 27 | val svgRoot = createSvgRoot() 28 | svgRoot.children().add(it.rootGroup) 29 | svgRoot 30 | } 31 | } 32 | 33 | private fun createSvgRoot(): SvgSvgElement { 34 | val svg = SvgSvgElement() 35 | svg.width().set(demoOuterSize.x) 36 | svg.height().set(demoOuterSize.y) 37 | svg.addClass(Style.PLOT_CONTAINER) 38 | 39 | svg.setStyle(object : SvgCssResource { 40 | override fun css(): String = cssStyle 41 | }) 42 | 43 | val viewport = DoubleRectangle(PADDING, demoInnerSize) 44 | val viewportRect = SvgRectElement(viewport) 45 | viewportRect.stroke().set(SvgColors.LIGHT_BLUE) 46 | viewportRect.fill().set(SvgColors.NONE) 47 | svg.children().add(viewportRect) 48 | 49 | return svg 50 | } 51 | 52 | companion object { 53 | private val PADDING = DoubleVector(20.0, 20.0) 54 | } 55 | } -------------------------------------------------------------------------------- /demo/svg/swing/src/main/kotlin/demo/svg/utils/DemoWindow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package demo.svg.utils 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 9 | import org.jetbrains.letsPlot.skia.awt.view.SvgPanel 10 | import java.awt.Color 11 | import java.awt.GridLayout 12 | import javax.swing.* 13 | import kotlin.math.min 14 | 15 | internal class DemoWindow( 16 | title: String, 17 | private val svgRoots: List, 18 | private val maxCol: Int = 2, 19 | ) : JFrame("$title (Skia Swing)") { 20 | private val rootPanel: JPanel 21 | 22 | init { 23 | defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE 24 | 25 | rootPanel = JPanel() 26 | rootPanel.layout = GridLayout(0, min(maxCol, svgRoots.size)) 27 | // rootPanel.background = Color.WHITE 28 | rootPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) 29 | 30 | 31 | // Fixed plot size 32 | val scrollPane = JScrollPane( 33 | rootPanel, 34 | ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 35 | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED 36 | 37 | ) 38 | contentPane.add(scrollPane) 39 | } 40 | 41 | fun open() { 42 | SwingUtilities.invokeLater { 43 | createWindowContent() 44 | 45 | pack() 46 | setLocationRelativeTo(null) // move to the screen center 47 | isVisible = true 48 | } 49 | } 50 | 51 | private fun createWindowContent() { 52 | for (svgRoot in svgRoots) { 53 | rootPanel.add(createSvgPanel(svgRoot)) 54 | } 55 | } 56 | 57 | private fun createSvgPanel(svgRoot: SvgSvgElement): JComponent { 58 | val component = SvgPanel(svgRoot) 59 | component.border = BorderFactory.createLineBorder(Color.ORANGE, 1) 60 | return component 61 | } 62 | } -------------------------------------------------------------------------------- /devdocs/PUBLISHING.md: -------------------------------------------------------------------------------- 1 | ### Publishing to local Maven Repository 2 | 3 | > **Note**: our custom local Maven repository is located at `/.maven-publish-dev-repo`. 4 | 5 | > **Note**: set **version** to "0.0.0-SNAPSHOT". 6 | 7 | `./gradlew publishAllPublicationsToMavenLocalRepository` 8 | 9 | 10 | ### Publishing to Sonatype Maven Repository 11 | 12 | > **Note**: When publishing a "Release" version to Sonatype, PGP signature is required. 13 | > 14 | > See: https://central.sonatype.org/pages/working-with-pgp-signatures.html 15 | 16 | 17 | #### Credentials 18 | 19 | In the `local.properties` file add the following properties: 20 | ```properties 21 | sonatype.username= 22 | sonatype.password= 23 | ``` 24 | 25 | #### SNAPSHOT version 26 | 27 | Specify "x.y.z-SNAPSHOT" version in `build.gradle.kts` file. 28 | 29 | `./gradlew publishAllPublicationsToSonatypeRepository` 30 | 31 | > You can find published SNAPSHOT artifacts here https://oss.sonatype.org/index.html#view-repositories;snapshots~browsestorage \ 32 | > In the "Browse Storage" tab enter ‘Path lookup’: org/jetbrains/lets-plot 33 | 34 | 35 | #### "Release" version 36 | 37 | a) Specify RELEASE or PRE-RELEASE (i.e. "x.y.z-alpha1", "x.y.z-rc1" etc.) version in `build.gradle.kts` file. 38 | 39 | b) Upload to the Nexus staging repository: 40 | 41 | `./gradlew publishAllPublicationsToSonatypeRepository` 42 | 43 | > Check artifacts are uploaded to staging repository: 44 | > 45 | > https://oss.sonatype.org/index.html#stagingRepositories 46 | > 47 | > Should see repository: "orgjetbrainslets-plot-NNNN" (where NNNN is a number) 48 | > with profile: "org.jetbrains.lets-plot". 49 | 50 | c) Publish all artifacts to "Releases" repository (from the staging): 51 | 52 | `./gradlew findSonatypeStagingRepository closeAndReleaseSonatypeStagingRepository` 53 | 54 | > Check artifacts are uploaded to Nexus Releases repository: 55 | > 56 | > https://oss.sonatype.org/index.html#view-repositories;releases~browsestorage 57 | > 58 | > In the "Browse Storage" tab enter ‘Path lookup’: org/jetbrains/lets-plot 59 | -------------------------------------------------------------------------------- /devdocs/RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Releasing the Project 2 | 3 | ### Make Version 4 | 5 | ##### 1. Update Documentation 6 | 7 | - Update CHANGELOG.md 8 | - Update the "Dependencies" section in README.md 9 | 10 | ##### 2. Set release version 11 | 12 | - remove _"-SNAPSHOT"_ qualifier (the 'version' property in the root 'build.gradle.kts'). 13 | 14 | ##### 3. Build and release artifacts to Sonatype repository / Maven Central 15 | 16 | Make sure that JNI libraries in the `skiko-jni-libs` directory are up-to-date and match the version of the `Skiko` library used in the `Compose Multiplatform`. Refer [DEVELOPMENT.md](DEVELOPMENT.md) for details. 17 | 18 | - `./gradlew clean` 19 | - `./gradlew build` 20 | - `./gradlew packageSkikoJniLibs` 21 | - `./gradlew publishAllPublicationsToSonatypeRepository` 22 | - `./gradlew findSonatypeStagingRepository closeAndReleaseSonatypeStagingRepository` 23 | 24 | > **Note**: For more details see [PUBLISHING.md](PUBLISHING.md). 25 | 26 | ##### 4. Prepare to the next dev cycle 27 | 28 | - Increment the version and add _"-SNAPSHOT"_ qualifier (the 'version' property in the root 'build.gradle.kts') 29 | - Push all to git and add the version git tag: 30 | - `git add --all && git commit -m "Release vX.X.X" && git push` 31 | - `git tag vX.X.X && git push --tags` 32 | 33 | ### Add the GitHub Release: 34 | 35 | * Open the link: https://github.com/JetBrains/lets-plot-skia/releases/new 36 | * Fill `Tag version` and `Release title` with the released version: "vX.X.X" 37 | * Fill the description field - copy from the CHANGELOG.md 38 | * **Attach the artifacts:** 39 | - `skiko-jni-libs.zip` from the project root 40 | 41 | 42 | ### Update Dependant Projects 43 | 44 | - Update the version of the `lets-plot-skia` dependency in the [lets-plot-compose-demos](https://github.com/JetBrains/lets-plot-compose-demos) project. 45 | -------------------------------------------------------------------------------- /future_changes.md: -------------------------------------------------------------------------------- 1 | ## [2.2.1] - 2025-mm-dd 2 | 3 | ### Compatibility 4 | 5 | - [Android](https://developer.android.com/compose) **temporarily not supported due to [SKIKO-761](https://youtrack.jetbrains.com/issue/SKIKO-761).** 6 | - [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform) 1.7.0-1.7.3 7 | - [Skiko](https://github.com/JetBrains/skiko) 0.8.15 and 0.8.18 8 | - [Lets-Plot Kotlin API](https://github.com/JetBrains/lets-plot-kotlin) 4.10.0 9 | - [Lets-Plot Multiplatform](https://github.com/JetBrains/lets-plot) 4.6.2 10 | 11 | ### Added 12 | 13 | ### Changed 14 | 15 | ### Fixed 16 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 3 | 4 | #Kotlin 5 | kotlin.code.style=official 6 | 7 | #MPP 8 | kotlin.mpp.stability.nowarn=true 9 | kotlin.mpp.enableCInteropCommonization=true 10 | kotlin.mpp.androidSourceSetLayoutVersion=2 11 | 12 | #Compose 13 | org.jetbrains.compose.experimental.uikit.enabled=true 14 | kotlin.native.cacheKind=none 15 | 16 | #Android 17 | android.useAndroidX=true 18 | android.compileSdk=34 19 | android.targetSdk=34 20 | android.minSdk=24 21 | 22 | #Versions 23 | # KMP is not compatible with AGP 8.3 yet. 24 | agp.version=8.2.2 25 | 26 | kotlin.version=2.1.0 27 | compose.version=1.7.3 28 | skiko.version=0.8.18 29 | 30 | androidx.activity.compose=1.8.2 31 | 32 | letsPlot.version=4.6.3-SNAPSHOT 33 | letsPlotKotlin.version=4.10.0 34 | 35 | nexusStaging.version=0.30.0 36 | nexusPublish.version=1.3.0 37 | 38 | kotlinLogging.version=2.0.5 39 | assertj.version=3.12.2 40 | 41 | #------- SKIKO ------- 42 | # skiko v0.7.93 and higher doens't work on Android: 43 | # java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__aarch64_ldadd4_relax" referenced by ".../lib/arm64-v8a/libskiko-android-arm64.so" 44 | # or 45 | # java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "_ZN4sksg4NodeD2Ev" referenced by ".../lib/x86_64/libskiko-android-x64.so" 46 | # see https://github.com/JetBrains/skiko/issues/761 47 | 48 | # Skiko version should be compatible with Compose version 49 | # Check skiko in the Compose version here (change the version in the URL if needed): 50 | # https://repo1.maven.org/maven2/org/jetbrains/compose/ui/ui-desktop/1.6.10/ui-desktop-1.6.10.pom 51 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/img-2.png -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/img.png -------------------------------------------------------------------------------- /lets-plot-compose/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /lets-plot-compose/src/androidMain/kotlin/org/jetbrains/letsPlot/skia/compose/PlotPanel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose 7 | 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.viewinterop.AndroidView 11 | import org.jetbrains.letsPlot.Figure 12 | import org.jetbrains.letsPlot.skia.compose.android.PlotComponentProvider 13 | import org.jetbrains.letsPlot.skia.compose.android.PlotViewContainer 14 | import org.jetbrains.letsPlot.skia.compose.util.NaiveLogger 15 | 16 | private val LOG = NaiveLogger("PlotPanel") 17 | 18 | @Suppress("FunctionName") 19 | @Composable 20 | actual fun PlotPanel( 21 | figure: Figure, 22 | preserveAspectRatio: Boolean, 23 | modifier: Modifier, 24 | computationMessagesHandler: (List) -> Unit 25 | ) { 26 | LOG.print { "Recompose PlotPanel() preserveAspectRatio: $preserveAspectRatio " } 27 | 28 | val provider by remember { 29 | mutableStateOf( 30 | PlotComponentProvider( 31 | computationMessagesHandler 32 | ) 33 | ) 34 | } 35 | 36 | DisposableEffect(provider) { 37 | onDispose { 38 | LOG.print { "DisposableEffect preserveAspectRatio: ${provider.plotViewContainer?.preserveAspectRatio} " } 39 | provider.dispose() 40 | } 41 | } 42 | 43 | AndroidView( 44 | factory = provider.factory, 45 | modifier = modifier, 46 | update = { plotViewContainer -> 47 | plotViewContainer as PlotViewContainer 48 | LOG.print { "UPDATE PlotViewContainer preserveAspectRatio ${plotViewContainer.preserveAspectRatio} -> $preserveAspectRatio" } 49 | 50 | plotViewContainer.figure = figure 51 | plotViewContainer.preserveAspectRatio = preserveAspectRatio 52 | } 53 | ) 54 | } 55 | 56 | -------------------------------------------------------------------------------- /lets-plot-compose/src/androidMain/kotlin/org/jetbrains/letsPlot/skia/compose/android/ColorRect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose.android 7 | 8 | import android.graphics.Canvas 9 | import android.graphics.ColorFilter 10 | import android.graphics.Paint 11 | import android.graphics.PixelFormat 12 | import android.graphics.drawable.Drawable 13 | 14 | internal class ColorRect(val color: Int) : Drawable() { 15 | override fun draw(canvas: Canvas) { 16 | canvas.drawRect( 17 | canvas.clipBounds, 18 | Paint().also { it.color = color } 19 | ) 20 | } 21 | 22 | override fun setAlpha(alpha: Int) { 23 | } 24 | 25 | override fun setColorFilter(colorFilter: ColorFilter?) { 26 | } 27 | 28 | override fun getOpacity(): Int { 29 | return PixelFormat.OPAQUE 30 | } 31 | } -------------------------------------------------------------------------------- /lets-plot-compose/src/androidMain/kotlin/org/jetbrains/letsPlot/skia/compose/android/PlotComponentProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose.android 7 | 8 | import android.content.Context 9 | import android.view.View 10 | import org.jetbrains.letsPlot.skia.compose.util.NaiveLogger 11 | 12 | private val LOG = NaiveLogger("PlotComponentProvider") 13 | 14 | internal class PlotComponentProvider( 15 | private val computationMessagesHandler: ((List) -> Unit) 16 | ) { 17 | var plotViewContainer: PlotViewContainer? = null 18 | 19 | val factory: (Context) -> View = { ctx -> 20 | check(plotViewContainer == null) { "An attempt to reuse a single-use view factory." } 21 | PlotViewContainer( 22 | ctx, 23 | computationMessagesHandler 24 | ).also { 25 | plotViewContainer = it 26 | } 27 | } 28 | 29 | fun dispose() { 30 | LOG.print("dispose PlotComponentProvider preserveAspectRatio: ${plotViewContainer?.preserveAspectRatio}") 31 | plotViewContainer?.disposePlotView() 32 | plotViewContainer = null 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /lets-plot-compose/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/compose/PlotPanel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose 7 | 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import org.jetbrains.letsPlot.Figure 11 | 12 | 13 | @Suppress("FunctionName") 14 | @Composable 15 | expect fun PlotPanel( 16 | figure: Figure, 17 | preserveAspectRatio: Boolean = false, 18 | modifier: Modifier, 19 | computationMessagesHandler: (List) -> Unit 20 | ) -------------------------------------------------------------------------------- /lets-plot-compose/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/compose/util/NaiveLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose.util 7 | 8 | private const val ENABLED = false 9 | 10 | class NaiveLogger(val key: String) { 11 | fun print(s: String) { 12 | if (ENABLED) { 13 | println("[$key] $s") 14 | } 15 | } 16 | 17 | fun print(message: () -> String) { 18 | if (ENABLED) { 19 | print(message()) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/PlotFigureModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose 7 | 8 | import org.jetbrains.letsPlot.core.interact.event.ToolEventDispatcher 9 | import org.jetbrains.letsPlot.core.plot.builder.interact.FigureImplicitInteractionSpecs 10 | import org.jetbrains.letsPlot.core.plot.builder.interact.tools.FigureModel 11 | 12 | class PlotFigureModel( 13 | val onUpdateView: (Map?) -> Unit 14 | ) : FigureModel { 15 | private var toolEventCallback: ((Map) -> Unit)? = null 16 | 17 | var toolEventDispatcher: ToolEventDispatcher? = null 18 | set(value) { 19 | // De-activate and re-activate ongoing interactions when replacing the dispatcher. 20 | val wereInteractions = field?.deactivateAllSilently() ?: emptyMap() 21 | field = value 22 | value?.let { newDispatcher -> 23 | newDispatcher.initToolEventCallback { event -> toolEventCallback?.invoke(event) } 24 | 25 | // reactivate interactions in new plot component 26 | wereInteractions.forEach { (origin, interactionSpecList) -> 27 | newDispatcher.activateInteractions(origin, interactionSpecList) 28 | } 29 | } 30 | } 31 | 32 | init { 33 | toolEventDispatcher?.initToolEventCallback { event -> toolEventCallback?.invoke(event) } 34 | } 35 | 36 | override fun onToolEvent(callback: (Map) -> Unit) { 37 | toolEventCallback = callback 38 | 39 | // Make snsure that 'implicit' interaction activated. 40 | deactivateInteractions(origin = ToolEventDispatcher.ORIGIN_FIGURE_IMPLICIT) 41 | activateInteractions( 42 | origin = ToolEventDispatcher.ORIGIN_FIGURE_IMPLICIT, 43 | interactionSpecList = FigureImplicitInteractionSpecs.LIST 44 | ) 45 | 46 | } 47 | override fun activateInteractions(origin: String, interactionSpecList: List>) { 48 | toolEventDispatcher?.activateInteractions(origin, interactionSpecList) 49 | } 50 | 51 | override fun deactivateInteractions(origin: String){ 52 | toolEventDispatcher?.deactivateInteractions(origin) 53 | } 54 | 55 | override fun updateView(specOverride: Map?) { 56 | onUpdateView(specOverride) 57 | } 58 | } -------------------------------------------------------------------------------- /lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/desktop/DebouncedRunner.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose.desktop 7 | 8 | import javax.swing.Timer 9 | 10 | sealed interface DebouncedRunner { 11 | fun run() 12 | fun cancel() 13 | 14 | companion object { 15 | fun debounce(delayMs: Int, action: () -> Unit): DebouncedRunner = when (delayMs) { 16 | 0 -> InstantRunner(action) 17 | else -> Debouncer(delayMs, action) 18 | } 19 | } 20 | 21 | private class Debouncer( 22 | delayMs: Int, 23 | action: () -> Unit, 24 | ) : DebouncedRunner { 25 | private val timer = Timer(delayMs) { action() }.apply { isRepeats = false } 26 | override fun run() = when (timer.isRunning) { 27 | true -> timer.restart() 28 | false -> timer.start() 29 | } 30 | 31 | override fun cancel() { 32 | if (timer.isRunning) { 33 | timer.stop() 34 | } 35 | } 36 | } 37 | 38 | private class InstantRunner(private val action: () -> Unit) : DebouncedRunner { 39 | override fun run() = action() 40 | override fun cancel() = Unit 41 | } 42 | } -------------------------------------------------------------------------------- /lets-plot-compose/src/desktopMain/kotlin/org/jetbrains/letsPlot/skia/compose/desktop/PlotContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.compose.desktop 7 | 8 | import org.jetbrains.letsPlot.commons.geometry.DoubleVector 9 | import org.jetbrains.letsPlot.skia.awt.view.SvgPanel 10 | import org.jetbrains.letsPlot.skia.builderLW.ViewModel 11 | import java.awt.Cursor 12 | import java.awt.Rectangle 13 | import javax.swing.JPanel 14 | 15 | class PlotContainer : JPanel() { 16 | private val plotSvgPanel = SvgPanel() 17 | private var viewModel: ViewModel? = null 18 | 19 | init { 20 | layout = null // plot redraw may jump without this 21 | cursor = Cursor(Cursor.CROSSHAIR_CURSOR) 22 | add(plotSvgPanel) 23 | } 24 | 25 | fun updatePlotView(viewModel: ViewModel, preferredSize: DoubleVector, position: DoubleVector) { 26 | this.viewModel = viewModel 27 | 28 | plotSvgPanel.svg = viewModel.svg 29 | plotSvgPanel.eventDispatcher = viewModel.eventDispatcher 30 | plotSvgPanel.bounds = Rectangle( 31 | position.x.toInt(), 32 | position.y.toInt(), 33 | preferredSize.x.toInt(), 34 | preferredSize.y.toInt(), 35 | ) 36 | } 37 | 38 | fun dispose() { 39 | check(componentCount == 1) { "Unexpected number of children: $componentCount" } 40 | check(components[0] == plotSvgPanel) { "Unexpected child: should be SvgPanel but was ${components[0]::class.simpleName}" } 41 | 42 | removeAll() 43 | plotSvgPanel.dispose() 44 | viewModel?.dispose() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lets-plot-swing-skia/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("jvm") 8 | `maven-publish` 9 | signing 10 | } 11 | 12 | val skikoVersion = extra["skiko.version"] as String 13 | val letsPlotVersion = extra["letsPlot.version"] as String 14 | val letsPlotKotlinVersion = extra["letsPlotKotlin.version"] as String 15 | 16 | dependencies { 17 | compileOnly("org.jetbrains.skiko:skiko:$skikoVersion") 18 | 19 | compileOnly("org.jetbrains.lets-plot:lets-plot-kotlin-kernel:$letsPlotKotlinVersion") 20 | compileOnly("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 21 | compileOnly("org.jetbrains.lets-plot:platf-awt:$letsPlotVersion") 22 | 23 | api(project(":platf-skia")) 24 | api(project(":platf-skia-awt")) 25 | 26 | testImplementation(kotlin("test")) 27 | } 28 | 29 | // Create publication 30 | java { 31 | withSourcesJar() 32 | } 33 | 34 | publishing { 35 | publications { 36 | create("mavenJava") { 37 | from(components["java"]) 38 | } 39 | } 40 | } 41 | 42 | signing { 43 | if (!(project.version as String).contains("SNAPSHOT")) { 44 | sign(publishing.publications) 45 | } 46 | } -------------------------------------------------------------------------------- /lets-plot-swing-skia/src/main/kotlin/org/jetbrains/letsPlot/skia/swing/AwtAppEnv.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.swing 7 | 8 | import org.jetbrains.letsPlot.awt.plot.component.ApplicationContext 9 | import javax.swing.SwingUtilities 10 | 11 | internal object AwtAppEnv { 12 | val AWT_EDT_EXECUTOR = { action: () -> Unit -> 13 | runInEdt( 14 | Runnable { 15 | action() 16 | }, 17 | canRunImmediately = true 18 | ) 19 | } 20 | 21 | val AWT_APP_CONTEXT = object : ApplicationContext { 22 | override fun runWriteAction(action: Runnable) { 23 | action.run() 24 | } 25 | 26 | override fun invokeLater(action: Runnable, expared: () -> Boolean) { 27 | runInEdt( 28 | Runnable { 29 | if (!expared()) { 30 | action.run() 31 | } 32 | }, 33 | canRunImmediately = false 34 | ) 35 | } 36 | } 37 | 38 | private fun runInEdt(action: Runnable, canRunImmediately: Boolean) { 39 | if (canRunImmediately && SwingUtilities.isEventDispatchThread()) { 40 | action.run() 41 | } else { 42 | SwingUtilities.invokeLater(action) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /lets-plot-swing-skia/src/main/kotlin/org/jetbrains/letsPlot/skia/swing/FigureEx.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.swing 7 | 8 | import org.jetbrains.letsPlot.Figure 9 | import org.jetbrains.letsPlot.core.util.MonolithicCommon 10 | import org.jetbrains.letsPlot.intern.toSpec 11 | import javax.swing.JComponent 12 | 13 | fun Figure.createComponent( 14 | preserveAspectRatio: Boolean = false, 15 | preferredSizeFromPlot: Boolean = false, 16 | repaintDelay: Int = 300, // ms, 17 | computationMessagesHandler: (List) -> Unit 18 | ): JComponent { 19 | val rawSpec = this.toSpec() 20 | val processedSpec = MonolithicCommon.processRawSpecs(rawSpec, frontendOnly = false) 21 | 22 | return PlotPanelSkiaSwing( 23 | processedSpec = processedSpec, 24 | preserveAspectRatio = preserveAspectRatio, 25 | preferredSizeFromPlot = preferredSizeFromPlot, 26 | repaintDelay = repaintDelay, 27 | computationMessagesHandler = computationMessagesHandler 28 | ) 29 | } -------------------------------------------------------------------------------- /lets-plot-swing-skia/src/main/kotlin/org/jetbrains/letsPlot/skia/swing/PlotComponentProviderSkiaSwing.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.swing 7 | 8 | import org.jetbrains.letsPlot.awt.plot.component.PlotComponentProvider 9 | import org.jetbrains.letsPlot.commons.geometry.DoubleVector 10 | import org.jetbrains.letsPlot.commons.unsupported.UNSUPPORTED 11 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 12 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 13 | import org.jetbrains.letsPlot.skia.awt.builderHW.MonolithicSkiaAwt 14 | import java.awt.Dimension 15 | import javax.swing.JComponent 16 | 17 | internal class PlotComponentProviderSkiaSwing( 18 | private val processedSpec: MutableMap, 19 | private val computationMessagesHandler: (List) -> Unit 20 | ) : PlotComponentProvider { 21 | 22 | override fun createComponent( 23 | containerSize: Dimension?, 24 | sizingPolicy: SizingPolicy, 25 | specOverrideList: List> 26 | ): JComponent { 27 | return MonolithicSkiaAwt.buildPlotFromProcessedSpecs( 28 | plotSpec = processedSpec, 29 | containerSize = containerSize?.let { DoubleVector(it.width.toDouble(), it.height.toDouble()) }, 30 | sizingPolicy = sizingPolicy, 31 | computationMessagesHandler = computationMessagesHandler 32 | ) 33 | } 34 | 35 | companion object { 36 | private val DUMMY_SVG_COMPONENT_FACTORY = { _: SvgSvgElement -> 37 | UNSUPPORTED("This component factory should not be invoked.") 38 | } 39 | private val DUMMY_EXECUTOR: (() -> Unit) -> Unit = { 40 | UNSUPPORTED("This 'executor' should not be invoked.") 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /lets-plot-swing-skia/src/main/kotlin/org/jetbrains/letsPlot/skia/swing/PlotPanelSkiaSwing.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.swing 7 | 8 | import org.jetbrains.letsPlot.awt.plot.component.PlotPanel 9 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 10 | import org.jetbrains.letsPlot.skia.swing.AwtAppEnv.AWT_APP_CONTEXT 11 | 12 | open class PlotPanelSkiaSwing( 13 | processedSpec: MutableMap, 14 | preserveAspectRatio: Boolean, 15 | preferredSizeFromPlot: Boolean, 16 | repaintDelay: Int, // ms, 17 | computationMessagesHandler: (List) -> Unit 18 | ) : PlotPanel( 19 | plotComponentProvider = PlotComponentProviderSkiaSwing( 20 | processedSpec = processedSpec, 21 | computationMessagesHandler = computationMessagesHandler 22 | ), 23 | preferredSizeFromPlot = preferredSizeFromPlot, 24 | sizingPolicy = SizingPolicy.fitContainerSize(preserveAspectRatio), 25 | repaintDelay = repaintDelay, 26 | applicationContext = AWT_APP_CONTEXT, 27 | ) -------------------------------------------------------------------------------- /local.properties.template: -------------------------------------------------------------------------------- 1 | # Repositories where other projects publish their artifacts locally to. 2 | #maven.repo.local=path1, path1, ... 3 | 4 | # Android SDK 5 | #sdk.dir=/Users//Library/Android/sdk 6 | 7 | # ------------------------------------------------------------------- 8 | # Sonatype settings 9 | # 10 | # User with deployment permission to Sonatype Nexus repository. 11 | # 12 | # In addition, each publication must be signed using a PGP key. 13 | # Create file "~/.gradle/gradle.properties": 14 | # signing.keyId=your_keyid_here (short) 15 | # signing.password=your_password_here 16 | # signing.secretKeyRingFile=/Users/you/.gnupg/secring.kbx 17 | # ------------------------------------------------------------------- 18 | #sonatype.username= 19 | #sonatype.password= 20 | 21 | -------------------------------------------------------------------------------- /platf-skia-awt/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | plugins { 7 | kotlin("jvm") 8 | `maven-publish` 9 | signing 10 | } 11 | 12 | val skikoVersion = extra["skiko.version"] as String 13 | val letsPlotVersion = extra["letsPlot.version"] as String 14 | 15 | dependencies { 16 | compileOnly("org.jetbrains.skiko:skiko:$skikoVersion") 17 | 18 | compileOnly(project(":platf-skia")) 19 | 20 | compileOnly("org.jetbrains.lets-plot:lets-plot-common:$letsPlotVersion") 21 | compileOnly("org.jetbrains.lets-plot:platf-awt:$letsPlotVersion") 22 | 23 | testImplementation(kotlin("test")) 24 | } 25 | 26 | // Create publication 27 | java { 28 | withSourcesJar() 29 | } 30 | 31 | publishing { 32 | publications { 33 | create("mavenJava") { 34 | from(components["java"]) 35 | } 36 | } 37 | } 38 | 39 | signing { 40 | if (!(project.version as String).contains("SNAPSHOT")) { 41 | sign(publishing.publications) 42 | } 43 | } -------------------------------------------------------------------------------- /platf-skia-awt/src/main/kotlin/org/jetbrains/letsPlot/skia/awt/builderHW/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.awt.builderHW 7 | 8 | import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle 9 | import org.jetbrains.letsPlot.commons.geometry.Rectangle 10 | 11 | internal fun toAwtRect(from: DoubleRectangle): java.awt.Rectangle { 12 | return java.awt.Rectangle( 13 | from.origin.x.toInt(), 14 | from.origin.y.toInt(), 15 | (from.dimension.x + 0.5).toInt(), 16 | (from.dimension.y + 0.5).toInt() 17 | ) 18 | } 19 | 20 | internal fun toAwtRect(from: Rectangle): java.awt.Rectangle { 21 | return java.awt.Rectangle( 22 | from.origin.x, 23 | from.origin.y, 24 | from.dimension.x, 25 | from.dimension.y 26 | ) 27 | } 28 | 29 | internal fun fromDoubleRect(from: DoubleRectangle): Rectangle { 30 | return Rectangle( 31 | from.origin.x.toInt(), 32 | from.origin.y.toInt(), 33 | (from.dimension.x + 0.5).toInt(), 34 | (from.dimension.y + 0.5).toInt() 35 | ) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /platf-skia-awt/src/main/kotlin/org/jetbrains/letsPlot/skia/awt/view/SvgSkikoViewAwt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.awt.view 7 | 8 | import org.jetbrains.letsPlot.awt.util.AwtEventUtil.translate 9 | import org.jetbrains.letsPlot.commons.event.MouseEventSpec.* 10 | import org.jetbrains.letsPlot.skia.view.SvgSkikoView 11 | import org.jetbrains.skiko.SkiaLayer 12 | import java.awt.Desktop 13 | import java.awt.Dimension 14 | import java.awt.event.* 15 | 16 | 17 | internal class SvgSkikoViewAwt : SvgSkikoView() { 18 | override fun updateSkiaLayerSize(width: Int, height: Int) { 19 | skiaLayer.preferredSize = Dimension(width, height) 20 | } 21 | 22 | override fun createSkiaLayer(view: SvgSkikoView): SkiaLayer { 23 | return SkiaLayer().also { 24 | it.renderDelegate = view 25 | it.addMouseListener(object : MouseListener { 26 | override fun mouseClicked(e: MouseEvent) { 27 | val event = when (e.clickCount) { 28 | 1 -> MOUSE_CLICKED 29 | 2 -> MOUSE_DOUBLE_CLICKED 30 | else -> return 31 | } 32 | 33 | onMouseEvent(event, translate(e)) 34 | } 35 | override fun mousePressed(e: MouseEvent) { onMouseEvent(MOUSE_PRESSED, translate(e)) } 36 | override fun mouseReleased(e: MouseEvent) { onMouseEvent(MOUSE_RELEASED, translate(e)) } 37 | override fun mouseEntered(e: MouseEvent) { onMouseEvent(MOUSE_ENTERED, translate(e)) } 38 | override fun mouseExited(e: MouseEvent) { onMouseEvent(MOUSE_LEFT, translate(e)) } 39 | }) 40 | it.addMouseMotionListener(object : MouseMotionListener { 41 | override fun mouseDragged(e: MouseEvent) { onMouseEvent(MOUSE_DRAGGED, translate(e)) } 42 | override fun mouseMoved(e: MouseEvent) { onMouseEvent(MOUSE_MOVED, translate(e)) } 43 | }) 44 | it.addMouseWheelListener(object : MouseWheelListener { 45 | override fun mouseWheelMoved(e: MouseWheelEvent) { onMouseEvent(MOUSE_WHEEL_ROTATED, translate(e)) } 46 | }) 47 | } 48 | } 49 | 50 | override fun onHrefClick(href: String) { 51 | Desktop.getDesktop().browse(java.net.URI(href)) 52 | } 53 | } -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/arc_transform_after_restore.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/arc_transform_after_restore.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/bezier_curve_inside_path.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/bezier_curve_inside_path.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/circle_fill.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/circle_fill.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/circle_fill_stroke.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/circle_fill_stroke.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/circle_stroke.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/circle_stroke.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/clip_after_transform.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/clip_after_transform.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/clip_and_fill.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/clip_and_fill.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/clip_before_transform.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/clip_before_transform.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/clip_path.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/clip_path.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/clip_restore.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/clip_restore.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/ellipse.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/ellipse.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/ellipse_inside_path.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/ellipse_inside_path.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/ellipse_inside_path_diff.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/ellipse_inside_path_diff.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/multi_path_fill.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/multi_path_fill.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/multi_path_stroke.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/multi_path_stroke.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/nested_translates.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/nested_translates.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/path_transform_on_build.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/path_transform_on_build.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/rotated_ellipse.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/rotated_ellipse.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/rounded_rect_with_curves.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/rounded_rect_with_curves.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/sheared_circular_arc.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/sheared_circular_arc.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/sheared_ellipse.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/sheared_ellipse.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/simple_bezier_curve.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/simple_bezier_curve.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/simple_clip_path.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/simple_clip_path.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/text_skew_transform.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/text_skew_transform.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/zigzag_fill.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/zigzag_fill.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidInstrumentedTest/resources/zigzag_stroke.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/lets-plot-skia/22d4bc33aabe7693aa1ee46bc1f1dad1932fb546/platf-skia/src/androidInstrumentedTest/resources/zigzag_stroke.bmp -------------------------------------------------------------------------------- /platf-skia/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /platf-skia/src/androidMain/kotlin/org/jetbrains/letsPlot/android/canvas/AndroidAnimationTimerPeer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.android.canvas 7 | 8 | 9 | import org.jetbrains.letsPlot.commons.registration.Disposable 10 | import java.util.* 11 | 12 | 13 | internal class AndroidAnimationTimerPeer( 14 | val executor: (() -> Unit) -> Unit, 15 | private val updateRate: Int = 60 16 | ) : Disposable { 17 | private val myHandlers = ArrayList<(Long) -> Unit>() 18 | private var isRunning = false 19 | 20 | private val actionListener = object : TimerTask() { 21 | override fun run() { 22 | myHandlers.forEach { 23 | executor { 24 | it(System.currentTimeMillis()) 25 | } 26 | } 27 | } 28 | } 29 | 30 | private val myTimer: Timer = Timer() 31 | 32 | fun addHandler(handler: (Long) -> Unit) { 33 | synchronized(myHandlers) { 34 | myHandlers.add(handler) 35 | 36 | if (!isRunning) { 37 | isRunning = true 38 | myTimer.schedule(actionListener, 0L, 1000L / updateRate) 39 | } 40 | } 41 | } 42 | 43 | fun removeHandler(handler: (Long) -> Unit) { 44 | synchronized(myHandlers) { 45 | myHandlers.remove(handler) 46 | 47 | if (myHandlers.isEmpty() && isRunning) { 48 | myTimer.cancel() 49 | isRunning = false 50 | } 51 | } 52 | } 53 | 54 | override fun dispose() { 55 | isRunning = false 56 | myTimer.cancel() 57 | myHandlers.clear() 58 | } 59 | } -------------------------------------------------------------------------------- /platf-skia/src/androidMain/kotlin/org/jetbrains/letsPlot/android/canvas/AndroidCanvas.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.android.canvas 7 | 8 | import android.graphics.Bitmap 9 | import org.jetbrains.letsPlot.commons.geometry.Vector 10 | import org.jetbrains.letsPlot.commons.intern.async.Async 11 | import org.jetbrains.letsPlot.commons.intern.async.Asyncs 12 | import org.jetbrains.letsPlot.core.canvas.Canvas 13 | import org.jetbrains.letsPlot.core.canvas.Context2d 14 | import kotlin.math.roundToInt 15 | 16 | class AndroidCanvas( 17 | val bitmap: Bitmap, 18 | override val size: Vector, 19 | pixelDensity: Double 20 | ) : Canvas { 21 | companion object { 22 | fun create(size: Vector, pixelDensity: Double): AndroidCanvas { 23 | val w = (size.x * pixelDensity).roundToInt().coerceAtLeast(1) 24 | val h = (size.y * pixelDensity).roundToInt().coerceAtLeast(1) 25 | 26 | val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) 27 | 28 | return AndroidCanvas(bitmap, Vector(w, h), pixelDensity) 29 | } 30 | } 31 | 32 | override val context2d: Context2d = AndroidContext2d(bitmap, pixelDensity) 33 | 34 | override fun immidiateSnapshot(): Canvas.Snapshot { 35 | return AndroidSnapshot(bitmap.copy(this.bitmap.config, false)) 36 | } 37 | 38 | override fun takeSnapshot(): Async { 39 | return Asyncs.constant(immidiateSnapshot()) 40 | } 41 | 42 | class AndroidSnapshot( 43 | val bitmap: Bitmap 44 | ) : Canvas.Snapshot { 45 | override fun copy(): Canvas.Snapshot { 46 | val newBitmap = bitmap.copy(bitmap.config, false) 47 | return AndroidSnapshot(newBitmap) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /platf-skia/src/androidMain/kotlin/org/jetbrains/letsPlot/android/canvas/SizeConverter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.android.canvas 7 | 8 | import android.content.Context 9 | import android.content.res.Resources 10 | import android.util.TypedValue 11 | import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle 12 | import org.jetbrains.letsPlot.commons.geometry.Rectangle 13 | 14 | internal object SizeConverter { 15 | 16 | fun boundsPxToDp(from: DoubleRectangle, ctx: Context): Rectangle { 17 | val x: Int = pxToDp(from.origin.x, ctx).toInt() 18 | val y: Int = pxToDp(from.origin.y, ctx).toInt() 19 | val w: Int = (pxToDp(from.width, ctx) + 0.5).toInt() 20 | val h: Int = (pxToDp(from.height, ctx) + 0.5).toInt() 21 | return Rectangle(x, y, w, h) 22 | } 23 | 24 | fun pxToDp(v: Number, ctx: Context): Float { 25 | return TypedValue.applyDimension( 26 | TypedValue.COMPLEX_UNIT_DIP, // unit 27 | v.toFloat(), // value 28 | ctx.resources.displayMetrics // metrics 29 | ) 30 | } 31 | 32 | fun dpToPx(dipValue: Float): Float { 33 | return TypedValue.applyDimension( 34 | TypedValue.COMPLEX_UNIT_DIP, 35 | dipValue, 36 | Resources.getSystem().displayMetrics 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /platf-skia/src/androidMain/kotlin/org/jetbrains/letsPlot/android/canvas/Utils.kt: -------------------------------------------------------------------------------- 1 | package org.jetbrains.letsPlot.android.canvas 2 | 3 | import org.jetbrains.letsPlot.commons.values.Color 4 | 5 | object Utils { 6 | internal fun Color.toAndroidColor() = android.graphics.Color.argb(alpha, red, green, blue) 7 | } 8 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/builderLW/CompositeFigureEventDispatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.builderLW 7 | 8 | import org.jetbrains.letsPlot.commons.event.MouseEvent 9 | import org.jetbrains.letsPlot.commons.event.MouseEventSpec 10 | import org.jetbrains.letsPlot.commons.event.MouseWheelEvent 11 | import org.jetbrains.letsPlot.commons.geometry.Rectangle 12 | import org.jetbrains.letsPlot.commons.geometry.Vector 13 | import org.jetbrains.letsPlot.commons.intern.observable.event.EventHandler 14 | import org.jetbrains.letsPlot.commons.registration.Registration 15 | import org.jetbrains.letsPlot.skia.view.SkikoViewEventDispatcher 16 | 17 | class CompositeFigureEventDispatcher() : SkikoViewEventDispatcher { 18 | private val dispatchers = LinkedHashMap() 19 | 20 | fun addEventDispatcher(bounds: Rectangle, eventDispatcher: SkikoViewEventDispatcher) { 21 | dispatchers[bounds] = eventDispatcher 22 | } 23 | 24 | override fun dispatchMouseEvent(kind: MouseEventSpec, e: MouseEvent) { 25 | val loc = Vector(e.x, e.y) 26 | val (figureBounds, dispatcher) = dispatchers.entries.firstOrNull { (bounds, _) -> loc in bounds } ?: return 27 | val xInFigure = loc.x - figureBounds.origin.x 28 | val yInFigure = loc.y - figureBounds.origin.y 29 | 30 | val eventInFigure = when (e) { 31 | is MouseWheelEvent -> MouseWheelEvent(xInFigure, yInFigure, e.button, e.modifiers, e.scrollAmount) 32 | else -> MouseEvent(xInFigure, yInFigure, e.button, e.modifiers) 33 | } 34 | 35 | dispatcher.dispatchMouseEvent(kind, eventInFigure) 36 | } 37 | 38 | override fun addEventHandler(eventSpec: MouseEventSpec, eventHandler: EventHandler): Registration { 39 | // Looks like composite figures in Compose don't need this method because they are all placed in the same 40 | // container and this container is a dispatcher for all of them. 41 | return Registration.EMPTY 42 | } 43 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/builderLW/MonolithicSkiaLW.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.builderLW 7 | 8 | import org.jetbrains.letsPlot.commons.geometry.DoubleVector 9 | import org.jetbrains.letsPlot.core.interact.event.UnsupportedToolEventDispatcher 10 | import org.jetbrains.letsPlot.core.util.MonolithicCommon 11 | import org.jetbrains.letsPlot.core.util.sizing.SizingPolicy 12 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 13 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextElement 14 | 15 | /** 16 | * "lightweight" - no JComponents or Views are created here. 17 | */ 18 | object MonolithicSkiaLW { 19 | fun buildPlotFromProcessedSpecs( 20 | plotSpec: MutableMap, 21 | containerSize: DoubleVector?, 22 | sizingPolicy: SizingPolicy, 23 | computationMessagesHandler: (List) -> Unit 24 | ): ViewModel { 25 | val buildResult = MonolithicCommon.buildPlotsFromProcessedSpecs(plotSpec, containerSize, sizingPolicy) 26 | if (buildResult is MonolithicCommon.PlotsBuildResult.Error) { 27 | return SimpleModel(createErrorSvgText(buildResult.error), UnsupportedToolEventDispatcher()) 28 | } 29 | 30 | val success = buildResult as MonolithicCommon.PlotsBuildResult.Success 31 | computationMessagesHandler(success.buildInfo.computationMessages) 32 | 33 | return FigureToViewModel.eval(success.buildInfo) 34 | } 35 | 36 | private fun createErrorSvgText(s: String): SvgSvgElement { 37 | return SvgSvgElement().apply { 38 | children().add( 39 | SvgTextElement(s) 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/DebugOptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.letsPlot.commons.values.Color 9 | import org.jetbrains.letsPlot.skia.shape.* 10 | import org.jetbrains.skia.Canvas 11 | import org.jetbrains.skia.Paint 12 | import org.jetbrains.skia.PathEffect 13 | 14 | internal object DebugOptions { 15 | const val DEBUG_DRAWING_ENABLED: Boolean = false 16 | 17 | fun drawBoundingBoxes(rootElement: Pane, canvas: Canvas) { 18 | val strokePaint = Paint().setStroke(true) 19 | val fillPaint = Paint().setStroke(false) 20 | 21 | depthFirstTraversal(rootElement).forEach { el -> 22 | val color = when (el) { 23 | is Pane -> Color.CYAN 24 | is Group -> Color.YELLOW 25 | is Text -> Color.GREEN 26 | is TSpan -> Color.GREEN 27 | is Rectangle -> Color.BLUE 28 | is Circle -> Color.RED 29 | is Line -> Color.RED 30 | else -> Color.LIGHT_GRAY 31 | }.asSkiaColor 32 | 33 | fillPaint.color = color.withA(0.02f).toColor() 34 | canvas.drawRect(el.screenBounds, fillPaint) 35 | 36 | strokePaint.color = color.withA(0.7f).toColor() 37 | strokePaint.strokeWidth = if(el is Container) 3f else 1f 38 | strokePaint.pathEffect = if (el is Container) PathEffect.makeDash(floatArrayOf(3f, 8f), 0f) else null 39 | canvas.drawRect(el.screenBounds, strokePaint) 40 | } 41 | 42 | strokePaint.close() 43 | fillPaint.close() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/FontManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.skia.Font 9 | import org.jetbrains.skia.FontMgr 10 | import org.jetbrains.skia.FontStyle 11 | import org.jetbrains.skia.Typeface 12 | 13 | class FontManager { 14 | fun matchFamiliesStyle(fontFamily: List, fontStyle: FontStyle): Typeface { 15 | val fontConfig = fontFamily to fontStyle 16 | 17 | if (fontConfig in typefaceCache) { 18 | return typefaceCache.getValue(fontConfig) 19 | } 20 | 21 | var typeface = fontFamily.firstNotNullOfOrNull { 22 | FontMgr.default.matchFamilyStyle(it, fontStyle) 23 | } 24 | 25 | if (typeface == null || typeface.familyName == "") { 26 | typeface = fontFamily.firstNotNullOfOrNull { 27 | FontMgr.default.legacyMakeTypeface(it, fontStyle) 28 | } 29 | } 30 | 31 | if (typeface == null || typeface.familyName == "") { 32 | typeface = FontMgr.default.legacyMakeTypeface("sans-serif", fontStyle) 33 | } 34 | 35 | if (typeface == null || typeface.familyName == "") { 36 | println("Font not found: [${fontFamily.joinToString()}]") 37 | typeface = Typeface.makeEmpty() 38 | } 39 | 40 | typefaceCache[fontConfig] = typeface 41 | return typeface 42 | } 43 | 44 | fun font(typeface: Typeface, fontSize: Float): Font { 45 | return fontCache 46 | .getOrPut(typeface to fontSize) { 47 | Font(typeface, fontSize).apply { 48 | isSubpixel = true 49 | } 50 | } 51 | } 52 | 53 | fun dispose() { 54 | typefaceCache.values.forEach(Typeface::close) 55 | typefaceCache.clear() 56 | fontCache.values.forEach(Font::close) 57 | fontCache.clear() 58 | } 59 | 60 | private val typefaceCache = mutableMapOf, FontStyle>, Typeface>() 61 | private val fontCache = mutableMapOf, Font>() 62 | } 63 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SkiaTargetPeer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.letsPlot.commons.registration.Registration 9 | import org.jetbrains.letsPlot.datamodel.mapping.svg.shared.TargetPeer 10 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgElement 11 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextNode 12 | import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimElements 13 | import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimNode 14 | import org.jetbrains.letsPlot.datamodel.svg.event.SvgEventSpec 15 | import org.jetbrains.letsPlot.skia.mapping.svg.SvgUtils.getChildren 16 | import org.jetbrains.letsPlot.skia.mapping.svg.SvgUtils.newElement 17 | import org.jetbrains.letsPlot.skia.shape.* 18 | 19 | internal class SkiaTargetPeer( 20 | private val peer: SvgSkiaPeer 21 | ) : TargetPeer { 22 | override fun appendChild(target: Element, child: Element) { 23 | getChildren(target as Group).add(child) 24 | } 25 | 26 | override fun removeAllChildren(target: Element) { 27 | if (target is Group) { 28 | getChildren(target).clear() 29 | } 30 | } 31 | 32 | override fun newSvgElement(source: SvgElement): Element { 33 | return newElement(source, peer) 34 | } 35 | 36 | override fun newSvgTextNode(source: SvgTextNode): Element { 37 | TODO() // return Text(source.textContent().get()) 38 | } 39 | 40 | override fun newSvgSlimNode(source: SvgSlimNode): Element { 41 | return when (source.elementName) { 42 | SvgSlimElements.GROUP -> Group() 43 | SvgSlimElements.LINE -> Line() 44 | SvgSlimElements.CIRCLE -> Circle() 45 | SvgSlimElements.RECT -> Rectangle() 46 | SvgSlimElements.PATH -> Path() 47 | else -> throw IllegalStateException("Unsupported slim node " + source::class.simpleName + " '" + source.elementName + "'") 48 | } 49 | } 50 | 51 | override fun setAttribute(target: Element, name: String, value: String) { 52 | SvgUtils.setAttribute(target, name, value) 53 | } 54 | 55 | override fun hookEventHandlers(source: SvgElement, target: Element, eventSpecs: Set): Registration { 56 | error("UNSUPPORTED: hookEventHandlers") 57 | } 58 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SvgElementMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.letsPlot.commons.registration.Registration 9 | import org.jetbrains.letsPlot.datamodel.mapping.framework.Mapper 10 | import org.jetbrains.letsPlot.datamodel.mapping.framework.Synchronizer 11 | import org.jetbrains.letsPlot.datamodel.mapping.framework.SynchronizerContext 12 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgElement 13 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgElementListener 14 | import org.jetbrains.letsPlot.datamodel.svg.event.SvgAttributeEvent 15 | import org.jetbrains.letsPlot.datamodel.svg.event.SvgEventSpec 16 | import org.jetbrains.letsPlot.skia.shape.Element 17 | 18 | internal open class SvgElementMapper( 19 | source: SourceT, 20 | target: TargetT, 21 | peer: SvgSkiaPeer 22 | ) : SvgNodeMapper(source, target, peer) { 23 | 24 | private var myHandlerRegs: MutableMap? = null 25 | 26 | open fun setTargetAttribute(name: String, value: Any?) { 27 | SvgUtils.setAttribute(target, name, value) 28 | } 29 | 30 | open fun applyStyle() {} 31 | 32 | override fun registerSynchronizers(conf: Mapper.SynchronizersConfiguration) { 33 | super.registerSynchronizers(conf) 34 | 35 | conf.add(object : Synchronizer { 36 | private var myReg: Registration? = null 37 | 38 | override fun attach(ctx: SynchronizerContext) { 39 | applyStyle() 40 | 41 | myReg = source.addListener(object : SvgElementListener { 42 | override fun onAttrSet(event: SvgAttributeEvent<*>) { 43 | setTargetAttribute(event.attrSpec.name, event.newValue) 44 | } 45 | }) 46 | 47 | for (key in source.attributeKeys) { 48 | val name = key.name 49 | val value = source.getAttribute(name).get() 50 | setTargetAttribute(name, value) 51 | } 52 | } 53 | 54 | override fun detach() { 55 | myReg!!.remove() 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SvgGElementMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.letsPlot.datamodel.mapping.framework.Synchronizers 9 | import org.jetbrains.letsPlot.datamodel.mapping.svg.shared.SvgNodeSubtreeGeneratingSynchronizer 10 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgGElement 11 | import org.jetbrains.letsPlot.skia.shape.Group 12 | 13 | internal class SvgGElementMapper( 14 | source: SvgGElement, 15 | target: Group, 16 | peer: SvgSkiaPeer 17 | ) : SvgElementMapper(source, target, peer) { 18 | 19 | override fun registerSynchronizers(conf: SynchronizersConfiguration) { 20 | super.registerSynchronizers(conf) 21 | 22 | if (!source.isPrebuiltSubtree) { 23 | val targetList = SvgUtils.elementChildren(target) 24 | conf.add( 25 | Synchronizers.forObservableRole( 26 | this, 27 | source.children(), 28 | targetList, 29 | SvgNodeMapperFactory(peer) 30 | ) 31 | ) 32 | } else { 33 | // UNSUPPORTED("isPrebuiltSubtree") 34 | conf.add( 35 | SvgNodeSubtreeGeneratingSynchronizer( 36 | source, 37 | target, 38 | SkiaTargetPeer(peer) 39 | ) 40 | ) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SvgImageElementMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgImageElement 9 | import org.jetbrains.letsPlot.skia.mapping.svg.attr.SvgImageAttrMapping 10 | import org.jetbrains.letsPlot.skia.shape.Image 11 | 12 | internal class SvgImageElementMapper( 13 | source: SvgImageElement, 14 | target: Image, 15 | peer: SvgSkiaPeer 16 | ) : SvgElementMapper(source, target, peer) { 17 | 18 | private val myImageViewAttrSupport = ImageViewAttributesSupport(target) 19 | 20 | override fun setTargetAttribute(name: String, value: Any?) { 21 | myImageViewAttrSupport.setAttribute(name, value) 22 | } 23 | 24 | 25 | private class ImageViewAttributesSupport(val target: Image) { 26 | private var myImageBytes: ByteArray? = null 27 | 28 | init { 29 | // target.preserveRatio.addListener(object : ChangeListener { 30 | // override fun changed( 31 | // observable: ObservableValue?, 32 | // oldValue: Boolean?, 33 | // newValue: Boolean? 34 | // ) { 35 | // SvgImageAttrMapping.updateTargetImage(target, myImageBytes) 36 | // } 37 | // }) 38 | } 39 | 40 | fun setAttribute(name: String, value: Any?) { 41 | if (name == SvgImageElement.HREF.name) { 42 | myImageBytes = SvgImageAttrMapping.setHrefDataUrl(target, value as String) 43 | return 44 | } 45 | 46 | // set default 47 | SvgImageAttrMapping.setAttribute(target, name, value) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SvgNodeMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | 9 | import org.jetbrains.letsPlot.datamodel.mapping.framework.Mapper 10 | import org.jetbrains.letsPlot.datamodel.mapping.framework.MappingContext 11 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgNode 12 | import org.jetbrains.letsPlot.skia.shape.Element 13 | 14 | internal open class SvgNodeMapper( 15 | source: SourceT, 16 | target: TargetT, 17 | protected val peer: SvgSkiaPeer 18 | ) : Mapper(source, target) { 19 | 20 | override fun onAttach(ctx: MappingContext) { 21 | super.onAttach(ctx) 22 | 23 | peer.registerMapper(source, this) 24 | } 25 | 26 | override fun onDetach() { 27 | super.onDetach() 28 | 29 | peer.unregisterMapper(source) 30 | } 31 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SvgStyleElementMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgStyleElement 9 | import org.jetbrains.letsPlot.datamodel.svg.style.StyleSheet 10 | import org.jetbrains.letsPlot.datamodel.svg.style.TextStyle.Companion.NONE_FAMILY 11 | import org.jetbrains.letsPlot.datamodel.svg.style.TextStyle.Companion.NONE_SIZE 12 | import org.jetbrains.letsPlot.skia.shape.Group 13 | 14 | internal class SvgStyleElementMapper( 15 | source: SvgStyleElement, 16 | target: Group, 17 | peer: SvgSkiaPeer, 18 | ) : SvgElementMapper(source, target, peer) { 19 | 20 | override fun registerSynchronizers(conf: SynchronizersConfiguration) { 21 | val styleSheet = StyleSheet.fromCSS( 22 | css = source.resource.css(), 23 | defaultFamily = NONE_FAMILY, 24 | defaultSize = NONE_SIZE 25 | ) 26 | peer.applyStyleSheet(styleSheet) 27 | } 28 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/SvgSvgElementMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | 7 | package org.jetbrains.letsPlot.skia.mapping.svg 8 | 9 | import org.jetbrains.letsPlot.datamodel.mapping.framework.MappingContext 10 | import org.jetbrains.letsPlot.datamodel.mapping.framework.Synchronizers 11 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 12 | import org.jetbrains.letsPlot.skia.shape.Pane 13 | 14 | internal class SvgSvgElementMapper( 15 | source: SvgSvgElement, 16 | peer: SvgSkiaPeer 17 | ) : SvgElementMapper(source, createTargetContainer(), peer) { 18 | 19 | override fun registerSynchronizers(conf: SynchronizersConfiguration) { 20 | super.registerSynchronizers(conf) 21 | 22 | val targetList = SvgUtils.elementChildren(target) 23 | conf.add( 24 | Synchronizers.forObservableRole( 25 | this, 26 | source.children(), 27 | targetList, 28 | SvgNodeMapperFactory(peer) 29 | ) 30 | ) 31 | } 32 | 33 | override fun onAttach(ctx: MappingContext) { 34 | super.onAttach(ctx) 35 | 36 | if (!source.isAttached()) { 37 | throw IllegalStateException("Element must be attached") 38 | } 39 | source.container().setPeer(peer) 40 | } 41 | 42 | override fun onDetach() { 43 | if (source.isAttached()) { 44 | source.container().setPeer(null) 45 | } 46 | super.onDetach() 47 | } 48 | 49 | companion object { 50 | private fun createTargetContainer(): Pane { 51 | return Pane() 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgCircleAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgCircleElement 9 | import org.jetbrains.letsPlot.skia.shape.Circle 10 | 11 | internal object SvgCircleAttrMapping : SvgShapeMapping() { 12 | override fun setAttribute(target: Circle, name: String, value: Any?) { 13 | when (name) { 14 | SvgCircleElement.CX.name -> target.centerX = value?.asFloat ?: 0.0f 15 | SvgCircleElement.CY.name -> target.centerY = value?.asFloat ?: 0.0f 16 | SvgCircleElement.R.name -> target.radius = value?.asFloat ?: 0.0f 17 | else -> super.setAttribute(target, name, value) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgEllipseAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgEllipseElement 9 | import org.jetbrains.letsPlot.skia.shape.Ellipse 10 | 11 | internal object SvgEllipseAttrMapping : SvgShapeMapping() { 12 | override fun setAttribute(target: Ellipse, name: String, value: Any?) { 13 | when (name) { 14 | SvgEllipseElement.CX.name -> target.centerX = value?.asFloat ?: 0.0f 15 | SvgEllipseElement.CY.name -> target.centerY = value?.asFloat ?: 0.0f 16 | SvgEllipseElement.RX.name -> target.radiusX = value?.asFloat ?: 0.0f 17 | SvgEllipseElement.RY.name -> target.radiusY = value?.asFloat ?: 0.0f 18 | else -> super.setAttribute(target, name, value) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgGAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.skia.shape.Group 9 | 10 | 11 | internal object SvgGAttrMapping : SvgAttrMapping() { 12 | // no group-specific attributes 13 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgImageAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.commons.encoding.Base64 9 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgImageElement 10 | import org.jetbrains.letsPlot.skia.shape.Image 11 | 12 | internal object SvgImageAttrMapping : SvgAttrMapping() { 13 | override fun setAttribute(target: Image, name: String, value: Any?) { 14 | when (name) { 15 | SvgImageElement.X.name -> target.x = value?.asFloat ?: 0.0f 16 | SvgImageElement.Y.name -> target.y = value?.asFloat ?: 0.0f 17 | SvgImageElement.WIDTH.name -> target.width = value?.asFloat ?: 0.0f 18 | SvgImageElement.HEIGHT.name -> target.height = value?.asFloat ?: 0.0f 19 | SvgImageElement.PRESERVE_ASPECT_RATIO.name -> target.preserveRatio = asBoolean(value) 20 | SvgImageElement.HREF.name -> setHrefDataUrl(target, value as String) 21 | else -> super.setAttribute(target, name, value) 22 | } 23 | } 24 | 25 | fun setHrefDataUrl(target: Image, dataUrl: String): ByteArray { 26 | val imageData = dataUrl.split(",")[1] 27 | val imageBytes = Base64.decode(imageData) 28 | updateTargetImage(target, imageBytes) 29 | return imageBytes 30 | } 31 | 32 | private fun updateTargetImage(target: Image, imageBytes: ByteArray) { 33 | target.img = org.jetbrains.skia.Image.makeFromEncoded(imageBytes) 34 | } 35 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgLineAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgLineElement 9 | import org.jetbrains.letsPlot.skia.shape.Line 10 | 11 | internal object SvgLineAttrMapping : SvgShapeMapping() { 12 | override fun setAttribute(target: Line, name: String, value: Any?) { 13 | when (name) { 14 | SvgLineElement.X1.name -> target.x0 = value?.asFloat ?: 0.0f 15 | SvgLineElement.Y1.name -> target.y0 = value?.asFloat ?: 0.0f 16 | SvgLineElement.X2.name -> target.x1 = value?.asFloat ?: 0.0f 17 | SvgLineElement.Y2.name -> target.y1 = value?.asFloat ?: 0.0f 18 | else -> super.setAttribute(target, name, value) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgPathAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgPathData 9 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgPathElement 10 | import org.jetbrains.letsPlot.skia.shape.Path 11 | import org.jetbrains.skia.Path.Companion.makeFromSVGString 12 | 13 | internal object SvgPathAttrMapping : SvgShapeMapping() { 14 | override fun setAttribute(target: Path, name: String, value: Any?) { 15 | when (name) { 16 | SvgPathElement.STROKE_MITER_LIMIT.name -> target.strokeMiter = value?.asFloat 17 | 18 | SvgPathElement.FILL_RULE.name -> { 19 | val fillRule = when (value) { 20 | SvgPathElement.FillRule.NON_ZERO -> org.jetbrains.skia.PathFillMode.WINDING 21 | SvgPathElement.FillRule.EVEN_ODD -> org.jetbrains.skia.PathFillMode.EVEN_ODD 22 | null -> null 23 | else -> throw IllegalArgumentException("Unknown fill-rule: $value") 24 | } 25 | 26 | target.fillRule = fillRule 27 | } 28 | 29 | SvgPathElement.D.name -> { 30 | // Can be string (slim path) or SvgPathData 31 | val pathStr = when (value) { 32 | is String -> value 33 | is SvgPathData -> value.toString() 34 | null -> throw IllegalArgumentException("Undefined `path data`") 35 | else -> throw IllegalArgumentException("Unexpected `path data` type: ${value::class.simpleName}") 36 | } 37 | 38 | target.skiaPath = makeFromSVGString(pathStr) 39 | } 40 | else -> super.setAttribute(target, name, value) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgRectAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgRectElement 9 | import org.jetbrains.letsPlot.skia.shape.Rectangle 10 | 11 | internal object SvgRectAttrMapping : SvgShapeMapping() { 12 | override fun setAttribute(target: Rectangle, name: String, value: Any?) { 13 | when (name) { 14 | SvgRectElement.X.name -> target.x = value?.asFloat ?: 0.0f 15 | SvgRectElement.Y.name -> target.y = value?.asFloat ?: 0.0f 16 | SvgRectElement.WIDTH.name -> { 17 | if (!ignoredSizeValue(value)) { 18 | target.width = value?.asFloat ?: 0.0f 19 | } 20 | } 21 | 22 | SvgRectElement.HEIGHT.name -> { 23 | if (!ignoredSizeValue(value)) { 24 | target.height = value?.asFloat ?: 0.0f 25 | } 26 | } 27 | 28 | else -> super.setAttribute(target, name, value) 29 | } 30 | } 31 | 32 | private fun ignoredSizeValue(value: Any?): Boolean { 33 | // Do not fail on persentages, just ignore. 34 | return value is String && value.endsWith("%") 35 | } 36 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgShapeMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants 9 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgShape 10 | import org.jetbrains.letsPlot.skia.mapping.svg.SvgUtils.toColor 11 | import org.jetbrains.letsPlot.skia.shape.Figure 12 | 13 | internal abstract class SvgShapeMapping : SvgAttrMapping() { 14 | override fun setAttribute(target: TargetT, name: String, value: Any?) { 15 | when (name) { 16 | SvgShape.FILL.name -> target.fill = toColor(value) 17 | SvgShape.FILL_OPACITY.name -> target.fillOpacity = value!!.asFloat 18 | SvgShape.STROKE.name -> target.stroke = toColor(value) 19 | SvgShape.STROKE_OPACITY.name -> target.strokeOpacity = value!!.asFloat 20 | SvgShape.STROKE_WIDTH.name -> target.strokeWidth = value!!.asFloat 21 | SvgShape.STROKE_DASHOFFSET.name -> target.strokeDashOffset = value?.asFloat ?: 0f 22 | SvgConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE -> { 23 | val strokeDashArray = (value as String).split(",").map(String::toFloat) 24 | target.strokeDashArray = strokeDashArray 25 | } 26 | 27 | else -> super.setAttribute(target, name, value) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/mapping/svg/attr/SvgSvgAttrMapping.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.mapping.svg.attr 7 | 8 | import org.jetbrains.letsPlot.datamodel.svg.dom.SvgSvgElement 9 | import org.jetbrains.letsPlot.skia.shape.Pane 10 | import org.jetbrains.letsPlot.skia.shape.TRANSLATE_X 11 | import org.jetbrains.letsPlot.skia.shape.TRANSLATE_Y 12 | import org.jetbrains.letsPlot.skia.shape.with 13 | 14 | internal object SvgSvgAttrMapping : SvgAttrMapping() { 15 | 16 | override fun setAttribute(target: Pane, name: String, value: Any?) { 17 | when (name) { 18 | SvgSvgElement.X.name -> target.transform = target.transform.with(TRANSLATE_X, value?.asFloat ?: 0.0f) 19 | SvgSvgElement.Y.name -> target.transform = target.transform.with(TRANSLATE_Y, value?.asFloat ?: 0.0f) 20 | SvgSvgElement.WIDTH.name -> target.width = value?.asFloat ?: 0.0f 21 | SvgSvgElement.HEIGHT.name -> target.height = value?.asFloat ?: 0.0f 22 | "display" /*SvgSvgElement.DISPLAY.name*/ -> { } // ignore 23 | else -> super.setAttribute(target, name, value) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Circle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.Rect 10 | 11 | internal class Circle : Figure() { 12 | var centerX: Float by visualProp(0.0f) 13 | var centerY: Float by visualProp(0.0f) 14 | var radius: Float by visualProp(0.0f) 15 | 16 | override fun render(canvas: Canvas) { 17 | fillPaint?.let { 18 | canvas.drawCircle(centerX, centerY, radius, it) 19 | } 20 | strokePaint?.let { 21 | canvas.drawCircle(centerX, centerY, radius, it) 22 | } 23 | } 24 | 25 | override val localBounds: Rect 26 | get() = Rect.makeXYWH( 27 | centerX - radius, 28 | centerY - radius, 29 | radius * 2, 30 | radius * 2 31 | ) 32 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/ComputedProperty.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import kotlin.properties.ReadOnlyProperty 9 | import kotlin.reflect.KProperty 10 | 11 | internal class ComputedProperty( 12 | private val valueProvider: () -> T, 13 | private val onPropertyChanged: (KProperty<*>, T, T) -> Unit 14 | ) : ReadOnlyProperty { 15 | private var isDirty: Boolean = false 16 | private var value: T = valueProvider() 17 | private var computing = false 18 | 19 | fun invalidate() { 20 | if (computing) { 21 | return 22 | } 23 | isDirty = true 24 | } 25 | 26 | override fun getValue(thisRef: Any, property: KProperty<*>): T { 27 | if (isDirty) { 28 | computing = true 29 | 30 | isDirty = false 31 | val oldValue = value 32 | value = valueProvider() 33 | 34 | if (oldValue != value) { 35 | onPropertyChanged(property, oldValue, value) 36 | } 37 | 38 | computing = false 39 | } 40 | return value 41 | } 42 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Element.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.Matrix33 10 | import org.jetbrains.skia.Rect 11 | 12 | internal typealias SkPath = org.jetbrains.skia.Path 13 | 14 | internal abstract class Element() : Node() { 15 | var transform: Matrix33 by visualProp(Matrix33.IDENTITY) 16 | var styleClass: List? by visualProp(null) 17 | var clipPath: SkPath? by visualProp(null, managed = true) 18 | var parent: Container? by visualProp(null) 19 | var isMouseTransparent: Boolean = true // need proper hitTest for non-rectangular shapes for correct default "false" 20 | 21 | val parents: List by computedProp(Element::parent) { 22 | val parents = parent?.parents ?: emptyList() 23 | parents + listOfNotNull(parent) 24 | } 25 | 26 | // Not affected by org.jetbrains.skiko.SkiaLayer.getContentScale 27 | // (see org.jetbrains.letsPlot.skia.svg.view.SvgSkikoView.onRender) 28 | val ctm: Matrix33 by computedProp(Element::parent, Element::transform) { 29 | val parentCtm = parent?.ctm ?: Matrix33.IDENTITY 30 | parentCtm.makeConcat(transform) 31 | } 32 | 33 | open val localBounds: Rect = Rect.Companion.makeWH(0f, 0f) 34 | 35 | // Not affected by org.jetbrains.skiko.SkiaLayer.getContentScale 36 | // (see org.jetbrains.letsPlot.skia.svg.view.SvgSkikoView.onRender) 37 | open val screenBounds: Rect 38 | get() = ctm.apply(localBounds) 39 | 40 | open fun render(canvas: Canvas) {} 41 | 42 | override fun repr(): String? { 43 | return ", ctm: ${ctm.repr()}, $screenBounds" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Ellipse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.Rect 10 | 11 | internal class Ellipse : Figure() { 12 | var centerX: Float by visualProp(0.0f) 13 | var centerY: Float by visualProp(0.0f) 14 | var radiusX: Float by visualProp(0.0f) 15 | var radiusY: Float by visualProp(0.0f) 16 | 17 | private val rect: Rect by computedProp(Ellipse::centerX, Ellipse::centerY, Ellipse::radiusX, Ellipse::radiusY) { 18 | Rect( 19 | left = centerX - radiusX, 20 | top = centerY - radiusY, 21 | right = centerX + radiusX, 22 | bottom = centerY + radiusY 23 | ) 24 | } 25 | 26 | override fun render(canvas: Canvas) { 27 | fillPaint?.let { canvas.drawOval(rect, it) } 28 | strokePaint?.let { canvas.drawOval(rect, it) } 29 | } 30 | 31 | override val localBounds: Rect 32 | get() = Rect.makeXYWH(centerX - radiusX, centerY - radiusY, radiusX * 2, radiusY * 2) 33 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Figure.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Color4f 9 | import org.jetbrains.skia.Paint 10 | 11 | internal abstract class Figure : Element() { 12 | var stroke: Color4f? by visualProp(null) 13 | var strokeWidth: Float by visualProp(1f) 14 | var strokeOpacity: Float by visualProp(1f) 15 | var strokeDashArray: List? by visualProp(null) 16 | var strokeDashOffset: Float by visualProp(0f) 17 | var strokeMiter: Float? by visualProp(null) // not mandatory, default works fine 18 | 19 | var fill: Color4f? by visualProp(null) 20 | var fillOpacity: Float by visualProp(1f) 21 | 22 | val fillPaint: Paint? by computedProp(Figure::fill, Figure::fillOpacity, managed = true) { 23 | return@computedProp fillPaint(fill, fillOpacity) 24 | } 25 | 26 | val strokePaint: Paint? by computedProp( 27 | Figure::stroke, 28 | Figure::strokeWidth, 29 | Figure::strokeDashArray, 30 | Figure::strokeOpacity, 31 | Figure::strokeMiter, 32 | managed = true 33 | ) { 34 | return@computedProp strokePaint( 35 | stroke = stroke, 36 | strokeWidth = strokeWidth, 37 | strokeOpacity = strokeOpacity, 38 | strokeDashArray = strokeDashArray, 39 | strokeDashOffset = strokeDashOffset, 40 | strokeMiter = strokeMiter 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Group.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | internal class Group : Container() 9 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Image.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.Rect 10 | 11 | internal typealias SkImage = org.jetbrains.skia.Image 12 | 13 | internal class Image : Element() { 14 | var preserveRatio: Boolean by visualProp(false) 15 | var x: Float by visualProp(0.0f) 16 | var y: Float by visualProp(0.0f) 17 | var width: Float by visualProp(0.0f) 18 | var height: Float by visualProp(0.0f) 19 | var img: SkImage? by visualProp(null, managed = true) 20 | 21 | override fun render(canvas: Canvas) { 22 | img?.let { 23 | if (preserveRatio) { 24 | canvas.drawImage(it, x, y) 25 | } else { 26 | canvas.drawImageRect(it, Rect.makeXYWH(x, y, width, height)) 27 | } 28 | } 29 | } 30 | 31 | override val localBounds: Rect 32 | get() = Rect.makeXYWH(x, y, width, height) 33 | 34 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Line.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.Path 10 | import org.jetbrains.skia.Rect 11 | 12 | internal class Line : Figure() { 13 | var x0: Float by visualProp(0.0f) 14 | var y0: Float by visualProp(0.0f) 15 | var x1: Float by visualProp(0.0f) 16 | var y1: Float by visualProp(0.0f) 17 | 18 | override fun render(canvas: Canvas) { 19 | strokePaint?.let { canvas.drawLine(x0, y0, x1, y1, it) } 20 | } 21 | 22 | override val localBounds: Rect 23 | get() { 24 | // TODO: pref. Cache. 25 | val path = Path().moveTo(x0, y0).lineTo(x1, y1) 26 | // `paint.getFillPath()` is not available in skiko v. 0.7.63 27 | // return (strokePaint?.getFillPath(path) ?: path).bounds 28 | return strokePaint?.let { 29 | path.bounds.inflate(it.strokeWidth / 2) 30 | } ?: path.bounds 31 | } 32 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Pane.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | 9 | internal class Pane : Container() { 10 | var width: Float by visualProp(0.0f) 11 | var height: Float by visualProp(0.0f) 12 | } 13 | -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Path.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.PathFillMode 10 | import org.jetbrains.skia.Rect 11 | 12 | internal class Path : Figure() { 13 | var fillRule: PathFillMode? by visualProp(null) 14 | var skiaPath: SkPath? by visualProp(null, managed = true) 15 | 16 | private val path: SkPath? by computedProp(Path::fillRule, Path::skiaPath, managed = true) { 17 | val skiaPath = skiaPath ?: return@computedProp null 18 | 19 | SkPath().apply { 20 | fillRule?.let { fillMode = it } 21 | addPath(skiaPath) 22 | } 23 | } 24 | 25 | override fun render(canvas: Canvas) { 26 | val path = path ?: return 27 | 28 | fillPaint?.let { canvas.drawPath(path, it) } 29 | strokePaint?.let { canvas.drawPath(path, it) } 30 | } 31 | 32 | override val localBounds: Rect 33 | get() { 34 | // `paint.getFillPath()` is not available in skiko v. 0.7.63 35 | // return (strokePaint?.getFillPath(path) ?: path).bounds 36 | 37 | val path = path ?: return Rect.Companion.makeWH(0.0f, 0.0f) 38 | val strokeWidth = strokePaint?.strokeWidth ?: return path.bounds 39 | 40 | return path.bounds.inflate(strokeWidth / 2f) 41 | } 42 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/Rectangle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import org.jetbrains.skia.Canvas 9 | import org.jetbrains.skia.Rect 10 | 11 | internal class Rectangle : Figure() { 12 | var x: Float by visualProp(0.0f) 13 | var y: Float by visualProp(0.0f) 14 | var width: Float by visualProp(0.0f) 15 | var height: Float by visualProp(0.0f) 16 | private val rect: Rect by computedProp(Rectangle::x, Rectangle::y, Rectangle::width, Rectangle::height) { 17 | Rect.makeXYWH(x, y, width, height) 18 | } 19 | 20 | override fun render(canvas: Canvas) { 21 | fillPaint?.let { canvas.drawRect(rect, it) } 22 | strokePaint?.let { canvas.drawRect(rect, it) } 23 | } 24 | 25 | override val localBounds: Rect 26 | get() = Rect.makeXYWH(x, y, width, height) 27 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/shape/VisualProperty.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.shape 7 | 8 | import kotlin.properties.ReadWriteProperty 9 | import kotlin.reflect.KProperty 10 | 11 | internal class VisualProperty( 12 | initialValue: T, 13 | private val onPropertyChanged: (KProperty<*>, T, T) -> Unit 14 | ) : ReadWriteProperty { 15 | private var value = initialValue 16 | var isDefault = true 17 | private set 18 | 19 | override fun getValue(thisRef: Any, property: KProperty<*>): T = value 20 | override fun setValue(thisRef: Any, property: KProperty<*>, value: T) = run { 21 | isDefault = false 22 | val oldValue = this.value 23 | this.value = value 24 | 25 | if (oldValue != value) { 26 | onPropertyChanged(property, oldValue, value) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /platf-skia/src/commonMain/kotlin/org/jetbrains/letsPlot/skia/view/SkikoViewEventDispatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 JetBrains s.r.o. 3 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 4 | */ 5 | 6 | package org.jetbrains.letsPlot.skia.view 7 | 8 | import org.jetbrains.letsPlot.commons.event.MouseEvent 9 | import org.jetbrains.letsPlot.commons.event.MouseEventSpec 10 | import org.jetbrains.letsPlot.commons.intern.observable.event.EventHandler 11 | import org.jetbrains.letsPlot.commons.registration.Registration 12 | 13 | interface SkikoViewEventDispatcher { 14 | fun dispatchMouseEvent(kind: MouseEventSpec, e: MouseEvent) 15 | fun addEventHandler(eventSpec: MouseEventSpec, eventHandler: EventHandler): Registration 16 | } --------------------------------------------------------------------------------