Skip to content

Commit 1d82e3b

Browse files
eric-czechalexarchambault
authored andcommitted
Adding Heatmap trace implementation (#148)
1 parent f7dd66d commit 1d82e3b

File tree

14 files changed

+240
-8
lines changed

14 files changed

+240
-8
lines changed

core/shared/src/main/scala/plotly/Sequence.scala

100644100755
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ sealed abstract class Sequence extends Product with Serializable
77
object Sequence {
88
final case class Doubles(seq: Seq[Double]) extends Sequence
99
final case class NestedDoubles(seq: Seq[Seq[Double]]) extends Sequence
10+
final case class NestedInts(seq: Seq[Seq[Int]]) extends Sequence
1011
final case class Strings(seq: Seq[String]) extends Sequence
1112
final case class DateTimes(seq: Seq[LocalDateTime]) extends Sequence
1213

@@ -20,6 +21,8 @@ object Sequence {
2021
Doubles(s.map(_.toDouble))
2122
implicit def fromNestedDoubleSeq(s: Seq[Seq[Double]]): Sequence =
2223
NestedDoubles(s)
24+
implicit def fromNestedIntSeq(s: Seq[Seq[Int]]): Sequence =
25+
NestedInts(s)
2326
implicit def fromStringSeq(s: Seq[String]): Sequence =
2427
Strings(s)
2528
implicit def fromDateTimes(seq: Seq[LocalDateTime]): Sequence =

core/shared/src/main/scala/plotly/Trace.scala

100644100755
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,34 @@ object Surface {
260260
Option(opacity) .map(d => d: Double)
261261
)
262262
}
263+
264+
@data class Heatmap(
265+
y: Option[Sequence],
266+
x: Option[Sequence],
267+
z: Option[Sequence],
268+
autocolorscale: Option[Boolean],
269+
colorscale: Option[ColorScale],
270+
showscale: Option[Boolean],
271+
name: Option[String]
272+
) extends Trace
273+
274+
object Heatmap {
275+
def apply(
276+
y: Sequence = null,
277+
x: Sequence = null,
278+
z: Sequence = null,
279+
autocolorscale: JBoolean = null,
280+
colorscale: ColorScale = null,
281+
showscale: JBoolean = null,
282+
name: String = null
283+
): Heatmap =
284+
Heatmap(
285+
Option(y),
286+
Option(x),
287+
Option(z),
288+
Option(autocolorscale).map(b => b: Boolean),
289+
Option(colorscale),
290+
Option(showscale).map(b => b: Boolean),
291+
Option(name)
292+
)
293+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package plotly.element
2+
import dataclass.data
3+
4+
sealed abstract class ColorScale extends Product with Serializable
5+
6+
object ColorScale {
7+
@data class CustomScale(values: Seq[(Double, Color)]) extends ColorScale
8+
@data class NamedScale(name: String) extends ColorScale
9+
}

core/shared/src/main/scala/plotly/element/Ticks.scala

100644100755
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ sealed abstract class Ticks(val label: String) extends Product with Serializable
55

66
object Ticks {
77
case object Outside extends Ticks("outside")
8+
case object Inside extends Ticks("inside")
9+
case object Empty extends Ticks("")
810
}

core/shared/src/main/scala/plotly/layout/Axis.scala

100644100755
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import plotly.element._
2323
dtick: Option[Double],
2424
ticklen: Option[Int],
2525
tickfont: Option[Font],
26+
tickprefix: Option[String],
27+
ticksuffix: Option[String],
2628
zeroline: Option[Boolean],
2729
zerolinewidth: Option[Double],
2830
zerolinecolor: Option[Color],
@@ -60,6 +62,8 @@ object Axis {
6062
dtick: JDouble = null,
6163
ticklen: JInt = null,
6264
tickfont: Font = null,
65+
tickprefix: String = null,
66+
ticksuffix: String = null,
6367
zeroline: JBoolean = null,
6468
zerolinewidth: JDouble = null,
6569
zerolinecolor: Color = null,
@@ -95,6 +99,8 @@ object Axis {
9599
Option(dtick) .map(x => x: Double),
96100
Option(ticklen) .map(x => x: Int),
97101
Option(tickfont),
102+
Option(tickprefix),
103+
Option(ticksuffix),
98104
Option(zeroline) .map(x => x: Boolean),
99105
Option(zerolinewidth) .map(x => x: Double),
100106
Option(zerolinecolor),

demo/src/main/scala/plotly/demo/Demo.scala

100644100755
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ import scalatags.JsDom.all.{area => _, _}
3737
),
3838
"Filled Area Plots" -> Seq(
3939
area.BasicOverlaidAreaChart
40+
),
41+
"Heatmaps" -> Seq(
42+
heatmaps.BasicHeatmap,
43+
heatmaps.CategoricalAxisHeatmap,
44+
heatmaps.CustomColorScaleHeatmap,
45+
heatmaps.AnnotatedHeatmap
4046
)
4147
)
4248

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package plotly.demo.heatmaps
2+
3+
import plotly._
4+
import plotly.demo.DemoChart
5+
import plotly.element._
6+
import plotly.layout._
7+
8+
object AnnotatedHeatmap extends DemoChart {
9+
10+
def plotlyDocUrl = "https://plot.ly/javascript/heatmaps/#annotated-heatmap"
11+
def id = "annotated-heatmap"
12+
def source = AnnotatedHeatmapSource.source
13+
14+
// demo source start
15+
16+
val x = Seq("A", "B", "C", "D", "E");
17+
val y = Seq("W", "X", "Y", "Z");
18+
val z = Seq(
19+
Seq(0.00, 0.00, 0.75, 0.75, 0.00),
20+
Seq(0.00, 0.00, 0.75, 0.75, 0.00),
21+
Seq(0.75, 0.75, 0.75, 0.75, 0.75),
22+
Seq(0.00, 0.00, 0.00, 0.75, 0.00)
23+
)
24+
25+
val data = Seq(
26+
Heatmap(
27+
z=z, x=x, y=y, showscale=false,
28+
colorscale = ColorScale.CustomScale(Seq(
29+
(0, Color.StringColor("#3D9970")),
30+
(1, Color.StringColor("#001f3f"))
31+
))
32+
)
33+
)
34+
35+
val layout = Layout(
36+
title = "Annotated Heatmap",
37+
xaxis = Axis(ticks=Ticks.Empty, side=Side.Top),
38+
yaxis = Axis(ticks=Ticks.Empty, ticksuffix=" "),
39+
annotations = for {
40+
(xv, xi) <- x.zipWithIndex;
41+
(yv, yi) <- y.zipWithIndex
42+
} yield Annotation(
43+
x=xv,
44+
y=yv,
45+
xref=Ref.Axis(AxisReference.X1),
46+
yref=Ref.Axis(AxisReference.Y1),
47+
showarrow=false,
48+
text=z(yi)(xi).toString,
49+
font=Font(color=Color.StringColor("white"))
50+
)
51+
)
52+
53+
// demo source end
54+
55+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package plotly.demo.heatmaps
2+
3+
import plotly._
4+
import plotly.demo.NoLayoutDemoChart
5+
import plotly.element._
6+
7+
object BasicHeatmap extends NoLayoutDemoChart {
8+
9+
def plotlyDocUrl = "https://plot.ly/javascript/heatmaps/#basic-heatmap"
10+
def id = "basic-heatmap"
11+
def source = BasicHeatmapSource.source
12+
13+
// demo source start
14+
15+
val data = Seq(
16+
Heatmap(
17+
z=Seq(
18+
Seq(1, 20, 30),
19+
Seq(20, 1, 60),
20+
Seq(30, 60, 1)
21+
),
22+
colorscale=ColorScale.NamedScale("Portland")
23+
)
24+
)
25+
26+
// demo source end
27+
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package plotly.demo.heatmaps
2+
3+
import plotly._
4+
import plotly.demo.NoLayoutDemoChart
5+
import plotly.element._
6+
7+
object CategoricalAxisHeatmap extends NoLayoutDemoChart {
8+
9+
def plotlyDocUrl = "https://plot.ly/javascript/heatmaps/#heatmap-with-categorical-axis-labels"
10+
def id = "categorical-axis-heatmap"
11+
def source = CategoricalAxisHeatmapSource.source
12+
13+
// demo source start
14+
15+
val data = Seq(
16+
Heatmap(
17+
z=Seq(
18+
Seq(1, null.asInstanceOf[Int], 30, 50, 1),
19+
Seq(20, 1, 60, 80, 30),
20+
Seq(30, 60, 1, -10, 20)
21+
),
22+
x=Seq("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"),
23+
y=Seq("Morning", "Afternoon", "Evening"),
24+
)
25+
)
26+
27+
// demo source end
28+
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package plotly.demo.heatmaps
2+
3+
import plotly._
4+
import plotly.demo.NoLayoutDemoChart
5+
import plotly.element._
6+
7+
object CustomColorScaleHeatmap extends NoLayoutDemoChart {
8+
9+
def plotlyDocUrl = "https://plot.ly/javascript/colorscales/#custom-colorscale-for-contour-plot"
10+
def id = "custom-colorscale-heatmap"
11+
def source = CustomColorScaleHeatmapSource.source
12+
13+
// demo source start
14+
15+
val data = Seq(
16+
Heatmap(
17+
z=Seq(
18+
Seq(10.0, 10.625, 12.5, 15.625, 20.0),
19+
Seq(5.625, 6.25, 8.125, 11.25, 15.625),
20+
Seq(2.5, 3.125, 5.0, 8.125, 12.5),
21+
Seq(0.625, 1.25, 3.125, 6.25, 10.625),
22+
Seq(0.0, 0.625, 2.5, 5.625, 10.0)
23+
),
24+
colorscale=ColorScale.CustomScale(Seq(
25+
(0, Color.RGB(166,206,227)),
26+
(0.25, Color.RGB(31,120,180)),
27+
(0.45, Color.RGB(178,223,138)),
28+
(0.65, Color.RGB(51,160,44)),
29+
(0.85, Color.RGB(251,154,153)),
30+
(1, Color.RGB(227,26,28))
31+
))
32+
)
33+
)
34+
35+
// demo source end
36+
37+
}

project/make-ghpages.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ if [ -e gh-pages ]; then
66
exit 1
77
fi
88

9-
sbt demo/fullOptJS
9+
./sbt demo/fullOptJS
1010
mkdir gh-pages
1111

1212
cp \
13-
demo/target/scala-2.11/plotly-demo-opt.js \
14-
demo/target/scala-2.11/plotly-demo-opt.js.map \
15-
demo/target/scala-2.11/plotly-demo-jsdeps.js \
16-
demo/target/scala-2.11/plotly-demo-jsdeps.min.js \
13+
demo/target/scala-2.13/plotly-demo-opt.js \
14+
demo/target/scala-2.13/plotly-demo-opt.js.map \
15+
demo/target/scala-2.13/plotly-demo-jsdeps.js \
16+
demo/target/scala-2.13/plotly-demo-jsdeps.min.js \
1717
gh-pages
1818

19-
cat demo/target/scala-2.11/classes/index.html | \
19+
cat demo/target/scala-2.13/classes/index.html | \
2020
sed 's@\.\./plotly-demo-jsdeps\[email protected]@' | \
2121
sed 's@\.\./plotly-demo-fastopt\[email protected]@' | \
2222
cat > gh-pages/index.html

render/shared/src/main/scala/plotly/internals/ArgonautCodecsInternals.scala

100644100755
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ object ArgonautCodecsInternals extends ArgonautCodecsExtra {
4646
implicit val boxPointsBoolIsWrapper: IsWrapper[BoxPoints.Bool] = null
4747
implicit val sequenceDoublesIsWrapper: IsWrapper[Sequence.Doubles] = null
4848
implicit val sequenceNestedDoublesIsWrapper: IsWrapper[Sequence.NestedDoubles] = null
49+
implicit val sequenceNestedIntsIsWrapper: IsWrapper[Sequence.NestedInts] = null
4950
implicit val sequenceStringsIsWrapper: IsWrapper[Sequence.Strings] = null
5051
implicit val sequenceDatetimesIsWrapper: IsWrapper[Sequence.DateTimes] = null
5152
implicit val doubleElementIsWrapper: IsWrapper[Element.DoubleElement] = null
@@ -377,6 +378,30 @@ object ArgonautCodecsInternals extends ArgonautCodecsExtra {
377378
}
378379
}
379380

381+
implicit val encodeNamedColorScale: EncodeJson[ColorScale.NamedScale] =
382+
EncodeJson.of[String].contramap(_.name)
383+
384+
implicit val decodeNamedColorScale: DecodeJson[ColorScale.NamedScale] =
385+
DecodeJson { c =>
386+
c.as[String].flatMap { s =>
387+
// TODO: Add colorscale name enum?
388+
DecodeResult.ok(ColorScale.NamedScale(s))
389+
}
390+
}
391+
392+
implicit val encodeCustomColorScale: EncodeJson[ColorScale.CustomScale] =
393+
EncodeJson.of[Json].contramap(_.values.toList.asJson)
394+
395+
implicit val decodeCustomColorScale: DecodeJson[ColorScale.CustomScale] =
396+
DecodeJson { c =>
397+
c.as[Seq[(Double, Color)]].flatMap { s =>
398+
DecodeResult.ok(ColorScale.CustomScale(s))
399+
}
400+
}
401+
402+
implicit val colorscaleJsonCodec: JsonSumCodecFor[ColorScale] =
403+
JsonSumCodecFor(jsonSumDirectCodecFor("colorscale"))
404+
380405
implicit val elementJsonCodec: JsonSumCodecFor[Element] =
381406
JsonSumCodecFor(jsonSumDirectCodecFor("element"))
382407

tests/src/test/scala/plotly/doc/DocumentationTests.scala

100644100755
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ object DocumentationTests {
109109
// stub...
110110
def getElementById(id: String): String = id
111111
}
112-
112+
113113
private object Numeric {
114114
def linspace(from: Int, to: Int, count: Int) = {
115115
val step = (to - from).toDouble / (count - 1)
@@ -243,6 +243,7 @@ class DocumentationTests extends FlatSpec with Matchers {
243243
"statistical/box",
244244
// TODO 2D Density plots
245245
"statistical/histogram",
246+
"scientific/heatmap",
246247
// TODO 2D Histograms
247248
// TODO Wind rose charts
248249
// TODO Contour plots

0 commit comments

Comments
 (0)