Published unlisted
Edited
May 18, 2020
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function make_brackets() {
let div = d3
.create('div')
.style('width', div_width + 'px')
.style('height', div_height + 'px')
.style('position', 'relative');

let svg = div
.append('svg')
.attr('width', div_width)
.attr('height', div_height)
.style('width', div_width + 'px')
.style('height', div_height + 'px')
.style('position', 'absolute');

svg
.selectAll('path.link')
.data(tourney_tree.links())
.join('path')
.attr('class', function(d) {
let label = 'link';
if (
d.target.data.WTeam.TeamID == d.source.data.WTeam.TeamID ||
d.target.data.WTeam.TeamID == d.source.data.LTeam.TeamID
) {
label = label + ' Team' + d.target.data.WTeam.TeamID;
}
return label;
})
.attr('stroke', 'black')
.attr('stroke-width', 1)
.attr('fill', 'none')
.attr('d', connector);

div
.selectAll('div.game')
.data(tourney_tree.descendants())
.enter()
.append(d => game_container(d));

div
.selectAll('.team')
.on('mouseenter', function() {
let class_label = d3
.select(this)
.attr('class')
.split(' ')[2];
div
.selectAll('.team.' + class_label)
.style('background-color', '#99ee99');
div.selectAll('.game.' + class_label).style('border', 'solid 3px black');
svg.selectAll('.link.' + class_label).attr('stroke-width', 3);
})
.on('mouseleave', function() {
let class_label = d3
.select(this)
.attr('class')
.split(' ')[2];
div.selectAll('.team.' + class_label).style('background-color', null);
div.selectAll('.game.' + class_label).style('border', 'solid 1px black');
svg.selectAll('.link.' + class_label).attr('stroke-width', 1);
});

return div.node();
}
Insert cell
function connector(link) {
if (link.target.data.round >= 2) {
let x1 = link.source.x2;
let y1 = link.source.y2;
let x2 = link.target.x2;
let y2 = link.target.y2;
let xx1, yy1, xx2, yy2;

let path;
let [seed1, seed2] = link.target.data.expected_seeds;
if (seed1 < seed2) {
// upper
if (link.target.data.region == 'W' || link.target.data.region == 'Y') {
xx1 = x1 + team_width / 2;
yy1 = y1;
xx2 = x2 + team_width;
yy2 = y2 + game_height / 2;
path = [[xx1, yy1], [xx1, yy2], [xx2, yy2]];
return d3.line()(path);
} else if (
link.target.data.region == 'X' ||
link.target.data.region == 'Z'
) {
xx1 = x1 + team_width / 2;
yy1 = y1;
xx2 = x2;
yy2 = y2 + game_height / 2;
path = [[xx1, yy1], [xx1, yy2], [xx2, yy2]];
return d3.line()(path);
}
} else {
if (link.target.data.region == 'W' || link.target.data.region == 'Y') {
xx1 = x1 + team_width / 2;
yy1 = y1 + game_height;
xx2 = x2 + team_width;
yy2 = y2 + game_height / 2;
path = [[xx1, yy1], [xx1, yy2], [xx2, yy2]];
return d3.line()(path);
} else if (
link.target.data.region == 'X' ||
link.target.data.region == 'Z'
) {
xx1 = x1 + team_width / 2;
yy1 = y1 + game_height;
xx2 = x2;
yy2 = y2 + game_height / 2;
path = [[xx1, yy1], [xx1, yy2], [xx2, yy2]];
return d3.line()(path);
}
}
} else {
return null;
}
}
Insert cell
function horizontal_rescaler(d) {
let region = d.data.region;
let scaler;
let s = 3;
if (region == 'X' || region == 'WX') {
if (d.data.round == 2) {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([
0.5 * div_width - 1.5 * team_width - s,
div_width - team_width - s
]);
} else {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * div_width - 1.5 * team_width, div_width - team_width]);
}
} else if (region == 'W') {
if (d.data.round == 2) {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * (div_width + team_width) + s, s]);
} else {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * (div_width + team_width), 0]);
}
} else if (region == 'Y' || region == 'YZ') {
if (d.data.round == 2) {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * (div_width + team_width) + s, s]);
} else {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * (div_width + team_width), 0]);
}
} else if (region == 'Z') {
if (d.data.round == 2) {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([
0.5 * div_width - 1.5 * team_width - s,
div_width - team_width - s
]);
} else {
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * div_width - 1.5 * team_width, div_width - team_width]);
}
} else if (region == 'CH')
scaler = d3
.scaleLinear()
.domain([0, div_width])
.range([0.5 * (div_width - team_width), 0]);

