Skip to content

Commit 9649e37

Browse files
Added radar chart
1 parent e8d7037 commit 9649e37

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

src/main/resources/main.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,29 @@
8282
-webkit-border-radius: 5px;
8383
border-radius: 5px;
8484
background: #eeeeee;
85+
}
86+
87+
/*Radar*/
88+
89+
#radar path {
90+
opacity: 0.8;
91+
}
92+
93+
#radar text {
94+
font-size: 10px;
95+
}
96+
97+
#radar .pokemon-info {
98+
float: right;
99+
min-width: 140px;
100+
padding: 10px;
101+
border: 1px solid #cccccc;
102+
-moz-border-radius: 5px;
103+
-webkit-border-radius: 5px;
104+
border-radius: 5px;
105+
background: #eeeeee;
106+
}
107+
108+
#radar .label {
109+
float: right;
85110
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package demo.components
2+
3+
import scala.scalajs.js
4+
import japgolly.scalajs.react._
5+
import japgolly.scalajs.react.vdom.all.{ key, div, h4, p, span, select, option, value, onChange, className }
6+
import japgolly.scalajs.react.vdom.svg.all._
7+
import paths.high.Radar
8+
9+
import demo.colors._
10+
import demo.animate._
11+
12+
import org.scalajs.dom.{ document, window }
13+
14+
object radar {
15+
case class Pokemon(
16+
name: String,
17+
hp: Double,
18+
attack: Double,
19+
defense: Double,
20+
spAttack: Double,
21+
spDefense: Double,
22+
speed: Double
23+
)
24+
trait State
25+
case class Still(index: Int) extends State
26+
case class Moving(from: Int, to: Int, step: Double) extends State
27+
implicit object InterpolableState extends Interpolable[State] {
28+
def mix(a: State, b: State, t: Double) = if (t == 1) b else {
29+
val Still(i) = a
30+
val Still(j) = b
31+
Moving(i, j, t)
32+
}
33+
}
34+
35+
class Backend($: BackendScope[List[Pokemon], State]) {
36+
def onSelect(e: ReactEventI) = $.animateState(Still(e.target.value.toInt))
37+
}
38+
39+
private def interpolate(p1: Pokemon, p2: Pokemon, t: Double) = Pokemon(
40+
name = p2.name,
41+
hp = p1.hp + t * (p2.hp - p1.hp),
42+
attack = p1.attack + t * (p2.attack - p1.attack),
43+
defense = p1.defense + t * (p2.defense - p1.defense),
44+
spAttack = p1.spAttack + t * (p2.spAttack - p1.spAttack),
45+
spDefense = p1.spDefense + t * (p2.spDefense - p1.spDefense),
46+
speed = p1.speed + t * (p2.speed - p1.speed)
47+
)
48+
private val palette = mix(Color(130, 140, 210), Color(180, 205, 150))
49+
50+
val RadarChart = ReactComponentB[List[Pokemon]]("Radar chart")
51+
.initialState(Still(0): State)
52+
.backend(new Backend(_))
53+
.render((pokemons, state, backend) => {
54+
val pokemon = state match {
55+
case Still(i) => pokemons(i)
56+
case Moving(i, j, t) => interpolate(pokemons(i), pokemons(j), t)
57+
}
58+
59+
val radar = Radar[Pokemon](
60+
data = List(pokemon),
61+
accessor = Map(
62+
"hp" -> (_.hp),
63+
"attack" -> (_.attack),
64+
"defense" -> (_.defense),
65+
"speed" -> (_.speed),
66+
"sp_attack" -> (_.spAttack),
67+
"sp_defense" -> (_.spDefense)
68+
),
69+
max = Some(100),
70+
center = (0, 0),
71+
r = 130
72+
)
73+
val polygons = radar.curves map { curve =>
74+
path(d := curve.polygon.path.print, fill := string(palette(curve.index)))
75+
}
76+
val rings = radar.rings map { ring =>
77+
path(d := ring.path.print, fill := "none", stroke := "gray")
78+
}
79+
val choices = pokemons.zipWithIndex map { case (p, i) =>
80+
option(value := i, p.name)
81+
}
82+
83+
div(
84+
select(onChange ==> backend.onSelect _, choices),
85+
div(className := "pokemon-info",
86+
h4(pokemon.name),
87+
p("HP: ", span(className := "label label-info", pokemon.hp.toInt)),
88+
p("Attack: ", span(className := "label label-info", pokemon.attack.toInt)),
89+
p("Defense: ", span(className := "label label-info", pokemon.defense.toInt)),
90+
p("Speed: ", span(className := "label label-info", pokemon.speed.toInt)),
91+
p("Sp. Attack: ", span(className := "label label-info", pokemon.spAttack.toInt)),
92+
p("Sp. Defense: ", span(className := "label label-info", pokemon.spDefense.toInt))
93+
),
94+
svg(width := 375, height := 400,
95+
g(transform := "translate(200, 200)", rings, polygons)
96+
)
97+
)
98+
})
99+
.build
100+
}

src/main/scala/demo/components/toplevel.scala

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import japgolly.scalajs.react.vdom.all._
66
import panel._
77
import logo._
88
import piechart._
9+
import radar._
910

1011

1112
object toplevel {
@@ -16,6 +17,16 @@ object toplevel {
1617
Country("Argentina", 40117096),
1718
Country("Japan", 127290000)
1819
)
20+
val pokemons = List(
21+
Pokemon("Bulbasaur", 45, 49, 49, 65, 65, 45),
22+
Pokemon("Ivysaur", 60, 62, 63, 80, 80, 60),
23+
Pokemon("Venusaur", 80, 82, 83, 100, 100, 80),
24+
Pokemon("Kakuna", 45, 25, 50, 25, 25, 35),
25+
Pokemon("Chameleon", 58, 64, 58, 80, 65, 80),
26+
Pokemon("Squirtle", 40, 48, 65, 50, 64, 43),
27+
Pokemon("Blastoise", 79, 83, 100, 85, 105, 78),
28+
Pokemon("Butterfree", 60, 45, 50, 90, 80, 70)
29+
)
1930

2031
val TopLevel = ReactComponentB[Unit]("Top level component")
2132
.render(_ =>
@@ -26,7 +37,12 @@ object toplevel {
2637
id = Some("pie"),
2738
title = "Pie Chart",
2839
text = "Here is a pie chart example"
29-
), PieChart(countries))
40+
), PieChart(countries)),
41+
Panel(PanelContent(
42+
id = Some("radar"),
43+
title = "Radar Chart",
44+
text = "Here is a radar chart showing Pokémon stats. Try changing Pokémon."
45+
), RadarChart(pokemons))
3046
)
3147
)
3248
)

0 commit comments

Comments
 (0)