From 4ba627a9c27312a91cd7001ef6a65ecdfeb07ff7 Mon Sep 17 00:00:00 2001 From: vasilios Date: Mon, 24 May 2021 16:30:14 +0300 Subject: [PATCH 1/6] feat: Pie Chart? --- core/shared/src/main/scala/plotly/Trace.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/shared/src/main/scala/plotly/Trace.scala b/core/shared/src/main/scala/plotly/Trace.scala index 95c9139..5edc984 100755 --- a/core/shared/src/main/scala/plotly/Trace.scala +++ b/core/shared/src/main/scala/plotly/Trace.scala @@ -179,6 +179,13 @@ object Box { hoverlabel: Option[HoverLabel] = None ) extends Trace +@data(optionSetters = true) class Pie( + values: Option[Sequence] = None, + marker: Option[Marker] = None, + name: Option[String] = None, + showlegend: Option[Boolean] = None, +) extends Trace + @data(optionSetters = true) class Bar( x: Sequence, y: Sequence, From 758e7dad7cbcc3801b63efcc4a73798bc8740daf Mon Sep 17 00:00:00 2001 From: vasilios Date: Tue, 25 May 2021 11:14:48 +0300 Subject: [PATCH 2/6] feat: added pie elements for PieChart support feat: added pie elements for PieChart support --- core/shared/src/main/scala/plotly/Trace.scala | 24 +++++++++++++++---- .../scala/plotly/element/pie/Direction.scala | 8 +++++++ .../plotly/element/pie/PieTextPosition.scala | 10 ++++++++ .../plotly/element/pie/PieTitleFont.scala | 10 ++++++++ .../plotly/element/pie/PieTitlePosition.scala | 13 ++++++++++ .../main/scala/plotly/element/pie/Title.scala | 9 +++++++ 6 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 core/shared/src/main/scala/plotly/element/pie/Direction.scala create mode 100644 core/shared/src/main/scala/plotly/element/pie/PieTextPosition.scala create mode 100644 core/shared/src/main/scala/plotly/element/pie/PieTitleFont.scala create mode 100644 core/shared/src/main/scala/plotly/element/pie/PieTitlePosition.scala create mode 100644 core/shared/src/main/scala/plotly/element/pie/Title.scala diff --git a/core/shared/src/main/scala/plotly/Trace.scala b/core/shared/src/main/scala/plotly/Trace.scala index 5edc984..978112d 100755 --- a/core/shared/src/main/scala/plotly/Trace.scala +++ b/core/shared/src/main/scala/plotly/Trace.scala @@ -1,11 +1,10 @@ package plotly import scala.language.implicitConversions - -import java.lang.{ Boolean => JBoolean, Double => JDouble } - +import java.lang.{Boolean => JBoolean, Double => JDouble} import dataclass._ import plotly.element._ +import plotly.element.pie.{Direction, PieTextPosition, Title} sealed abstract class Trace extends Product with Serializable @@ -180,10 +179,25 @@ object Box { ) extends Trace @data(optionSetters = true) class Pie( - values: Option[Sequence] = None, - marker: Option[Marker] = None, name: Option[String] = None, + title: Option[Title] = None, showlegend: Option[Boolean] = None, + legendgroup: Option[String] = None, + opacity: Option[Double] = None, + ids: Option[Seq[String]] = None, + values: Option[Sequence] = None, + labels: Option[Sequence] = None, + pull: Option[OneOrSeq[Double]] = None, + text: Option[Seq[String]] = None, + textposition: Option[PieTextPosition] = None, + hovertext: Option[OneOrSeq[String]] = None, + hoverinfo: Option[HoverInfo] = None, + marker: Option[Marker] = None, + direction: Option[Direction] = None, + hole: Option[Double] = None, + hoverlabel: Option[HoverLabel] = None, + rotation: Option[Double] = None, + sort: Option[Boolean] = None, ) extends Trace @data(optionSetters = true) class Bar( diff --git a/core/shared/src/main/scala/plotly/element/pie/Direction.scala b/core/shared/src/main/scala/plotly/element/pie/Direction.scala new file mode 100644 index 0000000..b4d6e90 --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/Direction.scala @@ -0,0 +1,8 @@ +package plotly.element.pie + +sealed abstract class Direction(val label: String) extends Product with Serializable + +object Direction { + case object Clockwise extends Direction("clockwise") + case object CounterClockwise extends Direction("counterclockwise") +} diff --git a/core/shared/src/main/scala/plotly/element/pie/PieTextPosition.scala b/core/shared/src/main/scala/plotly/element/pie/PieTextPosition.scala new file mode 100644 index 0000000..ffcffb8 --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/PieTextPosition.scala @@ -0,0 +1,10 @@ +package plotly.element.pie + +sealed abstract class PieTextPosition(val label: String) extends Product with Serializable + +object PieTextPosition { + case object Inside extends PieTextPosition("inside") + case object Outside extends PieTextPosition("outside") + case object Auto extends PieTextPosition("auto") + case object None extends PieTextPosition("none") +} diff --git a/core/shared/src/main/scala/plotly/element/pie/PieTitleFont.scala b/core/shared/src/main/scala/plotly/element/pie/PieTitleFont.scala new file mode 100644 index 0000000..4d46d12 --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/PieTitleFont.scala @@ -0,0 +1,10 @@ +package plotly.element.pie + +import dataclass.data +import plotly.element.{Color, OneOrSeq} + +@data(optionSetters = true) class PieTitleFont( + family: Option[OneOrSeq[String]] = None, + size: Option[OneOrSeq[Double]] = None, + color: Option[OneOrSeq[Color]] = None +) diff --git a/core/shared/src/main/scala/plotly/element/pie/PieTitlePosition.scala b/core/shared/src/main/scala/plotly/element/pie/PieTitlePosition.scala new file mode 100644 index 0000000..1756e07 --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/PieTitlePosition.scala @@ -0,0 +1,13 @@ +package plotly.element.pie + +sealed abstract class PieTitlePosition(val label: String) extends Product with Serializable + +object PieTitlePosition { + case object TopLeft extends PieTitlePosition("top left") + case object TopCenter extends PieTitlePosition("top center") + case object TopRight extends PieTitlePosition("top right") + case object MiddleCenter extends PieTitlePosition("middle center") + case object BottomLeft extends PieTitlePosition("bottom left") + case object BottomCenter extends PieTitlePosition("bottom center") + case object BottomRight extends PieTitlePosition("bottom right") +} diff --git a/core/shared/src/main/scala/plotly/element/pie/Title.scala b/core/shared/src/main/scala/plotly/element/pie/Title.scala new file mode 100644 index 0000000..cf5671f --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/Title.scala @@ -0,0 +1,9 @@ +package plotly.element.pie + +import dataclass.data + +@data(optionSetters = true) class Title( + text: Option[String] = None, + font: Option[PieTitleFont] = None, + position: Option[PieTitlePosition] = None +) \ No newline at end of file From f7c28bd5709536bc25b897e5e325cbd56e699dd4 Mon Sep 17 00:00:00 2001 From: vasilios Date: Wed, 26 May 2021 10:15:14 +0300 Subject: [PATCH 3/6] feat: registered enums + fixed HoverInfo bug (added PieHoverInfo class) --- core/shared/src/main/scala/plotly/Trace.scala | 4 +-- .../plotly/element/pie/PieHoverInfo.scala | 35 +++++++++++++++++++ .../internals/ArgonautCodecsInternals.scala | 31 +++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 core/shared/src/main/scala/plotly/element/pie/PieHoverInfo.scala diff --git a/core/shared/src/main/scala/plotly/Trace.scala b/core/shared/src/main/scala/plotly/Trace.scala index 978112d..ae1b23a 100755 --- a/core/shared/src/main/scala/plotly/Trace.scala +++ b/core/shared/src/main/scala/plotly/Trace.scala @@ -4,7 +4,7 @@ import scala.language.implicitConversions import java.lang.{Boolean => JBoolean, Double => JDouble} import dataclass._ import plotly.element._ -import plotly.element.pie.{Direction, PieTextPosition, Title} +import plotly.element.pie.{Direction, PieHoverInfo, PieTextPosition, Title} sealed abstract class Trace extends Product with Serializable @@ -212,7 +212,7 @@ object Box { yaxis: Option[AxisReference] = None, error_y: Option[Error] = None, showlegend: Option[Boolean] = None, - hoverinfo: Option[HoverInfo] = None, + hoverinfo: Option[PieHoverInfo] = None, textposition: Option[BarTextPosition] = None, opacity: Option[Double] = None, width: Option[OneOrSeq[Double]] = None, diff --git a/core/shared/src/main/scala/plotly/element/pie/PieHoverInfo.scala b/core/shared/src/main/scala/plotly/element/pie/PieHoverInfo.scala new file mode 100644 index 0000000..ce0c5be --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/PieHoverInfo.scala @@ -0,0 +1,35 @@ +package plotly.element.pie + +sealed abstract class PieHoverInfo extends Product with Serializable { + def label: String +} + +object PieHoverInfo { + + def all: PieHoverInfo = All + def skip: PieHoverInfo = Skip + def none: PieHoverInfo = None + def apply(elements: Element*): PieHoverInfo = Combination(elements) + + sealed abstract class Element(override val label: String) extends PieHoverInfo + case object Text extends Element("text") + case object Name extends Element("name") + case object Percent extends Element("percent") + case object Value extends Element("value") + case object Label extends Element("label") + + + case object All extends PieHoverInfo { + def label = "all" + } + + case object Skip extends PieHoverInfo { + def label = "skip" + } + + val None: Combination = Combination(Nil) + + final case class Combination(elements: Seq[Element]) extends PieHoverInfo { + def label: String = elements.map(_.label).mkString("+") + } +} diff --git a/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala b/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala index 6f1e270..aac6985 100755 --- a/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala +++ b/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala @@ -2,7 +2,6 @@ package plotly package internals import java.math.BigInteger - import argonaut._ import argonaut.Argonaut._ import argonaut.ArgonautShapeless._ @@ -11,6 +10,7 @@ import shapeless._ import scala.util.Try import plotly.element._ +import plotly.element.pie.{Direction, PieHoverInfo, PieTextPosition, PieTitlePosition} import plotly.layout._ object ArgonautCodecsInternals extends ArgonautCodecsExtra { @@ -151,6 +151,9 @@ object ArgonautCodecsInternals extends ArgonautCodecsExtra { implicit val rowOrderIsEnum = IsEnum.instance[RowOrder](_.label) implicit val alignmentIsEnum = IsEnum.instance[Alignment](_.label) implicit val colorModelIsEnum = IsEnum.instance[ColorModel](_.label) + implicit val directionIsEnum = IsEnum.instance[Direction](_.label) + implicit val pieTextPositionIsEnum = IsEnum.instance[PieTextPosition](_.label) + implicit val pieTitlePositionIsEnum = IsEnum.instance[PieTitlePosition](_.label) def jsonSumDirectCodecFor(name: String): JsonSumCodec = new JsonSumCodec { def encodeEmpty: Nothing = @@ -242,6 +245,32 @@ object ArgonautCodecsInternals extends ArgonautCodecsExtra { } } + implicit val encodePieHoverInfo: EncodeJson[PieHoverInfo] = + EncodeJson.of[String].contramap(_.label) + implicit val decodePieHoverInfo: DecodeJson[PieHoverInfo] = + DecodeJson { c => + DecodeJson.of[String].apply(c).flatMap { + case "all" => DecodeResult.ok(PieHoverInfo.All) + case "skip" => DecodeResult.ok(PieHoverInfo.Skip) + case "none" => DecodeResult.ok(PieHoverInfo.None) + case combination => + val results = combination.split('+').map { + case "percent" => Right(PieHoverInfo.Percent) + case "value" => Right(PieHoverInfo.Value) + case "label" => Right(PieHoverInfo.Label) + case "text" => Right(PieHoverInfo.Text) + case "name" => Right(PieHoverInfo.Name) + case other => Left(s"Unrecognized pie hover info element: $other") + } + if (results.exists(_.isLeft)) + DecodeResult.fail( + s"Unrecognized pie hover info elements: ${results.flatMap(_.left.toSeq).mkString(", ")}", + c.history + ) + else + DecodeResult.ok(PieHoverInfo(results.flatMap(_.toSeq).toIndexedSeq: _*)) + } + } implicit def defaultJsonProductCodecFor[T]: JsonProductCodecFor[T] = JsonProductCodecFor(JsonProductObjCodecNoEmpty.default) From afc2f2b37f02be77252813ae8e350aff2330539d Mon Sep 17 00:00:00 2001 From: vasilios Date: Wed, 26 May 2021 10:15:53 +0300 Subject: [PATCH 4/6] test: added amazing test for pie/donut chart --- .../scala/plotly/doc/DocumentationTests.scala | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/plotly/doc/DocumentationTests.scala b/tests/src/test/scala/plotly/doc/DocumentationTests.scala index b341bc0..e20b839 100755 --- a/tests/src/test/scala/plotly/doc/DocumentationTests.scala +++ b/tests/src/test/scala/plotly/doc/DocumentationTests.scala @@ -11,8 +11,9 @@ import org.mozilla.javascript._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import plotly.element.HoverInfo -import plotly.element.HoverInfo.{X,Y,Z} +import plotly.element.HoverInfo.{X, Y, Z} import plotly.element.ColorModel._ +import plotly.element.pie.Direction import scala.util.matching.Regex @@ -243,7 +244,7 @@ class DocumentationTests extends AnyFlatSpec with Matchers { "basic/line-plots", "basic/bar", "basic/horizontal-bar", - // TODO? Pie charts + "basic/pie", "financial/time-series", "financial/candlestick-charts", // "financial/ohlc", @@ -359,4 +360,61 @@ class DocumentationTests extends AnyFlatSpec with Matchers { } } + it should "demo Pie Trace" in { + val js = + """ + |var data = [ + | { + | type: "pie", + | name: "Best donuts", + | values: [30, 60, 40, 20, 50], + | labels: ["Vanilla", "Choco", "Strawberry", "Peanutbutter", "Cherry"], + | showlegend: true, + | opacity: 0.9, + | pull: 0.02, + | hole: 0.3, + | direction: "clockwise", + | sort: true, + | rotation: -50 + | } + |]; + | + |var layout = { + | width: 400, + | height: 400, + | title: "Tasty donut chart" + |}; + | + |Plotly.newPlot('myDonut', data, layout); + |""".stripMargin + + val (data, maybeLayout) = plotlyDemoElements(js) + + maybeLayout should === (Some( + new Layout() + .withWidth(400) + .withHeight(400) + .withTitle("Tasty donut chart") + )) + + data.headOption match { + case Some(image) => + val expected = Pie() + .withName("Best donuts") + .withValues(Seq(30, 60, 40, 20, 50)) + .withLabels(Seq("Vanilla", "Choco", "Strawberry", "Peanutbutter", "Cherry")) + .withShowlegend(true) + .withOpacity(0.9) + .withPull(0.02) + .withHole(0.3) + .withDirection(Direction.Clockwise) + .withSort(true) + .withRotation(-50) + + image should === (expected) + case None => + fail("data must contain an pie trace") + } + } + } From 769e31b5a81bf7242f41d176d3eddafde1478fbd Mon Sep 17 00:00:00 2001 From: vasilios Date: Wed, 26 May 2021 10:20:41 +0300 Subject: [PATCH 5/6] fix: fixed Trace attributes --- core/shared/src/main/scala/plotly/Trace.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/plotly/Trace.scala b/core/shared/src/main/scala/plotly/Trace.scala index ae1b23a..7466a94 100755 --- a/core/shared/src/main/scala/plotly/Trace.scala +++ b/core/shared/src/main/scala/plotly/Trace.scala @@ -191,7 +191,7 @@ object Box { text: Option[Seq[String]] = None, textposition: Option[PieTextPosition] = None, hovertext: Option[OneOrSeq[String]] = None, - hoverinfo: Option[HoverInfo] = None, + hoverinfo: Option[PieHoverInfo] = None, marker: Option[Marker] = None, direction: Option[Direction] = None, hole: Option[Double] = None, @@ -212,7 +212,7 @@ object Box { yaxis: Option[AxisReference] = None, error_y: Option[Error] = None, showlegend: Option[Boolean] = None, - hoverinfo: Option[PieHoverInfo] = None, + hoverinfo: Option[HoverInfo] = None, textposition: Option[BarTextPosition] = None, opacity: Option[Double] = None, width: Option[OneOrSeq[Double]] = None, From e0c0450d5afa2ad135cd27b9f95c28ec7c839b9d Mon Sep 17 00:00:00 2001 From: vasilios Date: Wed, 26 May 2021 10:34:43 +0300 Subject: [PATCH 6/6] feat: added Domain element + reference Link (for pie chart) --- core/shared/src/main/scala/plotly/Trace.scala | 8 +++++++- .../src/main/scala/plotly/element/pie/Domain.scala | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 core/shared/src/main/scala/plotly/element/pie/Domain.scala diff --git a/core/shared/src/main/scala/plotly/Trace.scala b/core/shared/src/main/scala/plotly/Trace.scala index 7466a94..53a9de0 100755 --- a/core/shared/src/main/scala/plotly/Trace.scala +++ b/core/shared/src/main/scala/plotly/Trace.scala @@ -4,7 +4,7 @@ import scala.language.implicitConversions import java.lang.{Boolean => JBoolean, Double => JDouble} import dataclass._ import plotly.element._ -import plotly.element.pie.{Direction, PieHoverInfo, PieTextPosition, Title} +import plotly.element.pie.{Direction, Domain, PieHoverInfo, PieTextPosition, Title} sealed abstract class Trace extends Product with Serializable @@ -178,6 +178,11 @@ object Box { hoverlabel: Option[HoverLabel] = None ) extends Trace +/** + * See + * https://plotly.com/javascript/reference/pie + * for a description of the params. + */ @data(optionSetters = true) class Pie( name: Option[String] = None, title: Option[Title] = None, @@ -192,6 +197,7 @@ object Box { textposition: Option[PieTextPosition] = None, hovertext: Option[OneOrSeq[String]] = None, hoverinfo: Option[PieHoverInfo] = None, + domain: Option[Domain] = None, marker: Option[Marker] = None, direction: Option[Direction] = None, hole: Option[Double] = None, diff --git a/core/shared/src/main/scala/plotly/element/pie/Domain.scala b/core/shared/src/main/scala/plotly/element/pie/Domain.scala new file mode 100644 index 0000000..f0e5d13 --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/pie/Domain.scala @@ -0,0 +1,11 @@ +package plotly.element.pie + +import dataclass.data +import plotly.Sequence + +@data(optionSetters = true) class Domain( + x: Option[Sequence] = None, + y: Option[Sequence] = None, + row: Option[Int] = None, + column: Option[Int] = None, +) \ No newline at end of file