@@ -307,6 +307,7 @@ module.exports = {
307
307
{ label : 'Blocks Above Avg' , value : statcast . blocks_above_average , metric : 'blocks_above_average' , percentile : statcast . percent_rank_blocks_above_average } ,
308
308
{ label : 'CS Above Avg' , value : statcast . cs_above_average , metric : 'cs_above_average' , percentile : statcast . percent_rank_cs_above_average } ,
309
309
{ label : 'Framing' , value : statcast . fielding_run_value_framing , metric : 'fielding_run_value_framing' , percentile : statcast . percent_rank_fielding_run_value_framing } ,
310
+ { label : 'Framing' , value : statcast . fielding_run_value_framing , metric : 'fielding_run_value_framing' , percentile : statcast . percent_rank_fielding_run_value_framing } ,
310
311
{ label : 'Pop Time' , value : statcast . pop_2b , metric : 'pop_2b' , percentile : statcast . percent_rank_pop_2b }
311
312
] ;
312
313
const running = [
@@ -318,9 +319,8 @@ module.exports = {
318
319
buildSavantSection ( value , metricSummaries ) +
319
320
'<h3>Hitting</h3>' +
320
321
buildSavantSection ( hitting , metricSummaries ) +
321
- '<h3>Fielding</h3>' +
322
- buildSavantSection ( fielding , metricSummaries ) +
323
- ( statcast . blocks_above_average !== null ? '<h3>Catching</h3>' + buildSavantSection ( catching , metricSummaries ) : '' ) +
322
+ ( fielding . find ( stat => stat . value !== null ) ? '<h3>Fielding</h3>' + buildSavantSection ( fielding , metricSummaries ) : '' ) +
323
+ ( catching . find ( stat => stat . value !== null ) ? '<h3>Catching</h3>' + buildSavantSection ( catching , metricSummaries ) : '' ) +
324
324
'<h3>Running</h3>' +
325
325
buildSavantSection ( running , metricSummaries ) +
326
326
'</div>' ;
@@ -352,9 +352,9 @@ module.exports = {
352
352
const html = `
353
353
<div id='savant-table'>` +
354
354
'<h3>Value</h3>' +
355
- buildSavantSection ( value , metricSummaries ) +
355
+ buildSavantSection ( value , metricSummaries , true ) +
356
356
'<h3>Pitching</h3>' +
357
- buildSavantSection ( pitching , metricSummaries ) +
357
+ buildSavantSection ( pitching , metricSummaries , true ) +
358
358
'</div>' ;
359
359
360
360
return ( await getScreenshotOfSavantTable ( html ) ) ;
@@ -699,20 +699,26 @@ async function getScreenshotOfSavantTable (savantHTML) {
699
699
#savant-table {
700
700
background-color: #151820;
701
701
color: whitesmoke;
702
- padding: 15px;
703
- font-size: 20px;
702
+ font-size: 25px;
704
703
font-family: 'Segoe UI', sans-serif;
705
- width: 40%;
704
+ width: 65%;
705
+ display: flex;
706
+ padding: 17px 27.5px 17px 10px;
707
+ flex-direction: column;
708
+ align-items: center;
706
709
}
707
710
.savant-stat {
708
711
display: flex;
709
- width: 100 %;
712
+ width: 95 %;
710
713
justify-content: space-between;
711
714
margin: 5px 0;
712
715
align-items: center;
713
716
}
717
+ .savant-stat-pitcher {
718
+ margin: 12px 0;
719
+ }
714
720
h3 {
715
- font-size: 22px ;
721
+ font-size: 30px ;
716
722
font-weight: bold;
717
723
width: 100%;
718
724
text-align: center;
@@ -722,46 +728,48 @@ async function getScreenshotOfSavantTable (savantHTML) {
722
728
margin: 10px 0;
723
729
}
724
730
.percentile {
725
- width: 28px ;
726
- height: 28px ;
731
+ width: 35px ;
732
+ height: 35px ;
727
733
font-size: 0.7em;
728
734
display: flex;
729
735
align-items: center;
730
736
justify-content: center;
731
737
font-weight: bold;
732
- border-radius: 50%
738
+ border-radius: 50%;
739
+ position: absolute;
740
+ top: 50%;
741
+ left: -20px;
742
+ transform: translateY(-50%);
743
+ }
744
+ .percentile-slider-not-qualified {
745
+ background-image: repeating-linear-gradient(
746
+ -45deg,
747
+ transparent,
748
+ transparent 3px,
749
+ rgba(0, 0, 0, 0.95) 3px,
750
+ rgba(0, 0, 0, 0.95) 6px
751
+ );
733
752
}
734
753
.percentile-not-qualified {
735
- background-image: linear-gradient(
736
- -45deg,
737
- rgba(0,0,0,0.95) 10%,
738
- transparent 10%,
739
- transparent 20%,
740
- rgba(0,0,0,0.95) 20%,
741
- rgba(0,0,0,0.95) 30%,
742
- transparent 30%,
743
- transparent 40%,
744
- rgba(0,0,0,0.95) 40%,
745
- rgba(0,0,0,0.95) 50%,
746
- transparent 50%,
747
- transparent 60%,
748
- rgba(0,0,0,0.95) 60%,
749
- rgba(0,0,0,0.95) 70%,
750
- transparent 70%,
751
- transparent 80%,
752
- rgba(0,0,0,0.95) 80%,
753
- rgba(0,0,0,0.95) 90%,
754
- transparent 90%,
755
- transparent 100%
756
- );
757
- background-size: 0.42em;
754
+ display: none;
758
755
}
759
756
.stat-values {
760
757
display: flex;
761
- width: 5em;
758
+ width: 8. 5em;
762
759
justify-content: space-between;
763
760
align-items: center;
764
761
}
762
+ .percentile-slider {
763
+ position: relative;
764
+ width: 150px;
765
+ height: 0.75em;
766
+ background: #80808045;
767
+ }
768
+ .percentile-slider-portion {
769
+ position: absolute;
770
+ width: 100%;
771
+ height: 100%;
772
+ }
765
773
</style>` +
766
774
savantHTML
767
775
) ;
@@ -773,27 +781,38 @@ async function getScreenshotOfSavantTable (savantHTML) {
773
781
return buffer ;
774
782
}
775
783
776
- function buildSavantSection ( statCollection , metricSummaries ) {
784
+ function buildSavantSection ( statCollection , metricSummaries , isPitcher = false ) {
777
785
const scale = chroma . scale ( [ '#325aa1' , '#a8c1c3' , '#c91f26' ] ) ;
786
+ const sliderScale = chroma . scale ( [ '#3661ad' , '#b4cfd1' , '#d8221f' ] ) ;
787
+ statCollection . forEach ( stat => {
788
+ if ( ! stat . percentile ) {
789
+ stat . percentile = calculateRoundedPercentileFromNormalDistribution (
790
+ stat . metric ,
791
+ stat . value ,
792
+ metricSummaries [ stat . metric ] . avg_metric ,
793
+ metricSummaries [ stat . metric ] . stddev_metric ,
794
+ stat . shouldInvert
795
+ ) ;
796
+ stat . isQualified = false ;
797
+ } else {
798
+ stat . isQualified = true ;
799
+ }
800
+ } ) ;
778
801
return statCollection . reduce ( ( acc , value ) => acc + ( value . value !== null
779
802
? `
780
- <div class='savant-stat'>
781
- <div class='label'>${ value . label } </div>
782
- <div class='stat-values'>
783
- <div class='value'>${ value . value } </div>
784
- <div class='percentile ${ value . percentile ? '' : 'percentile-not-qualified' } ' style='background-color: ${ value . percentile
785
- ? scale ( value . percentile / 100 )
786
- : scale ( caculateRoundedPercentileFromNormalDistribution (
787
- value . metric ,
788
- value . value ,
789
- metricSummaries [ value . metric ] . avg_metric ,
790
- metricSummaries [ value . metric ] . stddev_metric ,
791
- value . shouldInvert
792
- ) ) } '>${ value . percentile || ' ' }
793
- </div>
803
+ <div class='savant-stat${ ( isPitcher ? ' savant-stat-pitcher' : '' ) } '>
804
+ <div class='label'>${ value . label } </div>
805
+ <div class='stat-values'>
806
+ <div class='value'>${ value . value } </div>
807
+ <div class='percentile-slider'>
808
+ <div class='percentile-slider-portion ${ value . isQualified ? '' : 'percentile-slider-not-qualified' } '
809
+ style='background-color: ${ sliderScale ( value . percentile / 100 ) } ; width: ${ ( value . percentile / 100 ) * 150 } px'></div>
810
+ <div class='percentile ${ value . isQualified ? '' : 'percentile-not-qualified' } '
811
+ style='background-color: ${ scale ( value . percentile / 100 ) } ; left: ${ - 17.5 + ( value . percentile / 100 ) * 150 } px '>${ value . percentile || ' ' }
794
812
</div>
795
813
</div>
796
- `
814
+ </div>
815
+ </div>`
797
816
: '' ) , '' ) ;
798
817
}
799
818
@@ -842,7 +861,9 @@ async function getScreenshotOfLineScore (tables, inning, half, awayScore, homeSc
842
861
return buffer ;
843
862
}
844
863
845
- function caculateRoundedPercentileFromNormalDistribution ( metric , value , mean , standardDeviation , shouldInvert ) {
864
+ function calculateRoundedPercentileFromNormalDistribution ( metric , value , mean , standardDeviation , shouldInvert ) {
846
865
if ( typeof value === 'string' ) { value = parseFloat ( value ) ; }
847
- return shouldInvert ? ( 1.00 - ztable ( ( value - mean ) / standardDeviation ) ) : ztable ( ( value - mean ) / standardDeviation ) ;
866
+ return Math . round (
867
+ ( shouldInvert ? ( 1.00 - ztable ( ( value - mean ) / standardDeviation ) ) : ztable ( ( value - mean ) / standardDeviation ) ) * 100
868
+ ) ;
848
869
}
0 commit comments