diff --git a/demo/jvm-batik/src/main/kotlin/frontendContextDemo/scripts/QQ.kt b/demo/jvm-batik/src/main/kotlin/frontendContextDemo/scripts/QQ.kt
index 9d117fa0c..9d7afe742 100644
--- a/demo/jvm-batik/src/main/kotlin/frontendContextDemo/scripts/QQ.kt
+++ b/demo/jvm-batik/src/main/kotlin/frontendContextDemo/scripts/QQ.kt
@@ -64,7 +64,7 @@ object QQ {
p.show()
}
run {
- val p = qqPlot(gData, sample = "y", group = "g", alpha = 0.8, color = "black", shape = 21) +
+ val p = qqPlot(gData, sample = "y", group = "g", alpha = 0.8, color = "black", shape = 21, marginal = "box:tr") +
ggtitle("Bistro grouping") +
xlab("Normal distribution quantiles") +
scaleColorBrewer(type = "qual", palette = "Set1") +
diff --git a/docs/examples/jupyter-notebooks/f-4.9.0/qq_plot_marginal.ipynb b/docs/examples/jupyter-notebooks/f-4.9.0/qq_plot_marginal.ipynb
new file mode 100644
index 000000000..c007a0038
--- /dev/null
+++ b/docs/examples/jupyter-notebooks/f-4.9.0/qq_plot_marginal.ipynb
@@ -0,0 +1,1610 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "189a4ddc-2628-4c6f-a2fc-f23ddc52185c",
+ "metadata": {},
+ "source": [
+ "# Parameter `marginal` of the Q-Q Plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "7025b5b4-5e88-48e2-a677-c369ad601fb8",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%useLatestDescriptors\n",
+ "%use dataframe\n",
+ "%use lets-plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "56979abe-5025-49cc-ab8f-98bf51fbf184",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Lets-Plot Kotlin API v.0.0.0-SNAPSHOT. Frontend: Notebook with dynamically loaded JS. Lets-Plot JS v.4.5.1."
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "LetsPlot.getInfo()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "9d4abfc5-a145-4c7f-a7ef-212f1ec8735e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/kotlindataframe+json": "{\"nrow\":5,\"ncol\":12,\"columns\":[\"untitled\",\"manufacturer\",\"model\",\"displ\",\"year\",\"cyl\",\"trans\",\"drv\",\"cty\",\"hwy\",\"fl\",\"class\"],\"kotlin_dataframe\":[{\"untitled\":1,\"manufacturer\":\"audi\",\"model\":\"a4\",\"displ\":1.8,\"year\":1999,\"cyl\":4,\"trans\":\"auto(l5)\",\"drv\":\"f\",\"cty\":18,\"hwy\":29,\"fl\":\"p\",\"class\":\"compact\"},{\"untitled\":2,\"manufacturer\":\"audi\",\"model\":\"a4\",\"displ\":1.8,\"year\":1999,\"cyl\":4,\"trans\":\"manual(m5)\",\"drv\":\"f\",\"cty\":21,\"hwy\":29,\"fl\":\"p\",\"class\":\"compact\"},{\"untitled\":3,\"manufacturer\":\"audi\",\"model\":\"a4\",\"displ\":2.0,\"year\":2008,\"cyl\":4,\"trans\":\"manual(m6)\",\"drv\":\"f\",\"cty\":20,\"hwy\":31,\"fl\":\"p\",\"class\":\"compact\"},{\"untitled\":4,\"manufacturer\":\"audi\",\"model\":\"a4\",\"displ\":2.0,\"year\":2008,\"cyl\":4,\"trans\":\"auto(av)\",\"drv\":\"f\",\"cty\":21,\"hwy\":30,\"fl\":\"p\",\"class\":\"compact\"},{\"untitled\":5,\"manufacturer\":\"audi\",\"model\":\"a4\",\"displ\":2.8,\"year\":1999,\"cyl\":6,\"trans\":\"auto(l5)\",\"drv\":\"f\",\"cty\":16,\"hwy\":26,\"fl\":\"p\",\"class\":\"compact\"}]}",
+ "text/html": [
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "DataFrame: rowsCount = 5, columnsCount = 12
\n",
+ "| untitled | manufacturer | model | displ | year | cyl | trans | drv | cty | hwy | fl | class |
|---|
| 1 | audi | a4 | 1,800000 | 1999 | 4 | auto(l5) | f | 18 | 29 | p | compact |
| 2 | audi | a4 | 1,800000 | 1999 | 4 | manual(m5) | f | 21 | 29 | p | compact |
| 3 | audi | a4 | 2,000000 | 2008 | 4 | manual(m6) | f | 20 | 31 | p | compact |
| 4 | audi | a4 | 2,000000 | 2008 | 4 | auto(av) | f | 21 | 30 | p | compact |
| 5 | audi | a4 | 2,800000 | 1999 | 6 | auto(l5) | f | 16 | 26 | p | compact |
\n",
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "val df = DataFrame.readCSV(\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv\")\n",
+ "val dataMap = df.toMap()\n",
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "855f2503-1275-4881-8140-2a4ff3337886",
+ "metadata": {},
+ "source": [
+ "## Default Plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "980cba2b-b223-476a-a375-bf23765a6ecb",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " "
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "gggrid(listOf(\n",
+ " qqPlot(dataMap, sample = \"hwy\") + ggtitle(\"Q-Q Plot\"),\n",
+ " qqPlot(dataMap, x = \"cty\", y = \"hwy\") + ggtitle(\"Q-Q-2 Plot\")\n",
+ "))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "b04768ea-5ce2-4fd0-83c3-ec4e80c07e3d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " "
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "val marginals = listOf(\"none\", \"hist:tr\", \"dens:tr,hist:bl\", \"box : tr : .05, dens : bl\")\n",
+ "gggrid(marginals.map { marginal ->\n",
+ " qqPlot(dataMap, sample = \"hwy\", distribution = \"exp\", marginal=marginal) +\n",
+ " ggtitle(\"marginal = '${marginal}'\")\n",
+ "}, ncol = 2)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Kotlin",
+ "language": "kotlin",
+ "name": "kotlin"
+ },
+ "language_info": {
+ "codemirror_mode": "text/x-kotlin",
+ "file_extension": ".kt",
+ "mimetype": "text/x-kotlin",
+ "name": "kotlin",
+ "nbconvert_exporter": "",
+ "pygments_lexer": "kotlin",
+ "version": "1.9.23"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlot.kt b/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlot.kt
index 71c835c05..5c650dc5c 100644
--- a/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlot.kt
+++ b/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlot.kt
@@ -42,6 +42,24 @@ package org.jetbrains.letsPlot.bistro.qq
* If it is specified and color-parameters isn't then different groups will have different colors.
* @param showLegend default = true.
* false - do not show legend for this layer.
+ * @param marginal default = "dens:tr".
+ * Description of marginal layers packed to string value.
+ * Different marginals are separated by the ',' char.
+ * Parameters of a marginal are separated by the ':' char.
+ *
+ * First parameter of a marginal is a geometry name.
+ * Possible values: "dens"/"density", "hist"/"histogram", "box"/"boxplot".
+ *
+ * Second parameter is a string specifying which sides of the plot the marginal layer will appear on.
+ * Possible values: 't' (top), 'b' (bottom), 'l' (left), 'r' (right).
+ *
+ * Third parameter (optional) is size of marginal.
+ * To suppress marginals use `marginal="none"`.
+ *
+ * Examples:
+ * - "hist:tr:0.3",
+ * - "dens:tr,hist:bl",
+ * - "box:tr:.05, hist:bl, dens:bl".
* @param color Color of a points.
* For more info see: [aesthetics.html#color-and-fill](https://lets-plot.org/kotlin/aesthetics.html#color-and-fill).
* @param fill Color to paint shape's inner points.
@@ -74,6 +92,7 @@ fun qqPlot(
quantiles: Pair? = null,
group: String? = null,
showLegend: Boolean = true,
+ marginal: String? = null,
color: String? = null,
fill: String? = null,
alpha: Number? = null,
@@ -92,6 +111,7 @@ fun qqPlot(
quantiles,
group,
showLegend,
+ marginal,
color,
fill,
alpha,
diff --git a/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlotBuilder.kt b/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlotBuilder.kt
index 76bd40787..d55d3a88d 100644
--- a/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlotBuilder.kt
+++ b/plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/bistro/qq/QQPlotBuilder.kt
@@ -5,137 +5,56 @@
package org.jetbrains.letsPlot.bistro.qq
-import org.jetbrains.letsPlot.Stat
-import org.jetbrains.letsPlot.core.plot.base.Aes
-import org.jetbrains.letsPlot.core.spec.back.transform.bistro.qq.QQPlotOptionsBuilder.Companion.DEF_DISTRIBUTION
-import org.jetbrains.letsPlot.core.spec.back.transform.bistro.qq.QQPlotOptionsBuilder.Companion.DEF_LINE_COLOR
-import org.jetbrains.letsPlot.core.spec.back.transform.bistro.qq.QQPlotOptionsBuilder.Companion.DEF_LINE_SIZE
-import org.jetbrains.letsPlot.core.spec.back.transform.bistro.qq.QQPlotOptionsBuilder.Companion.DEF_POINT_ALPHA
-import org.jetbrains.letsPlot.core.spec.back.transform.bistro.qq.QQPlotOptionsBuilder.Companion.DEF_POINT_SIZE
-import org.jetbrains.letsPlot.geom.geomQQ
-import org.jetbrains.letsPlot.geom.geomQQ2
-import org.jetbrains.letsPlot.geom.geomQQ2Line
-import org.jetbrains.letsPlot.geom.geomQQLine
-import org.jetbrains.letsPlot.intern.Feature
-import org.jetbrains.letsPlot.intern.GenericAesMapping
+import org.jetbrains.letsPlot.core.spec.Option.Plot.BISTRO
+import org.jetbrains.letsPlot.core.spec.back.transform.bistro.qq.Option.QQ
+import org.jetbrains.letsPlot.intern.OptionsMap
import org.jetbrains.letsPlot.intern.Plot
-import org.jetbrains.letsPlot.intern.asPlotData
-import org.jetbrains.letsPlot.intern.layer.stat.QQ2StatMapping
-import org.jetbrains.letsPlot.intern.layer.stat.QQStatMapping
-import org.jetbrains.letsPlot.label.labs
+import org.jetbrains.letsPlot.intern.filterNonNullValues
import org.jetbrains.letsPlot.letsPlot
-import org.jetbrains.letsPlot.scale.scaleColorDiscrete
-import org.jetbrains.letsPlot.scale.scaleFillDiscrete
internal class QQPlotBuilder(
private val data: Map<*, *>,
- private val sample: String? = null,
- private val x: String? = null,
- private val y: String? = null,
- distribution: String? = null,
- private val dParams: List? = null,
- private val quantiles: Pair? = null,
- private val group: String? = null,
- private val showLegend: Boolean = true,
- private val color: String? = null,
- private val fill: String? = null,
- alpha: Number? = null,
- size: Number? = null,
- private val shape: Int? = null,
- lineColor: String? = null,
- lineSize: Number? = null,
- private val linetype: Any? = null
+ private val sample: String?,
+ private val x: String?,
+ private val y: String?,
+ private val distribution: String?,
+ private val dParams: List?,
+ private val quantiles: Pair?,
+ private val group: String?,
+ private val showLegend: Boolean,
+ private val marginal: String?,
+ private val color: String?,
+ private val fill: String?,
+ private val alpha: Number?,
+ private val size: Number?,
+ private val shape: Int?,
+ private val lineColor: String?,
+ private val lineSize: Number?,
+ private val linetype: Any?
) {
- private val myDistribution = distribution ?: DEF_DISTRIBUTION
- private val myLineColor = lineColor ?: if (group == null) DEF_LINE_COLOR else null
- private val myAlpha = alpha ?: DEF_POINT_ALPHA
- private val mySize = size ?: DEF_POINT_SIZE
- private val myLineSize = lineSize ?: DEF_LINE_SIZE
-
fun build(): Plot {
- val mapping: GenericAesMapping.() -> Unit = {
- if (this@QQPlotBuilder.group != null) {
- group = this@QQPlotBuilder.group
- color = this@QQPlotBuilder.group
- fill = this@QQPlotBuilder.group
- }
- }
- return letsPlot(asPlotData(data), mapping) + layers() + scales()
- }
-
- private fun layers() = if (sample != null) qqLayers() else qq2Layers()
-
- private fun qqLayers(): Feature {
- val mapping = qqMapping()
- return geomQQ(
- showLegend = showLegend,
- color = color,
- fill = fill,
- alpha = myAlpha,
- size = mySize,
- shape = shape,
- stat = Stat.qq(myDistribution, dParams, mapping)
- ) + geomQQLine(
- showLegend = showLegend,
- color = myLineColor,
- size = myLineSize,
- linetype = linetype,
- stat = Stat.qqLine(myDistribution, dParams, quantiles, mapping)
- )
- }
-
- private fun qq2Layers(): Feature {
- val mapping = qq2Mapping()
- return geomQQ2(
- showLegend = showLegend,
- color = color,
- fill = fill,
- alpha = myAlpha,
- size = mySize,
- shape = shape,
- stat = Stat.qq2(mapping)
- ) + geomQQ2Line(
- showLegend = showLegend,
- color = myLineColor,
- size = myLineSize,
- linetype = linetype,
- stat = Stat.qq2Line(quantiles, mapping)
- )
- }
-
- private fun qqMapping(): QQStatMapping.() -> Unit {
- require(x == null)
- { "Parameter x shouldn't be specified when parameter sample is." }
- require(y == null)
- { "Parameter y shouldn't be specified when parameter sample is." }
-
- val mapping: QQStatMapping.() -> Unit = {
- sample = this@QQPlotBuilder.sample
- }
- return mapping
- }
-
- private fun qq2Mapping(): QQ2StatMapping.() -> Unit {
- require(x != null)
- { "Parameter x should be specified when parameter sample isn't." }
- require(y != null)
- { "Parameter y should be specified when parameter sample isn't." }
-
- val mapping: QQ2StatMapping.() -> Unit = {
- x = this@QQPlotBuilder.x
- y = this@QQPlotBuilder.y
- }
- return mapping
- }
-
- private fun scales(): Feature {
- val scaleNames = mapOf(
- Aes.X to if (sample != null) "\"$myDistribution\" distribution quantiles" else "$x quantiles",
- Aes.Y to if (sample != null) "$sample quantiles" else "$y quantiles",
+ return letsPlot(data) + OptionsMap(
+ kind = BISTRO,
+ name = QQ.NAME,
+ options = mapOf(
+ QQ.SAMPLE to sample,
+ QQ.X to x,
+ QQ.Y to y,
+ QQ.DISTRIBUTION to distribution,
+ QQ.DISTRIBUTION_PARAMETERS to dParams,
+ QQ.QUANTILES to quantiles,
+ QQ.GROUP to group,
+ QQ.SHOW_LEGEND to showLegend,
+ QQ.MARGINAL to marginal,
+ QQ.POINT_COLOR to color,
+ QQ.POINT_FILL to fill,
+ QQ.POINT_ALPHA to alpha,
+ QQ.POINT_SIZE to size,
+ QQ.POINT_SHAPE to shape,
+ QQ.LINE_COLOR to lineColor,
+ QQ.LINE_SIZE to lineSize,
+ QQ.LINE_TYPE to linetype
+ ).filterNonNullValues()
)
- return labs(
- x = scaleNames[Aes.X],
- y = scaleNames[Aes.Y]
- ) + scaleColorDiscrete() + scaleFillDiscrete()
}
}
\ No newline at end of file