return scaler(d.y);
}
Insert cell
function vertical_rescaler(d) {
let region = d.data.region;

if (region == 'W') {
let scaler = d3
.scaleLinear()
.domain([-0.25 * div_height, 0.25 * div_height])
.range([div_height - game_height / 2, -game_height / 2]);
return scaler(d.x);
} else if (region == 'X') {
let scaler = d3
.scaleLinear()
.domain([0, 0.5 * div_height])
.range([div_height - game_height / 2, -game_height / 2]);
return scaler(d.x);
} else if (region == 'Y') {
let scaler = d3
.scaleLinear()
.domain([div_height * 0.5, div_height])
.range([div_height - game_height / 2, -game_height / 2]);
return scaler(d.x);
} else if (region == 'Z') {
let scaler = d3
.scaleLinear()
.domain([div_height * 0.75, 1.25 * div_height])
.range([div_height - game_height / 2, -game_height / 2]);
return scaler(d.x);
} else if (region == 'WX') {
return 0.4 * div_height;
} else if (region == 'YZ') {
return 0.6 * div_height;
} else if (region == 'CH') {
return 0.5 * div_height;
}
}
Insert cell
function game_container(game) {
let top_team, bot_team;
if (game.data.WTeam.pos == 'top') {
top_team = game.data.WTeam;
bot_team = game.data.LTeam;
} else {
top_team = game.data.LTeam;
bot_team = game.data.WTeam;
}

let div = d3
.create('div')
.attr('class', function(d) {
let label = 'game ';
label = label + 'Team' + top_team.TeamID + ' ';
label = label + 'Team' + bot_team.TeamID;
return label;
})
.style('width', team_width + 'px')
.style('height', game_height + 'px')
.style('left', game.x2 + 'px')
.style('top', game.y2 + 'px')
.style('border', 'solid 0.5px black')
.style('position', 'absolute');

if (game.data.WTeam.seed - game.data.LTeam.seed > 4) {
div.append(() => team_container(top_team, game.data.round, 'upset'));
div.append(() => team_container(bot_team, game.data.round, 'upset'));
} else {
div.append(() => team_container(top_team, game.data.round, 'top_team'));
div.append(() => team_container(bot_team, game.data.round, 'bot_team'));
}

return div.node();
}
Insert cell
function team_container(team, round, tb) {
let div = d3.create('div').attr('class', function(d) {
let label = 'team ' + tb + ' ';
label = label + 'Team' + team.TeamID;
return label;
});
if (round == 1) {
let seed_span = div.append('span').text(team.seed + ' ');
if (team.playin) {
seed_span.style('color', 'blue');
let title = 'Play-in info <br />';
title = title + `${team.TeamName}: ${team.playin.score} <br /> `;
title =
title +
`${team.playin.playin_opponent_name}: ${team.playin.playin_opponent_score}`;
seed_span.attr('title', title);
tippy(seed_span.node());
}
}
div.append('span').text(team.TeamName);
div
.append('span')
.attr('class', 'score')
.text(team.score);

return div.node();
}
Insert cell
tourney_tree = {
let tree = d3.hierarchy(tourney);
tree.sort(function(x, y) {
return (
display_map.get(y.data.expected_seeds[0]) -
display_map.get(x.data.expected_seeds[0])
);
});
d3
.tree()
.separation((a, b) => (a.parent == b.parent ? 1 : 1.1) / a.depth)
.size([div_height, div_width])(tree);

tree.descendants().forEach(function(v) {
v.x2 = horizontal_rescaler(v);
v.y2 = vertical_rescaler(v);
});

return tree;
}
Insert cell
tourney = {
let tourney = {
region: 'CH',
round: 6,
expected_seeds: [1, 1],
children: [
{
region: 'WX',
round: 5,
expected_seeds: [1, 1],
children: [
{
region: 'W',
round: 4,
expected_seeds: [1, 2]
},
{
region: 'X',
round: 4,
expected_seeds: [1, 2]
}
]
},
{
region: 'YZ',
round: 5,
expected_seeds: [1, 1],
children: [
{
region: 'Y',
round: 4,
expected_seeds: [1, 2]
},
{
region: 'Z',
round: 4,
expected_seeds: [1, 2]
}
]
}
]
};

let stack = [
tourney.children[0].children[0],
tourney.children[0].children[1],
tourney.children[1].children[0],
tourney.children[1].children[1]
];
while (stack.length > 0) {
let node = stack.pop();
let high_seed = node.expected_seeds[0];
let new_low_seed = 2 ** (5 - node.round + 1) - high_seed + 1;
let child0 = {
region: node.region,
expected_seeds: [high_seed, new_low_seed],
round: node.round - 1
};
let low_seed = node.expected_seeds[1];
let lower_seed = 2 ** (5 - node.round + 1) - low_seed + 1;
let child1 = {
region: node.region,
expected_seeds: [lower_seed, low_seed],
round: node.round - 1
};
node.children = [child0, child1];
if (node.round > 2) {
stack.push(child0);
stack.push(child1);
} else {
Object.assign(child0, round1_info(child0));
Object.assign(child1, round1_info(child1));
}
}

stack = [tourney];
let cnt = 0;
while (stack.length > 0 && cnt++ < 100) {
let node = stack.pop();
let [child0, child1] = node.children;
let team0, team1;
if (child0.WTeam) {
team0 = Object.assign({}, child0.WTeam);
team1 = Object.assign({}, child1.WTeam);
let scores = get_scores(team0.TeamID, team1.TeamID);
team0.score = scores[0];
team1.score = scores[1];
let wTeam, lTeam;
if (team0.score > team1.score) {
wTeam = team0;
lTeam = team1;
} else {
wTeam = team1;
lTeam = team0;
}
if (display_map.get(wTeam.seed) < display_map.get(lTeam.seed)) {
wTeam.pos = 'top';
lTeam.pos = 'bot';
} else {
wTeam.pos = 'bot';
lTeam.pos = 'top';
}
node.WTeam = wTeam;
node.LTeam = lTeam;
} else {
stack.push(node);
if (node.round > 2) {
stack.push(child0);
stack.push(child1);
}
}
}
return tourney;
}
Insert cell
display_map = {
let display_order = [1, 16, 9, 8, 5, 12, 13, 4, 3, 14, 11, 6, 7, 10, 15, 2];
let display_map = new Map();
display_order.forEach((d, i) => display_map.set(d, i + 1));
return display_map;
}
Insert cell
function get_team(region, expected_seed) {
let slot, teamID, teamName;
let playin = false;
if (expected_seed < 10) {
slot = region + '0' + expected_seed.toString();
} else {
slot = region + expected_seed.toString();
}
let seed = this_years_seeds.map(d => d.Seed).indexOf(slot);
if (seed > -1) {
teamID = this_years_seeds[seed].TeamID;
teamName = team_map.get(teamID);
} else {
let seeda = this_years_seeds.map(d => d.Seed).indexOf(slot + 'a');
let seedb = this_years_seeds.map(d => d.Seed).indexOf(slot + 'b');
if (seeda > -1) {
let playin_opponentID, playin_opponent_name, playin_opponent_score, score;
let teamIDa = this_years_seeds[seeda].TeamID;
let teamNamea = team_map.get(teamIDa);
let teamIDb = this_years_seeds[seedb].TeamID;
let teamNameb = team_map.get(teamIDb);
let scores = get_scores(teamIDa, teamIDb);
if (scores[0] < scores[1]) {
teamName = teamNameb;
teamID = teamIDb;
score = scores[1];
playin_opponentID = teamIDa;
playin_opponent_name = teamNamea;
playin_opponent_score = scores[0];
} else {
teamName = teamNamea;
teamID = teamIDa;
score = scores[0];
playin_opponentID = teamIDb;
playin_opponent_name = teamNameb;
playin_opponent_score = scores[1];
}
playin = {
score: score,
playin_opponentID: playin_opponentID,
playin_opponent_name: playin_opponent_name,
playin_opponent_score: playin_opponent_score
};
}
}

return {
TeamName: teamName,
TeamID: teamID,
seed: expected_seed,
playin: playin
};
}
Insert cell
function round1_info(o) {
let team0 = get_team(o.region, o.expected_seeds[0]);
let team1 = get_team(o.region, o.expected_seeds[1]);
let scores = get_scores(team0.TeamID, team1.TeamID);
team0.score = scores[0];
team1.score = scores[1];

let wTeam, lTeam;
if (team0.score > team1.score) {
wTeam = team0;
lTeam = team1;
} else {
wTeam = team1;
lTeam = team0;
}
if (display_map.get(wTeam.seed) < display_map.get(lTeam.seed)) {
wTeam.pos = 'top';
lTeam.pos = 'bot';
} else {
wTeam.pos = 'bot';
lTeam.pos = 'top';
}
return { WTeam: wTeam, LTeam: lTeam };
}
Insert cell
function get_scores(teamID1, teamID2) {
let result = this_years_mresults.filter(function(o) {
return (
(o.WTeamID == teamID1 && o.LTeamID == teamID2) ||
(o.WTeamID == teamID2 && o.LTeamID == teamID1)
);
});
if (result.length == 1) {
result = result[0];
if (result.WTeamID == teamID1) {
return [result.WScore, result.LScore].map(d => parseInt(d));
} else if (result.LTeamID == teamID1) {
return [result.LScore, result.WScore].map(d => parseInt(d));
}
}
}
Insert cell
this_years_mresults = {
if (men_or_women == "Men") {
return mresults.filter(d => d.Season == year);
} else {
return wresults.filter(d => d.Season == year);
}
}
Insert cell
this_years_seeds = {
if (men_or_women == "Men") {
return mseeds.filter(d => d.Season == year);
} else {
return wseeds.filter(d => d.Season == year);
}
}
Insert cell
wseeds = d3.csvParse(await FileAttachment("WNCAATourneySeeds.csv").text())
Insert cell
wresults = d3.csvParse(
await FileAttachment("WNCAATourneyCompactResults.csv").text()
)
Insert cell
team_map = {
if (men_or_women == "Men") {
return mteam_map;
} else {
return wteam_map;
}
}
Insert cell
wteam_map = {
let wteams = d3.csvParse(await FileAttachment("WTeams.csv").text());
let wteam_map = new Map();
wteams.forEach(function(team) {
wteam_map.set(team.TeamID, team.TeamName);
});
return wteam_map;
}
Insert cell
mteam_map = {
let mteams = d3.csvParse(await FileAttachment("MTeams.csv").text());
let mteam_map = new Map();
mteams.forEach(function(team) {
mteam_map.set(team.TeamID, team.TeamName);
});
return mteam_map;
}
Insert cell
mseeds = d3.csvParse(await FileAttachment("MNCAATourneySeeds.csv").text())
Insert cell
mresults = d3.csvParse(
await FileAttachment("MNCAATourneyCompactResults.csv").text()
)
Insert cell
d3 = require('d3@5')
Insert cell
import { select, radio } from "@jashkenas/inputs"
Insert cell
tippy = require("https://unpkg.com/tippy.js@2.5.4/dist/tippy.all.min.js")
Insert cell
styles = html`
<style>
text {
cursor: default
}
.team {
cursor: default;
width: ${team_width}px;
height: ${game_height / 2}px;
//display: inline-block;
vertical-align: middle;
line-height: ${game_height / 2}px;
// padding: 0px 0;
white-space: nowrap;
overflow: hidden;
font: 12px sans-serif;
vertical-align: middle
}
.score {
display: inline-block;
position: absolute;
right: 0px;
color: white;
background-color: #333333;
height: ${game_height / 2}px;
}
.top_team {
background-color: #efefef
}
.bot_team {
background-color: #dedede
}
.upset {
background-color: #dd9999
}
.game {
//padding: 4px 0;
}
</style>`
Insert cell
// div_height = 0.9 * screen.availHeight
div_height = 0.9 * window.innerHeight
Insert cell
game_height = div_height / 20
Insert cell
div_width = 0.98*width
Insert cell
team_width = div_width / 9
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more