@@ -11,7 +11,7 @@ import {
1111 PointTooltipProps ,
1212 LineSvgProps ,
1313} from "@nivo/line" ;
14- import { COLOUR_SET , Difficulties } from "tachi-common" ;
14+ import { COLOUR_SET , Difficulties , Game } from "tachi-common" ;
1515import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations" ;
1616import ChartTooltip from "./ChartTooltip" ;
1717
@@ -22,21 +22,39 @@ const formatTime = (s: DatumValue) =>
2222 . toString ( )
2323 . padStart ( 2 , "0" ) } `;
2424
25- const scoreToLamp = ( s : number ) => {
26- switch ( s ) {
27- case 970000 :
28- return "S" ;
29- case 990000 :
30- return "SS" ;
31- case 1000000 :
32- return "SSS" ;
33- case 1007500 :
34- return "SSS+" ;
25+ const getScoreYAxisNotch = ( game : Game ) => ( s : number ) => {
26+ if ( game === "ongeki" ) {
27+ switch ( s ) {
28+ case 970_000 :
29+ return "S" ;
30+ case 990_000 :
31+ return "SS" ;
32+ case 1000_000 :
33+ return "SSS" ;
34+ case 1007_500 :
35+ return "SSS+" ;
36+ }
37+ }
38+ if ( game === "chunithm" ) {
39+ switch ( s ) {
40+ case 990_000 :
41+ return "S+" ;
42+ case 1000_000 :
43+ return "SS" ;
44+ case 1005_000 :
45+ return "SS+" ;
46+ case 1007_500 :
47+ return "SSS" ;
48+ case 1009_000 :
49+ return "SSS+" ;
50+ }
3551 }
3652 return "" ;
3753} ;
3854
39- const strokeColor = ( type : Difficulties [ "ongeki:Single" ] | "BELLS" ) => {
55+ const strokeColor = (
56+ type : Difficulties [ "ongeki:Single" ] | Difficulties [ "chunithm:Single" ] | "BELLS"
57+ ) => {
4058 const isLight = getTheme ( ) === "light" ;
4159 switch ( type ) {
4260 case "BASIC" :
@@ -49,21 +67,26 @@ const strokeColor = (type: Difficulties["ongeki:Single"] | "BELLS") => {
4967 return `hsl(280, 60%, ${ isLight ? 35 : 67 } %)` ;
5068 case "BELLS" :
5169 return `hsl(55, 90%, ${ isLight ? 35 : 42 } %)` ;
70+ case "ULTIMA" :
71+ return `hsl(360, 50%, ${ isLight ? 35 : 67 } %)` ;
5272 default :
5373 return `hsl(0, 0%, ${ isLight ? 35 : 67 } %)` ;
5474 }
5575} ;
5676
57- const limitScoreGraph = ( data : Serie [ ] ) => {
77+ const limitScoreGraph = ( game : Game , data : Serie [ ] ) => {
5878 for ( const val of data [ 0 ] . data ) {
59- if ( val . y === null || val . y === undefined ) {
79+ if ( typeof val . y !== "number" ) {
6080 break ;
6181 }
62- if ( val . y < 970000 ) {
82+ if ( game === "ongeki" && val . y < 970_000 ) {
6383 // 969999 will be used to represent values below S
6484 // Without this, the line would cross the bottom axis
6585 // which looks very bad
66- val . y = 969999 ;
86+ val . y = 969_999 ;
87+ }
88+ if ( game === "chunithm" && val . y < 990_000 ) {
89+ val . y = 989_999 ;
6790 }
6891 }
6992 return data ;
@@ -77,7 +100,7 @@ const bellFloor = (data: Datum[], totalBellCount: number) => {
77100 : clamp ( - totalBellCount , Math . floor ( lowestValue * 1.333 ) , - 1 ) ;
78101} ;
79102
80- export default function OngekiScoreChart ( {
103+ export default function GekichuScoreChart ( {
81104 width = "100%" ,
82105 height = "100%" ,
83106 mobileHeight = "100%" ,
@@ -86,28 +109,46 @@ export default function OngekiScoreChart({
86109 difficulty,
87110 totalBellCount,
88111 data,
112+ game,
113+ duration,
89114} : {
90115 mobileHeight ?: number | string ;
91116 mobileWidth ?: number | string ;
92117 width ?: number | string ;
93118 height ?: number | string ;
94119 type : "Score" | "Bells" | "Life" ;
95- difficulty : Difficulties [ "ongeki:Single" ] ;
96- totalBellCount : number ;
120+ difficulty : Difficulties [ "ongeki:Single" ] | Difficulties [ "chunithm:Single" ] ;
121+ totalBellCount ? : number ;
97122 data : Serie [ ] ;
123+ game : Game ;
124+ duration : number ;
98125} & ResponsiveLine [ "props" ] ) {
99- const color =
100- type === "Score"
101- ? GPT_CLIENT_IMPLEMENTATIONS [ "ongeki:Single" ] . difficultyColours [ difficulty ]
102- : type === "Bells"
103- ? COLOUR_SET . vibrantYellow
104- : COLOUR_SET . vibrantGreen ;
126+ let color = COLOUR_SET . gray ;
127+
128+ if ( type === "Score" ) {
129+ if ( game === "chunithm" ) {
130+ color =
131+ GPT_CLIENT_IMPLEMENTATIONS [ "chunithm:Single" ] . difficultyColours [
132+ difficulty as Difficulties [ "chunithm:Single" ]
133+ ] ;
134+ } else if ( game === "ongeki" ) {
135+ color =
136+ GPT_CLIENT_IMPLEMENTATIONS [ "ongeki:Single" ] . difficultyColours [
137+ difficulty as Difficulties [ "ongeki:Single" ]
138+ ] ;
139+ }
140+ } else if ( type === "Bells" ) {
141+ color = COLOUR_SET . vibrantYellow ;
142+ } else {
143+ color = COLOUR_SET . vibrantGreen ;
144+ }
145+
105146 const gradientId = type === "Score" ? difficulty : type ;
106147
107148 const commonProps : Omit < LineSvgProps , "data" > = {
108149 margin : { top : 30 , bottom : 50 , left : 50 , right : 50 } ,
109150 enableGridX : false ,
110- xScale : { type : "linear" , min : 0 , max : data [ 0 ] . data . length - 1 } ,
151+ xScale : { type : "linear" , min : 0 , max : duration } ,
111152 axisBottom : { format : ( d : number ) => formatTime ( d ) } ,
112153 motionConfig : "stiff" ,
113154 crosshairType : "x" ,
@@ -134,43 +175,68 @@ export default function OngekiScoreChart({
134175
135176 let component ;
136177 if ( type === "Score" ) {
137- component = (
138- < ResponsiveLine
139- { ...commonProps }
140- data = { limitScoreGraph ( data ) }
141- yScale = { { type : "linear" , min : 970000 , max : 1010000 } }
142- yFormat = { ">-,.0f" }
143- axisLeft = { {
144- tickValues : [ 970000 , 990000 , 1000000 , 1007500 , 1010000 ] ,
145- format : scoreToLamp ,
146- } }
147- gridYValues = { [ 970000 , 980000 , 990000 , 1000000 , 1007500 , 1010000 ] }
148- enableGridY = { true }
149- colors = { strokeColor ( difficulty ) }
150- areaBaselineValue = { 970000 }
151- tooltip = { ( d : PointTooltipProps ) => (
152- < ChartTooltip >
153- { d . point . data . y === 969999 ? "< 970,000 " : d . point . data . yFormatted } @{ " " }
154- { formatTime ( d . point . data . x ) }
155- </ ChartTooltip >
156- ) }
157- />
158- ) ;
178+ if ( game === "ongeki" ) {
179+ component = (
180+ < ResponsiveLine
181+ { ...commonProps }
182+ data = { limitScoreGraph ( game , data ) }
183+ yScale = { { type : "linear" , min : 970000 , max : 1010000 } }
184+ yFormat = { ">-,.0f" }
185+ axisLeft = { {
186+ tickValues : [ 970000 , 990000 , 1000000 , 1007500 , 1010000 ] ,
187+ format : getScoreYAxisNotch ( game ) ,
188+ } }
189+ gridYValues = { [ 970000 , 980000 , 990000 , 1000000 , 1007500 , 1010000 ] }
190+ enableGridY = { true }
191+ colors = { strokeColor ( difficulty ) }
192+ areaBaselineValue = { 970000 }
193+ tooltip = { ( d : PointTooltipProps ) => (
194+ < ChartTooltip >
195+ { d . point . data . y === 969999 ? "< 970,000 " : d . point . data . yFormatted } @{ " " }
196+ { formatTime ( d . point . data . x ) }
197+ </ ChartTooltip >
198+ ) }
199+ />
200+ ) ;
201+ } else if ( game === "chunithm" ) {
202+ component = (
203+ < ResponsiveLine
204+ { ...commonProps }
205+ data = { limitScoreGraph ( game , data ) }
206+ yScale = { { type : "linear" , min : 990_000 , max : 1010_000 } }
207+ yFormat = { ">-,.0f" }
208+ axisLeft = { {
209+ tickValues : [ 990_000 , 1000_000 , 1005_000 , 1007_500 , 1009_000 , 1010_000 ] ,
210+ format : getScoreYAxisNotch ( game ) ,
211+ } }
212+ gridYValues = { [ 990_000 , 1000_000 , 1005_000 , 1007_500 , 1009_000 , 1010_000 ] }
213+ enableGridY = { true }
214+ colors = { strokeColor ( difficulty ) }
215+ areaBaselineValue = { 990000 }
216+ tooltip = { ( d : PointTooltipProps ) => (
217+ < ChartTooltip >
218+ { d . point . data . y === 989_999 ? "< 990,000 " : d . point . data . yFormatted } @{ " " }
219+ { formatTime ( d . point . data . x ) }
220+ </ ChartTooltip >
221+ ) }
222+ />
223+ ) ;
224+ }
159225 } else if ( type === "Bells" ) {
160226 component = (
161227 < ResponsiveLine
162228 { ...commonProps }
163229 data = { data }
164230 yScale = { {
165231 type : "linear" ,
166- min : bellFloor ( data [ 0 ] . data , totalBellCount ) ,
232+ min : bellFloor ( data [ 0 ] . data , totalBellCount ! ) ,
167233 max : 0 ,
168234 stacked : false ,
169235 } }
170236 enableGridY = { false }
171237 axisLeft = { { format : ( e : number ) => Math . floor ( e ) === e && e } }
172238 colors = { strokeColor ( "BELLS" ) }
173- areaBaselineValue = { bellFloor ( data [ 0 ] . data , totalBellCount ) }
239+ areaBaselineValue = { bellFloor ( data [ 0 ] . data , totalBellCount ! ) }
174240 tooltip = { ( d : PointTooltipProps ) => (
175241 < ChartTooltip >
176242 MAX{ d . point . data . y === 0 ? "" : d . point . data . y } @{ " " }
@@ -179,19 +245,22 @@ export default function OngekiScoreChart({
179245 ) }
180246 />
181247 ) ;
182- } else {
248+ } else if ( type === "Life" ) {
249+ const max = game === "ongeki" ? 100 : ( data [ 0 ] . data [ 0 ] . y as number ) ;
250+ const suffix = game === "ongeki" ? "%" : "" ;
183251 component = (
184252 < ResponsiveLine
185253 { ...commonProps }
186254 data = { data }
187- yScale = { { type : "linear" , min : 0 , max : 100 } }
255+ yScale = { { type : "linear" , min : 0 , max } }
188256 enableGridY = { false }
189- axisLeft = { { format : ( d : number ) => `${ d } % ` } }
257+ axisLeft = { { format : ( d : number ) => `${ d } ${ suffix } ` } }
190258 colors = { strokeColor ( "BASIC" ) }
191259 areaBaselineValue = { 0 }
192260 tooltip = { ( d : PointTooltipProps ) => (
193261 < ChartTooltip >
194- { d . point . data . y } % @ { formatTime ( d . point . data . x ) }
262+ { d . point . data . y }
263+ { suffix } @ { formatTime ( d . point . data . x ) }
195264 </ ChartTooltip >
196265 ) }
197266 />
0 commit comments