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
+ }
0 commit comments