Refining UX

This commit is contained in:
Gabi 2025-02-04 16:44:46 -05:00
parent 9795e91740
commit 537226040d

@ -17,8 +17,9 @@ const coerceGameData = (d, i) => ({
const oddsfile = FileAttachment("./data/odds_data.json").json().then((D) => D.map(coerceGameData));
```
```js
const teamNames = FileAttachment("./data/teams.json").json();
const teamNames = FileAttachment("./data/teams.json").json().then((d) => d.sort((a,b)=>a.label.localeCompare(b.label)))
```
```js
function parseWeek(w)
{
@ -32,35 +33,55 @@ function parseWeek(w)
```
```js
const teamSelect = view(Inputs.select(teamNames, {
teamNames.unshift({label:"All",alts:["All"]});
const teamSelect = Inputs.select(teamNames, {
format: (d) => d.label,
valueof: (d) => d.alts,
value: teamNames[0].alts,
label: "Favorite"
}))
})
const teamValue = Generators.input(teamSelect);
```
```js
const highlight = view(Inputs.radio([{"label":"Fav vs. Und", "value":0}, {"label":"Predicted Score","value":1}], {value: 0, format: (x) => x.label}));
const highlight = view(
Inputs.radio(
new Map([
["Favorite vs. Underdog", 0],
["Predicted Score",1]
]),
{
value: 0,
}
)
);
```
```js
function oddsPlot(d, {width} = {}) {
const data = d.filter(function (game) {
const whitelist = teamSelect;
let data = {};
if(teamValue[0] != "All")
{
data = d.filter(function (game) {
const whitelist = teamValue;
return whitelist.indexOf(game.fav) > -1 || whitelist.indexOf(game.und) > -1;
});
}else{
data = d;
}
const marginTop = 20;
const marginRight = 20;
const marginBottom = 20;
const marginLeft = 30;
const yearGroups = d3.group(data, (d) => d.year);
const yearN = Array.from(yearGroups).length;
const yearN = d3.max(data, (d) => d.year)-1952;
const gameCount = longestString(yearGroups)[1].length;
const padding = 1;
const blockWidth =
(width - marginLeft - marginRight) / Array.from(yearGroups).length -
(width - marginLeft - marginRight) / yearN -
padding;
const blockHeight = d3.max([4, 500/gameCount]);
const blockHeight = blockWidth;
const height =
gameCount * (blockHeight + padding) +
marginBottom +
@ -69,7 +90,7 @@ function oddsPlot(d, {width} = {}) {
const x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d.year))
.domain([1952,d3.max(data, (d) => d.year)])
.range([marginLeft, width - marginRight]);
let yearCount = {};
@ -82,19 +103,13 @@ function oddsPlot(d, {width} = {}) {
return marginTop + yearCount[d.year] * (blockHeight + padding);
};
const playoffCount = gameCount - d3.count(longestString(yearGroups)[1], (d) => d.week);
const yAxis = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d.week))
.range([marginTop+blockHeight, (height - marginBottom-blockHeight)-(blockHeight*playoffCount)]);
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; background-color: grey;");
.attr("style", "max-width: 100%; height: auto; background-color: black;");
@ -117,25 +132,59 @@ function oddsPlot(d, {width} = {}) {
.ticks(yearInterval, "^c")
);
svg
.append("g")
.attr("transform", `translate(${marginLeft-blockHeight/2},0)`)
.call(d3.axisLeft(yAxis).ticks(gameCount-playoffCount, "^c"))
svg
.append("g")
.selectAll()
.data(data)
.join("g")
.attr("class","gameBlock")
.attr(
"transform",
(d) => `translate(${x(d.year) - blockWidth / 2},${y(d)})`
)
.html((d) => buildDualSquare(d, blockWidth, blockHeight));
.html((d) => buildDualSquare(d, blockWidth, blockHeight))
.on("mouseover", function(event, d){
infoTextContainer.html("<b>game!</b>")
const destX = x(d.year)-blockWidth/2;
const destY = y(d);
showTooltip(destX, destY);
})
.on("mouseout", function(event, d){
hideToolTip();
});
const infoBox = svg.append("g")
.attr("class", "info-box")
.attr("x", 5)
.attr("y", 5)
.attr("transform", `translate(-1000,-1000)`)
const infoRect = infoBox.append("rect");
const infoTextContainer = infoBox.append("g")
.attr("fill", "white")
.attr("dy", 20)
.attr("width", 120)
function showTooltip(x,y)
{
infoBox.attr("transform", `translate(${x}, ${y})`);
infoBox.transition().delay(100).duration(500).ease(d3.easeBackOut).attr("opacity",1)
}
function hideToolTip()
{
infoBox.transition().duration(500).ease(d3.easeBackOut).attr("opacity",0)
.transition().delay(500).attr("transform", "translate(-1000,-1000)")
}
return svg.node();
}
function longestString(map) {
return Array.from(map).sort(function (a, b) {
return b[1].length - a[1].length;
@ -145,79 +194,90 @@ function longestString(map) {
function buildDualSquare(d, width, height) {
const blockWidth = width;
const blockHeight = height;
const strokeWidth = 2;
let leftColor = "#EEE";
let rightColor = "#EEE";
let blockColor = "#EEE";
let fullStroke = "none";
let winFill = "none";
let beatSpreadFill = "none";
if(highlight.value==0)
if(highlight==0)
{
if(teamSelect.indexOf(d.fav) > -1)
if(d.sF > d.sU)
{
leftColor = "green";
winFill = "rgba(255,255,255,0.75)"
}
if(teamSelect.indexOf(d.und) > -1)
if(d.sF - d.sU > d.spread)
{
rightColor = "orange";
beatSpreadFill = "rgb(0,0,0,0.75)"
}
if(Math.abs(d.sF - d.sU) < d.spread && d.sF < d.sU)
{
beatSpreadFill = "rgb(0,0,0,0.75)"
}
if(teamValue[0]=="All")
{
blockColor = "#F25781";
}else{
if(teamValue.indexOf(d.fav) > -1)
{
blockColor = "#F25781";
}
if(teamValue.indexOf(d.und) > -1)
{
blockColor = "#0277D1";
if(d.sU > d.sF)
{
winFill = "rgba(255,255,255,0.75)"
}
}
if(d.week == undefined)
{
fullStroke = "rgba(255,255,255,0.5)";
}
}
}else{
if (d.pScore.sF) {
const aboveLightness = d3
.scaleLinear()
.domain([
0,
d3.max([
d3.max(oddsfile, (d) => d.pScore.sF - d.sF),
d3.max(oddsfile, (d) => d.pScore.sU - d.sU)
])
])
.range([50, 90]);
const belowLightness = d3
.scaleLinear()
.domain([
d3.min([
d3.min(oddsfile, (d) => d.pScore.sF - d.sF),
d3.min(oddsfile, (d) => d.pScore.sU - d.sU)
]),
0
])
.range([90, 50]);
if (d.pScore.sF == d.sF) {
leftColor = "red";
} else if (d.pScore.sF > d.sF) {
leftColor = `hsl(43,100%,${belowLightness(d.pScore.sF - d.sF)}%)`;
const combinedScore = d.sF + d.sU;
const combinedPredictedScore = d.pScore.sF + d.pScore.sU;
const pDiff = Math.abs((combinedScore - combinedPredictedScore)/combinedScore);
blockColor = d3.interpolateOrRd(pDiff);
} else {
leftColor = `hsl(167,100%,${aboveLightness(d.pScore.sF - d.sF)}%)`;
}
if (d.pScore.sU == d.sU) {
rightColor = "red";
} else if (d.pScore.sU > d.sU) {
rightColor = `hsl(43,100%,${belowLightness(d.pScore.sU - d.sU)}%)`;
} else {
rightColor = `hsl(167,100%,${aboveLightness(d.pScore.sU - d.sU)}%)`;
}
} else {
leftColor = "#EEE";
rightColor = "#EEE";
blockColor = "#555";
}
}
return `
<rect x="0" y="0" height="${height}" width="${
width / 2
}" fill="${leftColor}" />
<rect x="${width / 2}" y="0" height="${height}" width="${
width / 2
}" fill="${rightColor}"/>
width
}" fill="${blockColor}" />
<rect x="${strokeWidth/2}" y="${strokeWidth/2}" height="${height-strokeWidth}" width="${
width-strokeWidth
}" stroke="${fullStroke}" fill="none" stroke-width="${strokeWidth}"/>
<circle cx="${width/2}" cy="${height/2}" r="${width/4}" fill="${winFill}"/>
<circle cx="${width/2}" cy="${height/2}" r="${width/6}" fill="${beatSpreadFill}"/>
`;
}
```
```js
function buildToolTip()
{
}
```
<style>
.gameBlock
{
cursor: pointer;
}
</style>
<div class="grid grid-cols-2">
<div class="card">${teamSelect}</div>
</div>
<div class="grid grid-cols-1">
<div class="card">${resize((width) => oddsPlot(oddsfile, {width}))}</div>
</div>