Skip to content

Commit 2b1db7a

Browse files
committed
savant sliders
1 parent 14f4745 commit 2b1db7a

File tree

1 file changed

+76
-55
lines changed

1 file changed

+76
-55
lines changed

modules/command-util.js

+76-55
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ module.exports = {
307307
{ label: 'Blocks Above Avg', value: statcast.blocks_above_average, metric: 'blocks_above_average', percentile: statcast.percent_rank_blocks_above_average },
308308
{ label: 'CS Above Avg', value: statcast.cs_above_average, metric: 'cs_above_average', percentile: statcast.percent_rank_cs_above_average },
309309
{ 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 },
310311
{ label: 'Pop Time', value: statcast.pop_2b, metric: 'pop_2b', percentile: statcast.percent_rank_pop_2b }
311312
];
312313
const running = [
@@ -318,9 +319,8 @@ module.exports = {
318319
buildSavantSection(value, metricSummaries) +
319320
'<h3>Hitting</h3>' +
320321
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) : '') +
324324
'<h3>Running</h3>' +
325325
buildSavantSection(running, metricSummaries) +
326326
'</div>';
@@ -352,9 +352,9 @@ module.exports = {
352352
const html = `
353353
<div id='savant-table'>` +
354354
'<h3>Value</h3>' +
355-
buildSavantSection(value, metricSummaries) +
355+
buildSavantSection(value, metricSummaries, true) +
356356
'<h3>Pitching</h3>' +
357-
buildSavantSection(pitching, metricSummaries) +
357+
buildSavantSection(pitching, metricSummaries, true) +
358358
'</div>';
359359

360360
return (await getScreenshotOfSavantTable(html));
@@ -699,20 +699,26 @@ async function getScreenshotOfSavantTable (savantHTML) {
699699
#savant-table {
700700
background-color: #151820;
701701
color: whitesmoke;
702-
padding: 15px;
703-
font-size: 20px;
702+
font-size: 25px;
704703
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;
706709
}
707710
.savant-stat {
708711
display: flex;
709-
width: 100%;
712+
width: 95%;
710713
justify-content: space-between;
711714
margin: 5px 0;
712715
align-items: center;
713716
}
717+
.savant-stat-pitcher {
718+
margin: 12px 0;
719+
}
714720
h3 {
715-
font-size: 22px;
721+
font-size: 30px;
716722
font-weight: bold;
717723
width: 100%;
718724
text-align: center;
@@ -722,46 +728,48 @@ async function getScreenshotOfSavantTable (savantHTML) {
722728
margin: 10px 0;
723729
}
724730
.percentile {
725-
width: 28px;
726-
height: 28px;
731+
width: 35px;
732+
height: 35px;
727733
font-size: 0.7em;
728734
display: flex;
729735
align-items: center;
730736
justify-content: center;
731737
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+
);
733752
}
734753
.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;
758755
}
759756
.stat-values {
760757
display: flex;
761-
width: 5em;
758+
width: 8.5em;
762759
justify-content: space-between;
763760
align-items: center;
764761
}
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+
}
765773
</style>` +
766774
savantHTML
767775
);
@@ -773,27 +781,38 @@ async function getScreenshotOfSavantTable (savantHTML) {
773781
return buffer;
774782
}
775783

776-
function buildSavantSection (statCollection, metricSummaries) {
784+
function buildSavantSection (statCollection, metricSummaries, isPitcher = false) {
777785
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+
});
778801
return statCollection.reduce((acc, value) => acc + (value.value !== null
779802
? `
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 || ' '}
794812
</div>
795813
</div>
796-
`
814+
</div>
815+
</div>`
797816
: ''), '');
798817
}
799818

@@ -842,7 +861,9 @@ async function getScreenshotOfLineScore (tables, inning, half, awayScore, homeSc
842861
return buffer;
843862
}
844863

845-
function caculateRoundedPercentileFromNormalDistribution (metric, value, mean, standardDeviation, shouldInvert) {
864+
function calculateRoundedPercentileFromNormalDistribution (metric, value, mean, standardDeviation, shouldInvert) {
846865
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+
);
848869
}

0 commit comments

Comments
 (0)