diff --git a/core/shared/src/main/scala/plotly/Sequence.scala b/core/shared/src/main/scala/plotly/Sequence.scala old mode 100644 new mode 100755 index 50b6cec..db872d4 --- a/core/shared/src/main/scala/plotly/Sequence.scala +++ b/core/shared/src/main/scala/plotly/Sequence.scala @@ -7,6 +7,7 @@ sealed abstract class Sequence extends Product with Serializable object Sequence { final case class Doubles(seq: Seq[Double]) extends Sequence final case class NestedDoubles(seq: Seq[Seq[Double]]) extends Sequence + final case class NestedInts(seq: Seq[Seq[Int]]) extends Sequence final case class Strings(seq: Seq[String]) extends Sequence final case class DateTimes(seq: Seq[LocalDateTime]) extends Sequence @@ -20,6 +21,8 @@ object Sequence { Doubles(s.map(_.toDouble)) implicit def fromNestedDoubleSeq(s: Seq[Seq[Double]]): Sequence = NestedDoubles(s) + implicit def fromNestedIntSeq(s: Seq[Seq[Int]]): Sequence = + NestedInts(s) implicit def fromStringSeq(s: Seq[String]): Sequence = Strings(s) implicit def fromDateTimes(seq: Seq[LocalDateTime]): Sequence = diff --git a/core/shared/src/main/scala/plotly/Trace.scala b/core/shared/src/main/scala/plotly/Trace.scala old mode 100644 new mode 100755 index 9b990b0..db0d457 --- a/core/shared/src/main/scala/plotly/Trace.scala +++ b/core/shared/src/main/scala/plotly/Trace.scala @@ -260,3 +260,34 @@ object Surface { Option(opacity) .map(d => d: Double) ) } + +@data class Heatmap( + y: Option[Sequence], + x: Option[Sequence], + z: Option[Sequence], +autocolorscale: Option[Boolean], + colorscale: Option[ColorScale], + showscale: Option[Boolean], + name: Option[String] +) extends Trace + +object Heatmap { + def apply( + y: Sequence = null, + x: Sequence = null, + z: Sequence = null, + autocolorscale: JBoolean = null, + colorscale: ColorScale = null, + showscale: JBoolean = null, + name: String = null + ): Heatmap = + Heatmap( + Option(y), + Option(x), + Option(z), + Option(autocolorscale).map(b => b: Boolean), + Option(colorscale), + Option(showscale).map(b => b: Boolean), + Option(name) + ) +} diff --git a/core/shared/src/main/scala/plotly/element/ColorScale.scala b/core/shared/src/main/scala/plotly/element/ColorScale.scala new file mode 100755 index 0000000..cefb566 --- /dev/null +++ b/core/shared/src/main/scala/plotly/element/ColorScale.scala @@ -0,0 +1,9 @@ +package plotly.element +import dataclass.data + +sealed abstract class ColorScale extends Product with Serializable + +object ColorScale { + @data class CustomScale(values: Seq[(Double, Color)]) extends ColorScale + @data class NamedScale(name: String) extends ColorScale +} \ No newline at end of file diff --git a/core/shared/src/main/scala/plotly/element/Ticks.scala b/core/shared/src/main/scala/plotly/element/Ticks.scala old mode 100644 new mode 100755 index 9142bd7..90dc43d --- a/core/shared/src/main/scala/plotly/element/Ticks.scala +++ b/core/shared/src/main/scala/plotly/element/Ticks.scala @@ -5,4 +5,6 @@ sealed abstract class Ticks(val label: String) extends Product with Serializable object Ticks { case object Outside extends Ticks("outside") + case object Inside extends Ticks("inside") + case object Empty extends Ticks("") } diff --git a/core/shared/src/main/scala/plotly/layout/Axis.scala b/core/shared/src/main/scala/plotly/layout/Axis.scala old mode 100644 new mode 100755 index 3aed94d..6dcb30e --- a/core/shared/src/main/scala/plotly/layout/Axis.scala +++ b/core/shared/src/main/scala/plotly/layout/Axis.scala @@ -23,6 +23,8 @@ import plotly.element._ dtick: Option[Double], ticklen: Option[Int], tickfont: Option[Font], + tickprefix: Option[String], + ticksuffix: Option[String], zeroline: Option[Boolean], zerolinewidth: Option[Double], zerolinecolor: Option[Color], @@ -60,6 +62,8 @@ object Axis { dtick: JDouble = null, ticklen: JInt = null, tickfont: Font = null, + tickprefix: String = null, + ticksuffix: String = null, zeroline: JBoolean = null, zerolinewidth: JDouble = null, zerolinecolor: Color = null, @@ -95,6 +99,8 @@ object Axis { Option(dtick) .map(x => x: Double), Option(ticklen) .map(x => x: Int), Option(tickfont), + Option(tickprefix), + Option(ticksuffix), Option(zeroline) .map(x => x: Boolean), Option(zerolinewidth) .map(x => x: Double), Option(zerolinecolor), diff --git a/demo/src/main/scala/plotly/demo/Demo.scala b/demo/src/main/scala/plotly/demo/Demo.scala old mode 100644 new mode 100755 index 20e2a97..df2d42f --- a/demo/src/main/scala/plotly/demo/Demo.scala +++ b/demo/src/main/scala/plotly/demo/Demo.scala @@ -37,6 +37,12 @@ import scalatags.JsDom.all.{area => _, _} ), "Filled Area Plots" -> Seq( area.BasicOverlaidAreaChart + ), + "Heatmaps" -> Seq( + heatmaps.BasicHeatmap, + heatmaps.CategoricalAxisHeatmap, + heatmaps.CustomColorScaleHeatmap, + heatmaps.AnnotatedHeatmap ) ) diff --git a/demo/src/main/scala/plotly/demo/heatmaps/AnnotatedHeatmap.scala b/demo/src/main/scala/plotly/demo/heatmaps/AnnotatedHeatmap.scala new file mode 100755 index 0000000..400d4fc --- /dev/null +++ b/demo/src/main/scala/plotly/demo/heatmaps/AnnotatedHeatmap.scala @@ -0,0 +1,55 @@ +package plotly.demo.heatmaps + +import plotly._ +import plotly.demo.DemoChart +import plotly.element._ +import plotly.layout._ + +object AnnotatedHeatmap extends DemoChart { + + def plotlyDocUrl = "https://plot.ly/javascript/heatmaps/#annotated-heatmap" + def id = "annotated-heatmap" + def source = AnnotatedHeatmapSource.source + + // demo source start + + val x = Seq("A", "B", "C", "D", "E"); + val y = Seq("W", "X", "Y", "Z"); + val z = Seq( + Seq(0.00, 0.00, 0.75, 0.75, 0.00), + Seq(0.00, 0.00, 0.75, 0.75, 0.00), + Seq(0.75, 0.75, 0.75, 0.75, 0.75), + Seq(0.00, 0.00, 0.00, 0.75, 0.00) + ) + + val data = Seq( + Heatmap( + z=z, x=x, y=y, showscale=false, + colorscale = ColorScale.CustomScale(Seq( + (0, Color.StringColor("#3D9970")), + (1, Color.StringColor("#001f3f")) + )) + ) + ) + + val layout = Layout( + title = "Annotated Heatmap", + xaxis = Axis(ticks=Ticks.Empty, side=Side.Top), + yaxis = Axis(ticks=Ticks.Empty, ticksuffix=" "), + annotations = for { + (xv, xi) <- x.zipWithIndex; + (yv, yi) <- y.zipWithIndex + } yield Annotation( + x=xv, + y=yv, + xref=Ref.Axis(AxisReference.X1), + yref=Ref.Axis(AxisReference.Y1), + showarrow=false, + text=z(yi)(xi).toString, + font=Font(color=Color.StringColor("white")) + ) + ) + + // demo source end + +} \ No newline at end of file diff --git a/demo/src/main/scala/plotly/demo/heatmaps/BasicHeatmap.scala b/demo/src/main/scala/plotly/demo/heatmaps/BasicHeatmap.scala new file mode 100755 index 0000000..281b27f --- /dev/null +++ b/demo/src/main/scala/plotly/demo/heatmaps/BasicHeatmap.scala @@ -0,0 +1,28 @@ +package plotly.demo.heatmaps + +import plotly._ +import plotly.demo.NoLayoutDemoChart +import plotly.element._ + +object BasicHeatmap extends NoLayoutDemoChart { + + def plotlyDocUrl = "https://plot.ly/javascript/heatmaps/#basic-heatmap" + def id = "basic-heatmap" + def source = BasicHeatmapSource.source + + // demo source start + + val data = Seq( + Heatmap( + z=Seq( + Seq(1, 20, 30), + Seq(20, 1, 60), + Seq(30, 60, 1) + ), + colorscale=ColorScale.NamedScale("Portland") + ) + ) + + // demo source end + +} \ No newline at end of file diff --git a/demo/src/main/scala/plotly/demo/heatmaps/CategoricalAxisHeatmap.scala b/demo/src/main/scala/plotly/demo/heatmaps/CategoricalAxisHeatmap.scala new file mode 100755 index 0000000..725a04f --- /dev/null +++ b/demo/src/main/scala/plotly/demo/heatmaps/CategoricalAxisHeatmap.scala @@ -0,0 +1,29 @@ +package plotly.demo.heatmaps + +import plotly._ +import plotly.demo.NoLayoutDemoChart +import plotly.element._ + +object CategoricalAxisHeatmap extends NoLayoutDemoChart { + + def plotlyDocUrl = "https://plot.ly/javascript/heatmaps/#heatmap-with-categorical-axis-labels" + def id = "categorical-axis-heatmap" + def source = CategoricalAxisHeatmapSource.source + + // demo source start + + val data = Seq( + Heatmap( + z=Seq( + Seq(1, null.asInstanceOf[Int], 30, 50, 1), + Seq(20, 1, 60, 80, 30), + Seq(30, 60, 1, -10, 20) + ), + x=Seq("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"), + y=Seq("Morning", "Afternoon", "Evening"), + ) + ) + + // demo source end + +} \ No newline at end of file diff --git a/demo/src/main/scala/plotly/demo/heatmaps/CustomColorScaleHeatmap.scala b/demo/src/main/scala/plotly/demo/heatmaps/CustomColorScaleHeatmap.scala new file mode 100755 index 0000000..1e2a69d --- /dev/null +++ b/demo/src/main/scala/plotly/demo/heatmaps/CustomColorScaleHeatmap.scala @@ -0,0 +1,37 @@ +package plotly.demo.heatmaps + +import plotly._ +import plotly.demo.NoLayoutDemoChart +import plotly.element._ + +object CustomColorScaleHeatmap extends NoLayoutDemoChart { + + def plotlyDocUrl = "https://plot.ly/javascript/colorscales/#custom-colorscale-for-contour-plot" + def id = "custom-colorscale-heatmap" + def source = CustomColorScaleHeatmapSource.source + + // demo source start + + val data = Seq( + Heatmap( + z=Seq( + Seq(10.0, 10.625, 12.5, 15.625, 20.0), + Seq(5.625, 6.25, 8.125, 11.25, 15.625), + Seq(2.5, 3.125, 5.0, 8.125, 12.5), + Seq(0.625, 1.25, 3.125, 6.25, 10.625), + Seq(0.0, 0.625, 2.5, 5.625, 10.0) + ), + colorscale=ColorScale.CustomScale(Seq( + (0, Color.RGB(166,206,227)), + (0.25, Color.RGB(31,120,180)), + (0.45, Color.RGB(178,223,138)), + (0.65, Color.RGB(51,160,44)), + (0.85, Color.RGB(251,154,153)), + (1, Color.RGB(227,26,28)) + )) + ) + ) + + // demo source end + +} \ No newline at end of file diff --git a/plotly-documentation b/plotly-documentation index ef7ce85..eae136b 160000 --- a/plotly-documentation +++ b/plotly-documentation @@ -1 +1 @@ -Subproject commit ef7ce858e251eb30d345b20e6684c5afc56de674 +Subproject commit eae136bb920c7542654a5e13cff04a0de175a08d diff --git a/project/make-ghpages.sh b/project/make-ghpages.sh index 7a2239d..276e5ba 100755 --- a/project/make-ghpages.sh +++ b/project/make-ghpages.sh @@ -6,17 +6,17 @@ if [ -e gh-pages ]; then exit 1 fi -sbt demo/fullOptJS +./sbt demo/fullOptJS mkdir gh-pages cp \ - demo/target/scala-2.11/plotly-demo-opt.js \ - demo/target/scala-2.11/plotly-demo-opt.js.map \ - demo/target/scala-2.11/plotly-demo-jsdeps.js \ - demo/target/scala-2.11/plotly-demo-jsdeps.min.js \ + demo/target/scala-2.13/plotly-demo-opt.js \ + demo/target/scala-2.13/plotly-demo-opt.js.map \ + demo/target/scala-2.13/plotly-demo-jsdeps.js \ + demo/target/scala-2.13/plotly-demo-jsdeps.min.js \ gh-pages -cat demo/target/scala-2.11/classes/index.html | \ +cat demo/target/scala-2.13/classes/index.html | \ sed 's@\.\./plotly-demo-jsdeps\.js@plotly-demo-jsdeps.min.js@' | \ sed 's@\.\./plotly-demo-fastopt\.js@plotly-demo-opt.js@' | \ cat > gh-pages/index.html diff --git a/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala b/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala old mode 100644 new mode 100755 index 59cc86d..0c5ded6 --- a/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala +++ b/render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala @@ -46,6 +46,7 @@ object ArgonautCodecsInternals extends ArgonautCodecsExtra { implicit val boxPointsBoolIsWrapper: IsWrapper[BoxPoints.Bool] = null implicit val sequenceDoublesIsWrapper: IsWrapper[Sequence.Doubles] = null implicit val sequenceNestedDoublesIsWrapper: IsWrapper[Sequence.NestedDoubles] = null + implicit val sequenceNestedIntsIsWrapper: IsWrapper[Sequence.NestedInts] = null implicit val sequenceStringsIsWrapper: IsWrapper[Sequence.Strings] = null implicit val sequenceDatetimesIsWrapper: IsWrapper[Sequence.DateTimes] = null implicit val doubleElementIsWrapper: IsWrapper[Element.DoubleElement] = null @@ -377,6 +378,30 @@ object ArgonautCodecsInternals extends ArgonautCodecsExtra { } } + implicit val encodeNamedColorScale: EncodeJson[ColorScale.NamedScale] = + EncodeJson.of[String].contramap(_.name) + + implicit val decodeNamedColorScale: DecodeJson[ColorScale.NamedScale] = + DecodeJson { c => + c.as[String].flatMap { s => + // TODO: Add colorscale name enum? + DecodeResult.ok(ColorScale.NamedScale(s)) + } + } + + implicit val encodeCustomColorScale: EncodeJson[ColorScale.CustomScale] = + EncodeJson.of[Json].contramap(_.values.toList.asJson) + + implicit val decodeCustomColorScale: DecodeJson[ColorScale.CustomScale] = + DecodeJson { c => + c.as[Seq[(Double, Color)]].flatMap { s => + DecodeResult.ok(ColorScale.CustomScale(s)) + } + } + + implicit val colorscaleJsonCodec: JsonSumCodecFor[ColorScale] = + JsonSumCodecFor(jsonSumDirectCodecFor("colorscale")) + implicit val elementJsonCodec: JsonSumCodecFor[Element] = JsonSumCodecFor(jsonSumDirectCodecFor("element")) diff --git a/tests/src/test/scala/plotly/doc/DocumentationTests.scala b/tests/src/test/scala/plotly/doc/DocumentationTests.scala old mode 100644 new mode 100755 index b7dfaad..c409e80 --- a/tests/src/test/scala/plotly/doc/DocumentationTests.scala +++ b/tests/src/test/scala/plotly/doc/DocumentationTests.scala @@ -109,7 +109,7 @@ object DocumentationTests { // stub... def getElementById(id: String): String = id } - + private object Numeric { def linspace(from: Int, to: Int, count: Int) = { val step = (to - from).toDouble / (count - 1) @@ -243,6 +243,7 @@ class DocumentationTests extends FlatSpec with Matchers { "statistical/box", // TODO 2D Density plots "statistical/histogram", + "scientific/heatmap", // TODO 2D Histograms // TODO Wind rose charts // TODO Contour plots