diff --git a/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormat.kt b/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormat.kt index dcf00aa6765..fd7fb838e83 100644 --- a/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormat.kt +++ b/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormat.kt @@ -25,10 +25,12 @@ fun length(v: Long): Int { return len } -class NumberFormat(private val spec: Spec) { +class NumberFormat(private val spec: Spec, private val scientificNotationIsPower: Boolean = true) { constructor(spec: String) : this(create(spec)) + constructor(spec: String, scientificNotation: Boolean) : this(create(spec), scientificNotation) + data class Spec( val fill: String = " ", val align: String = ">", @@ -150,7 +152,13 @@ class NumberFormat(private val spec: Spec) { ) { val fractionalLength = 0.takeIf { fractionalPart.isEmpty() } ?: FRACTION_DELIMITER_LENGTH + fractionalPart.length - val fullLength = integerPart.length + fractionalLength + exponentialPart.length + val exponentialLength: Int + get() { + val match = POWER_REGEX.find(exponentialPart) ?: return exponentialPart.length + val matchGroups = match.groups as MatchNamedGroupCollection + return matchGroups["degree"]?.value?.length?.plus(2) ?: exponentialPart.length + } + val fullLength = integerPart.length + fractionalLength + exponentialLength override fun toString() = "$integerPart${FRACTION_DELIMITER.takeIf { fractionalPart.isNotEmpty() } ?: ""}$fractionalPart$exponentialPart" @@ -218,7 +226,7 @@ class NumberFormat(private val spec: Spec) { var fullIntStr = zeroPadding + body.integerPart val commas = (ceil(fullIntStr.length / GROUP_SIZE.toDouble()) - 1).toInt() - val width = (spec.width - body.fractionalLength - body.exponentialPart.length) + val width = (spec.width - body.fractionalLength - body.exponentialLength) .coerceAtLeast(body.integerPart.length + commas) fullIntStr = group(fullIntStr) @@ -319,12 +327,7 @@ class NumberFormat(private val spec: Spec) { } private fun toSimpleFormat(numberInfo: NumberInfo, precision: Int = -1): FormattedNumber { - val exponentString = if (numberInfo.exponent != null) { - val expSign = if (numberInfo.exponent.sign >= 0) "+" else "" - "e$expSign${numberInfo.exponent}" - } else { - "" - } + val exponentString = buildExponentString(numberInfo.exponent) val expNumberInfo = createNumberInfo(numberInfo.integerPart + numberInfo.fractionalPart / NumberInfo.MAX_DECIMAL_VALUE.toDouble()) @@ -339,6 +342,22 @@ class NumberFormat(private val spec: Spec) { return FormattedNumber(integerString, fractionString, exponentString) } + private fun buildExponentString(exponent: Int?): String { + if (exponent == null) { + return "" + } + return if (scientificNotationIsPower) { + when (exponent) { + 0 -> "" + 1 -> "·10" + else -> "·\\(10^{${exponent}}\\)" + } + } else { + val expSign = if (exponent.sign >= 0) "+" else "" + "e$expSign${exponent}" + } + } + private fun toSiFormat(numberInfo: NumberInfo, precision: Int = -1): FormattedNumber { val expNumberInfo = if (numberInfo.exponent == null) { toExponential(numberInfo, precision - 1) @@ -449,6 +468,8 @@ class NumberFormat(private val spec: Spec) { private val SI_SUFFIXES = arrayOf("y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y") + private val POWER_REGEX = """^·\\\(10\^\{(?-?\d+)\}\\\)$""".toRegex() + fun create(spec: String): Spec { return create(parse(spec)) } diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatAlignTest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatAlignTest.kt index ef9c5b7e4c1..f96238f5168 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatAlignTest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatAlignTest.kt @@ -9,40 +9,42 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatAlignTest { + private fun format(spec: String): NumberFormat = NumberFormat(spec, false) + @Test fun alignLeft() { - assertEquals("0", NumberFormat("<1,d").apply(0)) - assertEquals("0 ", NumberFormat("<2,d").apply(0)) - assertEquals("0 ", NumberFormat("<3,d").apply(0)) - assertEquals("0 ", NumberFormat("<5,d").apply(0)) - assertEquals("0 ", NumberFormat("<8,d").apply(0)) - assertEquals("0 ", NumberFormat("<13,d").apply(0)) - assertEquals("0 ", NumberFormat("<21,d").apply(0)) + assertEquals("0", format("<1,d").apply(0)) + assertEquals("0 ", format("<2,d").apply(0)) + assertEquals("0 ", format("<3,d").apply(0)) + assertEquals("0 ", format("<5,d").apply(0)) + assertEquals("0 ", format("<8,d").apply(0)) + assertEquals("0 ", format("<13,d").apply(0)) + assertEquals("0 ", format("<21,d").apply(0)) } @Test fun alignRight() { - assertEquals("0", NumberFormat(">1,d").apply(0)) - assertEquals(" 0", NumberFormat(">2,d").apply(0)) - assertEquals(" 0", NumberFormat(">3,d").apply(0)) - assertEquals(" 0", NumberFormat(">5,d").apply(0)) - assertEquals(" 0", NumberFormat(">8,d").apply(0)) - assertEquals(" 0", NumberFormat(">13,d").apply(0)) - assertEquals(" 0", NumberFormat(">21,d").apply(0)) - assertEquals(" 1,000", NumberFormat(">21,d").apply(1000)) - assertEquals(" 1e+21", NumberFormat(">21,d").apply(1e21)) + assertEquals("0", format(">1,d").apply(0)) + assertEquals(" 0", format(">2,d").apply(0)) + assertEquals(" 0", format(">3,d").apply(0)) + assertEquals(" 0", format(">5,d").apply(0)) + assertEquals(" 0", format(">8,d").apply(0)) + assertEquals(" 0", format(">13,d").apply(0)) + assertEquals(" 0", format(">21,d").apply(0)) + assertEquals(" 1,000", format(">21,d").apply(1000)) + assertEquals(" 1e+21", format(">21,d").apply(1e21)) } @Test fun alignCenter() { - assertEquals("0", NumberFormat("^1,d").apply(0)) - assertEquals("0 ", NumberFormat("^2,d").apply(0)) - assertEquals(" 0 ", NumberFormat("^3,d").apply(0)) - assertEquals(" 0 ", NumberFormat("^5,d").apply(0)) - assertEquals(" 0 ", NumberFormat("^8,d").apply(0)) - assertEquals(" 0 ", NumberFormat("^13,d").apply(0)) - assertEquals(" 0 ", NumberFormat("^21,d").apply(0)) - assertEquals(" 1,000 ", NumberFormat("^21,d").apply(1000)) - assertEquals(" 1e+21 ", NumberFormat("^21,d").apply(1e21)) + assertEquals("0", format("^1,d").apply(0)) + assertEquals("0 ", format("^2,d").apply(0)) + assertEquals(" 0 ", format("^3,d").apply(0)) + assertEquals(" 0 ", format("^5,d").apply(0)) + assertEquals(" 0 ", format("^8,d").apply(0)) + assertEquals(" 0 ", format("^13,d").apply(0)) + assertEquals(" 0 ", format("^21,d").apply(0)) + assertEquals(" 1,000 ", format("^21,d").apply(1000)) + assertEquals(" 1e+21 ", format("^21,d").apply(1e21)) } } \ No newline at end of file diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatExtremesTest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatExtremesTest.kt index 9e70583b114..ca872d7f176 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatExtremesTest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatExtremesTest.kt @@ -9,9 +9,11 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatExtremesTest { + private fun format(spec: String): NumberFormat = NumberFormat(spec, false) + @Test fun typeS() { - val f = NumberFormat(".3s") + val f = format(".3s") assertEquals("0.00y", f.apply(Double.MIN_VALUE)) assertEquals("100000000000000Y", f.apply(1E38)) assertEquals("0.00y", f.apply(-Double.MIN_VALUE)) @@ -23,7 +25,7 @@ class NumberFormatExtremesTest { @Test fun typeE() { - val f = NumberFormat(".2e") + val f = format(".2e") assertEquals("1.00e-323", f.apply(NumberFormat.TYPE_E_MIN)) assertEquals("-1.00e-323", f.apply(-NumberFormat.TYPE_E_MIN)) diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatSignTest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatSignTest.kt index 770bc14c0c8..5de0b6ada31 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatSignTest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatSignTest.kt @@ -9,27 +9,29 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatSignTest { + private fun format(spec: String): NumberFormat = NumberFormat(spec, false) + @Test fun padAfterSign() { - assertEquals("+0", NumberFormat("=+1,d").apply(0)) - assertEquals("+0", NumberFormat("=+2,d").apply(0)) - assertEquals("+ 0", NumberFormat("=+3,d").apply(0)) - assertEquals("+ 0", NumberFormat("=+5,d").apply(0)) - assertEquals("+ 0", NumberFormat("=+8,d").apply(0)) - assertEquals("+ 0", NumberFormat("=+13,d").apply(0)) - assertEquals("+ 0", NumberFormat("=+21,d").apply(0)) - assertEquals("+ 1e+21", NumberFormat("=+21,d").apply(1e21)) + assertEquals("+0", format("=+1,d").apply(0)) + assertEquals("+0", format("=+2,d").apply(0)) + assertEquals("+ 0", format("=+3,d").apply(0)) + assertEquals("+ 0", format("=+5,d").apply(0)) + assertEquals("+ 0", format("=+8,d").apply(0)) + assertEquals("+ 0", format("=+13,d").apply(0)) + assertEquals("+ 0", format("=+21,d").apply(0)) + assertEquals("+ 1e+21", format("=+21,d").apply(1e21)) } @Test fun onlyUseSignForNegativeNumbers() { - assertEquals("-1", NumberFormat("-1,d").apply(-1)) - assertEquals("0", NumberFormat("-1,d").apply(0)) - assertEquals(" 0", NumberFormat("-2,d").apply(0)) - assertEquals(" 0", NumberFormat("-3,d").apply(0)) - assertEquals(" 0", NumberFormat("-5,d").apply(0)) - assertEquals(" 0", NumberFormat("-8,d").apply(0)) - assertEquals(" 0", NumberFormat("-13,d").apply(0)) - assertEquals(" 0", NumberFormat("-21,d").apply(0)) + assertEquals("-1", format("-1,d").apply(-1)) + assertEquals("0", format("-1,d").apply(0)) + assertEquals(" 0", format("-2,d").apply(0)) + assertEquals(" 0", format("-3,d").apply(0)) + assertEquals(" 0", format("-5,d").apply(0)) + assertEquals(" 0", format("-8,d").apply(0)) + assertEquals(" 0", format("-13,d").apply(0)) + assertEquals(" 0", format("-21,d").apply(0)) } } \ No newline at end of file diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeDTest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeDTest.kt index 896c5b1372d..1999a65179a 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeDTest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeDTest.kt @@ -9,9 +9,11 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatTypeDTest { + private fun format(spec: String): NumberFormat = NumberFormat(spec, false) + @Test fun alwaysUsesZeroPrecision() { - val f = NumberFormat(".2d") + val f = format(".2d") assertEquals("0", f.apply(0)) assertEquals("42", f.apply(42)) assertEquals("-4", f.apply(-4.2)) @@ -19,90 +21,90 @@ class NumberFormatTypeDTest { @Test fun roundsNonIntegers() { - assertEquals("4", NumberFormat("d").apply(4.2)) + assertEquals("4", format("d").apply(4.2)) } @Test fun groupThousands() { - assertEquals("0", NumberFormat("01,d").apply(0)) - assertEquals("0", NumberFormat("01,d").apply(0)) - assertEquals("00", NumberFormat("02,d").apply(0)) - assertEquals("000", NumberFormat("03,d").apply(0)) - assertEquals("0,000", NumberFormat("04,d").apply(0)) - assertEquals("0,000", NumberFormat("05,d").apply(0)) - assertEquals("00,000", NumberFormat("06,d").apply(0)) - assertEquals("0,000,000", NumberFormat("08,d").apply(0)) - assertEquals("0,000,000,000", NumberFormat("013,d").apply(0)) - assertEquals("0,000,000,000,000,000", NumberFormat("021,d").apply(0)) - assertEquals("-0,042,000,000", NumberFormat("013,d").apply(-42000000)) - assertEquals("0,000,001e+21", NumberFormat("012,d").apply(1e21)) - assertEquals("0,000,001e+21", NumberFormat("013,d").apply(1e21)) - assertEquals("00,000,001e+21", NumberFormat("014,d").apply(1e21)) - assertEquals("000,000,001e+21", NumberFormat("015,d").apply(1e21)) + assertEquals("0", format("01,d").apply(0)) + assertEquals("0", format("01,d").apply(0)) + assertEquals("00", format("02,d").apply(0)) + assertEquals("000", format("03,d").apply(0)) + assertEquals("0,000", format("04,d").apply(0)) + assertEquals("0,000", format("05,d").apply(0)) + assertEquals("00,000", format("06,d").apply(0)) + assertEquals("0,000,000", format("08,d").apply(0)) + assertEquals("0,000,000,000", format("013,d").apply(0)) + assertEquals("0,000,000,000,000,000", format("021,d").apply(0)) + assertEquals("-0,042,000,000", format("013,d").apply(-42000000)) + assertEquals("0,000,001e+21", format("012,d").apply(1e21)) + assertEquals("0,000,001e+21", format("013,d").apply(1e21)) + assertEquals("00,000,001e+21", format("014,d").apply(1e21)) + assertEquals("000,000,001e+21", format("015,d").apply(1e21)) } @Test fun groupThousandsAndZeroFillWithOverflow() { - assertEquals("1", NumberFormat("01,d").apply(1)) - assertEquals("1", NumberFormat("01,d").apply(1)) - assertEquals("12", NumberFormat("02,d").apply(12)) - assertEquals("123", NumberFormat("03,d").apply(123)) - assertEquals("12,345", NumberFormat("05,d").apply(12345)) - assertEquals("12,345,678", NumberFormat("08,d").apply(12345678)) - assertEquals("1,234,567,890,123", NumberFormat("013,d").apply(1234567890123)) + assertEquals("1", format("01,d").apply(1)) + assertEquals("1", format("01,d").apply(1)) + assertEquals("12", format("02,d").apply(12)) + assertEquals("123", format("03,d").apply(123)) + assertEquals("12,345", format("05,d").apply(12345)) + assertEquals("12,345,678", format("08,d").apply(12345678)) + assertEquals("1,234,567,890,123", format("013,d").apply(1234567890123)) } @Test fun groupThousandsAndSpaceFill() { - assertEquals("0", NumberFormat("1,d").apply(0)) - assertEquals("0", NumberFormat("1,d").apply(0)) - assertEquals(" 0", NumberFormat("2,d").apply(0)) - assertEquals(" 0", NumberFormat("3,d").apply(0)) - assertEquals(" 0", NumberFormat("5,d").apply(0)) - assertEquals(" 0", NumberFormat("8,d").apply(0)) - assertEquals(" 0", NumberFormat("13,d").apply(0)) - assertEquals(" 0", NumberFormat("21,d").apply(0)) + assertEquals("0", format("1,d").apply(0)) + assertEquals("0", format("1,d").apply(0)) + assertEquals(" 0", format("2,d").apply(0)) + assertEquals(" 0", format("3,d").apply(0)) + assertEquals(" 0", format("5,d").apply(0)) + assertEquals(" 0", format("8,d").apply(0)) + assertEquals(" 0", format("13,d").apply(0)) + assertEquals(" 0", format("21,d").apply(0)) } @Test fun groupThousandsAndSpaceFillWithOverflow() { - assertEquals("1", NumberFormat("1,d").apply(1)) - assertEquals("12", NumberFormat("2,d").apply(12)) - assertEquals("123", NumberFormat("3,d").apply(123)) - assertEquals("12,345", NumberFormat("5,d").apply(12345)) - assertEquals("12,345,678", NumberFormat("8,d").apply(12345678)) - assertEquals("1,234,567,890,123", NumberFormat("13,d").apply(1234567890123)) + assertEquals("1", format("1,d").apply(1)) + assertEquals("12", format("2,d").apply(12)) + assertEquals("123", format("3,d").apply(123)) + assertEquals("12,345", format("5,d").apply(12345)) + assertEquals("12,345,678", format("8,d").apply(12345678)) + assertEquals("1,234,567,890,123", format("13,d").apply(1234567890123)) } @Test fun padAfterSignWithCurrency() { - assertEquals("+$0", NumberFormat("=+$1,d").apply(0)) - assertEquals("+$0", NumberFormat("=+$1,d").apply(0)) - assertEquals("+$0", NumberFormat("=+$2,d").apply(0)) - assertEquals("+$0", NumberFormat("=+$3,d").apply(0)) - assertEquals("+$ 0", NumberFormat("=+$5,d").apply(0)) - assertEquals("+$ 0", NumberFormat("=+$8,d").apply(0)) - assertEquals("+$ 0", NumberFormat("=+$13,d").apply(0)) - assertEquals("+$ 0", NumberFormat("=+$21,d").apply(0)) - assertEquals("+$ 1e+21", NumberFormat("=+$21,d").apply(1e21)) + assertEquals("+$0", format("=+$1,d").apply(0)) + assertEquals("+$0", format("=+$1,d").apply(0)) + assertEquals("+$0", format("=+$2,d").apply(0)) + assertEquals("+$0", format("=+$3,d").apply(0)) + assertEquals("+$ 0", format("=+$5,d").apply(0)) + assertEquals("+$ 0", format("=+$8,d").apply(0)) + assertEquals("+$ 0", format("=+$13,d").apply(0)) + assertEquals("+$ 0", format("=+$21,d").apply(0)) + assertEquals("+$ 1e+21", format("=+$21,d").apply(1e21)) } @Test fun aSpaceCanDenotePositiveNumbers() { - assertEquals("-1", NumberFormat(" 1,d").apply(-1)) - assertEquals(" 0", NumberFormat(" 1,d").apply(0)) - assertEquals(" 0", NumberFormat(" 2,d").apply(0)) - assertEquals(" 0", NumberFormat(" 3,d").apply(0)) - assertEquals(" 0", NumberFormat(" 5,d").apply(0)) - assertEquals(" 0", NumberFormat(" 8,d").apply(0)) - assertEquals(" 0", NumberFormat(" 13,d").apply(0)) - assertEquals(" 0", NumberFormat(" 21,d").apply(0)) - assertEquals(" 1e+21", NumberFormat(" 21,d").apply(1e21)) + assertEquals("-1", format(" 1,d").apply(-1)) + assertEquals(" 0", format(" 1,d").apply(0)) + assertEquals(" 0", format(" 2,d").apply(0)) + assertEquals(" 0", format(" 3,d").apply(0)) + assertEquals(" 0", format(" 5,d").apply(0)) + assertEquals(" 0", format(" 8,d").apply(0)) + assertEquals(" 0", format(" 13,d").apply(0)) + assertEquals(" 0", format(" 21,d").apply(0)) + assertEquals(" 1e+21", format(" 21,d").apply(1e21)) } @Test fun formatNegativeZeroAsZero() { - assertEquals("0", NumberFormat("1d").apply(-0)) - assertEquals("0", NumberFormat("1d").apply(-1e-12)) + assertEquals("0", format("1d").apply(-0)) + assertEquals("0", format("1d").apply(-1e-12)) } } \ No newline at end of file diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeETest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeETest.kt index 1bac4a9e17c..f114f62702e 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeETest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeETest.kt @@ -9,10 +9,9 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatTypeETest { - @Test fun canOutputExponentNotation() { - val f = NumberFormat("e") + val f = NumberFormat("e", false) assertEquals("0.000000", f.apply(0)) assertEquals("4.200000e+1", f.apply(42)) assertEquals("4.200000e+7", f.apply(42000000)) @@ -22,13 +21,28 @@ class NumberFormatTypeETest { assertEquals("-4.200000e+6", f.apply(-4200000)) assertEquals("-4.200000e+7", f.apply(-42000000)) - assertEquals("4e+1", NumberFormat(".0e").apply(42)) - assertEquals("4.200e+1", NumberFormat(".3e").apply(42)) + assertEquals("4e+1", NumberFormat(".0e", false).apply(42)) + assertEquals("4.200e+1", NumberFormat(".3e", false).apply(42)) } @Test fun canFormatNegativeZeroAsZero() { - assertEquals("0.000000", NumberFormat("1e").apply(-0)) - assertEquals("-1.000000e-12", NumberFormat("1e").apply(-1e-12)) + assertEquals("0.000000", NumberFormat("1e", false).apply(-0)) + assertEquals("-1.000000e-12", NumberFormat("1e", false).apply(-1e-12)) + } + + @Test + fun canOutputScientificExponentNotation() { + val f = NumberFormat("e", true) + assertEquals("0.000000", f.apply(0)) + assertEquals("1.500000", f.apply(1.5e0)) + assertEquals("1.500000·10", f.apply(1.5e1)) + assertEquals("1.500000·\\(10^{-1}\\)", f.apply(1.5e-1)) + assertEquals("1.500000·\\(10^{2}\\)", f.apply(1.5e2)) + assertEquals("1.500000·\\(10^{-2}\\)", f.apply(1.5e-2)) + assertEquals("1.500000·\\(10^{16}\\)", f.apply(1.5e16)) + assertEquals("1.500000·\\(10^{-16}\\)", f.apply(1.5e-16)) + assertEquals("-1.500000·\\(10^{16}\\)", f.apply(-1.5e16)) + assertEquals("-1.500000·\\(10^{-16}\\)", f.apply(-1.5e-16)) } } \ No newline at end of file diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeGTest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeGTest.kt index d457d6af424..f7b6af6b718 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeGTest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeGTest.kt @@ -9,25 +9,27 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatTypeGTest { + private fun format(spec: String): NumberFormat = NumberFormat(spec, false) + @Test fun canOutputGeneralNotation() { - assertEquals("0.00026986", NumberFormat("g").apply(2.6985974025974023E-4)) - assertEquals("0.05", NumberFormat(".1g").apply(0.049)) - assertEquals("0.5", NumberFormat(".1g").apply(0.49)) - assertEquals("0.45", NumberFormat(".2g").apply(0.449)) - assertEquals("0.445", NumberFormat(".3g").apply(0.4449)) - assertEquals("0.44445", NumberFormat(".5g").apply(0.444449)) - assertEquals("1e+2", NumberFormat(".1g").apply(100)) - assertEquals("1e+2", NumberFormat(".2g").apply(100)) - assertEquals("100", NumberFormat(".3g").apply(100)) - assertEquals("100", NumberFormat(".5g").apply(100)) - assertEquals("100.2", NumberFormat(".5g").apply(100.2)) - assertEquals("0.002", NumberFormat(".2g").apply(0.002)) + assertEquals("0.00026986", format("g").apply(2.6985974025974023E-4)) + assertEquals("0.05", format(".1g").apply(0.049)) + assertEquals("0.5", format(".1g").apply(0.49)) + assertEquals("0.45", format(".2g").apply(0.449)) + assertEquals("0.445", format(".3g").apply(0.4449)) + assertEquals("0.44445", format(".5g").apply(0.444449)) + assertEquals("1e+2", format(".1g").apply(100)) + assertEquals("1e+2", format(".2g").apply(100)) + assertEquals("100", format(".3g").apply(100)) + assertEquals("100", format(".5g").apply(100)) + assertEquals("100.2", format(".5g").apply(100.2)) + assertEquals("0.002", format(".2g").apply(0.002)) } @Test fun canGroupThousandsWithGeneralNotation() { - val f = NumberFormat(",.12g") + val f = format(",.12g") assertEquals("0", f.apply(0)) assertEquals("42", f.apply(42)) assertEquals("42,000,000", f.apply(42000000)) diff --git a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeNoneTest.kt b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeNoneTest.kt index c653cb76c00..5b3e5cda731 100644 --- a/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeNoneTest.kt +++ b/commons/src/commonTest/kotlin/org/jetbrains/letsPlot/commons/formatting/number/NumberFormatTypeNoneTest.kt @@ -9,43 +9,45 @@ import kotlin.test.Test import kotlin.test.assertEquals class NumberFormatTypeNoneTest { + private fun format(spec: String): NumberFormat = NumberFormat(spec, false) + @Test fun usesSignificantPrecisionAndTrimsInsignificantZeros() { //assertEquals("5", Format(".1").apply(4.9)) - assertEquals("0.5", NumberFormat(".1").apply(0.49)) - assertEquals("4.9", NumberFormat(".2").apply(4.9)) - assertEquals("0.49", NumberFormat(".2").apply(0.49)) - assertEquals("0.45", NumberFormat(".2").apply(0.449)) - assertEquals("4.9", NumberFormat(".3").apply(4.9)) - assertEquals("0.49", NumberFormat(".3").apply(0.49)) - assertEquals("0.449", NumberFormat(".3").apply(0.449)) - assertEquals("0.445", NumberFormat(".3").apply(0.4449)) - assertEquals("0.44445", NumberFormat(".5").apply(0.444449)) + assertEquals("0.5", format(".1").apply(0.49)) + assertEquals("4.9", format(".2").apply(4.9)) + assertEquals("0.49", format(".2").apply(0.49)) + assertEquals("0.45", format(".2").apply(0.449)) + assertEquals("4.9", format(".3").apply(4.9)) + assertEquals("0.49", format(".3").apply(0.49)) + assertEquals("0.449", format(".3").apply(0.449)) + assertEquals("0.445", format(".3").apply(0.4449)) + assertEquals("0.44445", format(".5").apply(0.444449)) } @Test fun doesNotTrimSignificantZeros() { - assertEquals("10", NumberFormat(".5").apply(10)) - assertEquals("100", NumberFormat(".5").apply(100)) - assertEquals("1000", NumberFormat(".5").apply(1000)) - assertEquals("21010", NumberFormat(".5").apply(21010)) - assertEquals("1.1", NumberFormat(".5").apply(1.10001)) - assertEquals("1.1e+6", NumberFormat(".5").apply(1.10001e6)) - assertEquals("1.10001", NumberFormat(".6").apply(1.10001)) - assertEquals("1.10001e+6", NumberFormat(".6").apply(1.10001e6)) + assertEquals("10", format(".5").apply(10)) + assertEquals("100", format(".5").apply(100)) + assertEquals("1000", format(".5").apply(1000)) + assertEquals("21010", format(".5").apply(21010)) + assertEquals("1.1", format(".5").apply(1.10001)) + assertEquals("1.1e+6", format(".5").apply(1.10001e6)) + assertEquals("1.10001", format(".6").apply(1.10001)) + assertEquals("1.10001e+6", format(".6").apply(1.10001e6)) } @Test fun alsoTrimsDecimalPointIfThereAreOnlyInsignificantZeros() { - assertEquals("1", NumberFormat(".5").apply(1.00001)) - assertEquals("1e+6", NumberFormat(".5").apply(1.00001e6)) - assertEquals("1.00001", NumberFormat(".6").apply(1.00001)) - assertEquals("1.00001e+6", NumberFormat(".6").apply(1.00001e6)) + assertEquals("1", format(".5").apply(1.00001)) + assertEquals("1e+6", format(".5").apply(1.00001e6)) + assertEquals("1.00001", format(".6").apply(1.00001)) + assertEquals("1.00001e+6", format(".6").apply(1.00001e6)) } @Test fun canOutputCurrency() { - val f = NumberFormat("$") + val f = format("$") assertEquals("$0", f.apply(0)) assertEquals("$0.042", f.apply(.042)) assertEquals("$0.42", f.apply(.42)) @@ -58,6 +60,6 @@ class NumberFormatTypeNoneTest { @Test fun canFormatNegativeZeroAsZero() { - assertEquals("0", NumberFormat("").apply(-0)) + assertEquals("0", format("").apply(-0)) } } \ No newline at end of file diff --git a/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgTSpanElement.kt b/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgTSpanElement.kt index a94c869f107..f6c03434b56 100644 --- a/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgTSpanElement.kt +++ b/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgTSpanElement.kt @@ -23,6 +23,10 @@ class SvgTSpanElement() : SvgElement(), SvgTextContent { SvgAttributeSpec.createSpec("x") private val Y: SvgAttributeSpec = SvgAttributeSpec.createSpec("y") + val BASELINE_SHIFT: SvgAttributeSpec = + SvgAttributeSpec.createSpec("baseline-shift") + val FONT_SIZE: SvgAttributeSpec = + SvgAttributeSpec.createSpec("font-size") } override val elementName = "tspan" @@ -94,4 +98,9 @@ class SvgTSpanElement() : SvgElement(), SvgTextContent { override fun textDy(): Property { return getAttribute(TEXT_DY) } -} \ No newline at end of file +} + +enum class BaselineShift(val value: String) { + SUPER("super"), + SUB("sub"), +} diff --git a/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgUtils.kt b/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgUtils.kt index bb2c170ca8b..d17ba1efb7f 100644 --- a/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgUtils.kt +++ b/datamodel/src/commonMain/kotlin/org/jetbrains/letsPlot/datamodel/svg/dom/SvgUtils.kt @@ -113,4 +113,16 @@ object SvgUtils { .append(base64EncodedPngImage) .toString() } + + // Useful for debugging + fun getRoot(node: SvgNode): SvgNode { + tailrec fun findRoot(currNode: SvgNode): SvgNode { + return if (currNode.parent().get() == null) { + currNode + } else { + findRoot(currNode.parent().get()!!) + } + } + return findRoot(node) + } } diff --git a/demo/common-batik/build.gradle b/demo/common-batik/build.gradle deleted file mode 100644 index 747ae04e83c..00000000000 --- a/demo/common-batik/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2019. JetBrains s.r.o. - * Use of this source code is governed by the MIT license that can be found in the LICENSE file. - */ - -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - - -kotlin { - jvm() - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - implementation project(':commons') - implementation project(':datamodel') - implementation project(':plot-base') - implementation project(':plot-builder') - implementation project(':plot-stem') - } - } - jvmMain { - dependencies { - implementation kotlin('stdlib-jdk8') - implementation project(':canvas') - implementation project(':platf-awt') - implementation project(':platf-batik') - implementation project(':demo-common-util') - - implementation("org.apache.xmlgraphics:batik-codec:$batik_version") - } - } - } -} - diff --git a/demo/common-batik/build.gradle.kts b/demo/common-batik/build.gradle.kts new file mode 100644 index 00000000000..e0084e35a25 --- /dev/null +++ b/demo/common-batik/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + + val batikVersion = extra["batik_version"] as String + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":plot-base")) + implementation(project(":plot-builder")) + implementation(project(":plot-stem")) + } + } + jvmMain { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + implementation(project(":canvas")) + implementation(project(":platf-awt")) + implementation(project(":platf-batik")) + implementation(project(":demo-common-util")) + + implementation("org.apache.xmlgraphics:batik-codec:${batikVersion}") + } + } + } +} diff --git a/demo/common-jfx/build.gradle b/demo/common-jfx/build.gradle deleted file mode 100644 index 41c3a2604b1..00000000000 --- a/demo/common-jfx/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2019. JetBrains s.r.o. - * Use of this source code is governed by the MIT license that can be found in the LICENSE file. - */ - -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -kotlin { - jvm() - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - implementation project(':commons') - implementation project(':datamodel') - implementation project(':plot-base') - implementation project(':plot-builder') - implementation project(':plot-stem') - api project(':demo-common-util') - } - } - jvmMain { - dependencies { - implementation kotlin('stdlib-jdk8') - - compileOnly("org.openjfx:javafx-swing:$jfx_version:${jfx_platform()}") - - implementation project(':canvas') - implementation project(':platf-awt') - implementation project(':platf-jfx-swing') - } - } - } -} diff --git a/demo/common-jfx/build.gradle.kts b/demo/common-jfx/build.gradle.kts new file mode 100644 index 00000000000..8cd89601947 --- /dev/null +++ b/demo/common-jfx/build.gradle.kts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + + val jfxPlatform = extra["jfx_platform_resolved"] as String + val jfxVersion = extra["jfx_version"] as String + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":plot-base")) + implementation(project(":plot-builder")) + implementation(project(":plot-stem")) + api(project(":demo-common-util")) + } + } + jvmMain { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + implementation(project(":canvas")) + implementation(project(":platf-awt")) + implementation(project(":platf-jfx-swing")) + + compileOnly("org.openjfx:javafx-swing:${jfxVersion}:${jfxPlatform}") + } + } + } +} diff --git a/demo/common-util/build.gradle b/demo/common-util/build.gradle deleted file mode 100644 index 9900c236028..00000000000 --- a/demo/common-util/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -kotlin { - jvm() - js() { - browser() - } - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - implementation project(':commons') - implementation project(':datamodel') - implementation project(':plot-builder') - } - } - jvmMain { - dependencies { - implementation kotlin('stdlib-jdk8') - implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinx_html_version}" - } - } - jsMain { - dependencies { - implementation kotlin('stdlib-js') - } - } - } -} diff --git a/demo/common-util/build.gradle.kts b/demo/common-util/build.gradle.kts new file mode 100644 index 00000000000..f74938606fb --- /dev/null +++ b/demo/common-util/build.gradle.kts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + js { + browser() + } + + val kotlinxHtmlVersion = extra["kotlinx_html_version"] as String + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":plot-builder")) + } + } + jvmMain { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinxHtmlVersion}") + } + } + jsMain { + dependencies { + implementation(kotlin("stdlib-js")) + } + } + } +} diff --git a/demo/livemap/build.gradle b/demo/livemap/build.gradle deleted file mode 100644 index d9df73ac9ee..00000000000 --- a/demo/livemap/build.gradle +++ /dev/null @@ -1,121 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -// KT-55751. MPP / Gradle: Consumable configurations must have unique attributes. -// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes -// -def dummyAttribute = Attribute.of("dummyAttribute", String) - -kotlin { - jvm("jvmJfx") { - attributes.attribute(dummyAttribute, "jvmJfx") - } - jvm("jvmRawJfx") { - attributes.attribute(dummyAttribute, "jvmRawJfx") - } - jvm("jvmRawAwt") { - attributes.attribute(dummyAttribute, "jvmRawAwt") - } - jvm("jvmBrowser") { - attributes.attribute(dummyAttribute, "jvmBrowser") - } - jvm("jvmJfxPlot") { - attributes.attribute(dummyAttribute, "jvmJfxPlot") - } - jvm("jvmBatikPlot") - js { - browser() - binaries.executable() - } - - - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - implementation project(':commons') - implementation project(':datamodel') - implementation project(':plot-base') - implementation project(':plot-builder') - implementation project(':plot-stem') - implementation project(':canvas') - implementation project(':gis') - implementation project(':livemap') - implementation project(':plot-livemap') - implementation project(':demo-and-test-shared') - implementation project(':demo-common-util') - } - } - allJvm { - dependencies { - implementation kotlin('stdlib-jdk8') - compileOnly "io.github.microutils:kotlin-logging-jvm:$kotlinLogging_version" - implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinx_html_version}" - implementation "io.ktor:ktor-client-cio:$ktor_version" - implementation "org.slf4j:slf4j-simple:$slf4j_version" // Enable logging to console - implementation("org.openjfx:javafx-base:$jfx_version:${jfx_platform()}") - implementation("org.openjfx:javafx-controls:$jfx_version:${jfx_platform()}") - implementation("org.openjfx:javafx-graphics:$jfx_version:${jfx_platform()}") - implementation("org.openjfx:javafx-swing:$jfx_version:${jfx_platform()}") - } - } - - jvmJfxMain { - dependsOn allJvm - dependencies { - implementation project(':platf-jfx-swing') - } - } - - jvmJfxPlotMain { - dependsOn allJvm - dependencies { - implementation project(':platf-jfx-swing') - implementation project(':platf-awt') - implementation project(':demo-common-jfx') - } - } - - jvmBatikPlotMain { - dependsOn allJvm - dependencies { - implementation project(':demo-common-batik') - implementation project(':platf-awt') - implementation project(':platf-batik') - - implementation("org.apache.xmlgraphics:batik-codec:$batik_version") - } - } - - jvmRawJfxMain { - dependsOn allJvm - dependencies { - implementation project(':platf-jfx-swing') - } - } - - jvmRawAwtMain { - dependsOn allJvm - dependencies { - implementation project(':platf-awt') - } - } - - jvmBrowserMain { - dependsOn allJvm - } - - jsMain { - dependencies { - implementation kotlin('stdlib-js') - implementation project(':platf-w3c') - - implementation("io.ktor:ktor-client-websockets:$ktor_version") - implementation("io.ktor:ktor-client-js:$ktor_version") - - languageSettings.optIn("kotlin.js.ExperimentalJsExport") - } - } - } -} diff --git a/demo/livemap/build.gradle.kts b/demo/livemap/build.gradle.kts new file mode 100644 index 00000000000..0cd7058e594 --- /dev/null +++ b/demo/livemap/build.gradle.kts @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +// KT-55751. MPP / Gradle: Consumable configurations must have unique attributes. +// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes +// +val dummyAttribute = Attribute.of("dummyAttribute", String::class.java) + +kotlin { + jvm("jvmJfx") { + attributes.attribute(dummyAttribute, "jvmJfx") + } + jvm("jvmRawJfx") { + attributes.attribute(dummyAttribute, "jvmRawJfx") + } + jvm("jvmRawAwt") { + attributes.attribute(dummyAttribute, "jvmRawAwt") + } + jvm("jvmBrowser") { + attributes.attribute(dummyAttribute, "jvmBrowser") + } + jvm("jvmJfxPlot") { + attributes.attribute(dummyAttribute, "jvmJfxPlot") + } + jvm("jvmBatikPlot") + js { + browser() + binaries.executable() + } + + val batikVersion = extra["batik_version"] as String + val kotlinLoggingVersion = extra["kotlinLogging_version"] as String + val kotlinxHtmlVersion = extra["kotlinx_html_version"] as String + val ktorVersion = extra["ktor_version"] as String + val jfxPlatform = extra["jfx_platform_resolved"] as String + val jfxVersion = extra["jfx_version"] as String + + // Fix "The Default Kotlin Hierarchy Template was not applied to 'project'..." warning + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":plot-base")) + implementation(project(":plot-builder")) + implementation(project(":plot-stem")) + implementation(project(":canvas")) + implementation(project(":gis")) + implementation(project(":livemap")) + implementation(project(":plot-livemap")) + implementation(project(":demo-and-test-shared")) + implementation(project(":demo-common-util")) + } + } + val allJvm by creating { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + compileOnly("io.github.microutils:kotlin-logging-jvm:${kotlinLoggingVersion}") + implementation("io.ktor:ktor-client-cio:${ktorVersion}") + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinxHtmlVersion}") + implementation("org.openjfx:javafx-base:${jfxVersion}:${jfxPlatform}") + implementation("org.openjfx:javafx-controls:${jfxVersion}:${jfxPlatform}") + implementation("org.openjfx:javafx-graphics:${jfxVersion}:${jfxPlatform}") + implementation("org.openjfx:javafx-swing:${jfxVersion}:${jfxPlatform}") + implementation("org.slf4j:slf4j-simple:${extra["slf4j_version"]}") // Enable logging to console + } + } + named("jvmJfxMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":platf-jfx-swing")) + } + } + named("jvmRawJfxMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":platf-jfx-swing")) + } + } + named("jvmRawAwtMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":platf-awt")) + } + } + named("jvmBrowserMain") { + dependsOn(allJvm) + } + named("jvmJfxPlotMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":platf-jfx-swing")) + implementation(project(":platf-awt")) + implementation(project(":demo-common-jfx")) + } + } + named("jvmBatikPlotMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":demo-common-batik")) + implementation(project(":platf-awt")) + implementation(project(":platf-batik")) + + implementation("org.apache.xmlgraphics:batik-codec:${batikVersion}") + } + } + jsMain { + languageSettings.optIn("kotlin.js.ExperimentalJsExport") + dependencies { + implementation(kotlin("stdlib-js")) + implementation(project(":platf-w3c")) + + implementation("io.ktor:ktor-client-js:${ktorVersion}") + implementation("io.ktor:ktor-client-websockets:${ktorVersion}") + } + } + } +} diff --git a/demo/plot-common/build.gradle b/demo/plot-common/build.gradle deleted file mode 100644 index 162d4c21f61..00000000000 --- a/demo/plot-common/build.gradle +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019. JetBrains s.r.o. - * Use of this source code is governed by the MIT license that can be found in the LICENSE file. - */ - -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -kotlin { - jvm() - js() { - browser() - } - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - implementation project(':commons') - implementation project(':datamodel') - implementation project(':plot-base') - implementation project(':plot-builder') - implementation project(':plot-stem') - implementation project(':demo-common-util') - implementation project(':demo-and-test-shared') - } - } - - jvmMain { - dependencies { - implementation kotlin('stdlib-jdk8') - compileOnly "io.github.microutils:kotlin-logging-jvm:$kotlinLogging_version" -// implementation "org.slf4j:slf4j-simple:$slf4j_version" // Enable logging to console - } - } - - jsMain { - dependencies { - implementation kotlin('stdlib-js') - } - } - } -} diff --git a/demo/plot-common/build.gradle.kts b/demo/plot-common/build.gradle.kts new file mode 100644 index 00000000000..c4f370d5d78 --- /dev/null +++ b/demo/plot-common/build.gradle.kts @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + js { + browser() + } + + val kotlinLoggingVersion = extra["kotlinLogging_version"] as String + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":plot-base")) + implementation(project(":plot-builder")) + implementation(project(":plot-stem")) + implementation(project(":demo-common-util")) + implementation(project(":demo-and-test-shared")) + } + } + jvmMain { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + compileOnly("io.github.microutils:kotlin-logging-jvm:${kotlinLoggingVersion}") +// implementation ("org.slf4j:slf4j-simple:${extra["slf4j_version"]}") // Enable logging to console + } + } + jsMain { + dependencies{ + implementation(kotlin("stdlib-js")) + } + } + } +} diff --git a/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/GGGridTheme.kt b/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/GGGridTheme.kt index 37a749f28f4..60eda279f38 100644 --- a/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/GGGridTheme.kt +++ b/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/GGGridTheme.kt @@ -44,16 +44,9 @@ open class GGGridTheme { private fun theme(name: String?, flavor: String?, plotMargin: Int? = null): Map { return HashMap().also { m -> - name?.let { m["name"] = name } - flavor?.let { m["flavor"] = flavor } - plotMargin?.let { - m["plot_margin"] = mapOf( - "t" to plotMargin, - "r" to plotMargin, - "b" to plotMargin, - "l" to plotMargin, - ) - } + name?.let { m["name"] = it } + flavor?.let { m["flavor"] = it } + plotMargin?.let { m["plot_margin"] = it } } } diff --git a/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/ThemeOptions.kt b/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/ThemeOptions.kt index e17aa05898f..acc868d6d0c 100644 --- a/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/ThemeOptions.kt +++ b/demo/plot-common/src/commonMain/kotlin/demo/plot/common/model/plotConfig/ThemeOptions.kt @@ -11,15 +11,33 @@ class ThemeOptions { fun plotSpecList(): List> { return listOf( // use predefined themes - withTheme("none"), - withTheme("minimal"), - withTheme("minimal2"), - withTheme("classic"), - withTheme("light"), - withTheme("grey"), - withTheme("bw"), + withTheme("none"), + withTheme("minimal"), + withTheme("minimal2"), + withTheme("classic"), + withTheme("light"), + withTheme("grey"), + withTheme("bw"), - setThemeOptions() + setThemeOptions(), + margins(), + ) + } + + private fun margins(): MutableMap { + val theme = """ + 'theme': { + 'plot_background': {'size': 6, 'blank': false}, + 'legend_position': 'none', + 'axis_title': {'margin': [ null, null, 0, 0 ], 'size': 15, 'blank': false}, + 'axis_text_x': {'margin': [ 20, null, null, null ], 'size': 15, 'blank': false}, + 'plot_margin': [80, 10, null] + } + """.trimIndent() + + return plot( + plotTitle = "Margins for axis_title, axis_text_x and plot_margin", + theme = theme ) } diff --git a/demo/plot-export/build.gradle b/demo/plot-export/build.gradle deleted file mode 100644 index c3d806af0e0..00000000000 --- a/demo/plot-export/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019. JetBrains s.r.o. - * Use of this source code is governed by the MIT license that can be found in the LICENSE file. - */ - -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - - -kotlin { - jvm("jvmBrowser") - - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - - implementation project(':commons') - implementation project(':plot-image-export') - implementation project(':demo-plot-common') - implementation project(':demo-common-util') - } - } - - jvmBrowserMain { - dependencies { - implementation kotlin('stdlib-jdk8') - compileOnly "io.github.microutils:kotlin-logging-jvm:$kotlinLogging_version" - implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinx_html_version}" - implementation "org.slf4j:slf4j-simple:$slf4j_version" // Enable logging to console - } - } - } -} diff --git a/demo/plot-export/build.gradle.kts b/demo/plot-export/build.gradle.kts new file mode 100644 index 00000000000..8434504120e --- /dev/null +++ b/demo/plot-export/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm("jvmBrowser") + + val kotlinLoggingVersion = extra["kotlinLogging_version"] as String + val kotlinxHtmlVersion = extra["kotlinx_html_version"] as String + + sourceSets{ + commonMain{ + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":plot-image-export")) + implementation(project(":demo-plot-common")) + implementation(project(":demo-common-util")) + } + } + named("jvmBrowserMain") { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + compileOnly("io.github.microutils:kotlin-logging-jvm:${kotlinLoggingVersion}") + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinxHtmlVersion}") + implementation("org.slf4j:slf4j-simple:${extra["slf4j_version"]}") // Enable logging to console + } + } + } +} diff --git a/demo/plot/build.gradle b/demo/plot/build.gradle deleted file mode 100644 index 62c0de738e0..00000000000 --- a/demo/plot/build.gradle +++ /dev/null @@ -1,117 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - - -/* - * Copyright (c) 2019. JetBrains s.r.o. - * Use of this source code is governed by the MIT license that can be found in the LICENSE file. - */ - -// KT-55751. MPP / Gradle: Consumable configurations must have unique attributes. -// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes -// -def dummyAttribute = Attribute.of("dummyAttribute", String) - -kotlin { - jvm("jvmBatik") - jvm("jvmJfx") { - attributes.attribute(dummyAttribute, "jvmJfx") - } - jvm("jvmBrowser") { - attributes.attribute(dummyAttribute, "jvmBrowser") - } - js() { - browser() - binaries.executable() - } - - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - - implementation project(':commons') - implementation project(':datamodel') - implementation project(':plot-base') - implementation project(':plot-builder') - implementation project(':plot-stem') - implementation project(':demo-plot-common') - implementation project(':demo-common-util') - implementation project(':demo-and-test-shared') - } - } - - allJvm { - dependencies { - implementation kotlin('stdlib-jdk8') - implementation "io.github.microutils:kotlin-logging-jvm:$kotlinLogging_version" - implementation "org.slf4j:slf4j-simple:$slf4j_version" // Enable logging to console - implementation project(':canvas') - implementation project(':livemap') - implementation project(':plot-livemap') - implementation project(':gis') - implementation "io.ktor:ktor-client-cio:$ktor_version" - } - } - - jvmBatikMain { - dependsOn allJvm - dependencies { - implementation project(':demo-common-batik') - implementation project(':platf-awt') - implementation project(':platf-batik') - - implementation("org.apache.xmlgraphics:batik-codec:$batik_version") - } - } - - jvmJfxMain { - dependsOn allJvm - dependencies { - implementation project(':canvas') - implementation project(':platf-awt') - implementation project(':platf-jfx-swing') - implementation project(':demo-common-jfx') - - implementation("org.openjfx:javafx-base:$jfx_version:${jfx_platform()}") - implementation("org.openjfx:javafx-graphics:$jfx_version:${jfx_platform()}") - implementation("org.openjfx:javafx-swing:$jfx_version:${jfx_platform()}") - } - } - - jvmBrowserMain { - dependsOn allJvm - dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinx_html_version}" - implementation project(':platf-awt') - } - } - - jsMain { - dependencies { - implementation kotlin('stdlib-js') - implementation project(':platf-w3c') - - languageSettings.optIn("kotlin.js.ExperimentalJsExport") - } - } - } -} - - -// Fix Gradle 7 error: -// Execution failed for task ':plot-demo:jvm*Jar'. -// Entry diamonds.csv is a duplicate but no duplicate handling strategy has been set. - -jvmBatikJar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -jvmBrowserJar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -jvmJfxJar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} diff --git a/demo/plot/build.gradle.kts b/demo/plot/build.gradle.kts new file mode 100644 index 00000000000..ec84302b7a2 --- /dev/null +++ b/demo/plot/build.gradle.kts @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +// KT-55751. MPP / Gradle: Consumable configurations must have unique attributes. +// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes +// +val dummyAttribute = Attribute.of("dummyAttribute", String::class.java) + +kotlin { + jvm("jvmBatik") { + attributes.attribute(dummyAttribute, "jvmBatik") + } + jvm("jvmJfx") { + attributes.attribute(dummyAttribute, "jvmJfx") + } + jvm("jvmBrowser") + js { + browser() + binaries.executable() + } + + val batikVersion = extra["batik_version"] as String + val kotlinLoggingVersion = extra["kotlinLogging_version"] as String + val kotlinxHtmlVersion = extra["kotlinx_html_version"] as String + val ktorVersion = extra["ktor_version"] as String + val jfxPlatform = extra["jfx_platform_resolved"] as String + val jfxVersion = extra["jfx_version"] as String + + // Fix "The Default Kotlin Hierarchy Template was not applied to 'project'..." warning + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":plot-base")) + implementation(project(":plot-builder")) + implementation(project(":plot-stem")) + implementation(project(":demo-plot-common")) + implementation(project(":demo-common-util")) + implementation(project(":demo-and-test-shared")) + } + } + val allJvm by creating { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + implementation(project(":canvas")) + implementation(project(":livemap")) + implementation(project(":plot-livemap")) + implementation(project(":gis")) + + implementation("io.github.microutils:kotlin-logging-jvm:${kotlinLoggingVersion}") + implementation("io.ktor:ktor-client-cio:${ktorVersion}") + implementation("org.slf4j:slf4j-simple:${extra["slf4j_version"]}") // Enable logging to console + } + } + named("jvmBatikMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":demo-common-batik")) + implementation(project(":platf-awt")) + implementation(project(":platf-batik")) + + implementation("org.apache.xmlgraphics:batik-codec:${batikVersion}") + } + } + named("jvmJfxMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":canvas")) + implementation(project(":platf-awt")) + implementation(project(":platf-jfx-swing")) + implementation(project(":demo-common-jfx")) + + implementation("org.openjfx:javafx-base:${jfxVersion}:${jfxPlatform}") + implementation("org.openjfx:javafx-graphics:${jfxVersion}:${jfxPlatform}") + implementation("org.openjfx:javafx-swing:${jfxVersion}:${jfxPlatform}") + } + } + named("jvmBrowserMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":platf-awt")) + + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinxHtmlVersion}") + } + } + jsMain { + languageSettings.optIn("kotlin.js.ExperimentalJsExport") + dependencies { + implementation(kotlin("stdlib-js")) + + implementation(project(":platf-w3c")) + } + } + } +} diff --git a/demo/plot/src/commonMain/kotlin/demo/plot/shared/model/component/RichTextDemo.kt b/demo/plot/src/commonMain/kotlin/demo/plot/shared/model/component/RichTextDemo.kt new file mode 100644 index 00000000000..13c8b3430bf --- /dev/null +++ b/demo/plot/src/commonMain/kotlin/demo/plot/shared/model/component/RichTextDemo.kt @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package demo.plot.shared.model.component + +import demo.plot.common.model.SimpleDemoBase +import org.jetbrains.letsPlot.commons.geometry.DoubleVector +import org.jetbrains.letsPlot.core.plot.base.render.svg.GroupComponent +import org.jetbrains.letsPlot.core.plot.base.render.svg.Text +import org.jetbrains.letsPlot.core.plot.base.render.svg.TextLabel +import org.jetbrains.letsPlot.datamodel.svg.dom.* + +class RichTextDemo : SimpleDemoBase(DEMO_BOX_SIZE) { + fun createModel(): GroupComponent { + val groupComponent = GroupComponent() + + val exampleIdIter = generateSequence(1) { it + 1 }.iterator() + val shiftIter = generateSequence(INIT_SHIFT) { DoubleVector(it.x, it.y + DY_SHIFT) }.iterator() + + // Example #1 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + )) + // Example #2 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + hAnchor = Text.HorizontalAnchor.MIDDLE, + vAnchor = Text.VerticalAnchor.CENTER, + )) + // Example #3 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + hAnchor = Text.HorizontalAnchor.RIGHT, + vAnchor = Text.VerticalAnchor.TOP, + )) + // Example #4 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + hAnchor = Text.HorizontalAnchor.MIDDLE, + vAnchor = Text.VerticalAnchor.CENTER, + angle = 45.0, + )) + // Example #5 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + hAnchor = Text.HorizontalAnchor.MIDDLE, + vAnchor = Text.VerticalAnchor.CENTER, + angle = 90.0, + )) + // Example #6 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + hAnchor = Text.HorizontalAnchor.MIDDLE, + vAnchor = Text.VerticalAnchor.CENTER, + angle = 180.0, + )) + // Example #7 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + fontFamily = "Times", + )) + // Example #8 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + fontFamily = "Courier", + )) + // Example #9 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + fontSize = 10.0, + )) + // Example #10 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + fontSize = 24.0, + )) + // Example #11 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + fontStyle = "italic", + )) + // Example #12 + groupComponent.add(createLabelExample( + exampleIdIter.next(), + shiftIter.next(), + fontWeight = "bold", + )) + + return groupComponent + } + + companion object { + private val DEMO_BOX_SIZE = DoubleVector(800.0, 1200.0) + private val INIT_SHIFT = DoubleVector(300.0, 0.0) + private const val DY_SHIFT = 100.0 + private val DIM = DoubleVector(200.0, 50.0) + private const val FORMULA = """-1.5·\(10^{-15}\)""" + + private fun createLabelExample( + exampleId: Int, + shift: DoubleVector, + hAnchor: Text.HorizontalAnchor = Text.HorizontalAnchor.LEFT, + vAnchor: Text.VerticalAnchor = Text.VerticalAnchor.BOTTOM, + angle: Double = 0.0, + fontFamily: String = "Arial", + fontSize: Double = 18.0, + fontStyle: String = "normal", + fontWeight: String = "normal" + ): SvgGElement { + val textLabel = createTextLabel(exampleId, hAnchor, vAnchor, angle, fontFamily, fontSize, fontStyle, fontWeight).also { + it.moveTo(DIM.x / 2, DIM.y / 2) + } + val exampleSvgGElement = SvgGElement() + exampleSvgGElement.children().add(createAxis()) + exampleSvgGElement.children().add(textLabel.rootGroup) + SvgUtils.transformTranslate(exampleSvgGElement, shift.x, shift.y) + return exampleSvgGElement + } + + private fun createAxis(): SvgElement { + val hAxis = SvgLineElement(0.0, DIM.y / 2, DIM.x, DIM.y / 2).also { + it.stroke().set(SvgColors.RED) + } + val vAxis = SvgLineElement(DIM.x / 2, 0.0, DIM.x / 2, DIM.y).also { + it.stroke().set(SvgColors.RED) + } + val origin = SvgCircleElement(DIM.x / 2, DIM.y / 2, 2.0).also { + it.fill().set(SvgColors.WHITE) + it.stroke().set(SvgColors.RED) + } + + val g = SvgGElement() + g.children().add(hAxis) + g.children().add(vAxis) + g.children().add(origin) + return g + } + + private fun createTextLabel( + exampleId: Int, + hAnchor: Text.HorizontalAnchor, + vAnchor: Text.VerticalAnchor, + angle: Double, + fontFamily: String, + fontSize: Double, + fontStyle: String, + fontWeight: String + ): TextLabel { + val label = TextLabel("$FORMULA ($exampleId)") + label.setHorizontalAnchor(hAnchor) + label.setVerticalAnchor(vAnchor) + label.rotate(angle) + label.setFontFamily(fontFamily) + label.setFontSize(fontSize) + label.setFontStyle(fontStyle) + label.setFontWeight(fontWeight) + return label + } + } +} \ No newline at end of file diff --git a/demo/plot/src/jvmBatikMain/kotlin/demo/plot/batik/component/RichTextDemoBatik.kt b/demo/plot/src/jvmBatikMain/kotlin/demo/plot/batik/component/RichTextDemoBatik.kt new file mode 100644 index 00000000000..a51ecc22334 --- /dev/null +++ b/demo/plot/src/jvmBatikMain/kotlin/demo/plot/batik/component/RichTextDemoBatik.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package demo.plot.batik.component + +import demo.common.batik.demoUtils.SvgViewerDemoWindowBatik +import demo.plot.shared.model.component.RichTextDemo + +fun main() { + with(RichTextDemo()) { + SvgViewerDemoWindowBatik( + "Rich text", + createSvgRoots(listOf(createModel())) + ).open() + } +} \ No newline at end of file diff --git a/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/RichTextDemoJfx.kt b/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/RichTextDemoJfx.kt new file mode 100644 index 00000000000..3e7b9fd8d3d --- /dev/null +++ b/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/RichTextDemoJfx.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package demo.plot.jfx.component + +import demo.common.jfx.demoUtils.SvgViewerDemoWindowJfx +import demo.plot.shared.model.component.RichTextDemo + +fun main() { + with(RichTextDemo()) { + SvgViewerDemoWindowJfx( + "Rich text", + createSvgRoots(listOf(createModel())) + ).open() + } +} \ No newline at end of file diff --git a/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/TooltipBoxDemoJfx.kt b/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/TooltipBoxDemoJfx.kt index 26e1f8d37ca..b2bc3f1755f 100644 --- a/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/TooltipBoxDemoJfx.kt +++ b/demo/plot/src/jvmJfxMain/kotlin/demo/plot/jfx/component/TooltipBoxDemoJfx.kt @@ -5,9 +5,9 @@ package demo.plot.jfx.component -import javafx.application.Platform.runLater -import demo.plot.shared.model.component.TooltipBoxDemo import demo.common.jfx.demoUtils.SvgViewerDemoWindowJfx +import demo.plot.shared.model.component.TooltipBoxDemo +import javafx.application.Platform.runLater import org.jetbrains.letsPlot.jfx.util.runOnFxThread import java.awt.EventQueue.invokeLater @@ -23,7 +23,7 @@ fun main() { runOnFxThread { invokeLater { runLater { - models.forEach { it.second() } + models.forEach { runLater { it.second() } } } } } diff --git a/demo/svg/build.gradle b/demo/svg/build.gradle deleted file mode 100644 index ad4b2804d0b..00000000000 --- a/demo/svg/build.gradle +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2019. JetBrains s.r.o. - * Use of this source code is governed by the MIT license that can be found in the LICENSE file. - */ - -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -// KT-55751. MPP / Gradle: Consumable configurations must have unique attributes. -// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes -// -def dummyAttribute = Attribute.of("dummyAttribute", String) - -kotlin { - jvm("jvmBatik") - jvm("jvmJfx") { - attributes.attribute(dummyAttribute, "jvmJfx") - } - jvm("jvmBrowser") { // generates index.html and opens it in browser - attributes.attribute(dummyAttribute, "jvmBrowser") - } - js() { - browser() - binaries.executable() - } - - sourceSets { - commonMain { - dependencies { - implementation kotlin('stdlib-common') - - implementation project(':commons') - implementation project(':datamodel') - implementation project(':demo-common-util') - } - } - - allJvm { - dependencies { - implementation kotlin('stdlib-jdk8') - compileOnly "io.github.microutils:kotlin-logging-jvm:$kotlinLogging_version" - } - } - jvmBatikMain { - dependsOn allJvm - dependencies { - implementation project(":platf-batik") - implementation project(':demo-common-batik') - - implementation("org.apache.xmlgraphics:batik-codec:$batik_version") - } - } - jvmJfxMain { - dependsOn allJvm - dependencies { - implementation project(':canvas') // needed for `svg transform` parsing - implementation project(":platf-jfx-swing") - implementation project(':demo-common-jfx') - } - } - jvmBrowserMain { - dependsOn allJvm - dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinx_html_version}" - } - } - - jsMain { - dependencies { - implementation kotlin('stdlib-js') - implementation project(":plot-base") - implementation project(":platf-w3c") - - languageSettings.optIn("kotlin.js.ExperimentalJsExport") - } - } - } -} diff --git a/demo/svg/build.gradle.kts b/demo/svg/build.gradle.kts new file mode 100644 index 00000000000..e13a7d8a97b --- /dev/null +++ b/demo/svg/build.gradle.kts @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +plugins { + kotlin("multiplatform") +} + +// KT-55751. MPP / Gradle: Consumable configurations must have unique attributes. +// https://youtrack.jetbrains.com/issue/KT-55751/MPP-Gradle-Consumable-configurations-must-have-unique-attributes +// +val dummyAttribute = Attribute.of("dummyAttribute", String::class.java) + +kotlin { + jvm("jvmBatik") { + attributes.attribute(dummyAttribute, "jvmBatik") + } + jvm("jvmJfx") { + attributes.attribute(dummyAttribute, "jvmJfx") + } + jvm("jvmBrowser") + js { + browser() + binaries.executable() + } + + val batikVersion = extra["batik_version"] as String + val kotlinLoggingVersion = extra["kotlinLogging_version"] as String + val kotlinxHtmlVersion = extra["kotlinx_html_version"] as String + + // Fix "The Default Kotlin Hierarchy Template was not applied to 'project'..." warning + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(project(":commons")) + implementation(project(":datamodel")) + implementation(project(":demo-common-util")) + } + } + val allJvm by creating { + dependencies { + implementation(kotlin("stdlib-jdk8")) + + compileOnly("io.github.microutils:kotlin-logging-jvm:${kotlinLoggingVersion}") + } + } + named("jvmBatikMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":platf-batik")) + implementation(project(":demo-common-batik")) + + implementation("org.apache.xmlgraphics:batik-codec:${batikVersion}") + } + } + named("jvmJfxMain") { + dependsOn(allJvm) + dependencies { + implementation(project(":canvas")) // needed for `svg transform` parsing + implementation(project(":platf-jfx-swing")) + implementation(project(":demo-common-jfx")) + } + } + named("jvmBrowserMain") { + dependsOn(allJvm) + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinxHtmlVersion}") + } + } + jsMain { + languageSettings.optIn("kotlin.js.ExperimentalJsExport") + dependencies { + implementation(kotlin("stdlib-js")) + + implementation(project(":plot-base")) + implementation(project(":platf-w3c")) + } + } + } +} diff --git a/demo/svg/src/commonMain/kotlin/demo/svgMapping/model/DemoModelA.kt b/demo/svg/src/commonMain/kotlin/demo/svgMapping/model/DemoModelA.kt index ca01c03b3f2..3d3903f411b 100644 --- a/demo/svg/src/commonMain/kotlin/demo/svgMapping/model/DemoModelA.kt +++ b/demo/svg/src/commonMain/kotlin/demo/svgMapping/model/DemoModelA.kt @@ -9,11 +9,11 @@ import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.values.Color import org.jetbrains.letsPlot.commons.values.FontFace import org.jetbrains.letsPlot.commons.values.FontFamily -import org.jetbrains.letsPlot.datamodel.svg.style.StyleSheet -import org.jetbrains.letsPlot.datamodel.svg.style.TextStyle +import org.jetbrains.letsPlot.datamodel.svg.dom.* import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimElements -import org.jetbrains.letsPlot.datamodel.svg.dom.* +import org.jetbrains.letsPlot.datamodel.svg.style.StyleSheet +import org.jetbrains.letsPlot.datamodel.svg.style.TextStyle object DemoModelA { @@ -34,10 +34,24 @@ object DemoModelA { face = FontFace.BOLD, size = 20.0, color = Color.RED - ) + ), + "EMC2" to TextStyle(FontFamily.HELVETICA.name, face = FontFace.BOLD, size = 22.0, color = Color.BLUE), + ) svgRoot.children().add(createStyleElement(textStyles)) + svgRoot.children().add( + SvgTextElement().apply { + addClass("EMC2") + transform().set(SvgTransformBuilder().translate(300.0, 150.0).build()) + addTSpan(SvgTSpanElement("E=mc")) + addTSpan(SvgTSpanElement("2").apply { + setAttribute("baseline-shift", "super") + setAttribute("font-size", "75%") + }) + } + ) + var text = SvgTextElement(30.0, 85.0, "Slim elements") text.addClass("TEXT1") SvgUtils.transformRotate(text, -45.0, 20.0, 100.0) diff --git a/docs/f-23e/scientific_notation.ipynb b/docs/f-23e/scientific_notation.ipynb new file mode 100644 index 00000000000..54ebaa5a481 --- /dev/null +++ b/docs/f-23e/scientific_notation.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0f7426d7-4987-4b9f-ab69-cf3924d48a29", + "metadata": {}, + "source": [ + "# Scientific Notation\n", + "\n", + "Default format for exponents changed from e-notation to power of 10." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f128c69b-f753-4f8d-b08e-306f91431703", + "metadata": {}, + "outputs": [], + "source": [ + "from lets_plot import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "10e77858-570a-443d-b7fb-c53be8d14d89", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9ec8add6-9971-476f-8015-27d620c9fb1c", + "metadata": {}, + "outputs": [], + "source": [ + "data = dict(\n", + " name = [\"electron\", \"muon\", \"tau\"],\n", + " mass = [9.1e-28, 1.9e-25, 3.2e-24],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6e84c515-7de1-4e95-9321-a8c4b379a8a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(data) + \\\n", + " geom_point(aes(x=\"mass\"), y=0, tooltips=layer_tooltips().line(\"@name\").line(\"@|@{mass}g\")) + \\\n", + " theme(axis_title_y='blank', axis_text_y='blank') + \\\n", + " ggtitle(\"Masses of charged leptons in grams\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/f-23f/margins.ipynb b/docs/f-23f/margins.ipynb new file mode 100644 index 00000000000..c3055cfe2b1 --- /dev/null +++ b/docs/f-23f/margins.ipynb @@ -0,0 +1,445 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "moving-ownership", + "metadata": {}, + "source": [ + "# Margins\n", + "\n", + " \n", + "The margins around the plot and text elements are controlled by the `plot_margin` parameter in `theme()` and the `margin` parameter in `element_text()` respectively.\n", + "\n", + "Now the parameters `plot_margin` and `margin` accept a number or a list of numbers.\n", + "\n", + "- A number or list of one number is specified: the same margin it applied to **all four sides**.\n", + "- A list of two numbers: the first margin applies to the **top and bottom**, the second - to the **left and right**.\n", + "- A list of three numbers: the first margin applies to the **top**, the second - to the **right and left**, the third - to the **bottom**.\n", + "- A list of four numbers: the margins are applied to the **top, right, bottom and left** in that order.\n", + "\n", + "It is acceptable to use None for any side; in this case, the default side value for this element will be used." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "liable-jacksonville", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "from lets_plot import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fiscal-rates", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "indie-yeast", + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(42)\n", + "data = {'x': np.random.randint(10, size=100)}\n", + "\n", + "p = ggplot(data, aes(x='x')) + \\\n", + " geom_bar() + \\\n", + " ggtitle(\"Bar Сhart\") + \\\n", + " theme_light() + \\\n", + " theme(plot_background=element_rect(size = 4))" + ] + }, + { + "cell_type": "markdown", + "id": "cooked-sender", + "metadata": {}, + "source": [ + "#### Plot without Margins" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "buried-birth", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p" + ] + }, + { + "cell_type": "markdown", + "id": "brutal-sword", + "metadata": {}, + "source": [ + "#### Margins Around the Plot" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "functional-client", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gggrid([\n", + " p + theme(plot_margin=40) + ggtitle(\"plot_margin=40 (all sides)\"),\n", + " p + theme(plot_margin=[40,20]) + ggtitle(\"plot_margin=[40,20]\\n(top/bottom, left/right)\"),\n", + " p + theme(plot_margin=[40,20,10]) + ggtitle(\"plot_margin=[40,20,10]\\n(top, left/right, bottom)\"),\n", + " p + theme(plot_margin=[40,10,10,20]) + ggtitle(\"plot_margin=[40,10,10,20]\\n(top, right, bottom, left)\")\n", + "], ncol=2)" + ] + }, + { + "cell_type": "markdown", + "id": "interstate-rover", + "metadata": {}, + "source": [ + "#### Margins Around the Text Element" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "placed-aerospace", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p + theme(plot_title = element_text(margin=[40,None]),\n", + " axis_title = element_text(margin=[20,20,None,None])) + \\\n", + " ggtitle(\"plot_title=[40,None] -> top/bottom=40,\\n\" +\n", + " \"axis_title=[20,20,None,None] -> top/right=20\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/f-23f/new_save_methods.ipynb b/docs/f-23f/new_save_methods.ipynb new file mode 100644 index 00000000000..7a61c8363c3 --- /dev/null +++ b/docs/f-23f/new_save_methods.ipynb @@ -0,0 +1,1296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "employed-rebate", + "metadata": {}, + "source": [ + "# Save Methods of `ggplot()` and `gggrid()`\n", + "\n", + "Use methods `to_svg()`, `to_html()`,`to_png()`,`to_pdf()` to save plots on disc or in file-like objects.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "arranged-meter", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import io\n", + "import os\n", + "from IPython import display\n", + "from lets_plot import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c38745bd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "remarkable-toolbox", + "metadata": {}, + "outputs": [], + "source": [ + "x1 = np.random.randint(10, size=100)\n", + "p1 = ggplot({'x': x1}, aes(x='x')) + geom_bar()\n", + "\n", + "n = 100\n", + "x2 = np.arange(n)\n", + "y2 = np.random.normal(size=n)\n", + "w, h = 200, 150\n", + "p2 = ggplot({'x': x2, 'y': y2}, aes(x='x', y='y')) + ggsize(w, h)" + ] + }, + { + "cell_type": "markdown", + "id": "b8aadbde", + "metadata": {}, + "source": [ + "When `path` parameter is a string, the image is written to a file with that name." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7b0fdb65", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'D:\\\\Projects\\\\lets-plot-master\\\\lets-plot-885-2\\\\docs\\\\f-23f\\\\plot.svg'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "file = 'plot.svg'\n", + "p1.to_svg(file)" + ] + }, + { + "cell_type": "markdown", + "id": "d3e3fef9", + "metadata": {}, + "source": [ + "You can use the same methods for gggrid()." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "135b5a28", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'D:\\\\Projects\\\\lets-plot-master\\\\lets-plot-885-2\\\\docs\\\\f-23f\\\\grid.png'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "file = 'grid.png'\n", + "gggrid([p2 + geom_point(), p2 + geom_line()]).to_png(file)" + ] + }, + { + "cell_type": "markdown", + "id": "8c3b3b37", + "metadata": {}, + "source": [ + "When `path` is a file-like object, the data is written to it by calling its write() method.\n", + "\n", + "Below is how you can write SVG data into a file-like object without saving it to a file, and use it for display or anything else." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "faced-integral", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 4\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 5\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 6\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 7\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 8\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 9\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 4\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 6\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 8\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 10\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 12\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " count\n", + " \n", + " \n", + " \n", + " \n", + " x\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "file_like = io.BytesIO()\n", + "p1.to_svg(file_like)\n", + "display.SVG(file_like.getvalue())" + ] + }, + { + "cell_type": "markdown", + "id": "6280b266", + "metadata": {}, + "source": [ + "You can do the same with binary data in PNG or PDF format." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0de51f34", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAABmJLR0QA/wD/AP+gvaeTAAAUoElEQVR4nO3dTYwkZ30H4KrqnpmdmZ3Zmd2FiNgJRAgQB64gRSiygsC9jjCKjbAVWzlyiiKRY3KLcs0592gV2QgbYYE9kgELyRIgpMSSFWEpNnEAG2PP587u7Hx0V+XQdm/PeBivt2ur3pn/85yqd6W3f11d/f66vqbzqqoyAIiqaDsAALRJEQIQmiIEIDRFCEBoihCA0BQhAKEpQgBCU4QAhKYIAQhNER51cHCwsbHRdoqj+v3++vp62ymOGgwGa2trbac4qizL1dXVtlMcVVXVO++803aKY7z99tttRzjGO++8k+AfvVpdXS3Lsu0UR62trQ0Gg7ZTHLW+vt7v99tOcbsUIQChKUIAQlOEAISmCAEITRECEJoiBCA0RQhAaIoQgNAUIQChKUIAQlOEAISmCAEITRECEJoiBCA0Rcidy/P83Llzbac4NWZnZ9uOcIy5ubm2I3AGnTt3rihOTb902w7AKVYUxfz8fNspmlBlWT7ZCHmenz9/vp40taol1eTrhzPmdH3BUoSh/eTXO//8Ysu/Fvv9b/zpbDf1WbSqspff2fvWD99qN8a/f/WeexZufWarLPvVxv43n/tdi5GyLPu33sc+dXG63QzU7kv/8X/tBviHL1z6q0829N1REYa2sTv4r9/vtpuhrE7H7sSNg7L1dbXbP/rz6Dv9qvVUOwfJ/Wg7k3vp97tVqwHWdgaNPdepOYYLAHeDIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhDaXfyF+l6vt7KyMv5wtDz+7wDQortVhOO1lx1XiroQgBTclUOj7+85tQdAmu5KEao9AE4LF8sAENpdvFjmD3n/gdOqqtbW1ppP8odUVbW6utp2iqNqT3X58uUaR5vE/v7+tWvXahywLMt619XS8sUaR5tEv9/f3NwcLi8tL7cbZmQwGGxubNQ4YGpzwlBZluvr622nOKqqqo1aV/7CwsLMzEyNA05ibW2tqqrJxzl5umu6CI+9TCbP84sXU5loDg4Obty4sbS01HaQQ/r9/vb29nIyE1+9pqamatwAyrLc3NyseYvK8zpHm0C32x29tDwvsqzfbp6hTtGpd4Wvra0tLy/nyaz2ofX19aWlpaJI60DaxsbG4uJip9Opa8CkVnszk16jRXjCxaLpbFtFUeR5nk6eoTRT1SXP89o/e/Wuq7KGr6S1Gb20hELlWZHXvHEOt/l6x5xcURQJfgzTTFWLZl5Xc+vOLRMAJKihPcLhbYXuqQcgNXexCMerTu0BkKazeVgZAG6TIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACE1r17Q/d6vZWVlfGHo+XxfweAFt2tIhyvvey4UtSFAKTgrhwa1XMAnBZ3pQi1IACnhYtlAAjtLl4s86HcuHGj7QjvKsuyLMt688zOzk44Ql4UixculGU54TiDwWB/f3+4PD8/P+FodRkMBru7u3WNVlVVVVX1voPnZudqHG0SZVnevHlzuDz5dlWXsix330tVl52dnXoHnFxVVTs7O3metx3kkKqqbt68WWOqmZmZbjeVatjZ2amqavJxTp7uUnm16RhOo/WOWRTF+u7gje1+vcN+WJ/7yMzkVQoNGH4MU6ucNFlXk0ulCNPZOzk4OBgMBrXnefbV63///Fv1jvlhrX/rM91uN53veiOdTqfGFV6W5e7ubr3vYFnzV6M7VxTF6KUlE+pQqlrs7OzMz8+nNrnfvHlzbm6uKNI6o7S7uzs3N9fpdNoOclfMzTVxMCatdxQAGtbQzsHKyoob6gFI0F0swiNtp/wASJBDowCEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIuSsyfN8YWGh7RTAqaEISUtVTTpCnuczMzN1ZAFC6LYdAA6psuyH/3vjb77323Zj/Pc3P/nROZ8OCMFHnRQNJt4vBLhNDo0CEJoiBCA0RQhAaIoQgNAUIQChKUIAQlOEAISmCAEITRECEJoiBCA0RQhAaIoQgNAUIQChKUIAQlOEAISmCAEITRECEFpzv1Df6/VGyysrK409LwCcoKEi7PV64+V35CEAtMWhUQBCU4QAhNbQodGVlZWTzxEOBoNmknygsiyrqqo3T6fTqXG0SVRVVZblcDnNVHmRZqpUvjKOpyqSSZVV1WC0rvJ88vFmZ2erqqqqasJxRiPUkurcuXPj6/+O1Z4qy7IaUxVFUUuwWtQ1FZ883SVxjrCqqo2NjWaSfKDhx6/GPHmeX7p0qa7RJrS/v7+9vT1cvnz5crthRg4ODq5duzZcXr6Yyrrq9/tbW1vD5aXli+2GGRkMBpubm8PlpeXldsOMDMpy872PzNLSUrc76cRy/vz5iUNlg7IcfZAvXLgwNTU14YC1pCqramN9fbi8uLg4PT094YDz8/MTh8qyLFtdXR0uLCwszMzM1DLm5DY3Nyf/PpR90HTX3FWjJ8jzPKlJ+fr168vJTDH1mpmZSWcTH5menh5tAGUN23w9pqamEkzV7XZHqaosy7J+m2ne0+l0xj/Cb273//b7b7SYJ8uyf/3SH33uI+fGU23tDR5++rctRsqy7F/+4qNf+OPZ8VT7g+qBb/+6xUhZlv3jn1/+y4/PpzMJj2tmLyKJIgTOkr1B9Z9v7bab4fr+0UOFB2XWeqqtvaMH+sqq/VQbu6mcmWpLMucYAKANqVwsAwCtaO7QqPIDIEEOjQIQmiIEIDRFCEBoihCA0BQhAKEpQgBCO6YIx2/4O/kfAeC0s0cIQGiHbqgf7fbZ/wMgiENFOPzjL0d+IwkAzrBjDo1qQQDiOP5vjR57aFRBAnD2HFOEDo0CEIerRgEITRECENrxF8u4fQKAII4/R5gdd72ME4cAnD3HFKHCAyAO5wgBCO0PHhp9P3uKAJw9t3Vo1J2FAJxVt3Vo1HWkAJxVzhECEJoiBCC0271YxjlCAM4k9xECEJpDowCEdru/R2g3EYAz6XZ/j9CthACcSQ6NAhCaIgQgNEUIQGjH3z7hYhkAgjj+qlG1B0AQDo0CENrxRXjk0KifngDgrDqmCN9/y6CfYQLgrHJoFIDQFCEAoR1/1ejdMH5w1VWpACSiofsIj5x39JdLAUhEO/cRakEAEuEcIQChNXeOMBs7TWiPEIBENHqxzKj/jpwjrKpqa2ursSQnq6qq3+9vbm7WNWCe5xcuXKhrtAnt7+/v7OwMl5eWltoNM3JwcHDjxo3h8mIy66rf71+/fn24vLC42G6YkcFgsL29PVxeWFhoN8xIOSi3t68Nl9NJVVXl5ta7qc6fP59lebt5hqqs2tp8d7qbn5/P8kb3Rk4wmvTm5uamp6fbDTOytbVVVdXk45w83TX3HpywF5jn+dzcXGNJTtbv96uqSidPvbrdboIvrdPpJJ4qz5OYQ7MsK4riVqoilVMbeXHrI1wURZaV7eYZGp9YiqLIshrm0zrcStXpdAZJrKosy7JRqm43lW7Osmx2draBZ0nlBafzBSTP8729vXTy1KsoigRf2niqMpHJKsvyPE88VTKhDqVKyZFUg9aCjMmPTHfJbFtJvoMNpUrlGyUAtKKhPcIj9ya6WAaARCRxjhAA2uLQKAChKUIAQlOEAISmCAEITRECEJoiBCA0RQhAaIoQgNAUIQChKUIAQlOEAISmCAEITRECEJoiBCA0RQhAaIoQgNAUIQChKUIAQlOEAISmCI/qdruLi4ttpwCgId22A9Rv7ebgrRv9iYe58xGKLPvs5ZmJAwDQhDNYhN/+5bV/+snbLQaYnyp+83efajEAALfPoVEAQlOEAISmCAEITRECEJoiBCA0RQhAaIoQgNAUIQChKUIAQlOEAISmCAEITRECEJoiBCA0RQhAaIoQgNAUIQChKUIAQmuhCHu9XvNPCgDHaroItSAASXFoFIDQGi3CXq+3srLS5DMCwMnsEQIQWrexZzp5d3Bvb2/yp8jzfHp6evJxajEYDPr9/nB5Zmam3TAjZVkeHBwMl9NMNZXMO3go1VQqqaqq2t/fHy6ns67GU6XzGcyyam/vvXU1NdVulJEqq/YPpcrbzTMymoSnpqaKIpV9pFqqIfug6a65IjxBVVW7u7uTj5NaEQ5fVJ7n6VTOKFWWWBGOUqUzuY+n6iYzjR5K1U3i85tlWVVWKaYam1g6nU46lTNKVRRFlqeyusZTJVWEVVVNPk4SRTi8WHR0yeiRvcM8zy9cuNBMksZMT0+n08ojU1NTCa7qbrc7SlXWsM3XI81UnU5nlKrKsizrt5nmPUWnOLxdla1FGZPnh1MdDNrLckueHZruBv1Utq0EZ4YsyxYXFxt4loaKcLz2XDIDQDpS2f8FgFa0UIR2BwFIhz1CAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoXUbe6ZerzdaXllZaex5AeAEDRVhr9cbL78jDwGgLQ0dGlV7AKTJOUIAQlOEAITW3MUyI+8/QVhV1dra2uQjF0Vx8eLFycepxe7u7vXr17Msy/P80qVLbcd5197e3vb29nD58uXL7YYZ2d/fv3bt2nB5+WIq6+rg4GBra2u4vLScynbV7/c3NzeHy0vLy+2GGRkMBpsbG8PlpaWlLKvazTNUluX6xvpw+cKFC1mVxPf+KqvWVt+d7hYXF7Niqt08I6urq8OFhYWFmZmZdsOMrK2tVVUNm9PJ013TRXjsZTJ5nqdTYHWZmZmZnp5uO8VR09PTCa7qqampW6nyvNUst6SZqtvtjlLleZFl/XbzDHWKzihVURRZdtBunqHxb8Z5nmf9st0878nHU+0N2g1zy6F1lYzlRr7tNVqEJ1wsWhRJfFmrUZ7nSW1PQ+mnKpPYl3jXaLNMM1VCofKsyFP8CCc4seRZlh9KlcrbmOC6yppK1dwrd8sEAAlq7j7CzD31AKSnoSJUewCkKcWDwgDQGEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGiKEIDQFCEAoSlCAEJThACEpggBCK3b2DP1er3R8srKSmPPCwAnaKgIe73eePkdeQgAbXFoFIDQFCEAoSlCAELLq6pq4GlOPkfY7/efeOKJyZ/l3LlzX//619d3B7+/3p98tDtW5PlnLk2/+uqrP/vZz7Is63a7jz766Obu4Hetpsqy7LOXZ15//fUXX3xx+PCxxx7b3q/e2D5oN9VnLs387s03XnjhheHDbzzyyH7V+c21llN96uL02jtvP//888OHf/3Qw8X0zOubLaf65PL09a2NZ599dvjwq1998Nz8wq8299tN9WdL0/s3rz/zve8NH165cmVh6eJrGy2n+viFqay/9/RTTw0ffvnLX770kY/+z3rLqf5kcWqmKJ98b7q777777rn33ldW99pNdc/C1OJMcfXq1eHDL37xi5/4xCd+2Xaqj53vLp3rPPHEE/1+DTPn448/fsL/nqkirMXbb7/90ksvfeUrX2k7yCEbGxs//elPH3jggbaDHLK9vf3jH//4a1/7WttBDtnd3f3BD37w8MMPtx3kkH6//53vfOfRRx9tO8hRV69efeyxx/I8bzvIIU8++eRDDz00NTXVdpBDnn766StXrszOzrYd5JBnnnnmvvvuW1xcbDvIIc8999znP//5S5cutR3kXScXYXO3T5yg2+2enLJJL7/88ptvvplOnqHXXnvtlVdeSS3VG2+88Ytf/CK1VBsbGz/60Y9SS7W7u/vd7343tVRZll29evXxxx9PrQifeuqpRx55ZG5uru0ghzz33HMPPfRQOpP70AsvvPDggw/ee++9bQc55Oc///mVK1c+/elPtx3ktjhHCEBoDe0RrqysuKEegAQ1dI4QANLk0CgAoSVxsUwiEj94m9TfpUt2XSUbbCjZNzFLZnUl+A4eWVFZksESiZSlmuoDVFRVVVX333//CQ9bd//996cTKdl1lWywoaTexCq99VMl/w5WKUVKc12lmeoDOTR6CiS1G5El/C0v2WDcGW8ozVCEp4Dp4AxI7dtManlOBSvtrHKOkLNmdIrCnHWyZM/leAdvR/r3pL3/3GqyFCETSfA78ihPOtnSSTIuzZ8IHU+STqossTBZqj/yOl7PR6o6ZYqQO5fIZ487kOwbl2wwbtNpfAedI+QOacHbNPxS3Ov1RgttJ+JDs7WfbYqQO5HmvJBmx6yMyZL5vpzmuuK0G9+u0pwljuVPrN2S/pnnRFIle39x5k38MNJcV8mmSifMSLLrariQTqQPpAgBCM2hUQBCU4QAhKYIAQhNEQIQmiIEIDRFCEBoihCA0BQhAKEpQgBCU4QAhKYIAQhNEQIQmiKE0+H9P/rhp5SgFooQToeVlZVT+mNvkDhFCKfGqAu1INTI7xHCKaMFoV72CAEITRHCaTLcHXSZDNRIEcKpMTooqguhRooQTocjpwZ1IdTFxTIAhGaPEIDQFCEAoSlCAEJThACEpggBCE0RAhCaIgQgNEUIQGj/D5Tc6aqN+p7OAAAAAElFTkSuQmCC", + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "file_like = io.BytesIO()\n", + "p1.to_png(file_like, scale = 1.0)\n", + "display.Image(file_like.getvalue())" + ] + }, + { + "cell_type": "markdown", + "id": "8d863635", + "metadata": {}, + "source": [ + "You can also write `gggrid()` to a file-like object." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9a77dc46", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 20\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 40\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 60\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 80\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -2\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -1\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " y\n", + " \n", + " \n", + " \n", + " \n", + " x\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 20\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 40\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 60\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 80\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -2\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -1\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " y\n", + " \n", + " \n", + " \n", + " \n", + " x\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "file_like = io.BytesIO()\n", + "gggrid([p2 + geom_point(), p2 + geom_line()]).to_svg(file_like)\n", + "display.SVG(file_like.getvalue())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/f-23f/scale_lablim.ipynb b/docs/f-23f/scale_lablim.ipynb new file mode 100644 index 00000000000..2394fd43627 --- /dev/null +++ b/docs/f-23f/scale_lablim.ipynb @@ -0,0 +1,321 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d13bb6d9-afd1-4f5d-90ed-83892a0ed8d2", + "metadata": {}, + "source": [ + "## `lablim` parameter to control the length of scale labels.\n", + "With this parameter each scale can be configured independently. Note that this parameter doesn't affect tooltip content. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "96bdba02-7084-4a95-a363-bfaf2fdc00f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from lets_plot import *\n", + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "markdown", + "id": "40f63905-9c3f-4593-9795-b8667d1daf91", + "metadata": {}, + "source": [ + "#### By default lets-plot now displays labels without any modification:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "89f7671f-5e1f-4830-b5f8-dee8e5142c4b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data = {\n", + " 'x': ['ABCDEFGHIJKLMNOPQRSTUVWXYZ', '1234567890']\n", + "}\n", + "\n", + "p = ggplot(data, aes(x='x', color='x')) \\\n", + " + geom_point() \\\n", + " + geom_text(aes(label='x'))\n", + "p" + ] + }, + { + "cell_type": "markdown", + "id": "cf8daa5e-0887-4353-a18f-129b66916de9", + "metadata": {}, + "source": [ + "#### `lablim` can be set separately for each scale:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1235d88c-0ca4-424f-93d6-54e8d36086b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p \\\n", + " + scale_x_discrete(lablim=5) \\\n", + " + scale_color_hue(lablim=10)" + ] + }, + { + "cell_type": "markdown", + "id": "23bd0079-7398-4684-a556-30cd92db8562", + "metadata": {}, + "source": [ + "#### Tooltips are not affected by `lablim`:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3032a387-8e1a-424b-b9ca-69cb9e41c893", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p \\\n", + " + geom_point(tooltips=layer_tooltips().line(\"^x\")) \\\n", + " + scale_x_discrete(lablim=5) \\\n", + " + scale_color_hue(lablim=10)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/f-23f/scale_params_with_dict.ipynb b/docs/f-23f/scale_params_with_dict.ipynb new file mode 100644 index 00000000000..300607fd873 --- /dev/null +++ b/docs/f-23f/scale_params_with_dict.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "answering-manner", + "metadata": {}, + "source": [ + "# Using a Dictionary as Parameter Values in Scaling Functions\n", + "\n", + "\n", + "The `labels` argument for scales can be specified using a dictinary where keys are treated as the original values (breaks) and the corresponding values are used as the labels to display. The `breaks` dictionary maps labels to breaks.\n", + "\n", + "The same opportunity is for `values` parameter in manual scale: if this is a dictinary, then the values will be matched based on the names." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "pressed-angola", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "from lets_plot import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "recorded-horizon", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "honest-stewart", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Unnamed: 0manufacturermodeldisplyearcyltransdrvctyhwyflclass
01audia41.819994auto(l5)f1829pcompact
12audia41.819994manual(m5)f2129pcompact
23audia42.020084manual(m6)f2031pcompact
\n", + "
" + ], + "text/plain": [ + " Unnamed: 0 manufacturer model displ year cyl trans drv cty hwy \\\n", + "0 1 audi a4 1.8 1999 4 auto(l5) f 18 29 \n", + "1 2 audi a4 1.8 1999 4 manual(m5) f 21 29 \n", + "2 3 audi a4 2.0 2008 4 manual(m6) f 20 31 \n", + "\n", + " fl class \n", + "0 p compact \n", + "1 p compact \n", + "2 p compact " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mpg_df = pd.read_csv (\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv\")\n", + "mpg_df.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "previous-serbia", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p = ggplot(mpg_df, aes(x='displ', y='hwy', color='drv')) + geom_point() \n", + "p" + ] + }, + { + "cell_type": "markdown", + "id": "brazilian-stack", + "metadata": {}, + "source": [ + "#### Use `labels` and `breaks` Dictionaries" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "amber-jackson", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "drv_dict = {\n", + " 'f': 'front-wheel',\n", + " 'r': 'rear-wheel',\n", + " '4': '4wd' \n", + "}\n", + "breaks_dict = {\n", + " 'min': 1.6,\n", + " '3.4': 3.4,\n", + " '5.2': 5.2,\n", + " 'max': 7\n", + "}\n", + "\n", + "p + scale_color_discrete(labels=drv_dict) + scale_x_continuous(breaks=breaks_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "classical-tournament", + "metadata": {}, + "source": [ + "#### Use `values` Dictionary" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "departmental-inventory", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "values_dict = { \n", + " 'f': 'dark_blue', \n", + " 'r': 'dark_green',\n", + " '4': 'dark_magenta'\n", + "}\n", + "\n", + "p + scale_color_manual(values=values_dict)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/future_changes.md b/future_changes.md index 804d36e5a09..83ae3dcabe6 100644 --- a/future_changes.md +++ b/future_changes.md @@ -7,8 +7,29 @@ See: [example notebook](https://nbviewer.jupyter.org/github/JetBrains/lets-plot/blob/master/docs/f-23f/new_stat_count_vars.ipynb). +- Using a dictionary as a value of `'labels'`, `'breaks'` ([[#169](https://github.com/JetBrains/lets-plot/issues/169)]) + and `'values'` ([[#882](https://github.com/JetBrains/lets-plot/issues/882)]) parameters in scaling functions: + + See: [example notebook](https://nbviewer.jupyter.org/github/JetBrains/lets-plot/blob/master/docs/f-23f/scale_params_with_dict.ipynb). + + +- The `lablim` parameter for `scale_xxx()` functions [[#939](https://github.com/JetBrains/lets-plot/issues/939), [#946](https://github.com/JetBrains/lets-plot/issues/946)]. + + See: [example notebook](https://nbviewer.jupyter.org/github/JetBrains/lets-plot/blob/master/docs/f-23f/scale_lablim.ipynb). + + ### Changed +- The `plot_margin` parameter in `theme()` and the `margin` parameter in `element_text()` accept a number or a list of numbers: + - a number or list of one number - the same margin it applied to **all four sides**; + - a list of two numbers - the first margin applies to the **top and bottom**, the second - to the **left and right**; + - a list of three numbers - the first margin applies to the **top**, the second - to the **right and left**, + the third - to the **bottom**; + - a list of four numbers - the margins are applied to the **top, right, bottom and left** in that order. + + See: [example notebook](https://nbviewer.jupyter.org/github/JetBrains/lets-plot/blob/master/docs/f-23f/margins.ipynb). + + ### Fixed - Jitter reproducibility in geom_jitter, position_jitter, position_jitterdodge [[#911](https://github.com/JetBrains/lets-plot/issues/911)]. - Facets: order = 0 doesn't work as expected [[#923](https://github.com/JetBrains/lets-plot/issues/923)]. @@ -16,5 +37,8 @@ - geom_livemap: freeze at zoom 10 [[#892](https://github.com/JetBrains/lets-plot/issues/892)]. - Enormous CPU / Time/ Memory consumption on some data [[#932](https://github.com/JetBrains/lets-plot/issues/932)]. - scale_x_log2(), scale_y_log2() as a shortcut for trans='log2' [[#922](https://github.com/JetBrains/lets-plot/issues/922)]. +- export functions to export to a file-like object [[#885](https://github.com/JetBrains/lets-plot/issues/885)]. - How to calculate proportion of points with same coordinate [[#936](https://github.com/JetBrains/lets-plot/issues/936)]. -- gggrid: composite plot is not visible if saved with ggsave [[#942](https://github.com/JetBrains/lets-plot/issues/942)]. \ No newline at end of file +- gggrid: composite plot is not visible if saved with ggsave [[#942](https://github.com/JetBrains/lets-plot/issues/942)]. +- Make scale's 'breaks' / 'labels' parameters understand dict of breaks as keys and labels as values [[#169](https://github.com/JetBrains/lets-plot/issues/169)]. +- scale manual: "values" parameter should accept dictionary as a value [[#882](https://github.com/JetBrains/lets-plot/issues/882)]. \ No newline at end of file diff --git a/gis/src/commonMain/kotlin/org/jetbrains/letsPlot/gis/tileprotocol/TileService.kt b/gis/src/commonMain/kotlin/org/jetbrains/letsPlot/gis/tileprotocol/TileService.kt index 3c68d5836fd..13a2a39d399 100644 --- a/gis/src/commonMain/kotlin/org/jetbrains/letsPlot/gis/tileprotocol/TileService.kt +++ b/gis/src/commonMain/kotlin/org/jetbrains/letsPlot/gis/tileprotocol/TileService.kt @@ -5,6 +5,10 @@ package org.jetbrains.letsPlot.gis.tileprotocol +import org.jetbrains.letsPlot.commons.intern.async.Async +import org.jetbrains.letsPlot.commons.intern.async.ThreadSafeAsync +import org.jetbrains.letsPlot.commons.intern.concurrent.Lock +import org.jetbrains.letsPlot.commons.intern.concurrent.execute import org.jetbrains.letsPlot.commons.intern.json.JsonSupport import org.jetbrains.letsPlot.commons.intern.json.JsonSupport.formatJson import org.jetbrains.letsPlot.commons.intern.spatial.LonLat @@ -19,17 +23,14 @@ import org.jetbrains.letsPlot.gis.tileprotocol.mapConfig.MapConfig import org.jetbrains.letsPlot.gis.tileprotocol.socket.SafeSocketHandler import org.jetbrains.letsPlot.gis.tileprotocol.socket.SocketBuilder import org.jetbrains.letsPlot.gis.tileprotocol.socket.SocketHandler -import org.jetbrains.letsPlot.commons.intern.async.Async -import org.jetbrains.letsPlot.commons.intern.async.ThreadSafeAsync -import org.jetbrains.letsPlot.commons.intern.concurrent.Lock -import org.jetbrains.letsPlot.commons.intern.concurrent.execute open class TileService(socketBuilder: SocketBuilder, private val myTheme: Theme) { enum class Theme { COLOR, LIGHT, - DARK + DARK, + BW } private val mySocket = socketBuilder.build(SafeSocketHandler(TileSocketHandler())) diff --git a/gradle.properties b/gradle.properties index a9d912c514d..4d8b30f65d4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ hamcrest_version=1.3 mockito_version=2.23.4 mockk_version=1.9.3 -batik_version=1.16 +batik_version=1.17 ktor_version=2.3.4 twelvemonkeys_imageio_version=3.8.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586a54a..a5952066425 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgNodeMapperFactory.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgNodeMapperFactory.kt index 9e355fc28c0..26c10bc486e 100644 --- a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgNodeMapperFactory.kt +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgNodeMapperFactory.kt @@ -8,11 +8,10 @@ package org.jetbrains.letsPlot.jfx.mapping.svg import javafx.scene.Group import javafx.scene.Node import javafx.scene.image.ImageView -import javafx.scene.text.Text +import org.jetbrains.letsPlot.awt.util.RGBEncoderAwt import org.jetbrains.letsPlot.datamodel.mapping.framework.Mapper import org.jetbrains.letsPlot.datamodel.mapping.framework.MapperFactory import org.jetbrains.letsPlot.datamodel.svg.dom.* -import org.jetbrains.letsPlot.awt.util.RGBEncoderAwt internal class SvgNodeMapperFactory(private val peer: SvgJfxPeer) : MapperFactory { @@ -39,7 +38,7 @@ internal class SvgNodeMapperFactory(private val peer: SvgJfxPeer) : MapperFactor is SvgStyleElement -> SvgStyleElementMapper(src, target as Group, peer) is SvgGElement -> SvgGElementMapper(src, target as Group, peer) is SvgSvgElement -> SvgSvgElementMapper(src, peer) - is SvgTextElement -> SvgTextElementMapper(src, target as Text, peer) + is SvgTextElement -> SvgTextElementMapper(src, target as TextLine, peer) // is SvgTextNode -> result = SvgTextNodeMapper(src, target as Text, myDoc, peer) is SvgImageElement -> SvgImageElementMapper(src, target as ImageView, peer) is SvgElement -> SvgElementMapper(src, target, peer) diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgTextElementMapper.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgTextElementMapper.kt index dab66870ca5..b9da363d397 100644 --- a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgTextElementMapper.kt +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/SvgTextElementMapper.kt @@ -5,48 +5,27 @@ package org.jetbrains.letsPlot.jfx.mapping.svg -import javafx.beans.value.ChangeListener -import javafx.beans.value.ObservableValue -import javafx.geometry.Bounds +import javafx.scene.paint.Color import javafx.scene.text.Font import javafx.scene.text.FontPosture import javafx.scene.text.FontWeight -import javafx.scene.text.Text import org.jetbrains.letsPlot.commons.intern.observable.collections.ObservableCollection import org.jetbrains.letsPlot.commons.intern.observable.property.ReadableProperty import org.jetbrains.letsPlot.commons.intern.observable.property.SimpleCollectionProperty import org.jetbrains.letsPlot.commons.intern.observable.property.WritableProperty import org.jetbrains.letsPlot.datamodel.mapping.framework.Synchronizers +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgNode +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTSpanElement +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextElement +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextNode import org.jetbrains.letsPlot.datamodel.svg.style.StyleSheet -import org.jetbrains.letsPlot.datamodel.svg.style.TextStyle -import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants.SVG_STYLE_ATTRIBUTE -import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgTextElementAttrMapping -import org.jetbrains.letsPlot.datamodel.svg.dom.* + internal class SvgTextElementMapper( source: SvgTextElement, - target: Text, + target: TextLine, peer: SvgJfxPeer -) : SvgElementMapper(source, target, peer) { - - private val myTextAttrSupport = TextAttributesSupport(target) - - override fun setTargetAttribute(name: String, value: Any?) { -// println("text -> $name = $value") -// println("text -> ${target.font}") -// val def = Font.getDefault() -// println("text -> ${def.family}") -// val font = Font.font(def.family, FontPosture.ITALIC, def.size) -// val font = Font.font("Times New Roman", FontPosture.ITALIC, def.size) -// target.font = font -// println("text -> ${target.font}") -// Font.getFamilies().forEach { println(it) } -// Font.getFontNames().forEach { println(it) } -// throw RuntimeException("The End") - - - myTextAttrSupport.setAttribute(name, value) - } +) : SvgElementMapper(source, target, peer) { override fun applyStyle() { setFontProperties(target, peer.styleSheet) @@ -56,93 +35,96 @@ internal class SvgTextElementMapper( super.registerSynchronizers(conf) // Sync TextNodes, TextSpans - val sourceTextProperty = sourceTextProperty(source.children()) -// sourceTextProperty.addHandler(object : EventHandler> { -// override fun onEvent(event: PropertyChangeEvent) { -// println("new text: ${event.newValue}") -// } -// }) + val sourceTextRunProperty = sourceTextRunProperty(source.children()) + val targetTextRunProperty = targetTextRunProperty(target) conf.add( Synchronizers.forPropsOneWay( - sourceTextProperty, - targetTextProperty(target) + sourceTextRunProperty, + targetTextRunProperty ) ) } - private fun setFontProperties(target: Text, styleSheet: StyleSheet?) { + private fun setFontProperties(target: TextLine, styleSheet: StyleSheet?) { if (styleSheet == null) { return } + + // Note: in SVG style and CSS have higher priority than attributes. + // We don't support this behavior because right now we never set + // font and fill properties via attributes and style at the same time. val className = source.fullClass() if (className.isNotEmpty()) { val style = styleSheet.getTextStyle(className) - target.font = style.createFont() - myTextAttrSupport.setAttribute(SVG_STYLE_ATTRIBUTE, "fill:${style.color.toHexColor()};") + + val posture = if (style.face.italic) FontPosture.ITALIC else null + val weight = if (style.face.bold) FontWeight.BOLD else null + val familyList = style.family.split(",").map { it.trim(' ', '\"') } + val family = familyList + .map { Font.font(it, weight, posture, style.size) } + .firstOrNull { + (if (style.face.italic) it.style.contains("italic", ignoreCase = true) else true) && + (if (style.face.bold) it.style.contains("bold", ignoreCase = true) else true) + }?.family + ?: familyList.firstOrNull() + + target.fontFamily = family + target.fontWeight = weight + target.fontPosture = posture + target.fontSize = style.size + target.fill = Color.web(style.color.toHexColor()) } } companion object { - private fun sourceTextProperty(nodes: ObservableCollection): ReadableProperty { - return object : SimpleCollectionProperty(nodes, joinToString(nodes)) { - override val propExpr = "joinToString($collection)" - override fun doGet() = joinToString(collection) - } - } - - private fun joinToString(nodes: ObservableCollection): String { - return nodes.asSequence() - .flatMap{((it as? SvgTSpanElement)?.children() ?: listOf(it as SvgTextNode)).asSequence()} - .joinToString ("\n") { (it as SvgTextNode).textContent().get() } - } - - private fun targetTextProperty(target: Text): WritableProperty { - return object : WritableProperty { - override fun set(value: String?) { - target.text = value ?: "n/a" + private fun sourceTextRunProperty(nodes: ObservableCollection): ReadableProperty> { + fun textRuns(nodes: ObservableCollection): List { + return nodes.flatMap { node -> + val nodeTextRuns = when (node) { + is SvgTextNode -> listOf(TextLine.TextRun(node.textContent().get())) + is SvgTSpanElement -> node.children().map { child -> + require(child is SvgTextNode) + val fontScale = node.getAttribute("font-size").get()?.let { + require(it is String) { "font-size: only string value is supported" } + require(it.endsWith("%")) { "font-size: only percent value is supported" } + it.removeSuffix("%").toFloat() / 100.0 + } + + // TODO: replace with Specs from LP + val baselineShift = node.getAttribute("baseline-shift").get()?.let { + when (it) { + "sub" -> TextLine.BaselineShift.SUB + "super" -> TextLine.BaselineShift.SUPER + else -> error("Unexpected baseline-shift value: $it") + } + } + + TextLine.TextRun( + text = child.textContent().get(), + baselineShift = baselineShift, + fontScale = fontScale + ) + } + + else -> error("Unexpected node type: ${node::class.simpleName}") + } + + nodeTextRuns } } - } - private fun TextStyle.createFont(): Font { - val posture = if (face.italic) FontPosture.ITALIC else null - val weight = if (face.bold) FontWeight.BOLD else null - // todo Need to choose an available font: - // 'TextStyle.family' string may contain a comma-separated list of families - val familyList = family.toString().split(",").map { it.trim(' ', '\"') } - return familyList - .map { Font.font(it, weight, posture, size) } - .firstOrNull { - // todo choose a font with a supported style ('bold' or/and 'italic') - (if (face.italic) it.style.contains("italic", ignoreCase = true) else true) && - (if (face.bold) it.style.contains("bold", ignoreCase = true) else true) - } - ?: Font.font( - familyList.firstOrNull(), - weight, - posture, - size - ) + return object : SimpleCollectionProperty>(nodes, textRuns(nodes)) { + override val propExpr = "textRuns($collection)" + override fun doGet() = textRuns(collection) + } } - } - - private class TextAttributesSupport(val target: Text) { - private var mySvgTextAnchor: String? = null - init { - @Suppress("ObjectLiteralToLambda") - target.boundsInLocalProperty().addListener(object : ChangeListener { - override fun changed(observable: ObservableValue?, oldValue: Bounds?, newValue: Bounds?) { - SvgTextElementAttrMapping.revalidatePositionAttributes(mySvgTextAnchor, target) + private fun targetTextRunProperty(target: TextLine): WritableProperty?> { + return object : WritableProperty?> { + override fun set(value: List?) { + target.content = value ?: emptyList() } - }) - } - - fun setAttribute(name: String, value: Any?) { - if (name == SvgTextContent.TEXT_ANCHOR.name) { - mySvgTextAnchor = value as String? } - SvgTextElementAttrMapping.setAttribute(target, name, value) } } -} \ No newline at end of file +} diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/TextLine.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/TextLine.kt new file mode 100644 index 00000000000..45a092aea32 --- /dev/null +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/TextLine.kt @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package org.jetbrains.letsPlot.jfx.mapping.svg + +import javafx.collections.ListChangeListener +import javafx.geometry.VPos +import javafx.scene.layout.Region +import javafx.scene.paint.Color +import javafx.scene.text.* + +// Manually positions text runs along the x-axis, supports super/subscript. + +// Limitations: +// Supports only a single line of text. +// Does not allow tspan elements to have their own styles. +internal class TextLine : Region() { + enum class BaselineShift { + SUB, + SUPER + } + + data class TextRun( + val text: String, + val baselineShift: BaselineShift? = null, + val fontScale: Double? = null, + ) + + init { + transforms.addListener( + ListChangeListener { + rebuild() + } + ) + } + + var content: List = emptyList() + set(value) { + field = value + rebuild() + } + + var x: Double = 0.0 + set(value) { + field = value + rebuild() + } + + var y: Double = 0.0 + set(value) { + field = value + rebuild() + } + + var fill: Color? = null + set(value) { + field = value + rebuild() + } + + var stroke: Color? = null + set(value) { + field = value + rebuild() + } + + var strokeWidth: Double? = null + set(value) { + field = value + rebuild() + } + + // May be null - system will use default font + var fontFamily: String? = null + set(value) { + if (value == field) return + + field = value + rebuildFont() + } + + var fontSize: Double = -1.0 + set(value) { + if (value == field) return + + field = value + rebuildFont() + } + + var fontWeight: FontWeight? = null + set(value) { + if (value == field) return + + field = value + rebuildFont() + } + + var fontPosture: FontPosture? = null + set(value) { + if (value == field) return + + field = value + rebuildFont() + } + + var textOrigin: VPos? = null + set(value) { + field = value + rebuild() + } + + var textAlignment: TextAlignment? = null + set(value) { + field = value + rebuild() + } + + private var font: Font? = null + set(value) { + field = value + rebuild() + } + + private fun rebuildFont() { + font = Font.font(fontFamily, fontWeight, fontPosture, fontSize) + rebuild() + } + + private fun rebuild() { + val texts = content.map(::textRunToTextFx) + + val width = texts.sumOf { it.boundsInLocal.width } + val dx = when (textAlignment) { + TextAlignment.RIGHT -> -width + TextAlignment.CENTER -> -width / 2 + else -> 0.0 + } + + // Arrange runs one after another + var currentRunPosX = x + texts.forEach { text -> + text.x = currentRunPosX + dx + text.y += y + currentRunPosX += text.boundsInLocal.width + } + + children.clear() + children.addAll(texts) + } + + private fun textRunToTextFx(textRun: TextRun): Text { + val font = font ?: error("Font is not specified") + val lineHeight = font.size + val scaleFactor = textRun.fontScale ?: 1.0 + val baseline = when (textRun.baselineShift) { + BaselineShift.SUPER -> lineHeight * 0.4 + BaselineShift.SUB -> lineHeight * -0.4 + else -> 0.0 + } + + val text = Text() + + fill?.let { text.fill = it } + stroke?.let { text.stroke = it } + strokeWidth?.let { text.strokeWidth = it } + textOrigin?.let { text.textOrigin = it } + text.text = textRun.text + text.y = -baseline + text.font = when (scaleFactor) { + 1.0 -> font + else -> { + val fontWeight = FontWeight.BOLD.takeIf { font.style.contains("bold") } + val fontPosture = FontPosture.ITALIC.takeIf { font.style.contains("italic") } + val fontSize = font.size * scaleFactor + Font.font(font.family, fontWeight, fontPosture, fontSize) + } + } + + return text + } + + override fun toString(): String { + return "TextLine(content=$content, fill=$fill, stroke=$stroke, font=$font, textOrigin=$textOrigin, textAlignment=$textAlignment)" + } +} diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/Utils.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/Utils.kt index bc42a40ffa9..ca9b195fe33 100644 --- a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/Utils.kt +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/Utils.kt @@ -15,16 +15,10 @@ import javafx.scene.input.MouseEvent import javafx.scene.layout.Pane import javafx.scene.layout.StackPane import javafx.scene.shape.* -import javafx.scene.text.Text import org.jetbrains.letsPlot.commons.event.Button import org.jetbrains.letsPlot.commons.event.KeyModifiers import org.jetbrains.letsPlot.datamodel.svg.dom.* import org.jetbrains.letsPlot.jfx.mapping.svg.attr.* -import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgAttrMapping -import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgCircleAttrMapping -import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgEllipseAttrMapping -import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgGAttrMapping -import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgImageAttrMapping import kotlin.reflect.KClass @@ -37,7 +31,7 @@ private val ATTR_MAPPINGS: Map, SvgAttrMapping> = mapOf( Line::class to (SvgLineAttrMapping as SvgAttrMapping), Ellipse::class to (SvgEllipseAttrMapping as SvgAttrMapping), Circle::class to (SvgCircleAttrMapping as SvgAttrMapping), - Text::class to (SvgTextElementAttrMapping as SvgAttrMapping), + TextLine::class to (SvgTextElementAttrMapping as SvgAttrMapping), SVGPath::class to (SvgPathAttrMapping as SvgAttrMapping), ImageView::class to (SvgImageAttrMapping as SvgAttrMapping) ) @@ -85,7 +79,7 @@ internal object Utils { is SvgEllipseElement -> Ellipse() is SvgCircleElement -> Circle() is SvgRectElement -> Rectangle() - is SvgTextElement -> Text() + is SvgTextElement -> TextLine() is SvgPathElement -> SVGPath() is SvgLineElement -> Line() is SvgSvgElement -> Rectangle() diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgAttrMapping.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgAttrMapping.kt index b358f7f497a..4991eef8160 100644 --- a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgAttrMapping.kt +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgAttrMapping.kt @@ -19,7 +19,7 @@ internal abstract class SvgAttrMapping { SvgGraphicsElement.CLIP_BOUNDS_JFX.name -> target.clip = (value as? DoubleRectangle)?.run { Rectangle(left, top, width, height) } SvgGraphicsElement.CLIP_PATH.name -> Unit // TODO: ignored - SvgConstants.SVG_STYLE_ATTRIBUTE -> setStyle(value as? String ?: "", target) + SvgConstants.SVG_STYLE_ATTRIBUTE -> target.style = svgStyleToFx(value as? String ?: "") SvgStylableElement.CLASS.name -> setStyleClass(value as String?, target) SvgTransformable.TRANSFORM.name -> setTransform((value as SvgTransform).toString(), target) @@ -40,9 +40,8 @@ internal abstract class SvgAttrMapping { } companion object { - private fun setStyle(value: String, target: Node) { - val valueFx = value.split(";").joinToString(";") { if (it.isNotEmpty()) "-fx-${it.trim()}" else it } - target.style = valueFx + internal fun svgStyleToFx(value: String): String { + return value.split(";").joinToString(";") { if (it.isNotEmpty()) "-fx-${it.trim()}" else it } } private fun setStyleClass(value: String?, target: Node) { diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgShapeMapping.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgShapeMapping.kt index d1ae02ccd91..1ec9c6fd97c 100644 --- a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgShapeMapping.kt +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgShapeMapping.kt @@ -6,7 +6,6 @@ package org.jetbrains.letsPlot.jfx.mapping.svg.attr import javafx.scene.paint.Color -import javafx.scene.paint.Paint import javafx.scene.shape.Shape import org.jetbrains.letsPlot.datamodel.svg.dom.SvgColors import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants @@ -61,24 +60,24 @@ internal abstract class SvgShapeMapping : SvgAttrMapping Color, set: (Color) -> Unit) { + internal fun setColor(value: Any?, get: () -> Color, set: (Color) -> Unit) { if (value == null) return + set(asColor(value, get().opacity)) + } + + internal fun asColor(value: Any, opacity: Double): Color { + return when (val svgColorString = value.toString()) { + SvgColors.NONE.toString() -> Color.TRANSPARENT + else -> Color.web(svgColorString, opacity) + } + } - val svgColorString = value.toString() - val newColor = - if (svgColorString == SvgColors.NONE.toString()) { - Color(0.0, 0.0, 0.0, 0.0) - } else { - val new = Paint.valueOf(svgColorString) as Color - val curr = get() - Color.color(new.red, new.green, new.blue, curr.opacity) - } - set(newColor) + internal fun changeOpacity(color: Color, newOpacity: Double): Color { + return Color.color(color.red, color.green, color.blue, newOpacity) } - private fun setOpacity(value: Double, get: () -> Color, set: (Color) -> Unit) { - val c = get() - set(Color.color(c.red, c.green, c.blue, value)) + internal fun setOpacity(value: Double, get: () -> Color, set: (Color) -> Unit) { + set(changeOpacity(get(), value)) } } } \ No newline at end of file diff --git a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgTextElementAttrMapping.kt b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgTextElementAttrMapping.kt index 884f648f67a..3e6537b3a9f 100644 --- a/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgTextElementAttrMapping.kt +++ b/platf-jfx-swing/src/jvmMain/kotlin/org/jetbrains/letsPlot/jfx/mapping/svg/attr/SvgTextElementAttrMapping.kt @@ -6,56 +6,108 @@ package org.jetbrains.letsPlot.jfx.mapping.svg.attr import javafx.geometry.VPos -import javafx.scene.text.Text +import javafx.scene.paint.Color +import javafx.scene.text.FontPosture +import javafx.scene.text.FontWeight import javafx.scene.text.TextAlignment import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants.SVG_TEXT_DY_CENTER import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants.SVG_TEXT_DY_TOP +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgShape import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextContent import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextElement +import org.jetbrains.letsPlot.jfx.mapping.svg.TextLine +import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgShapeMapping.Companion.asColor +import org.jetbrains.letsPlot.jfx.mapping.svg.attr.SvgShapeMapping.Companion.changeOpacity -internal object SvgTextElementAttrMapping : SvgShapeMapping() { - override fun setAttribute(target: Text, name: String, value: Any?) { +internal object SvgTextElementAttrMapping : SvgAttrMapping() { + override fun setAttribute(target: TextLine, name: String, value: Any?) { when (name) { - SvgTextElement.X.name -> target.x = asDouble(value) - SvgTextElement.Y.name -> target.y = asDouble(value) - SvgTextContent.TEXT_ANCHOR.name -> { - val svgTextAnchor = value as String? - revalidatePositionAttributes(svgTextAnchor, target) - } - SvgTextContent.TEXT_DY.name -> { - when (value) { - SVG_TEXT_DY_TOP -> target.textOrigin = VPos.TOP - SVG_TEXT_DY_CENTER -> target.textOrigin = VPos.CENTER - else -> throw IllegalStateException("Unexpected text 'dy' value: $value") - } - } + SvgShape.STROKE_WIDTH.name -> target.strokeWidth = asDouble(value) - SvgTextContent.FILL.name, - SvgTextContent.FILL_OPACITY.name, - SvgTextContent.STROKE.name, - SvgTextContent.STROKE_OPACITY.name, - SvgTextContent.STROKE_WIDTH.name -> super.setAttribute(target, name, value) + SvgShape.FILL.name -> value?.let { fill -> + target.fill = asColor(fill, target.fill?.opacity ?: 1.0) + } - else -> super.setAttribute(target, name, value) - } - } + SvgShape.FILL_OPACITY.name -> { + val color = target.fill ?: Color.BLACK + val opacity = asDouble(value) + target.fill = changeOpacity(color, opacity) + } - fun revalidatePositionAttributes(svgTextAnchor: String?, target: Text) { - val width = target.boundsInLocal.width - when (svgTextAnchor) { - SvgConstants.SVG_TEXT_ANCHOR_END -> { - target.translateX = -width - target.textAlignment = TextAlignment.RIGHT + SvgShape.STROKE.name -> value?.let { stroke -> + target.stroke = asColor(stroke, target.stroke?.opacity ?: 1.0) } - SvgConstants.SVG_TEXT_ANCHOR_MIDDLE -> { - target.translateX = -width / 2 - target.textAlignment = TextAlignment.CENTER + + SvgShape.STROKE_OPACITY.name -> { + val color = target.stroke ?: Color.BLACK + val newOpacity = asDouble(value) + target.stroke = changeOpacity(color, newOpacity) } - else -> { - target.translateX = 0.0 - target.textAlignment = TextAlignment.LEFT + + + SvgConstants.SVG_STYLE_ATTRIBUTE -> { + require(value is String) + val style = value.split(";") + .map(String::trim) + .filter(String::isNotEmpty) + .map { it.split(":").map(String::trim) } + .associate { it[0] to it[1] } + + style["font-family"]?.let { + target.fontFamily = it + } + + style["font-weight"]?.let { weight -> + target.fontWeight = FontWeight.BOLD.takeIf { weight == "bold" } + } + + style["font-style"]?.let { posture -> + target.fontPosture = FontPosture.ITALIC.takeIf { posture == "italic" } + } + + style["font-size"]?.let { size -> + target.fontSize = size.removeSuffix("px").toDoubleOrNull() ?: -1.0 + } + + style["fill"]?.let { + target.fill = Color.web(it, target.fill?.opacity ?: 1.0) + } + + style["stroke"]?.let { + target.stroke = Color.web(it, target.stroke?.opacity ?: 1.0) + } + + val unhandledStyleProperties = style.keys - setOf( + "font-family", + "font-weight", + "font-style", + "font-size", + "fill", + "stroke" + ) + if (unhandledStyleProperties.isNotEmpty()) { + throw IllegalStateException("Unhandled style attributes: $unhandledStyleProperties") + } } + + SvgTextElement.X.name -> target.x = asDouble(value) + SvgTextElement.Y.name -> target.y = asDouble(value) + SvgTextContent.TEXT_ANCHOR.name -> + target.textAlignment = when (value as String?) { + SvgConstants.SVG_TEXT_ANCHOR_END -> TextAlignment.RIGHT + SvgConstants.SVG_TEXT_ANCHOR_MIDDLE -> TextAlignment.CENTER + else -> TextAlignment.LEFT + } + + SvgTextContent.TEXT_DY.name -> + target.textOrigin = when (value) { + SVG_TEXT_DY_TOP -> VPos.TOP + SVG_TEXT_DY_CENTER -> VPos.CENTER + else -> throw IllegalStateException("Unexpected text 'dy' value: $value") + } + + else -> super.setAttribute(target, name, value) } } -} \ No newline at end of file +} diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/Scale.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/Scale.kt index b1f93b8333e..f24ac43a902 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/Scale.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/Scale.kt @@ -32,6 +32,9 @@ interface Scale { fun getScaleBreaks(): ScaleBreaks + // For axis and legend (truncated labels). For tooltips the getScaleBreaks functions should be used (full labels). + fun getShortenedScaleBreaks(): ScaleBreaks + fun with(): Builder interface Builder { @@ -41,6 +44,8 @@ interface Scale { fun labels(l: List): Builder + fun labelLengthLimit(v: Int): Builder + fun labelFormatter(v: (Any) -> String): Builder fun multiplicativeExpand(v: Double): Builder diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/MultilineLabel.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/MultilineLabel.kt index 40d1ec74a3d..7d41a8e1832 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/MultilineLabel.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/MultilineLabel.kt @@ -14,7 +14,7 @@ import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextElement class MultilineLabel(val text: String) : SvgComponent() { - private val myLines: List = splitLines(text).map(::SvgTextElement) + private val myLines: List = splitLines(text).map(RichText::toSvg) private var myTextColor: Color? = null private var myFontSize = 0.0 private var myFontWeight: String? = null diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/RichText.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/RichText.kt new file mode 100644 index 00000000000..7afb35675b0 --- /dev/null +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/RichText.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package org.jetbrains.letsPlot.core.plot.base.render.svg + +import org.jetbrains.letsPlot.commons.values.Font +import org.jetbrains.letsPlot.datamodel.svg.dom.BaselineShift +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTSpanElement +import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextElement +import kotlin.math.roundToInt + +object RichText { + fun toSvg(text: String): SvgTextElement { + val richTextElement = SvgTextElement() + extractTerms(text) + .flatMap(Term::toTSpanElements) + .forEach(richTextElement::addTSpan) + return richTextElement + } + + fun enrichWidthCalculator(widthCalculator: (String, Font) -> Double): (String, Font) -> Double { + fun enrichedWidthCalculator(text: String, font: Font): Double { + return extractTerms(text).sumOf { term -> + term.calculateWidth(widthCalculator, font) + } + } + return ::enrichedWidthCalculator + } + + private fun extractTerms(text: String): List { + val powerTerms = Power.toPowerTerms(text) + // If there will be another formula terms (like fractionTerms), + // then join all of them to the val formulaTerms, + // sort it by PositionedTerm::range.first and use it further instead of powerTerms + return if (powerTerms.isEmpty()) { + listOf(Text(text)) + } else { + val textTerms = subtractRange(IntRange(0, text.length - 1), powerTerms.map { it.range }) + .map { position -> PositionedTerm(Text(text.substring(position)), position) } + (powerTerms + textTerms).sortedBy { it.range.first }.map(PositionedTerm::term) + } + } + + private fun subtractRange(range: IntRange, toSubtract: List): List { + val sortedToSubtract = toSubtract.sortedBy(IntRange::first) + val firstRange = IntRange(0, sortedToSubtract.first().first - 1) + val intermediateRanges = sortedToSubtract.windowed(2).map { (prevRange, nextRange) -> + IntRange(prevRange.last + 1, nextRange.first - 1) + } + val lastRange = IntRange(sortedToSubtract.last().last + 1, range.last) + + return (listOf(firstRange) + intermediateRanges + listOf(lastRange)).filterNot(IntRange::isEmpty) + } + + private class Text(private val text: String) : Term { + override fun toTSpanElements(): List { + return listOf(SvgTSpanElement(text)) + } + + override fun calculateWidth(widthCalculator: (String, Font) -> Double, font: Font): Double { + return widthCalculator(text, font) + } + } + + private class Power( + private val base: String, + private val degree: String + ) : Term { + override fun toTSpanElements(): List { + val baseTSpan = SvgTSpanElement(base) + val degreeTSpan = SvgTSpanElement(degree) + degreeTSpan.setAttribute(SvgTSpanElement.BASELINE_SHIFT, BaselineShift.SUPER.value) + degreeTSpan.setAttribute(SvgTSpanElement.FONT_SIZE, "${(SUPERSCRIPT_SIZE_FACTOR * 100).roundToInt()}%") + return listOf(baseTSpan, degreeTSpan) + } + + override fun calculateWidth(widthCalculator: (String, Font) -> Double, font: Font): Double { + val baseWidth = widthCalculator(base, font) + val degreeFontSize = (font.size * SUPERSCRIPT_SIZE_FACTOR).roundToInt() + val superscriptFont = Font(font.family, degreeFontSize, font.isBold, font.isItalic) + val degreeWidth = widthCalculator(degree, superscriptFont) + return baseWidth + degreeWidth + } + + companion object { + private const val SUPERSCRIPT_SIZE_FACTOR = 0.6 + val REGEX = """\\\(\s*(?\d+)\^(\{\s*)?(?-?\d+)(\s*\})?\s*\\\)""".toRegex() + + fun toPowerTerms(text: String): List { + return REGEX.findAll(text).map { match -> + val groups = match.groups as MatchNamedGroupCollection + PositionedTerm( + Power(groups["base"]!!.value, groups["degree"]!!.value), + match.range + ) + }.toList() + } + } + } + + private interface Term { + fun toTSpanElements(): List + + fun calculateWidth(widthCalculator: (String, Font) -> Double, font: Font): Double + } + + private data class PositionedTerm(val term: Term, val range: IntRange) +} \ No newline at end of file diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/TextLabel.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/TextLabel.kt index 60bfa807825..3b2f0e64842 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/TextLabel.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/render/svg/TextLabel.kt @@ -15,7 +15,7 @@ import org.jetbrains.letsPlot.datamodel.svg.dom.SvgConstants.SVG_STYLE_ATTRIBUTE import org.jetbrains.letsPlot.datamodel.svg.dom.SvgTextElement class TextLabel(text: String) : SvgComponent() { - private val myText: SvgTextElement = SvgTextElement(text) + private val myText: SvgTextElement = RichText.toSvg(text) private var myTextColor: Color? = null private var myFontSize = 0.0 private var myFontWeight: String? = null diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/AbstractScale.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/AbstractScale.kt index 61fd1c7dadd..40e2458a0e5 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/AbstractScale.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/AbstractScale.kt @@ -11,6 +11,7 @@ internal abstract class AbstractScale : Scale { private val definedBreaks: List? private val definedLabels: List? + private val labelLengthLimit: Int final override val name: String @@ -23,6 +24,7 @@ internal abstract class AbstractScale : Scale { protected constructor(name: String, breaks: List? = null) { this.name = name this.definedBreaks = breaks + labelLengthLimit = 0 definedLabels = null labelFormatter = null } @@ -31,6 +33,7 @@ internal abstract class AbstractScale : Scale { name = b.myName definedBreaks = b.myBreaks definedLabels = b.myLabels + labelLengthLimit = b.myLabelLengthLimit labelFormatter = b.myLabelFormatter multiplicativeExpand = b.myMultiplicativeExpand @@ -54,12 +57,20 @@ internal abstract class AbstractScale : Scale { } override fun getScaleBreaks(): ScaleBreaks { + return createScaleBreaks(shortenLabels = false) + } + + override fun getShortenedScaleBreaks(): ScaleBreaks { + return createScaleBreaks(shortenLabels = true) + } + + private fun createScaleBreaks(shortenLabels: Boolean): ScaleBreaks { if (!hasBreaks()) { return ScaleBreaks.EMPTY } val breakValuesIntern = getBreaksIntern() - val labels = getLabels(breakValuesIntern) + val labels = getLabels(breakValuesIntern).map { if (shortenLabels) shorten(it) else it } val transformCore = transform.unwrap() // make sure 'original' transform is used. val transformed = ScaleUtil.applyTransform(breakValuesIntern, transformCore) @@ -77,6 +88,14 @@ internal abstract class AbstractScale : Scale { ) } + private fun shorten(str: String): String { + return if (labelLengthLimit > 0 && str.length > labelLengthLimit) { + str.take(labelLengthLimit) + "..." + } else { + str + } + } + private fun getLabels(breaks: List): List { if (definedLabels != null) { val labels = getLabelsIntern() @@ -97,6 +116,7 @@ internal abstract class AbstractScale : Scale { internal var myBreaks: List? = scale.definedBreaks internal var myLabels: List? = scale.definedLabels + internal var myLabelLengthLimit: Int = scale.labelLengthLimit internal var myLabelFormatter: ((Any) -> String)? = scale.labelFormatter internal var myMultiplicativeExpand: Double = scale.multiplicativeExpand @@ -120,6 +140,11 @@ internal abstract class AbstractScale : Scale { return this } + override fun labelLengthLimit(v: Int): Scale.Builder { + myLabelLengthLimit = v + return this + } + override fun labelFormatter(v: (Any) -> String): Scale.Builder { myLabelFormatter = v return this diff --git a/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumericBreakFormatterTest.kt b/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumericBreakFormatterTest.kt index d1c0b8ad986..26de1af070c 100644 --- a/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumericBreakFormatterTest.kt +++ b/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumericBreakFormatterTest.kt @@ -50,7 +50,7 @@ class NumericBreakFormatterTest { @Test fun formatExtremesTypeE() { assertEquals( - listOf("-1.80e+308", "-1.35e+308", "-8.99e+307", "-4.49e+307", "0"), + listOf("-1.80·\\(10^{308}\\)", "-1.35·\\(10^{308}\\)", "-8.99·\\(10^{307}\\)", "-4.49·\\(10^{307}\\)", "0"), formatRange( min = -Double.MAX_VALUE, max = 0.0, @@ -59,7 +59,7 @@ class NumericBreakFormatterTest { ) assertEquals( - listOf("0", "4.49e+307", "8.99e+307", "1.35e+308", "1.80e+308"), + listOf("0", "4.49·\\(10^{307}\\)", "8.99·\\(10^{307}\\)", "1.35·\\(10^{308}\\)", "1.80·\\(10^{308}\\)"), formatRange( min = 0.0, max = Double.MAX_VALUE, @@ -68,7 +68,7 @@ class NumericBreakFormatterTest { ) assertEquals( - listOf("-8.99e+307", "-4.49e+307", "0", "4.49e+307", "8.99e+307"), + listOf("-8.99·\\(10^{307}\\)", "-4.49·\\(10^{307}\\)", "0", "4.49·\\(10^{307}\\)", "8.99·\\(10^{307}\\)"), formatRange( min = -Double.MAX_VALUE / 2, max = Double.MAX_VALUE / 2, diff --git a/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumberTickFormatTest.kt b/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumberTickFormatTest.kt index d8e408e89eb..fb7999241c3 100644 --- a/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumberTickFormatTest.kt +++ b/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/scale/breaks/NumberTickFormatTest.kt @@ -5,7 +5,6 @@ package org.jetbrains.letsPlot.core.plot.base.scale.breaks -import org.jetbrains.letsPlot.core.plot.base.scale.breaks.NumericBreakFormatter import java.util.* import kotlin.test.BeforeTest import kotlin.test.Test @@ -155,15 +154,15 @@ class NumberTickFormatTest { fun both_ultraSmall() { val domainAndStep = doubleArrayOf(1e-3, 5e-6) assertEquals( - "5.000e-4", + "5.000·\\(10^{-4}\\)", format(.0005, domainAndStep) ) assertEquals( - "5.050e-4", + "5.050·\\(10^{-4}\\)", format(.0005 + 5e-6, domainAndStep) ) assertEquals( - "1.505e-3", + "1.505·\\(10^{-3}\\)", format(.0015 + 5e-6, domainAndStep) ) } @@ -223,28 +222,28 @@ class NumberTickFormatTest { fun both_ultraLarge_scientific() { val domainAndStep = doubleArrayOf(1e8, 5e6) assertEquals( - "5.00e+7", + "5.00·\\(10^{7}\\)", formatScientific( 5e7, domainAndStep ) ) assertEquals( - "5.00e+7", + "5.00·\\(10^{7}\\)", formatScientific( 5e7 + 5, domainAndStep ) ) assertEquals( - "5.50e+7", + "5.50·\\(10^{7}\\)", formatScientific( 5e7 + 5e6, domainAndStep ) ) assertEquals( - "1.05e+8", + "1.05·\\(10^{8}\\)", formatScientific( 1e8 + 5e6, domainAndStep diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/BogusScale.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/BogusScale.kt index 70603dc4758..07baee58b3c 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/BogusScale.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/BogusScale.kt @@ -40,6 +40,10 @@ internal class BogusScale : Scale { throw IllegalStateException("Bogus scale is not supposed to be used.") } + override fun getShortenedScaleBreaks(): ScaleBreaks { + throw IllegalStateException("Bogus scale is not supposed to be used.") + } + override fun getBreaksGenerator(): BreaksGenerator { throw IllegalStateException("Bogus scale is not supposed to be used.") } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/LegendAssembler.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/LegendAssembler.kt index f23a9fb6bf9..3c58fa06006 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/LegendAssembler.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/LegendAssembler.kt @@ -143,7 +143,7 @@ class LegendAssembler( } check(scale.hasBreaks()) { "No breaks were defined for scale $aes" } - val scaleBreaks = scale.getScaleBreaks() + val scaleBreaks = scale.getShortenedScaleBreaks() val aesValues = scaleBreaks.transformedValues.map { scaleMappers.getValue(aes)(it) as Any // Don't expect nulls. } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultGeomTheme.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultGeomTheme.kt index 7bac5a043a0..2a461201937 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultGeomTheme.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultGeomTheme.kt @@ -111,6 +111,7 @@ internal class DefaultGeomTheme private constructor( GeomKind.CONTOURF, GeomKind.DENSITY2DF, GeomKind.DOT_PLOT, + GeomKind.RASTER, GeomKind.Y_DOT_PLOT -> colorTheme.brush() GeomKind.HISTOGRAM, diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultTheme.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultTheme.kt index 202db1b487b..6af2c3a1108 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultTheme.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/defaultTheme/DefaultTheme.kt @@ -65,12 +65,12 @@ class DefaultTheme internal constructor( ) } - ThemeOption.PLOT_MARGIN -> { + ThemeOption.PLOT_MARGIN -> with (this.plot.plotMargins()) { mapOf( - Margin.TOP to this.plot.plotMargins().top, - Margin.RIGHT to this.plot.plotMargins().right, - Margin.BOTTOM to this.plot.plotMargins().bottom, - Margin.LEFT to this.plot.plotMargins().left + Margin.TOP to top, + Margin.RIGHT to right, + Margin.BOTTOM to bottom, + Margin.LEFT to left ) } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/guide/ColorBarComponentLayout.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/guide/ColorBarComponentLayout.kt index 5977767ba26..28a0244d56e 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/guide/ColorBarComponentLayout.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/guide/ColorBarComponentLayout.kt @@ -71,7 +71,6 @@ abstract class ColorBarComponentLayout( reverse, theme ) { - override val graphSize: DoubleVector private val labelDistance: Double get() = PlotLabelSpecFactory.legendItem(theme).height() / 3 override val guideBarLength: Double get() = guideBarSize.x diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisBreaksProviderFactory.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisBreaksProviderFactory.kt index 7493008a178..b18f7759e81 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisBreaksProviderFactory.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisBreaksProviderFactory.kt @@ -15,7 +15,7 @@ abstract class AxisBreaksProviderFactory { companion object { fun forScale(scale: Scale): AxisBreaksProviderFactory { return if (scale.hasBreaks()) { - FixedBreaksProviderFactory(FixedAxisBreaksProvider(scale.getScaleBreaks())) + FixedBreaksProviderFactory(FixedAxisBreaksProvider(scale.getShortenedScaleBreaks())) } else { AdaptableBreaksProviderFactory(scale.getBreaksGenerator()) } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisLayouter.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisLayouter.kt index e90346d0c14..c748bd43e8c 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisLayouter.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/AxisLayouter.kt @@ -8,14 +8,16 @@ package org.jetbrains.letsPlot.core.plot.builder.layout.axis import org.jetbrains.letsPlot.commons.interval.DoubleSpan import org.jetbrains.letsPlot.core.plot.base.ScaleMapper import org.jetbrains.letsPlot.core.plot.base.scale.Mappers -import org.jetbrains.letsPlot.core.plot.base.scale.ScaleBreaks import org.jetbrains.letsPlot.core.plot.base.theme.AxisTheme import org.jetbrains.letsPlot.core.plot.builder.guide.Orientation import org.jetbrains.letsPlot.core.plot.builder.layout.AxisLayoutInfo import org.jetbrains.letsPlot.core.plot.builder.layout.axis.label.AxisLabelsLayout +import org.jetbrains.letsPlot.core.plot.builder.layout.axis.label.AxisLabelsLayout.Companion.horizontalFixedBreaks +import org.jetbrains.letsPlot.core.plot.builder.layout.axis.label.AxisLabelsLayout.Companion.horizontalFlexBreaks +import org.jetbrains.letsPlot.core.plot.builder.layout.axis.label.AxisLabelsLayout.Companion.verticalFixedBreaks +import org.jetbrains.letsPlot.core.plot.builder.layout.axis.label.AxisLabelsLayout.Companion.verticalFlexBreaks import org.jetbrains.letsPlot.core.plot.builder.layout.axis.label.BreakLabelsLayoutUtil import org.jetbrains.letsPlot.core.plot.builder.layout.util.Insets -import org.jetbrains.letsPlot.core.plot.builder.presentation.Defaults.Common.Axis internal abstract class AxisLayouter( val orientation: Orientation, @@ -62,51 +64,22 @@ internal abstract class AxisLayouter( geomAreaInsets: Insets, theme: AxisTheme ): AxisLayouter { - - if (orientation.isHorizontal) { - val labelsLayout: AxisLabelsLayout = if (breaksProvider.isFixedBreaks) { - val trimmedScaleBreaks = with(breaksProvider.fixedBreaks) { - ScaleBreaks(domainValues, transformedValues, labels.map(::trimLongValues)) + val labelsLayout = + if (breaksProvider.isFixedBreaks) { + if (orientation.isHorizontal) { + horizontalFixedBreaks(orientation, axisDomain, breaksProvider.fixedBreaks, geomAreaInsets, theme) + } else { + verticalFixedBreaks(orientation, axisDomain, breaksProvider.fixedBreaks, theme) } - AxisLabelsLayout.horizontalFixedBreaks( - orientation, - axisDomain, - trimmedScaleBreaks, - geomAreaInsets, - theme - ) } else { - AxisLabelsLayout.horizontalFlexBreaks(orientation, axisDomain, breaksProvider, theme) - } - return HorizontalAxisLayouter( - orientation, - axisDomain, - labelsLayout - ) - } - - // vertical - val labelsLayout: AxisLabelsLayout = if (breaksProvider.isFixedBreaks) { - val trimmedScaleBreaks = with(breaksProvider.fixedBreaks) { - ScaleBreaks(domainValues, transformedValues, labels.map(::trimLongValues)) + if (orientation.isHorizontal) { + horizontalFlexBreaks(orientation, axisDomain, breaksProvider, theme) + } else { + verticalFlexBreaks(orientation, axisDomain, breaksProvider, theme) + } } - AxisLabelsLayout.verticalFixedBreaks(orientation, axisDomain, trimmedScaleBreaks, theme) - } else { - AxisLabelsLayout.verticalFlexBreaks(orientation, axisDomain, breaksProvider, theme) - } - return VerticalAxisLayouter( - orientation, - axisDomain, - labelsLayout - ) - } - private fun trimLongValues(text: String): String { - return if (text.length <= Axis.LABEL_MAX_LENGTH) { - text - } else { - text.take(Axis.LABEL_MAX_LENGTH) + ".." - } + return VerticalAxisLayouter(orientation, axisDomain, labelsLayout) } } } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/presentation/PlotLabelSpec.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/presentation/PlotLabelSpec.kt index 13b8f49325f..f55aa033849 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/presentation/PlotLabelSpec.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/presentation/PlotLabelSpec.kt @@ -8,6 +8,7 @@ package org.jetbrains.letsPlot.core.plot.builder.presentation import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.unsupported.UNSUPPORTED import org.jetbrains.letsPlot.commons.values.Font +import org.jetbrains.letsPlot.core.plot.base.render.svg.RichText class PlotLabelSpec( override val font: Font @@ -18,28 +19,7 @@ class PlotLabelSpec( } override fun width(labelText: String): Double { - return if (font.isMonospased) { - // ToDo: should take in account font family adjustment parameters. - monospacedWidth(labelText.length) - } else { - FONT_WIDTH_SCALE_FACTOR * TextWidthEstimator.textWidth(labelText, font) - }.let { - it * font.family.widthFactor - } - } - - /** - * The old way. - */ - private fun monospacedWidth(labelLength: Int): Double { - val ratio = FONT_SIZE_TO_GLYPH_WIDTH_RATIO_MONOSPACED - val width = labelLength.toDouble() * font.size * ratio + 2 * LABEL_PADDING - return if (font.isBold) { - // ToDo: switch to new ratios. - width * FONT_WEIGHT_BOLD_TO_NORMAL_WIDTH_RATIO - } else { - width - } + return RichText.enrichWidthCalculator(::widthCalculator)(labelText, font) } override fun height(): Double { @@ -53,6 +33,31 @@ class PlotLabelSpec( private const val LABEL_PADDING = 0.0 //2; private const val FONT_WIDTH_SCALE_FACTOR = 0.85026 // See explanation here: font_width_scale_factor.md + private fun widthCalculator(text: String, font: Font): Double { + return if (font.isMonospased) { + // ToDo: should take in account font family adjustment parameters. + monospacedWidth(text.length, font) + } else { + FONT_WIDTH_SCALE_FACTOR * TextWidthEstimator.textWidth(text, font) + }.let { + it * font.family.widthFactor + } + } + + /** + * The old way. + */ + private fun monospacedWidth(labelLength: Int, font: Font): Double { + val ratio = FONT_SIZE_TO_GLYPH_WIDTH_RATIO_MONOSPACED + val width = labelLength.toDouble() * font.size * ratio + 2 * LABEL_PADDING + return if (font.isBold) { + // ToDo: switch to new ratios. + width * FONT_WEIGHT_BOLD_TO_NORMAL_WIDTH_RATIO + } else { + width + } + } + val DUMMY: LabelSpec = object : LabelSpec { override val font: Font get() = UNSUPPORTED("Dummy Label Spec") diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/scale/ScaleProviderBuilder.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/scale/ScaleProviderBuilder.kt index 8623f497383..9fde6f4baad 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/scale/ScaleProviderBuilder.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/scale/ScaleProviderBuilder.kt @@ -17,6 +17,7 @@ class ScaleProviderBuilder constructor(private val aes: Aes) { private var myName: String? = null private var myBreaks: List? = null private var myLabels: List? = null + private var myLabelLengthLimit: Int? = null private var myLabelFormat: String? = null private var myMultiplicativeExpand: Double? = null private var myAdditiveExpand: Double? = null @@ -56,6 +57,11 @@ class ScaleProviderBuilder constructor(private val aes: Aes) { return this } + fun labelLengthLimit(v: Int): ScaleProviderBuilder { + myLabelLengthLimit = v + return this + } + fun labelFormat(format: String?): ScaleProviderBuilder { myLabelFormat = format return this @@ -129,6 +135,7 @@ class ScaleProviderBuilder constructor(private val aes: Aes) { private val myName: String? = b.myName private val myLabels: List? = b.myLabels?.let { ArrayList(it) } + private val myLabelLengthLimit: Int? = b.myLabelLengthLimit private val myLabelFormat: String? = b.myLabelFormat private val myMultiplicativeExpand: Double? = b.myMultiplicativeExpand private val myAdditiveExpand: Double? = b.myAdditiveExpand @@ -216,6 +223,9 @@ class ScaleProviderBuilder constructor(private val aes: Aes) { if (myLabels != null) { with.labels(myLabels) } + if (myLabelLengthLimit != null) { + with.labelLengthLimit(myLabelLengthLimit) + } if (myLabelFormat != null) { with.labelFormatter(StringFormat.forOneArg(myLabelFormat)::format) } diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt index da1cac09e63..3327145fc3d 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt @@ -499,6 +499,7 @@ object Option { const val AES = "aesthetic" const val BREAKS = "breaks" const val LABELS = "labels" + const val LABLIM = "lablim" const val EXPAND = "expand" const val LIMITS = "limits" const val DISCRETE_DOMAIN = "discrete" diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ScaleConfig.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ScaleConfig.kt index 185fbbfc546..30bcd8fc21e 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ScaleConfig.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ScaleConfig.kt @@ -30,6 +30,7 @@ import org.jetbrains.letsPlot.core.spec.Option.Scale.GUIDE import org.jetbrains.letsPlot.core.spec.Option.Scale.HIGH import org.jetbrains.letsPlot.core.spec.Option.Scale.HUE_RANGE import org.jetbrains.letsPlot.core.spec.Option.Scale.LABELS +import org.jetbrains.letsPlot.core.spec.Option.Scale.LABLIM import org.jetbrains.letsPlot.core.spec.Option.Scale.LIMITS import org.jetbrains.letsPlot.core.spec.Option.Scale.LOW import org.jetbrains.letsPlot.core.spec.Option.Scale.LUMINANCE @@ -251,24 +252,31 @@ class ScaleConfig constructor( if (has(NAME)) { b.name(getString(NAME)!!) } + if (has(BREAKS)) { b.breaks(getList(BREAKS).mapNotNull { it }) } + if (has(LABELS)) { b.labels(getStringList(LABELS)) } else { // Skip format is labels are defined b.labelFormat(getString(FORMAT)) } + + if (has(LABLIM)) { + b.labelLengthLimit(getInteger(LABLIM)!!) + } + if (has(EXPAND)) { - val list = getList(EXPAND) - if (list.isNotEmpty()) { - val multiplicativeExpand = list[0] as Number - b.multiplicativeExpand(multiplicativeExpand.toDouble()) - if (list.size > 1) { - val additiveExpand = list[1] as Number - b.additiveExpand(additiveExpand.toDouble()) - } + val expandList = getDoubleList(EXPAND) + + expandList.getOrNull(0)?.let { + b.multiplicativeExpand(it) + } + + expandList.getOrNull(1)?.let { + b.additiveExpand(it) } } if (has(LIMITS)) { diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ThemeConfig.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ThemeConfig.kt index efc30f515a9..ea241c40583 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ThemeConfig.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/config/ThemeConfig.kt @@ -51,25 +51,71 @@ class ThemeConfig constructor( } private fun convertMargins(key: String, value: Any): Any { - fun toMargin(value: Any?) = when (value) { - "t" -> ThemeOption.Elem.Margin.TOP - "r" -> ThemeOption.Elem.Margin.RIGHT - "b" -> ThemeOption.Elem.Margin.BOTTOM - "l" -> ThemeOption.Elem.Margin.LEFT - else -> throw IllegalArgumentException( - "Illegal value: '$value'.\n${ThemeOption.Elem.MARGIN} " + - "Expected values are: value is either a string: t|r|b|l." - ) + + fun toMarginSpec(value: Any?): Map { + val margins: List = when (value) { + is Number -> listOf(value.toDouble()) + is List<*> -> { + require(value.all { it == null || it is Number }) { + "The margins option requires a list of numbers, but was: $value." + } + value.map { (it as? Number)?.toDouble() } + } + else -> error("The margins option should be specified using number or list of numbers, but was: $value.") } - return when { - key == PLOT_MARGIN && value is Map<*, *> -> { - value.map { (k, v) -> toMargin(k) to v }.toMap() + val top: Double? + val right: Double? + val bottom: Double? + val left: Double? + + when (margins.size) { + 1 -> { + val margin = margins.single() + top = margin + right = margin + left = margin + bottom = margin + } + 2 -> { + val (vMargin, hMargin) = margins + top = vMargin + bottom = vMargin + right = hMargin + left = hMargin + } + 3 -> { + top = margins[0] + right = margins[1] + left = margins[1] + bottom = margins[2] + } + 4 -> { + top = margins[0] + right = margins[1] + bottom = margins[2] + left = margins[3] + } + else -> { + error("The margins accept a number or a list of one, two, three or four numbers, but was: $value.") + } } + return mapOf( + ThemeOption.Elem.Margin.TOP to top, + ThemeOption.Elem.Margin.RIGHT to right, + ThemeOption.Elem.Margin.BOTTOM to bottom, + ThemeOption.Elem.Margin.LEFT to left + ) + .filterValues { it != null } + .mapValues { (_, v) -> v as Any } + } + + return when { + key == PLOT_MARGIN -> toMarginSpec(value) value is Map<*, *> && value.containsKey(ThemeOption.Elem.MARGIN) -> { - val oldMargins = value[ThemeOption.Elem.MARGIN] as Map<*, *> - val newMargins = oldMargins.map { (k, v) -> toMargin(k) to v } - value - ThemeOption.Elem.MARGIN + newMargins + val margins = toMarginSpec(value[ThemeOption.Elem.MARGIN]) + // to keep other options + value - ThemeOption.Elem.MARGIN + margins } else -> { value diff --git a/python-package/lets_plot/export/ggsave_.py b/python-package/lets_plot/export/ggsave_.py index 8c1b7b8f1dd..8ddfb790ab3 100644 --- a/python-package/lets_plot/export/ggsave_.py +++ b/python-package/lets_plot/export/ggsave_.py @@ -5,7 +5,7 @@ from os.path import join from typing import Union -from .simple import export_svg, export_html, export_png, export_pdf +from ..plot.core import _to_svg, _to_html, _to_png, _to_pdf from ..plot.core import PlotSpec from ..plot.plot import GGBunch from ..plot.subplots import SupPlotsSpec @@ -74,20 +74,17 @@ def ggsave(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, *, path: if not path: path = join(os.getcwd(), _DEF_EXPORT_DIR) - if not os.path.exists(path): - os.makedirs(path) - pathname = join(path, filename) ext = ext[1:].lower() if ext == 'svg': - return export_svg(plot, pathname) + return _to_svg(plot, pathname) elif ext in ['html', 'htm']: - return export_html(plot, pathname, iframe=iframe) + return _to_html(plot, pathname, iframe=iframe) elif ext == 'png': - return export_png(plot, pathname, scale) + return _to_png(plot, pathname, scale) elif ext == 'pdf': - return export_pdf(plot, pathname, scale) + return _to_pdf(plot, pathname, scale) else: raise ValueError( "Unsupported file extension: '{}'\nPlease use one of: 'png', 'svg', 'pdf', 'html', 'htm'".format(ext) diff --git a/python-package/lets_plot/export/simple.py b/python-package/lets_plot/export/simple.py deleted file mode 100644 index 501a631356a..00000000000 --- a/python-package/lets_plot/export/simple.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright (c) 2020. JetBrains s.r.o. -# Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -import io -from os.path import abspath -from typing import Union - -from ..plot.core import PlotSpec -from ..plot.plot import GGBunch -from ..plot.subplots import SupPlotsSpec - - -def export_svg(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str) -> str: - """ - Export plot or `bunch` to a file in SVG format. - - Parameters - ---------- - plot : `PlotSpec` or `SupPlotsSpec` or `GGBunch` - Plot specification to export. - filename : str - Filename to save SVG under. - - Returns - ------- - str - Absolute pathname of created SVG file. - - """ - if not (isinstance(plot, PlotSpec) or isinstance(plot, SupPlotsSpec) or isinstance(plot, GGBunch)): - raise ValueError("PlotSpec, SupPlotsSpec or GGBunch expected but was: {}".format(type(plot))) - - from .. import _kbridge as kbr - - svg = kbr._generate_svg(plot.as_dict()) - with io.open(filename, mode="w", encoding="utf-8") as f: - f.write(svg) - - return abspath(filename) - - -def export_html(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, iframe: bool = False) -> str: - """ - Export plot or `bunch` to a file in HTML format. - - Parameters - ---------- - plot : `PlotSpec` or `SupPlotsSpec` or `GGBunch` - Plot specification to export. - filename : str - Filename to save HTML page under. - iframe : bool, default=False - Whether to wrap HTML page into a iFrame. - - Returns - ------- - str - Absolute pathname of created HTML file. - - """ - if not (isinstance(plot, PlotSpec) or isinstance(plot, SupPlotsSpec) or isinstance(plot, GGBunch)): - raise ValueError("PlotSpec, SupPlotsSpec or GGBunch expected but was: {}".format(type(plot))) - - from .. import _kbridge as kbr - - html_page = kbr._generate_static_html_page(plot.as_dict(), iframe) - with io.open(filename, mode="w", encoding="utf-8") as f: - f.write(html_page) - - return abspath(filename) - - -def export_png(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, scale: float = 2.0) -> str: - """ - Export plot or `bunch` to a file in PNG format. - - Parameters - ---------- - plot : `PlotSpec` or `SupPlotsSpec` or `GGBunch` - Plot specification to export. - filename : str - Filename to save PNG under. - scale : float, default=2.0 - Scaling factor for raster output. - - Returns - ------- - str - Absolute pathname of created PNG file. - - Notes - ----- - Export to PNG file uses the CairoSVG library. - CairoSVG is free and distributed under the LGPL-3.0 license. - For more details visit: https://cairosvg.org/documentation/ - - """ - if not (isinstance(plot, PlotSpec) or isinstance(plot, SupPlotsSpec) or isinstance(plot, GGBunch)): - raise ValueError("PlotSpec, SupPlotsSpec or GGBunch expected but was: {}".format(type(plot))) - - try: - import cairosvg - - - except ImportError: - import sys - print("\n" - "To export Lets-Plot figure to a PNG file please install CairoSVG library to your Python environment.\n" - "CairoSVG is free and distributed under the LGPL-3.0 license.\n" - "For more details visit: https://cairosvg.org/documentation/\n", file=sys.stderr) - return None - - from .. import _kbridge - # Use SVG image-rendering style as Cairo doesn't support CSS image-rendering style, - svg = _kbridge._generate_svg(plot.as_dict(), use_css_pixelated_image_rendering=False) - - cairosvg.svg2png(bytestring=svg, write_to=filename, scale=scale) - - return abspath(filename) - - -def export_pdf(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, scale: float = 2.0) -> str: - """ - Export plot or `bunch` to a file in PDF format. - - Parameters - ---------- - plot : `PlotSpec` or `SupPlotsSpec` or `GGBunch` - Plot specification to export. - filename : str - Filename to save PDF under. - scale : float, default=2.0 - Scaling factor for raster output. - - Returns - ------- - str - Absolute pathname of created PDF file. - - Notes - ----- - Export to PDF file uses the CairoSVG library. - CairoSVG is free and distributed under the LGPL-3.0 license. - For more details visit: https://cairosvg.org/documentation/ - - """ - if not (isinstance(plot, PlotSpec) or isinstance(plot, SupPlotsSpec) or isinstance(plot, GGBunch)): - raise ValueError("PlotSpec, SupPlotsSpec or GGBunch expected but was: {}".format(type(plot))) - - try: - import cairosvg - - - except ImportError: - import sys - print("\n" - "To export Lets-Plot figure to a PDF file please install CairoSVG library to your Python environment.\n" - "CairoSVG is free and distributed under the LGPL-3.0 license.\n" - "For more details visit: https://cairosvg.org/documentation/\n", file=sys.stderr) - return None - - from .. import _kbridge - # Use SVG image-rendering style as Cairo doesn't support CSS image-rendering style, - svg = _kbridge._generate_svg(plot.as_dict(), use_css_pixelated_image_rendering=False) - - cairosvg.svg2pdf(bytestring=svg, write_to=filename, scale=scale) - - return abspath(filename) diff --git a/python-package/lets_plot/plot/core.py b/python-package/lets_plot/plot/core.py index 120bbda6523..724cab6f4b0 100644 --- a/python-package/lets_plot/plot/core.py +++ b/python-package/lets_plot/plot/core.py @@ -2,7 +2,9 @@ # Copyright (c) 2019. JetBrains s.r.o. # Use of this source code is governed by the MIT license that can be found in the LICENSE file. # +import io import json +import os __all__ = ['aes', 'layer'] @@ -473,6 +475,173 @@ def show(self): from ..frontend_context._configuration import _display_plot _display_plot(self) + def to_svg(self, path) -> str: + """ + Export a plot to a file or to a file-like object in SVG format. + + Parameters + ---------- + self : `PlotSpec` + Plot specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 9 + + import numpy as np + import io + from lets_plot import * + from IPython import display + LetsPlot.setup_html() + x = np.random.randint(10, size=100) + p = ggplot({'x': x}, aes(x='x')) + geom_bar() + file_like = io.BytesIO() + p.to_svg(file_like) + display.SVG(file_like.getvalue()) + """ + return _to_svg(self, path) + + def to_html(self, path, iframe: bool = None) -> str: + """ + Export a plot to a file or to a file-like object in HTML format. + + Parameters + ---------- + self : `PlotSpec` + Plot specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + iframe : bool, default=False + Whether to wrap HTML page into a iFrame. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 8 + + import numpy as np + import io + from lets_plot import * + LetsPlot.setup_html() + x = np.random.randint(10, size=100) + p = ggplot({'x': x}, aes(x='x')) + geom_bar() + file_like = io.BytesIO() + p.to_html(file_like) + """ + return _to_html(self, path, iframe) + + def to_png(self, path, scale: float = None) -> str: + """ + Export a plot to a file or to a file-like object in PNG format. + + Parameters + ---------- + self : `PlotSpec` + Plot specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + scale : float + Scaling factor for raster output. Default value is 2.0. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Notes + ----- + Export to PNG file uses the CairoSVG library. + CairoSVG is free and distributed under the LGPL-3.0 license. + For more details visit: https://cairosvg.org/documentation/ + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 9 + + import numpy as np + import io + from lets_plot import * + from IPython import display + LetsPlot.setup_html() + x = np.random.randint(10, size=100) + p = ggplot({'x': x}, aes(x='x')) + geom_bar() + file_like = io.BytesIO() + p.to_png(file_like) + display.Image(file_like.getvalue()) + """ + return _to_png(self, path, scale) + + def to_pdf(self, path, scale: float = None) -> str: + """ + Export a plot to a file or to a file-like object in PDF format. + + Parameters + ---------- + self : `PlotSpec` + Plot specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + scale : float + Scaling factor for raster output. Default value is 2.0. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Notes + ----- + Export to PDF file uses the CairoSVG library. + CairoSVG is free and distributed under the LGPL-3.0 license. + For more details visit: https://cairosvg.org/documentation/ + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 13 + + import numpy as np + import io + import os + from lets_plot import * + from IPython import display + LetsPlot.setup_html() + n = 60 + np.random.seed(42) + x = np.random.choice(list('abcde'), size=n) + y = np.random.normal(size=n) + p = ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + geom_jitter() + file_like = io.BytesIO() + p.to_pdf(file_like) + """ + return _to_pdf(self, path, scale) + class LayerSpec(FeatureSpec): """ @@ -621,3 +790,97 @@ def _theme_dicts_merge(x, y): overlapping_keys = x.keys() & y.keys() z = {k: {**x[k], **y[k]} for k in overlapping_keys if type(x[k]) is dict and type(y[k]) is dict} return {**x, **y, **z} + + +def _to_svg(spec, path) -> str | None: + from .. import _kbridge as kbr + + svg = kbr._generate_svg(spec.as_dict()) + if isinstance(path, str): + abspath = _makedirs(path) + with io.open(abspath, mode="w", encoding="utf-8") as f: + f.write(svg) + return abspath + else: + path.write(svg.encode()) + return None + + +def _to_html(spec, path, iframe: bool) -> str | None: + if iframe is None: + iframe = False + + from .. import _kbridge as kbr + html_page = kbr._generate_static_html_page(spec.as_dict(), iframe) + + if isinstance(path, str): + abspath = _makedirs(path) + with io.open(abspath, mode="w", encoding="utf-8") as f: + f.write(html_page) + return abspath + else: + path.write(html_page.encode()) + return None + + +def _to_png(spec, path, scale: float) -> str | None: + if scale is None: + scale = 2.0 + + try: + import cairosvg + except ImportError: + import sys + print("\n" + "To export Lets-Plot figure to a PNG file please install CairoSVG library to your Python environment.\n" + "CairoSVG is free and distributed under the LGPL-3.0 license.\n" + "For more details visit: https://cairosvg.org/documentation/\n", file=sys.stderr) + return None + + from .. import _kbridge + # Use SVG image-rendering style as Cairo doesn't support CSS image-rendering style, + svg = _kbridge._generate_svg(spec.as_dict(), use_css_pixelated_image_rendering=False) + + if isinstance(path, str): + abspath = _makedirs(path) + cairosvg.svg2png(bytestring=svg, write_to=abspath, scale=scale) + return abspath + else: + cairosvg.svg2png(bytestring=svg, write_to=path, scale=scale) + return None + + +def _to_pdf(spec, path, scale: float) -> str | None: + if scale is None: + scale = 2.0 + + try: + import cairosvg + except ImportError: + import sys + print("\n" + "To export Lets-Plot figure to a PDF file please install CairoSVG library to your Python environment.\n" + "CairoSVG is free and distributed under the LGPL-3.0 license.\n" + "For more details visit: https://cairosvg.org/documentation/\n", file=sys.stderr) + return None + + from .. import _kbridge + # Use SVG image-rendering style as Cairo doesn't support CSS image-rendering style, + svg = _kbridge._generate_svg(spec.as_dict(), use_css_pixelated_image_rendering=False) + + if isinstance(path, str): + abspath = _makedirs(path) + cairosvg.svg2pdf(bytestring=svg, write_to=abspath, scale=scale) + return abspath + else: + cairosvg.svg2pdf(bytestring=svg, write_to=path, scale=scale) + return None + + +def _makedirs(path: str) -> str: + """Return absolute path to a file after creating all directories in the path.""" + abspath = os.path.abspath(path) + dirname = os.path.dirname(abspath) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + return abspath diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 50ee017f1a2..de022957e2c 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -5752,9 +5752,11 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= label_format : str Format used to transform label mapping values to a string. Examples: - '.2f' -> '12.45', - 'Num {}' -> 'Num 12.456789', - 'TTL: {.2f}$' -> 'TTL: 12.45$'. + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. na_text : str, default='n/a' Text to show for missing values. @@ -5940,9 +5942,11 @@ def geom_label(mapping=None, *, data=None, stat=None, position=None, show_legend label_format : str Format used to transform label mapping values to a string. Examples: - '.2f' -> '12.45', - 'Num {}' -> 'Num 12.456789', - 'TTL: {.2f}$' -> 'TTL: 12.45$'. + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. nudge_x : float Horizontal adjustment to nudge labels by. diff --git a/python-package/lets_plot/plot/scale.py b/python-package/lets_plot/plot/scale.py index 49cf9ea4763..27883bd3b7d 100644 --- a/python-package/lets_plot/plot/scale.py +++ b/python-package/lets_plot/plot/scale.py @@ -25,7 +25,7 @@ ] -def scale_shape(solid=True, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): +def scale_shape(solid=True, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Scale for shapes. @@ -35,10 +35,12 @@ def scale_shape(solid=True, name=None, breaks=None, labels=None, limits=None, na Are the shapes solid (default) True, or hollow (False). name : str The name of the scale - used as the axis label or the legend title. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -49,9 +51,11 @@ def scale_shape(solid=True, name=None, breaks=None, labels=None, limits=None, na A result returned by `guide_legend()` function or 'none' to hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -84,6 +88,7 @@ def scale_shape(solid=True, name=None, breaks=None, labels=None, limits=None, na name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -99,7 +104,7 @@ def scale_shape(solid=True, name=None, breaks=None, labels=None, limits=None, na # def scale_manual(aesthetic, values, *, - name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for the specified aesthetics. @@ -107,17 +112,20 @@ def scale_manual(aesthetic, values, *, ---------- aesthetic : str or list The name(s) of the aesthetic(s) that this scale works with. - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -130,9 +138,11 @@ def scale_manual(aesthetic, values, *, specifying additional arguments. 'none' will hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -159,10 +169,28 @@ def scale_manual(aesthetic, values, *, breaks=[2, 4, 7], labels=['red', 'green', 'blue']) """ + + # 'values' - dict of limits or breaks as keys and values as values + if isinstance(values, dict): + if breaks is None and limits is None: + breaks = list(values.keys()) + values = list(values.values()) + else: + base_order = breaks if limits is None else limits + if isinstance(base_order, dict): + base_order = list(base_order.values()) + new_values = [values[break_value] for break_value in base_order if break_value in values] + if new_values: + no_match_values = list(set(values.values()) - set(new_values)) # doesn't preserve order + values = new_values + no_match_values + else: + values = None + return _scale(aesthetic, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -173,24 +201,27 @@ def scale_manual(aesthetic, values, *, values=values) -def scale_color_manual(values, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, +def scale_color_manual(values, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for `color` aesthetic. Parameters ---------- - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -203,9 +234,11 @@ def scale_color_manual(values, name=None, breaks=None, labels=None, limits=None, specifying additional arguments. 'none' will hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -237,29 +270,33 @@ def scale_color_manual(values, name=None, breaks=None, labels=None, limits=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_fill_manual(values, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): +def scale_fill_manual(values, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for `fill` aesthetic. Parameters ---------- - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -272,9 +309,11 @@ def scale_fill_manual(values, name=None, breaks=None, labels=None, limits=None, specifying additional arguments. 'none' will hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -306,29 +345,33 @@ def scale_fill_manual(values, name=None, breaks=None, labels=None, limits=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_size_manual(values, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): +def scale_size_manual(values, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for `size` aesthetic. Parameters ---------- - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -339,9 +382,11 @@ def scale_size_manual(values, name=None, breaks=None, labels=None, limits=None, A result returned by `guide_legend()` function or 'none' to hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -374,30 +419,34 @@ def scale_size_manual(values, name=None, breaks=None, labels=None, limits=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_shape_manual(values, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, +def scale_shape_manual(values, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for `shape` aesthetic. Parameters ---------- - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -408,9 +457,11 @@ def scale_shape_manual(values, name=None, breaks=None, labels=None, limits=None, A result returned by `guide_legend()` function or 'none' to hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -443,30 +494,34 @@ def scale_shape_manual(values, name=None, breaks=None, labels=None, limits=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_linetype_manual(values, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, +def scale_linetype_manual(values, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for line type aesthetic. Parameters ---------- - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -477,9 +532,11 @@ def scale_linetype_manual(values, name=None, breaks=None, labels=None, limits=No A result returned by `guide_legend()` function or 'none' to hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -513,30 +570,34 @@ def scale_linetype_manual(values, name=None, breaks=None, labels=None, limits=No name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_alpha_manual(values, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, +def scale_alpha_manual(values, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Create your own discrete scale for `alpha` (transparency) aesthetic. Parameters ---------- - values : list of str + values : list of str or dict A set of aesthetic values to map data values to. - The values will be matched in order (usually alphabetical) with the limits of the scale. + If this is a list, the values will be matched in order (usually alphabetical) with the limits of the scale. + If a dictionary, then the values will be matched based on the names. name : str The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -547,9 +608,11 @@ def scale_alpha_manual(values, name=None, breaks=None, labels=None, limits=None, A result returned by `guide_legend()` function or 'none' to hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -581,6 +644,7 @@ def scale_alpha_manual(values, name=None, breaks=None, labels=None, limits=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -591,10 +655,11 @@ def scale_alpha_manual(values, name=None, breaks=None, labels=None, limits=None, # Gradient (continuous) Color Scales # def scale_continuous(aesthetic, *, - low=None, high=None, name=None, breaks=None, labels=None, + low=None, high=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ - Define smooth color gradient between two colors for the specified aesthetics. + General purpose scale for continuous data. + Use it to adjust most common properties of a default scale for given aesthetic. Parameters ---------- @@ -608,10 +673,12 @@ def scale_continuous(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. na_value @@ -624,9 +691,11 @@ def scale_continuous(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -658,6 +727,7 @@ def scale_continuous(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -670,7 +740,7 @@ def scale_continuous(aesthetic, *, def scale_gradient(aesthetic, *, - low=None, high=None, name=None, breaks=None, labels=None, + low=None, high=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between two colors for the specified aesthetics. @@ -687,10 +757,12 @@ def scale_gradient(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -705,9 +777,11 @@ def scale_gradient(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -741,6 +815,7 @@ def scale_gradient(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -748,7 +823,7 @@ def scale_gradient(aesthetic, *, format=format) -def scale_fill_gradient(low=None, high=None, name=None, breaks=None, labels=None, +def scale_fill_gradient(low=None, high=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between two colors for `fill` aesthetic. @@ -763,10 +838,12 @@ def scale_fill_gradient(low=None, high=None, name=None, breaks=None, labels=None The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -781,9 +858,11 @@ def scale_fill_gradient(low=None, high=None, name=None, breaks=None, labels=None Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -816,6 +895,7 @@ def scale_fill_gradient(low=None, high=None, name=None, breaks=None, labels=None name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -823,7 +903,7 @@ def scale_fill_gradient(low=None, high=None, name=None, breaks=None, labels=None format=format) -def scale_fill_continuous(low=None, high=None, name=None, breaks=None, labels=None, +def scale_fill_continuous(low=None, high=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between two colors for `fill` aesthetic. @@ -838,10 +918,12 @@ def scale_fill_continuous(low=None, high=None, name=None, breaks=None, labels=No The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. na_value @@ -854,9 +936,11 @@ def scale_fill_continuous(low=None, high=None, name=None, breaks=None, labels=No Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -889,6 +973,7 @@ def scale_fill_continuous(low=None, high=None, name=None, breaks=None, labels=No name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -896,7 +981,7 @@ def scale_fill_continuous(low=None, high=None, name=None, breaks=None, labels=No format=format) -def scale_color_gradient(low=None, high=None, name=None, breaks=None, labels=None, limits=None, +def scale_color_gradient(low=None, high=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between two colors for `color` aesthetic. @@ -911,10 +996,12 @@ def scale_color_gradient(low=None, high=None, name=None, breaks=None, labels=Non The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -929,9 +1016,11 @@ def scale_color_gradient(low=None, high=None, name=None, breaks=None, labels=Non Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -964,6 +1053,7 @@ def scale_color_gradient(low=None, high=None, name=None, breaks=None, labels=Non name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -971,7 +1061,7 @@ def scale_color_gradient(low=None, high=None, name=None, breaks=None, labels=Non format=format) -def scale_color_continuous(low=None, high=None, name=None, breaks=None, labels=None, limits=None, +def scale_color_continuous(low=None, high=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between two colors for `color` aesthetic. @@ -986,10 +1076,12 @@ def scale_color_continuous(low=None, high=None, name=None, breaks=None, labels=N The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. na_value @@ -1002,9 +1094,11 @@ def scale_color_continuous(low=None, high=None, name=None, breaks=None, labels=N Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1031,6 +1125,7 @@ def scale_color_continuous(low=None, high=None, name=None, breaks=None, labels=N name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1039,7 +1134,7 @@ def scale_color_continuous(low=None, high=None, name=None, breaks=None, labels=N def scale_gradient2(aesthetic, *, - low=None, mid=None, high=None, midpoint=0, name=None, breaks=None, labels=None, limits=None, + low=None, mid=None, high=None, midpoint=0, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define diverging color gradient for the specified aesthetics. @@ -1060,10 +1155,12 @@ def scale_gradient2(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1078,9 +1175,11 @@ def scale_gradient2(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1112,6 +1211,7 @@ def scale_gradient2(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -1124,7 +1224,7 @@ def scale_gradient2(aesthetic, *, scale_mapper_kind='color_gradient2') -def scale_fill_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, breaks=None, labels=None, limits=None, +def scale_fill_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define diverging color gradient for `fill` aesthetic. @@ -1143,10 +1243,12 @@ def scale_fill_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, b The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1161,9 +1263,11 @@ def scale_fill_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, b Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1197,6 +1301,7 @@ def scale_fill_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, b name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1204,8 +1309,8 @@ def scale_fill_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, b format=format) -def scale_color_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, breaks=None, labels=None, limits=None, - na_value=None, guide=None, trans=None, format=None): +def scale_color_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, breaks=None, labels=None, lablim=None, + limits=None, na_value=None, guide=None, trans=None, format=None): """ Define diverging color gradient for `color` aesthetic. @@ -1223,10 +1328,12 @@ def scale_color_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1241,9 +1348,11 @@ def scale_color_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1277,6 +1386,7 @@ def scale_color_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1285,7 +1395,7 @@ def scale_color_gradient2(low=None, mid=None, high=None, midpoint=0, name=None, def scale_gradientn(aesthetic, *, - colors=None, name=None, breaks=None, labels=None, limits=None, + colors=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between multiple colors for the specified aesthetics. @@ -1300,14 +1410,16 @@ def scale_gradientn(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale and the default order of their display in guides. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. na_value Missing values will be replaced with this value. guide @@ -1318,9 +1430,11 @@ def scale_gradientn(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1353,6 +1467,7 @@ def scale_gradientn(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -1364,7 +1479,7 @@ def scale_gradientn(aesthetic, *, scale_mapper_kind='color_gradientn') -def scale_color_gradientn(colors=None, name=None, breaks=None, labels=None, limits=None, +def scale_color_gradientn(colors=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between multiple colors for `color` aesthetic. @@ -1377,10 +1492,12 @@ def scale_color_gradientn(colors=None, name=None, breaks=None, labels=None, limi The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1395,9 +1512,11 @@ def scale_color_gradientn(colors=None, name=None, breaks=None, labels=None, limi Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1431,6 +1550,7 @@ def scale_color_gradientn(colors=None, name=None, breaks=None, labels=None, limi name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1438,7 +1558,7 @@ def scale_color_gradientn(colors=None, name=None, breaks=None, labels=None, limi format=format) -def scale_fill_gradientn(colors=None, name=None, breaks=None, labels=None, limits=None, +def scale_fill_gradientn(colors=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Define smooth color gradient between multiple colors for `fill` aesthetic. @@ -1451,10 +1571,12 @@ def scale_fill_gradientn(colors=None, name=None, breaks=None, labels=None, limit The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1469,9 +1591,11 @@ def scale_fill_gradientn(colors=None, name=None, breaks=None, labels=None, limit Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1505,6 +1629,7 @@ def scale_fill_gradientn(colors=None, name=None, breaks=None, labels=None, limit name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1513,7 +1638,7 @@ def scale_fill_gradientn(colors=None, name=None, breaks=None, labels=None, limit def scale_hue(aesthetic, *, - h=None, c=None, l=None, h_start=None, direction=None, name=None, breaks=None, labels=None, + h=None, c=None, l=None, h_start=None, direction=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Qualitative color scale with evenly spaced hues for the specified aesthetics. @@ -1534,10 +1659,12 @@ def scale_hue(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1552,9 +1679,11 @@ def scale_hue(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1586,6 +1715,7 @@ def scale_hue(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -1598,7 +1728,7 @@ def scale_hue(aesthetic, *, scale_mapper_kind='color_hue') -def scale_fill_hue(h=None, c=None, l=None, h_start=None, direction=None, name=None, breaks=None, labels=None, +def scale_fill_hue(h=None, c=None, l=None, h_start=None, direction=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Qualitative color scale with evenly spaced hues for `fill` aesthetic. @@ -1619,10 +1749,12 @@ def scale_fill_hue(h=None, c=None, l=None, h_start=None, direction=None, name=No The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1637,9 +1769,11 @@ def scale_fill_hue(h=None, c=None, l=None, h_start=None, direction=None, name=No Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1674,6 +1808,7 @@ def scale_fill_hue(h=None, c=None, l=None, h_start=None, direction=None, name=No name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1681,7 +1816,7 @@ def scale_fill_hue(h=None, c=None, l=None, h_start=None, direction=None, name=No format=format) -def scale_color_hue(h=None, c=None, l=None, h_start=None, direction=None, name=None, breaks=None, labels=None, +def scale_color_hue(h=None, c=None, l=None, h_start=None, direction=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Qualitative color scale with evenly spaced hues for `color` aesthetic. @@ -1700,10 +1835,12 @@ def scale_color_hue(h=None, c=None, l=None, h_start=None, direction=None, name=N The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -1718,9 +1855,11 @@ def scale_color_hue(h=None, c=None, l=None, h_start=None, direction=None, name=N Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1755,6 +1894,7 @@ def scale_color_hue(h=None, c=None, l=None, h_start=None, direction=None, name=N name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1764,10 +1904,10 @@ def scale_color_hue(h=None, c=None, l=None, h_start=None, direction=None, name=N def scale_discrete(aesthetic, *, direction=None, - name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ - Qualitative colors. - Defaults to the Brewer 'Set2' palette (or 'Set3' if the categories count > 8). + General purpose scale for discrete data. + Use it to adjust most common properties of a default scale for given aesthetic. Parameters ---------- @@ -1780,10 +1920,12 @@ def scale_discrete(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -1795,9 +1937,11 @@ def scale_discrete(aesthetic, *, specifying additional arguments. 'none' will hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1831,6 +1975,7 @@ def scale_discrete(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -1842,7 +1987,7 @@ def scale_discrete(aesthetic, *, def scale_fill_discrete(direction=None, - name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Qualitative colors. Defaults to the Brewer 'Set2' palette (or 'Set3' if the categories count > 8). @@ -1856,10 +2001,12 @@ def scale_fill_discrete(direction=None, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -1871,9 +2018,11 @@ def scale_fill_discrete(direction=None, specifying additional arguments. 'none' will hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1915,7 +2064,7 @@ def scale_fill_discrete(direction=None, def scale_color_discrete(direction=None, - name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, format=None): + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, format=None): """ Qualitative colors. Defaults to the Brewer 'Set2' palette (or 'Set3' if the categories count > 8). @@ -1929,10 +2078,12 @@ def scale_color_discrete(direction=None, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -1944,9 +2095,11 @@ def scale_color_discrete(direction=None, specifying additional arguments. 'none' will hide the guide. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -1981,6 +2134,7 @@ def scale_color_discrete(direction=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -1988,7 +2142,7 @@ def scale_color_discrete(direction=None, def scale_grey(aesthetic, *, - start=None, end=None, name=None, breaks=None, labels=None, limits=None, + start=None, end=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Sequential grey color scale for the specified aesthetics. @@ -2005,10 +2159,12 @@ def scale_grey(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2023,9 +2179,11 @@ def scale_grey(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2059,6 +2217,7 @@ def scale_grey(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -2070,7 +2229,7 @@ def scale_grey(aesthetic, *, scale_mapper_kind='color_grey') -def scale_fill_grey(start=None, end=None, name=None, breaks=None, labels=None, limits=None, +def scale_fill_grey(start=None, end=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Sequential grey color scale for `fill` aesthetic. @@ -2085,10 +2244,12 @@ def scale_fill_grey(start=None, end=None, name=None, breaks=None, labels=None, l The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2103,9 +2264,11 @@ def scale_fill_grey(start=None, end=None, name=None, breaks=None, labels=None, l Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2138,6 +2301,7 @@ def scale_fill_grey(start=None, end=None, name=None, breaks=None, labels=None, l name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -2145,7 +2309,7 @@ def scale_fill_grey(start=None, end=None, name=None, breaks=None, labels=None, l format=format) -def scale_color_grey(start=None, end=None, name=None, breaks=None, labels=None, limits=None, +def scale_color_grey(start=None, end=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Sequential grey color scale for `color` aesthetic. @@ -2160,10 +2324,12 @@ def scale_color_grey(start=None, end=None, name=None, breaks=None, labels=None, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2178,9 +2344,11 @@ def scale_color_grey(start=None, end=None, name=None, breaks=None, labels=None, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2213,6 +2381,7 @@ def scale_color_grey(start=None, end=None, name=None, breaks=None, labels=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -2241,7 +2410,7 @@ def _greyscale_check_parameters(start=None, end=None): def scale_brewer(aesthetic, *, - type=None, palette=None, direction=None, name=None, breaks=None, labels=None, limits=None, + type=None, palette=None, direction=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Sequential, diverging and qualitative color scales from colorbrewer2.org for the specified aesthetics. @@ -2263,10 +2432,12 @@ def scale_brewer(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2281,9 +2452,11 @@ def scale_brewer(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2328,6 +2501,7 @@ def scale_brewer(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -2341,7 +2515,7 @@ def scale_brewer(aesthetic, *, scale_mapper_kind='color_brewer') -def scale_fill_brewer(type=None, palette=None, direction=None, name=None, breaks=None, labels=None, limits=None, +def scale_fill_brewer(type=None, palette=None, direction=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Sequential, diverging and qualitative color scales from colorbrewer2.org for `fill` aesthetic. @@ -2361,10 +2535,12 @@ def scale_fill_brewer(type=None, palette=None, direction=None, name=None, breaks The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2379,9 +2555,11 @@ def scale_fill_brewer(type=None, palette=None, direction=None, name=None, breaks Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2429,6 +2607,7 @@ def scale_fill_brewer(type=None, palette=None, direction=None, name=None, breaks name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -2436,7 +2615,7 @@ def scale_fill_brewer(type=None, palette=None, direction=None, name=None, breaks format=format) -def scale_color_brewer(type=None, palette=None, direction=None, name=None, breaks=None, labels=None, limits=None, +def scale_color_brewer(type=None, palette=None, direction=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Sequential, diverging and qualitative color scales from colorbrewer2.org for `color` aesthetic. @@ -2456,10 +2635,12 @@ def scale_color_brewer(type=None, palette=None, direction=None, name=None, break The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2474,9 +2655,11 @@ def scale_color_brewer(type=None, palette=None, direction=None, name=None, break Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2522,6 +2705,7 @@ def scale_color_brewer(type=None, palette=None, direction=None, name=None, break name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -2531,7 +2715,7 @@ def scale_color_brewer(type=None, palette=None, direction=None, name=None, break def scale_viridis(aesthetic, *, alpha=None, begin=None, end=None, direction=None, option=None, - name=None, breaks=None, labels=None, limits=None, + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ The `viridis` color maps are designed to be perceptually-uniform, @@ -2568,10 +2752,12 @@ def scale_viridis(aesthetic, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2586,9 +2772,11 @@ def scale_viridis(aesthetic, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2621,6 +2809,7 @@ def scale_viridis(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -2637,7 +2826,7 @@ def scale_viridis(aesthetic, *, def scale_fill_viridis(alpha=None, begin=None, end=None, direction=None, option=None, - name=None, breaks=None, labels=None, limits=None, + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ The `viridis` color maps are designed to be perceptually-uniform, @@ -2672,10 +2861,12 @@ def scale_fill_viridis(alpha=None, begin=None, end=None, direction=None, option= The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2690,9 +2881,11 @@ def scale_fill_viridis(alpha=None, begin=None, end=None, direction=None, option= Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2728,6 +2921,7 @@ def scale_fill_viridis(alpha=None, begin=None, end=None, direction=None, option= name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -2736,7 +2930,7 @@ def scale_fill_viridis(alpha=None, begin=None, end=None, direction=None, option= def scale_color_viridis(alpha=None, begin=None, end=None, direction=None, option=None, - name=None, breaks=None, labels=None, limits=None, + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ The `viridis` color maps are designed to be perceptually-uniform, @@ -2771,10 +2965,12 @@ def scale_color_viridis(alpha=None, begin=None, end=None, direction=None, option The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -2789,9 +2985,11 @@ def scale_color_viridis(alpha=None, begin=None, end=None, direction=None, option Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2825,6 +3023,7 @@ def scale_color_viridis(alpha=None, begin=None, end=None, direction=None, option name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -2835,7 +3034,7 @@ def scale_color_viridis(alpha=None, begin=None, end=None, direction=None, option # Range Scale (alpha and size) # -def scale_alpha(range=None, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, trans=None, +def scale_alpha(range=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Scale for alpha. @@ -2848,10 +3047,12 @@ def scale_alpha(range=None, name=None, breaks=None, labels=None, limits=None, na The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -2863,9 +3064,11 @@ def scale_alpha(range=None, name=None, breaks=None, labels=None, limits=None, na Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2894,6 +3097,7 @@ def scale_alpha(range=None, name=None, breaks=None, labels=None, limits=None, na name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -2904,7 +3108,7 @@ def scale_alpha(range=None, name=None, breaks=None, labels=None, limits=None, na range=range) -def scale_size(range=None, name=None, breaks=None, labels=None, limits=None, na_value=None, guide=None, trans=None, +def scale_size(range=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Scale for size. @@ -2917,10 +3121,12 @@ def scale_size(range=None, name=None, breaks=None, labels=None, limits=None, na_ The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -2932,9 +3138,11 @@ def scale_size(range=None, name=None, breaks=None, labels=None, limits=None, na_ Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -2964,6 +3172,7 @@ def scale_size(range=None, name=None, breaks=None, labels=None, limits=None, na_ name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -2974,7 +3183,7 @@ def scale_size(range=None, name=None, breaks=None, labels=None, limits=None, na_ range=range) -def scale_size_area(max_size=None, name=None, breaks=None, labels=None, limits=None, +def scale_size_area(max_size=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Continuous scale for size that maps 0 to 0. @@ -2987,10 +3196,12 @@ def scale_size_area(max_size=None, name=None, breaks=None, labels=None, limits=N The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -3002,9 +3213,11 @@ def scale_size_area(max_size=None, name=None, breaks=None, labels=None, limits=N Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -3038,6 +3251,7 @@ def scale_size_area(max_size=None, name=None, breaks=None, labels=None, limits=N name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -3049,7 +3263,7 @@ def scale_size_area(max_size=None, name=None, breaks=None, labels=None, limits=N scale_mapper_kind='size_area') -def scale_linewidth(range=None, name=None, breaks=None, labels=None, limits=None, +def scale_linewidth(range=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Scale for linewidth. @@ -3062,10 +3276,12 @@ def scale_linewidth(range=None, name=None, breaks=None, labels=None, limits=None The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -3077,9 +3293,11 @@ def scale_linewidth(range=None, name=None, breaks=None, labels=None, limits=None Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -3108,6 +3326,7 @@ def scale_linewidth(range=None, name=None, breaks=None, labels=None, limits=None name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -3118,7 +3337,7 @@ def scale_linewidth(range=None, name=None, breaks=None, labels=None, limits=None range=range) -def scale_stroke(range=None, name=None, breaks=None, labels=None, limits=None, +def scale_stroke(range=None, name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide=None, trans=None, format=None): """ Scale for stroke. @@ -3131,10 +3350,12 @@ def scale_stroke(range=None, name=None, breaks=None, labels=None, limits=None, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. @@ -3146,9 +3367,11 @@ def scale_stroke(range=None, name=None, breaks=None, labels=None, limits=None, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -3177,6 +3400,7 @@ def scale_stroke(range=None, name=None, breaks=None, labels=None, limits=None, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -3190,6 +3414,7 @@ def scale_stroke(range=None, name=None, breaks=None, labels=None, limits=None, def _scale(aesthetic, *, name=None, breaks=None, labels=None, + lablim=None, limits=None, expand=None, na_value=None, @@ -3207,10 +3432,12 @@ def _scale(aesthetic, *, The name(s) of the aesthetic(s) that this scale works with. name : str The name of the scale - used as the axis label or the legend title - breaks : list - A numeric vector of positions (of ticks) - labels : list - A vector of labels (on ticks) + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -3240,6 +3467,30 @@ def _scale(aesthetic, *, args = locals().copy() args.pop('other') + # 'breaks' - dict of labels as keys and breaks as values + if isinstance(breaks, dict): + if labels is None: + args['labels'] = list(breaks.keys()) + breaks = list(breaks.values()) + args['breaks'] = breaks + + # 'labels' - dict of breaks as keys and labels as values + if isinstance(labels, dict): + if breaks is None: + args['breaks'] = list(labels.keys()) + args['labels'] = list(labels.values()) + else: + new_labels = [] + new_breaks = [] + for break_value in breaks: + if break_value in labels: + new_labels.append(labels[break_value]) + new_breaks.append(break_value) + + breaks_without_label = [item for item in breaks if item not in new_breaks] # keeps order + args['breaks'] = new_breaks + breaks_without_label + args['labels'] = new_labels + specs = [] if isinstance(aesthetic, list): args.pop('aesthetic') diff --git a/python-package/lets_plot/plot/scale_identity_.py b/python-package/lets_plot/plot/scale_identity_.py index 268e6d4ff18..1a21d78dabe 100644 --- a/python-package/lets_plot/plot/scale_identity_.py +++ b/python-package/lets_plot/plot/scale_identity_.py @@ -21,7 +21,7 @@ def scale_identity(aesthetic, *, - name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None, **other): + name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None, **other): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -33,10 +33,12 @@ def scale_identity(aesthetic, *, The name(s) of the aesthetic(s) that this scale works with. name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -47,9 +49,11 @@ def scale_identity(aesthetic, *, Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -79,6 +83,7 @@ def scale_identity(aesthetic, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=None, na_value=na_value, @@ -90,7 +95,7 @@ def scale_identity(aesthetic, *, **other) -def scale_color_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_color_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -100,10 +105,12 @@ def scale_color_identity(name=None, breaks=None, labels=None, limits=None, na_va ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -112,9 +119,11 @@ def scale_color_identity(name=None, breaks=None, labels=None, limits=None, na_va Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -152,13 +161,14 @@ def scale_color_identity(name=None, breaks=None, labels=None, limits=None, na_va name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_fill_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_fill_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -168,10 +178,12 @@ def scale_fill_identity(name=None, breaks=None, labels=None, limits=None, na_val ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -180,9 +192,11 @@ def scale_fill_identity(name=None, breaks=None, labels=None, limits=None, na_val Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -220,13 +234,14 @@ def scale_fill_identity(name=None, breaks=None, labels=None, limits=None, na_val name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_shape_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_shape_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -236,10 +251,12 @@ def scale_shape_identity(name=None, breaks=None, labels=None, limits=None, na_va ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -248,9 +265,11 @@ def scale_shape_identity(name=None, breaks=None, labels=None, limits=None, na_va Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -285,6 +304,7 @@ def scale_shape_identity(name=None, breaks=None, labels=None, limits=None, na_va name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -294,7 +314,7 @@ def scale_shape_identity(name=None, breaks=None, labels=None, limits=None, na_va discrete=True) -def scale_linetype_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_linetype_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -304,10 +324,12 @@ def scale_linetype_identity(name=None, breaks=None, labels=None, limits=None, na ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -316,9 +338,11 @@ def scale_linetype_identity(name=None, breaks=None, labels=None, limits=None, na Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -357,6 +381,7 @@ def scale_linetype_identity(name=None, breaks=None, labels=None, limits=None, na name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, @@ -365,7 +390,7 @@ def scale_linetype_identity(name=None, breaks=None, labels=None, limits=None, na discrete=True) -def scale_alpha_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_alpha_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -375,10 +400,12 @@ def scale_alpha_identity(name=None, breaks=None, labels=None, limits=None, na_va ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -387,9 +414,11 @@ def scale_alpha_identity(name=None, breaks=None, labels=None, limits=None, na_va Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -424,13 +453,14 @@ def scale_alpha_identity(name=None, breaks=None, labels=None, limits=None, na_va name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_size_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_size_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that the library can handle directly. @@ -440,10 +470,12 @@ def scale_size_identity(name=None, breaks=None, labels=None, limits=None, na_val ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -452,9 +484,11 @@ def scale_size_identity(name=None, breaks=None, labels=None, limits=None, na_val Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -489,13 +523,14 @@ def scale_size_identity(name=None, breaks=None, labels=None, limits=None, na_val name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_linewidth_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_linewidth_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that can be handled directly. @@ -505,10 +540,12 @@ def scale_linewidth_identity(name=None, breaks=None, labels=None, limits=None, n ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -517,9 +554,11 @@ def scale_linewidth_identity(name=None, breaks=None, labels=None, limits=None, n Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -552,13 +591,14 @@ def scale_linewidth_identity(name=None, breaks=None, labels=None, limits=None, n name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, format=format) -def scale_stroke_identity(name=None, breaks=None, labels=None, limits=None, na_value=None, guide='none', format=None): +def scale_stroke_identity(name=None, breaks=None, labels=None, lablim=None, limits=None, na_value=None, guide='none', format=None): """ Use this scale when your data has already been scaled. I.e. it already represents aesthetic values that can be handled directly. @@ -568,10 +608,12 @@ def scale_stroke_identity(name=None, breaks=None, labels=None, limits=None, na_v ---------- name : str The name of the scale - used as the axis label or the legend title. - breaks : list of float - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list Continuous scale: a numeric vector of length two providing limits of the scale. Discrete scale: a vector specifying the data range for the scale @@ -580,9 +622,11 @@ def scale_stroke_identity(name=None, breaks=None, labels=None, limits=None, na_v Guide to use for this scale. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. Returns @@ -615,6 +659,7 @@ def scale_stroke_identity(name=None, breaks=None, labels=None, limits=None, na_v name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, na_value=na_value, guide=guide, diff --git a/python-package/lets_plot/plot/scale_position.py b/python-package/lets_plot/plot/scale_position.py index 1345d22888a..cc8190372fe 100644 --- a/python-package/lets_plot/plot/scale_position.py +++ b/python-package/lets_plot/plot/scale_position.py @@ -25,7 +25,7 @@ # def scale_x_continuous(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -42,10 +42,12 @@ def scale_x_continuous(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -58,9 +60,11 @@ def scale_x_continuous(name=None, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -93,6 +97,7 @@ def scale_x_continuous(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -104,7 +109,7 @@ def scale_x_continuous(name=None, *, def scale_y_continuous(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -121,10 +126,12 @@ def scale_y_continuous(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -137,9 +144,11 @@ def scale_y_continuous(name=None, *, Name of built-in transformation. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -172,6 +181,7 @@ def scale_y_continuous(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -183,7 +193,7 @@ def scale_y_continuous(name=None, *, def scale_x_log10(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -199,10 +209,12 @@ def scale_x_log10(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -213,9 +225,11 @@ def scale_x_log10(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -245,6 +259,7 @@ def scale_x_log10(name=None, *, return scale_x_continuous(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -255,7 +270,7 @@ def scale_x_log10(name=None, *, def scale_y_log10(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -271,10 +286,12 @@ def scale_y_log10(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -285,9 +302,11 @@ def scale_y_log10(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -317,6 +336,7 @@ def scale_y_log10(name=None, *, return scale_y_continuous(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -327,7 +347,7 @@ def scale_y_log10(name=None, *, def scale_x_log2(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -343,10 +363,12 @@ def scale_x_log2(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -357,9 +379,11 @@ def scale_x_log2(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -389,6 +413,7 @@ def scale_x_log2(name=None, *, return scale_x_continuous(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -399,7 +424,7 @@ def scale_x_log2(name=None, *, def scale_y_log2(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -415,10 +440,12 @@ def scale_y_log2(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -429,9 +456,11 @@ def scale_y_log2(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -461,6 +490,7 @@ def scale_y_log2(name=None, *, return scale_y_continuous(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -471,7 +501,7 @@ def scale_y_log2(name=None, *, def scale_x_reverse(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -487,10 +517,12 @@ def scale_x_reverse(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -501,9 +533,11 @@ def scale_x_reverse(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -533,6 +567,7 @@ def scale_x_reverse(name=None, *, return scale_x_continuous(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -543,7 +578,7 @@ def scale_x_reverse(name=None, *, def scale_y_reverse(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -559,10 +594,12 @@ def scale_y_reverse(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -573,9 +610,11 @@ def scale_y_reverse(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -605,6 +644,7 @@ def scale_y_reverse(name=None, *, return scale_y_continuous(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -619,7 +659,7 @@ def scale_y_reverse(name=None, *, # def scale_x_discrete(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -636,10 +676,12 @@ def scale_x_discrete(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. expand : list @@ -652,9 +694,11 @@ def scale_x_discrete(name=None, *, When True the scale is reversed. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -688,6 +732,7 @@ def scale_x_discrete(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -702,7 +747,7 @@ def scale_x_discrete(name=None, *, def scale_x_discrete_reversed(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -718,10 +763,12 @@ def scale_x_discrete_reversed(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. expand : list @@ -732,9 +779,11 @@ def scale_x_discrete_reversed(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -767,6 +816,7 @@ def scale_x_discrete_reversed(name=None, *, return scale_x_discrete(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -778,7 +828,7 @@ def scale_x_discrete_reversed(name=None, *, def scale_y_discrete(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -795,10 +845,12 @@ def scale_y_discrete(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. expand : list @@ -811,9 +863,11 @@ def scale_y_discrete(name=None, *, When True the scale is reversed. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -847,6 +901,7 @@ def scale_y_discrete(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -861,7 +916,7 @@ def scale_y_discrete(name=None, *, def scale_y_discrete_reversed(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -877,10 +932,12 @@ def scale_y_discrete_reversed(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector specifying the data range for the scale and the default order of their display in guides. expand : list of two numbers @@ -891,9 +948,11 @@ def scale_y_discrete_reversed(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' + + - '.2f' -> '12.45' + - 'Num {}' -> 'Num 12.456789' + - 'TTL: {.2f}$' -> 'TTL: 12.45$' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -926,6 +985,7 @@ def scale_y_discrete_reversed(name=None, *, return scale_y_discrete(name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -944,6 +1004,7 @@ def scale_y_discrete_reversed(name=None, *, def scale_x_datetime(name=None, *, breaks=None, labels=None, + lablim=None, limits=None, expand=None, na_value=None, @@ -959,10 +1020,12 @@ def scale_x_datetime(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector of length two providing limits of the scale. expand : list @@ -973,9 +1036,11 @@ def scale_x_datetime(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '%d.%m.%y' -> '06.08.19' - '%B %Y' -> 'August 2019' - '%a, %e %b %Y %H:%M:%S' -> 'Tue, 6 Aug 2019 04:46:35' + + - '%d.%m.%y' -> '06.08.19' + - '%B %Y' -> 'August 2019' + - '%a, %e %b %Y %H:%M:%S' -> 'Tue, 6 Aug 2019 04:46:35' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -1013,6 +1078,7 @@ def scale_x_datetime(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -1025,7 +1091,7 @@ def scale_x_datetime(name=None, *, def scale_y_datetime(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -1041,10 +1107,12 @@ def scale_y_datetime(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A vector specifying values to display as ticks on axis. - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A vector of length two providing limits of the scale. expand : list of two numbers @@ -1055,9 +1123,11 @@ def scale_y_datetime(name=None, *, Missing values will be replaced with this value. format : str Define the format for labels on the scale. The syntax resembles Python's: - '%d.%m.%y' -> '06.08.19' - '%B %Y' -> 'August 2019' - '%a, %e %b %Y %H:%M:%S' -> 'Tue, 6 Aug 2019 04:46:35' + + - '%d.%m.%y' -> '06.08.19' + - '%B %Y' -> 'August 2019' + - '%a, %e %b %Y %H:%M:%S' -> 'Tue, 6 Aug 2019 04:46:35' + For more info see https://lets-plot.org/pages/formats.html. position : str The position of the axis: @@ -1096,6 +1166,7 @@ def scale_y_datetime(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -1108,7 +1179,7 @@ def scale_y_datetime(name=None, *, def scale_x_time(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -1124,10 +1195,12 @@ def scale_x_time(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -1172,6 +1245,7 @@ def scale_x_time(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, @@ -1184,7 +1258,7 @@ def scale_x_time(name=None, *, def scale_y_time(name=None, *, - breaks=None, labels=None, + breaks=None, labels=None, lablim=None, limits=None, expand=None, na_value=None, @@ -1200,10 +1274,12 @@ def scale_y_time(name=None, *, The name of the scale - used as the axis label or the legend title. If None, the default, the name of the scale is taken from the first mapping used for that aesthetic. - breaks : list - A numeric vector of positions (of ticks). - labels : list of str - A vector of labels (on ticks). + breaks : list or dict + A list of data values specifying the positions of ticks, or a dictionary which maps the tick labels to the breaks values. + labels : list of str or dict + A list of labels on ticks, or a dictionary which maps the breaks values to the tick labels. + lablim : int, default=None + The maximum label length (in characters) before trimming is applied. limits : list A numeric vector of length two providing limits of the scale. expand : list @@ -1248,6 +1324,7 @@ def scale_y_time(name=None, *, name=name, breaks=breaks, labels=labels, + lablim=lablim, limits=limits, expand=expand, na_value=na_value, diff --git a/python-package/lets_plot/plot/subplots.py b/python-package/lets_plot/plot/subplots.py index c84492cb9a0..bbec2de7fbf 100644 --- a/python-package/lets_plot/plot/subplots.py +++ b/python-package/lets_plot/plot/subplots.py @@ -11,6 +11,7 @@ from lets_plot.plot.core import FeatureSpecArray from lets_plot.plot.core import _specs_to_dict from lets_plot.plot.core import _theme_dicts_merge +from lets_plot.plot.core import _to_svg, _to_html, _to_png, _to_pdf __all__ = ['SupPlotsSpec'] @@ -113,3 +114,181 @@ def show(self): """ from ..frontend_context._configuration import _display_plot _display_plot(self) + + def to_svg(self, path) -> str: + """ + Export all plots currently in this 'bunch' to a file or file-like object in SVG format. + + Parameters + ---------- + self : `SupPlotsSpec` + Subplots specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 13 + + import numpy as np + import io + import os + from lets_plot import * + from IPython import display + LetsPlot.setup_html() + n = 60 + np.random.seed(42) + x = np.random.choice(list('abcde'), size=n) + y = np.random.normal(size=n) + p = ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + geom_jitter() + file_like = io.BytesIO() + p.to_svg(file_like) + display.SVG(file_like.getvalue()) + """ + return _to_svg(self, path) + + def to_html(self, path, iframe: bool = None) -> str: + """ + Export all plots currently in this 'bunch' to a file or file-like object in HTML format. + + Parameters + ---------- + self : `SupPlotsSpec` + Subplots specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + iframe : bool, default=False + Whether to wrap HTML page into a iFrame. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 12 + + import numpy as np + import io + import os + from lets_plot import * + LetsPlot.setup_html() + n = 60 + np.random.seed(42) + x = np.random.choice(list('abcde'), size=n) + y = np.random.normal(size=n) + p = ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + geom_jitter() + file_like = io.BytesIO() + p.to_html(file_like) + """ + return _to_html(self, path, iframe) + + def to_png(self, path, scale=None) -> str: + """ + Export all plots currently in this 'bunch' to a file or file-like object in PNG format. + + Parameters + ---------- + self : `SupPlotsSpec` + Subplots specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + scale : float + Scaling factor for raster output. Default value is 2.0. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Notes + ----- + Export to PNG file uses the CairoSVG library. + CairoSVG is free and distributed under the LGPL-3.0 license. + For more details visit: https://cairosvg.org/documentation/ + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 13 + + import numpy as np + import io + import os + from lets_plot import * + from IPython import display + LetsPlot.setup_html() + n = 60 + np.random.seed(42) + x = np.random.choice(list('abcde'), size=n) + y = np.random.normal(size=n) + p = ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + geom_jitter() + file_like = io.BytesIO() + p.to_png(file_like) + display.Image(file_like.getvalue()) + """ + return _to_png(self, path, scale) + + def to_pdf(self, path, scale=None) -> str: + """ + Export all plots currently in this 'bunch' to a file or file-like object in PDF format. + + Parameters + ---------- + self : `SupPlotsSpec` + Subplots specification to export. + path : str, file-like object + Сan be either a string specifying a file path or a file-like object. + If a string is provided, the result will be exported to the file at that path. + If a file-like object is provided, the result will be exported to that object. + scale : float + Scaling factor for raster output. Default value is 2.0. + + Returns + ------- + str + Absolute pathname of created file or None if file-like object is provided. + + Notes + ----- + Export to PDF file uses the CairoSVG library. + CairoSVG is free and distributed under the LGPL-3.0 license. + For more details visit: https://cairosvg.org/documentation/ + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 12 + + import numpy as np + import io + import os + from lets_plot import * + LetsPlot.setup_html() + n = 60 + np.random.seed(42) + x = np.random.choice(list('abcde'), size=n) + y = np.random.normal(size=n) + p = ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + geom_jitter() + file_like = io.BytesIO() + p.to_pdf(file_like) + """ + return _to_pdf(self, path, scale) diff --git a/python-package/lets_plot/plot/theme_.py b/python-package/lets_plot/plot/theme_.py index d2990c3b63e..6757ba47a4d 100644 --- a/python-package/lets_plot/plot/theme_.py +++ b/python-package/lets_plot/plot/theme_.py @@ -180,8 +180,17 @@ def theme(*, Plot message (e.g. sampling messages). Set 'blank' or result of `element_blank()` to show nothing. Set `element_text()` to show sampling messages (`element_text()` options don't affect a message text). - plot_margin : `margin` - Margin around entire plot. See `margin()` for more details. + plot_margin : number or list of numbers + Margin around entire plot. + The margin may be specified using a number or a list of numbers: + + - a number or list of one number - the same margin it applied to all four sides; + - a list of two numbers - the first margin applies to the top and bottom, the second - to the left and right; + - a list of three numbers - the first margin applies to the top, the second - to the right and left, + the third - to the bottom; + - a list of four numbers - the margins are applied to the top, right, bottom and left in that order. + + It is acceptable to use None for any side; in this case, the default value for the plot margin side will be used. strip_background : str or dict Background of facet labels. Set 'blank' or result of `element_blank()` to draw nothing. @@ -427,18 +436,27 @@ def element_text( Angle to rotate the text (in degrees). hjust : float Horizontal justification (in [0, 1]). - 0 - left-justified - 1 - right-justified - 0.5 - center-justified + 0 - left-justified; + 1 - right-justified; + 0.5 - center-justified. Can be used with values out of range, but behaviour is not specified. vjust : float Vertical justification (in [0, 1]). - 0 - bottom-justified - 1 - top-justified - 0.5 - middle-justified + 0 - bottom-justified; + 1 - top-justified; + 0.5 - middle-justified. Can be used with values out of range, but behaviour is not specified. - margin : `margin` - Margins around the text. See `margin()` for more details. + margin : number or list of numbers + Margins around the text. + + The margin may be specified using a number or a list of numbers: + - a number or list of one number - the same margin it applied to all four sides; + - a list of two numbers - the first margin applies to the top and bottom, the second - to the left and right; + - a list of three numbers - the first margin applies to the top, the second - to the right and left, + the third - to the bottom; + - a list of four numbers - the margins are applied to the top, right, bottom and left in that order. + + It is acceptable to use None for any side; in this case, the default side value for this element will be used. blank : bool, default=False If True - draws nothing, and assigns no space. @@ -465,42 +483,16 @@ def element_text( return locals() -def margin(t=None, r=None, b=None, l=None) -> dict: +def margin(t=None, r=None, b=None, l=None): """ - Dimensions of each margin. - - Parameters - ---------- - t : float - Top margin. - r : float - Right margin. - b : float - Bottom margin. - l : float - Left margin. - - Returns - ------- - `dict` - Margins specification. - - Examples - -------- - .. jupyter-execute:: - :linenos: - :emphasize-lines: 7 - - import numpy as np - from lets_plot import * - LetsPlot.setup_html() - np.random.seed(42) - data = {'x': np.random.normal(size=1000)} - ggplot(data, aes(x='x')) + geom_histogram() + \\ - theme(axis_title=element_text(margin=margin(t=10,r=10,b=4,l=4))) + Function `margin()` is deprecated. + Please, use a number or list of numbers to specify margins (see description of the parameter used). """ - return locals() + print("WARN: The margin() is deprecated and will be removed in future releases.\n" + " Please, use a number or list of numbers to specify margins (see description of the parameter used).") + + return [t, r, b, l] def element_geom( diff --git a/python-package/test/plot/test_scale.py b/python-package/test/plot/test_scale.py index 9e5d174770c..81a817a5b34 100644 --- a/python-package/test/plot/test_scale.py +++ b/python-package/test/plot/test_scale.py @@ -65,3 +65,67 @@ def test_scale_x_discrete(): assert as_dict['aesthetic'] == 'x' assert as_dict['name'] == 'N' assert as_dict['discrete'] + + +# Use dictionary in scale_xxx(labels) + +def test_scale_labels_dict(): + spec = gg.scale_x_discrete(labels=dict(a="A", b="B", c="C")) + as_dict = spec.as_dict() + assert as_dict['breaks'] == ['a', 'b', 'c'] + assert as_dict['labels'] == ['A', 'B', 'C'] + + +def test_scale_labels_dict_with_specified_breaks(): + spec = gg.scale_x_discrete(labels=dict(a="A", b="B", c="C"), breaks=['a', 'd', 'c']) + as_dict = spec.as_dict() + # use the order as in original 'breaks' + correct 'breaks' (without label - to the end of the list) + assert as_dict['breaks'] == ['a', 'c', 'd'] + assert as_dict['labels'] == ['A', 'C'] + + +def test_scale_labels_dict_no_matches_with_specified_breaks(): + spec = gg.scale_x_discrete(labels=dict(a="A", b="B", c="C"), breaks=['d', 'e']) + as_dict = spec.as_dict() + assert as_dict['breaks'] == ['d', 'e'] + assert as_dict['labels'] == [] + + +def test_scale_breaks_dict(): + spec = gg.scale_x_discrete(breaks=dict(A="a", B="b", C="c")) + as_dict = spec.as_dict() + assert as_dict['breaks'] == ['a', 'b', 'c'] + assert as_dict['labels'] == ['A', 'B', 'C'] + + +# Use dictionary in scale_manual(values) + +def test_scale_manual_values_dict(): + spec = gg.scale_fill_manual(values=dict(a="A", b="B", c="C")) + as_dict = spec.as_dict() + assert as_dict['breaks'] == ['a', 'b', 'c'] + assert as_dict['values'] == ['A', 'B', 'C'] + + +def test_scale_manual_values_dict_with_specified_breaks(): + spec = gg.scale_fill_manual(values=dict(a="A", b="B", c="C"), breaks=['a', 'c']) + as_dict = spec.as_dict() + # order as in original 'breaks', missing breaks - to the end of the list + assert as_dict['breaks'] == ['a', 'c'] + assert as_dict['values'] == ['A', 'C', 'B'] + + +def test_scale_manual_values_dict_with_specified_breaks_and_limits(): + spec = gg.scale_fill_manual(values=dict(a="A", b="B", c="C"), breaks=['a', 'c'], limits=['b', 'c']) + as_dict = spec.as_dict() + # priority to 'limits' as a base list to choose the order of values + assert as_dict['breaks'] == ['a', 'c'] + assert as_dict['limits'] == ['b', 'c'] + assert as_dict['values'] == ['B', 'C', 'A'] + + +def test_scale_manual_values_dict_no_matches_with_specified_breaks(): + spec = gg.scale_fill_manual(values=dict(a="A", b="B", c="C"), breaks=['d', 'e']) + as_dict = spec.as_dict() + assert as_dict['breaks'] == ['d', 'e'] + assert 'values' not in as_dict