kandy 0.7.0 Help

Quick Start Guide

Basics

Usage

To integrate Kandy and Dataframe into an interactive notebook, use the following commands:


// Fetches the latest versions %useLatestDescriptors // Adds the dataframe library with the latest version %use dataframe // Adds the kandy library with the latest version %use kandy
// Adds the dataframe library with a specific version %use dataframe(0.13.1) // Adds the kandy library with a specific version %use kandy(0.7.0)

To include Kandy in your Gradle project, add the following to your dependencies:

dependencies { implementation("org.jetbrains.kotlinx:kandy-lets-plot:0.7.0") }

Additionally, to use static plots, include the following in your build script:

repositories { maven("https://packages.jetbrains.team/maven/p/kds/kotlin-ds-maven") } dependencies { implementation("org.jetbrains.kotlinx:kotlin-statistics-jvm:0.3.1") }

Data

In Kandy, the primary data model for plotting is a "named data" or "dataframe". This model comprises a set of named columns with values of equal length. The input data should be structured as Map<String, List<*>>.

Example of a simple dataset in Kandy:

val simpleDataset = mapOf( "time, ms" to listOf(12, 87, 130, 149, 200, 221, 250), "relativeHumidity" to listOf(0.45, 0.3, 0.21, 0.15, 0.22, 0.36, 0.8), "flowOn" to listOf(true, true, false, false, true, false, false), )

To reference dataset columns in your plots, create pointers for each column. There are several ways to do this:

// 1. Using the `column()` function with the specified column type and name: val timeMs = column<Int>("time, ms") // 2. Utilizing the String API, similar to the method above, but using String invocation: val humidity = "relativeHumidity"<Double>() // 3. Delegating an unnamed column - its name will be derived from the variable name: val flowOn by column<Boolean>()

Plot Creation

In Kandy, to create a plot, you start by calling the plot() function and passing your dataset as an argument. This function establishes a context within which you can add various layers to your plot.

A layer is essentially a collection of mappings from your data to the visual attributes of the plot, known as aesthetic attributes. These attributes define how your data is represented visually, such as through points, lines, or bars.

Here's an example demonstrating the creation of a plot with a single layer:

plot(simpleDataset) { points { // Maps values from the "time, ms" column to the X-axis x(timeMs) // Maps values from the "relativeHumidity" column to the Y-axis y(humidity) // Sets the size of the points to 4.5 size = 4.5 // Maps values from the "flowOn" column to the color attribute color(flowOn) } }
Plot Humidity Data

Layers, aesthetics, mappings, and scales

Each plot layer is defined by its geometrical entity (or geom), which determines the layer's visual representation. Geoms have associated aesthetic attributes (or aesthetics/aes), which can be either positional (like x, y, yMin, yMax, middle) or non-positional (such as color, size, width). Non-positional aesthetics have specific types (e.g., size is associated with Double, color with Color).

Aesthetic values can be assigned in two ways: setting and mapping.

  • Setting involves assigning a constant value directly:

// Using `.constant()` for positional aes: x.constant(12.0f) yMin.constant(10.0) // Assignment for non-positional aes: size = 5.0 color = Color.RED
  • Mapping links data column values to aesthetic attributes. This can be defined in various ways:

// With `ColumnReference`: x(timeMs) size(humidity) color(flowOn) // Using raw `String`: x("time, ms") size("relativeHumidity") // Directly providing values, e.g., with `Iterable`: x(listOf(12, 87, 130, 149, 200, 221, 250)) // Optional source ID can be set: color(listOf(true, true, false, false, true, false, false), "flow on")

Scales determine how data values are translated into visual representations. They can be categorical (discrete) or continuous, based on their domain and range types. Continuous scales use limits for their domain and range, while categorical scales use lists of categories and corresponding values. Scales are typed and may include a transform parameter to define a transformation function (linear by default).

Explicitly specifying scales after mapping:

plot(simpleDataset) { points { x(listOf(12, 87, 130, 149, 200, 221, 250)) { scale = continuous(min = 0, max = 270) // Using `min`/`max` } y(humidity) { scale = continuous(0.0..1.0) // Using `Range` } size(humidity) { scale = continuous(range = 5.0..20.0) } color(flowOn) { scale = categorical(true to Color.RED, false to Color.BLUE) } symbol(flowOn) { scale = categorical()// default scale } alpha = 0.9 } }
Plot Customized Scales for Humidity Data

Scales can also be created separately and applied later:

val xReversedScale = Scale.continuousPos<Int>(transform = Transformation.REVERSE) val yScale = Scale.continuousPos(0.0..0.9) val colorScale = Scale.categorical<Color, Boolean>( range = listOf(Color.RED, Color.GREEN) )

Applying pre-defined scales:

plot(simpleDataset) { points { x(listOf(12, 87, 130, 149, 200, 221, 250)) { scale = xReversedScale } y(humidity) { scale = yScale } color(flowOn) { scale = colorScale } symbol = Symbol.ASTERIX size = 6.6 } }
Plot Humidity Data With Custom Scales

Scale parameters: axis and legend

Guides play a crucial role in interpreting charts. They function as mini-charts for scales, with positional scales using axes as guides and non-positional ones utilizing legends. Each scale comes with a default guide, but you can tailor it to your needs.

Here's how to customize the axis and legend in a plot:

// Creating a plot with customized axis and legend plot(simpleDataset) { points { // Configuring the x-axis for time x(timeMs) { axis.name = "Time from start of counting,\n milliseconds" } // Configuring the y-axis for humidity y(humidity) { scale = continuous(0.0..1.0) // Setting scale for humidity axis { name = "Relative humidity" // Axis label breaksLabeled(0.0 to "0%", 0.3 to "30%", 0.6 to "60%", 0.9 to "90%") // Custom axis breaks } } size = 12.0 // Set size of points // Configuring the legend for humidity color(humidity) { scale = continuous() legend { name = "rel. humidity" // Legend label type = LegendType.ColorBar(40.0, 190.0, 15) // Legend type and dimensions breaks(format = "e") // Legend breaks format } } } }
Plot Humidity Data With Time

Global X-axis and Y-axis mappings and scales

In Kandy, you can use global mappings for X and Y across multiple layers, simplifying the process when these mappings are common. Each layer inherits the global mapping unless overridden locally.

Example with global X and Y mappings:

// Using global mappings for X and Y plot(simpleDataset) { x(timeMs) { scale = continuous(max = 275) } y(humidity) points { // Inherits X and Y from the global context size = 4.5 color(flowOn) } line { // Inherits X, overrides Y y(listOf(0.49, 0.39, 0.1, 0.4, 0.8, 0.8, 0.9)) width = 3.0 color = Color.RED } }
Plot Humidity Data With Line

Configuring axis scales without explicit mapping:

// Configuring axis scales independently plot(simpleDataset) { points { x(listOf(10, 20, 30, 40, 50, 60, 70)) y(humidity) size = 4.5 color(flowOn) } x.axis.name = "time, ms" y { scale = continuous(min = 0.0) axis.breaks(format = ".2f") } }
Configure Axis Scales
plot(simpleDataset) { points { x(listOf(10, 20, 30, 40, 50, 60, 70)) y(humidity) size = 4.5 color(flowOn) } // Alternate brief notation if we want to set the axis limits (continuous scale) x.limits = 0..80 y.limits = 0.0..1.0 }
Configure Axis Scales Alternative API

Free scale mechanism in Kandy:

This feature, known as "free scale" allows the setting of scale parameters without direct data mapping. It's particularly useful for sub-positional < aesthetics that don't have individual scales but depend on the parent positional aesthetics scale.

Example of using free scale in a boxplot:

// Demonstrating free scale in a boxplot plot( mapOf( "x" to listOf("a", "b", "c"), // Boxplot sub-positional aesthetics "min" to listOf(0.8, 0.4, 0.6), "lower" to listOf(0.9, 1.4, 0.8), "middle" to listOf(1.5, 2.4, 1.6), "upper" to listOf(1.9, 3.4, 1.7), "max" to listOf(3.1, 4.4, 2.6), "width" to listOf(0.1, 1.4, 14.0) ) ) { boxes { x("x"<String>()) // Sub-y aesthetics with a free scale yMin("min"<Double>()) lower("lower"<Double>()) middle("middle"<Double>()) upper("upper"<Double>()) yMax("max"<Double>()) fatten = 4.5 alpha = 0.2 borderLine.color = Color.hex(0x9A2A2A) } y { axis.name = "weight" limits = 0.0..5.0 } }
Boxplot With Free Scale

Raw source mapping

Kandy allows for direct data source mapping in plots, providing an alternative to using dataset column pointers. This method supports mapping from an Iterable source and offers the option to name these sources for clearer visualization.

Example dataset:

// Sample data with months, number of days, and seasons val month = listOf( "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ) val numberOfDays = listOf(31, 28, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30) val season = listOf( "winter", "winter", "spring", "spring", "spring", "summer", "summer", "summer", "autumn", "autumn", "autumn", "winter" )

Using raw source mapping:

// Plotting using raw source mapping plot { bars { // Mapping 'month' directly with a name x(month, "month") { scale = categorical() } // Mapping 'numberOfDays' directly y(numberOfDays, "number of days") // Mapping 'season' with color, named source, and categorical scale fillColor(season, "season") { scale = categorical( listOf(Color.BLUE, Color.GREEN, Color.RED, Color.ORANGE), listOf("winter", "spring", "summer", "autumn"), ) } } }
Bars with Raw Source Mapping

Kotlin DataFrame API

Kandy integrates seamlessly with the Kotlin DataFrame library, allowing the use of DataFrame as a data source and DataColumn for mappings. This integration simplifies the process of creating visualizations, as you can utilize auto-generated property columns without manually creating column pointers.

// Reading a CSV file into a DataFrame val mpgDF = DataFrame.readCSV("https://raw.githubusercontent.com/JetBrains/lets-plot-kotlin/master/docs/examples/data/mpg.csv") // Display the first five rows of the DataFrame mpgDF.head()

untitled

manufacturer

model

displ

year

cyl

trans

drv

cty

hwy

fl

class

1

audi

a4

18,0

1999

4

auto(l5)

f

18

29

p

compact

2

audi

a4

18,0

1999

4

manual(m5)

f

21

29

p

compact

3

audi

a4

2,0

2008

4

manual(m6)

f

20

31

p

compact

4

audi

a4

2,0

2008

4

auto(av)

f

21

30

p

compact

5

audi

a4

28,0

1999

6

auto(l5)

f

16

26

p

compact

// Show the schema of the DataFrame mpgDF.schema()
untitled: Int manufacturer: String model: String displ: Double year: Int cyl: Int trans: String drv: String cty: Int hwy: Int fl: String class: String

Example of using DataFrame with Kandy:

// Create a plot using the DataFrame val mpgInfoPlot = mpgDF.plot { points { x(displ) // Auto-generated DataFrame columns y(cty) { scale = continuous(8..34) } symbol = Symbol.CIRCLE_FILLED color = Color.GREY alpha = 0.7 fillColor(drv) size(hwy) { scale = continuous(5.0..15.0) legend.breaks(listOf(15, 30, 40), format = "d") } } } mpgInfoPlot
Mpg Info Plot

In scenarios where auto-generated property-columns are not available, you can manually create pointers to DataFrame columns:

// Creating a plot with manual column pointers val mpgCountPlot = mpgDF.groupBy { drv }.aggregate { count() into "count" }.plot { bars { x(drv) y("count"<Int>()) alpha = 0.75 } } mpgCountPlot
drv count Plot

This integration with Kotlin DataFrame enriches Kandy's plotting capabilities, providing a more streamlined and efficient approach to data visualization in Kotlin projects.

Grouping

The grouping feature in Kandy is particularly useful for plotting collective geometries, where multiple data units are represented by a single geometric object, such as a line. Grouping can be performed either by providing a grouped dataframe (GroupBy) as a dataset or directly within the plot DSL.

Here's how you can use grouping in Kandy:

  1. Grouping with a Map as Dataset: You can define your dataset as a map and then use groupBy within the plotting DSL to create groups based on a specific column.

// Dataset with a grouping column val lineDataset = mapOf( "timeG" to listOf(1.0, 2.2, 3.4, 6.6, 2.1, 4.4, 6.0, 1.5, 4.7, 6.7), "value" to listOf(112.0, 147.3, 111.1, 200.6, 90.8, 110.2, 130.4, 100.1, 90.0, 121.8), "c-type" to listOf("A", "A", "A", "A", "B", "B", "B", "C", "C", "C") )
// Using grouping in the plot plot(lineDataset) { groupBy("c-type") { line { x("timeG") y("value") } } }
Grouping in the Plot
  1. Grouping with DataFrame API: If you are using the Kotlin DataFrame API, you can apply grouping on a dataframe.

// Convert the map to a DataFrame val lineDF = lineDataset.toDataFrame()
// Apply grouping on the DataFrame lineDF.groupBy { `c-type` }.plot { line { x(timeG) y(value) } }
Grouping in the Plot with DataFrame
  1. Advanced Grouping with Color Mapping: For more complex visualizations, you can use grouping along with additional aesthetic mappings like color.

// Grouping with additional color mapping lineDF.plot { groupBy(`c-type`) { line { x(timeG) y(value) color(`c-type`) width = 4.0 } } }
Grouping in the Plot with Color Mapping

Implicit grouping

Kandy provides a convenient way to perform implicit grouping by utilizing categorical scales. This approach is especially useful when you want to differentiate data points based on a specific category without explicitly defining groups.

Plotting with Implicit Grouping: By specifying a categorical scale for an aesthetic attribute like color, Kandy automatically groups the data based on the categories presented in the data column.

// Plotting with implicit grouping lineDF.plot { line { x(timeG) y(value) // Implicit grouping based on `c-type` color(`c-type`) width = 4.0 } }
Implicit Grouping in the Plot

In this example, the color aesthetic is mapped to the c-type column, which is a categorical variable. Kandy implicitly groups the data based on the unique values in the c-type column and assigns different colors to each group. This results in a visually distinct representation for each category, making it easier to differentiate between them in the plot.

Position

The position parameter plays a crucial role in adjusting the spatial arrangement of objects within a single layer, especially when dealing with grouped data. It controls how objects from different groups are positioned relative to each other.

Here's a breakdown of how position can be applied:

val meanCylDf = mpgDF.groupBy { cyl and drv }.mean { cty } // Display the first five rows of the grouped DataFrame meanCylDf.head(5)

cyl

drv

cty

4

f

22,068966

6

f

17,186047

4

4

18,347826

6

4

14,781250

8

4

12,104167

  1. Dodge Position: Separates bars side by side, making it easy to compare groups.

val meanCylPlot = meanCylDf.groupBy { cyl }.plot { bars { x(drv) y(cty) fillColor(cyl) { legend.breaks(format = "d") } position = Position.dodge() } } meanCylPlot
Dodge Position
  1. Identity Position: Overlays bars directly on top of each other, useful for highlighting overlaps.

meanCylDf.groupBy { cyl }.plot { bars { x(drv) y(cty) fillColor(cyl) { legend.breaks(format = "d") } alpha = 0.4 position = Position.identity() } }
Identity Position
  1. Stack Position: Stacks bars on top of each other, ideal for cumulative comparisons.

meanCylDf.groupBy { cyl }.plot { bars { x(drv) y(cty) fillColor(cyl) { legend.breaks(format = "d") } position = Position.stack() } }
Stack Position

Experimental

This section of Kandy explores experimental APIs which are subject to change in future versions. They offer innovative features for advanced data visualization, inviting exploration and feedback for further development.

Multiplot

There are three ways to create a multiplot: plotGrid, plotBunch and faceting.

Plot Grid

The plotGrid function allows for arranging multiple plots in a structured grid format, offering a cohesive view of different data visualizations. Example:

plotGrid(listOf(mpgInfoPlot, mpgCountPlot, meanCylPlot), nCol = 2)
Plot Grid

Plot Bunch

plotBunch provides a more flexible approach to multiplot creation, enabling custom placement and sizing for each plot. This is ideal for tailored data presentations. Example:

plotBunch { add(mpgCountPlot, 0, 0, 400, 200) add(meanCylPlot, 400, 0, 300, 200) add(mpgInfoPlot, 0, 200, 700, 300) }
Plot Bunch

Faceting

Faceting in Kandy is a powerful feature that splits a single plot into multiple plots based on dataset categories. This method is akin to groupBy in data manipulation, providing detailed insights into subcategories. Faceting methods include:

  • facetGridX and facetGridY for single-parameter faceting along the X or Y axes, respectively:

mpgDF.plot { points { x(displ) y(cty) { scale = continuous(8..34) } symbol = Symbol.CIRCLE_FILLED color = Color.GREY alpha = 0.7 fillColor(drv) size(hwy) { scale = continuous(2.0..10.0) } } layout.size = 750 to 450 facetGridX(drv, scalesSharing = ScalesSharing.FREE_X) }
Plot Facet Grid X
mpgDF.plot { points { x(displ) y(cty) { scale = continuous(8..34) } symbol = Symbol.CIRCLE_FILLED color = Color.GREY alpha = 0.7 fillColor(drv) size(hwy) { scale = continuous(2.0..10.0) } } layout.size = 750 to 450 facetGridY(cyl, format = "d") }
Plot Facet Grid Y
  • facetGrid for two-parameter faceting along both X and Y axes:

mpgDF.plot { points { x(displ) y(cty) { scale = continuous(8..34) } symbol = Symbol.CIRCLE_FILLED color = Color.GREY fillColor(drv) size(hwy) { scale = continuous(2.0..10.0) } } layout.size = 750 to 450 facetGrid(drv, cyl, yFormat = "d") }
Plot Facet Grid
  • facetWrap for multi-parameter faceting with additional layout control:

mpgDF.plot { points { x(displ) y(cty) { scale = continuous(8..34) } symbol = Symbol.CIRCLE_FILLED color = Color.GREY fillColor(drv) size(hwy) { scale = continuous(2.0..10.0) } } layout.size = 750 to 450 facetWrap(nCol = 3, scalesSharing = ScalesSharing.FREE) { facet(drv) facet(cyl, order = OrderDirection.DESCENDING, format = "d") } }
Plot Facet Grid Wrap

Statistics

Kandy offers a robust API for computing statistics directly within its DSL. This functionality is achieved through the stat family of functions, which transform raw data into a new dataset with calculated statistics. These statistics become accessible as columns and can be mapped to various plot aesthetics, scaled, or incorporated into tooltips.

Here's an example using a dataset of random observations:

val random = kotlin.random.Random(42) val observation = List(1000) { random.nextDouble() } val observationDataset = dataFrameOf( "observations" to observation )

You can create statistical visualizations such as a bin plot with bars and lines:

plot(observationDataset) { statBin(obs) { bars { // Simple mapping x(Stat.x) // Mapping with the scale y(Stat.count) { scale = continuous(0..100, transform = Transformation.REVERSE) } alpha = 0.5 // Formatting of stat value format tooltips { // Line with the name of stat (i.e. "count") on the left side and its value on the right side line(Stat.count, format = "d") } } line { x(Stat.x) y(Stat.count) width = 2.5 color = Color.RED } } }
Stats Bin with Bars and Lines

Kandy also simplifies the creation of standard statistical charts, like histograms, by combining statistical calculations and layer creation into a single function:

val histPlot = plot(observationDataset) { histogram(obs) layout.title = "`histogram`" } histPlot
Simple Histogram

You can compare it to a bar chart with the calculation of bins stat:

val binBarPlot = plot(observationDataset) { statBin(obs) { bars { x(Stat.x) y(Stat.count) } } layout.title = "`statBin` + `bar`" } plotGrid(listOf(histPlot, binBarPlot), 2)
StatBin Bars and Histogram

The histogram in Kandy is flexible, allowing for custom aesthetic bindings and the use of "stat-bin" statistics for mapping:

plot(observationDataset) { histogram(obs, binsOption = BinsOption.byWidth(0.05), binsAlign = BinsAlign.boundary(0.1)) { y(Stat.density) fillColor(Stat.count) { scale = continuous(Color.GREEN..Color.RED) } borderLine { color = Color.BLACK width = 0.3 } tooltips(title = "${value(Stat.x)} ± 0.025") { line(Stat.density) } } }
Configured Histogram

This statistical API is also compatible with Iterable data types, expanding its applicability across various data structures:

plotGrid(listOf( plot { statBin(observation) { points { x(Stat.density) y(Stat.x) } } }, plot { histogram(observation) } ), 2)
statBin and Histogram with Iterable Data

Layout

Title, subtitle, caption, and size

You can personalize your plot's layout by configuring the title, subtitle, caption, and overall size. The layout block provides direct access to these properties, making it easy to create visually compelling charts.

mpgDF.plot { points { x(cty) y(hwy) } // Can access of layout parameters in this context layout { subtitle = "plot subtitle" caption = "plot caption \n important info" size = 800 to 600 } // If you just want to put a title layout.title = "PLOT TITLE" }
Plot with Title, Subtitle, Caption, and Size

Styles

Styles in Kandy offer extensive customization options for your plot's appearance, including styles for lines, text, backgrounds, and more. You can use a pre-built style or create your own custom style.

To apply a style:

mpgDF.plot { points { x(cty) y(hwy) } layout.style(Style.Classic) }
Plot with Classic Style

For example, creating a plot with different styles:

fun plotWithStyle(style: Style? = null, title: String? = null): org.jetbrains.kotlinx.kandy.ir.Plot { return mpgDF.plot { points { x(cty) y(hwy) } layout { style?.let { style(it) } this.title = title } } }
plotGrid( listOf( plotWithStyle(Style.Classic, "\"Classic\" style"), plotWithStyle(Style.Grey, "\"Grey\" style"), plotWithStyle(Style.Light, "\"Light\" style"), plotWithStyle(Style.Minimal, "\"Minimal\" style"), plotWithStyle(Style.Minimal2, "\"Minimal2\" style (by default)"), plotWithStyle(Style.None, "\"None\" style"), ), 2 )
Plot with All Styles
Custom Styles

Kandy's DSL allows you to craft custom styles. You can set parameters for lines, text, backgrounds, etc.,either separately or in-place.

Creating a simple custom style:

val redLine = LayoutParameters.line(Color.RED) val simpleCustomStyle = Style.createCustom { // use previously created parameters xAxis.line(redLine) // set up parameters yAxis.line { color = Color.RED width = 0.3 } // remove ticks on both axes axis.ticks { blank = true } } plotWithStyle(simpleCustomStyle)
Plot with Simple Custom Style

Example of a style with blank axes:

val blankAxesStyle = Style.createCustom { blankAxes() } plotWithStyle(blankAxesStyle)
Plot with Blank Axes

Custom scales

Kandy extends the versatility of scales beyond standard options, providing custom color scales like hue, grey, brewer, gradient2, and gradientN.

Creating plots with different color scales:

mpgDF.plot { points { x(cty) y(hwy) size = 5.0 color(drv) { scale = categoricalColorHue() } } }
Plot with Categorical Color Scale
mpgDF.plot { points { x(cty) y(hwy) size = 5.0 color(cty) { scale = continuousColorGrey() } } }
Plot with Continuous Color Grey
mpgDF.plot { points { x(cty) y(hwy) size = 5.0 color(hwy) { scale = continuousColorGradient2(Color.BLUE, Color.ORANGE, Color.RED, 30.0) } } }
Plot with Continuous Color Gradient2
mpgDF.plot { points { x(cty) y(hwy) size = 5.0 color(cty) { scale = continuousColorGradientN( gradientColors = listOf( Color.RED, Color.hex("#fa89c7"), Color.rgb(200, 89, 12), Color.LIGHT_GREEN ) ) } } }
Plot with Continuous Color GradientN
mpgDF.plot { points { x(cty) y(hwy) size = 5.0 color(drv) { scale = categoricalColorBrewer(BrewerPalette.Qualitative.Set1) } } }
Plot with Categorical Color Brewer

Tooltips

Tooltips in Kandy allow for an interactive exploration of data by displaying additional information about visual objects. These tooltips are established within each layer's context using the tooltips() method.

While tooltips are enabled by default, Kandy allows for their customization or complete deactivation. This flexibility is achieved by adjusting the hide flag within the tooltips' settings:

mpgDF.plot { points { x(cty) y(hwy) color(drv) size = 3.5 tooltips(enable = false) } }
Plot without Tooltips

Kandy offers extensive customization for tooltips, enabling users to not only display data from specific columns but also format these data points for better clarity and interpretation:

mpgDF.plot { points { x(cty) y(hwy) color(drv) size = 3.5 tooltips(drv, cyl, displ, formats = mapOf(displ to ".1g")) } }
Display Data from specific Columns in Tooltips

Kandy allows for further customization of tooltips, including title adjustment, minimum width settings, and fixed positioning:

mpgDF.plot { points { x(cty) y(hwy) color(drv) size = 3.5 tooltips( cyl, displ, title = "Car info", anchor = Anchor.TOP_RIGHT, minWidth = 80.0, ) } }
Tooltips with title, position, anchor, width

For more detailed tooltip configurations, Kandy offers a special DSL. This DSL enables manual adjustments of tooltip lines, allowing users to add specific data values and customize the layout:

mpgDF.plot { points { x(cty) y(hwy) color(drv) size = 3.5 tooltips( // use column values in the title title = "${value(manufacturer)} ${value(model)}", ) { // standard line with column name and value line(displ) // solid line line(trans.tooltipValue()) // two-sided line line("cty/hwy [mpg]", "${cty.tooltipValue()}/${hwy.tooltipValue()}") } } }
Customizing Tooltips

Export

Kandy's plotting library provides an efficient way to export your plots as images in various formats, including .jpg/.jpeg, .png, .html, and .svg. This feature is facilitated by the save() extension method.

First, create a plot that you wish to export. Here's an example plot with some basic configurations:

val plotForExport = mpgDF.plot { points { x(cty) y(hwy) color(drv) size = 3.5 } layout { title = "Plot for export" size = 600 to 400 } } plotForExport
Plot For Export

Once your plot is ready, you can export it as an image file. The save() method allows you to specify the file format, scale, and dpi, and the path where the image will be saved:

val pathPNG = plotForExport.save("plot.png", scale = 2.5, dpi = 9000, path = "./saved_plots")

To view the exported image, you can use the following code snippet:

javax.imageio.ImageIO.read(java.io.File(pathPNG))
Import Plot

In addition to saving as a file, you can also export the plot as a BufferedImage. This is particularly useful for further manipulation or display within a Kotlin application:

plotForExport.toBufferedImage(scale = 2.5, dpi = 9000)
BufferedImage Plot
Last modified: 15 February 2024