Public
Edited
Jul 7
Insert cell
Insert cell
viewof iptTeamId = Inputs.text({label: "Tournament ID:", value:"think"});
Insert cell
viewof radios = Inputs.radio(selectLigas, { label: "Select one", format: d => d.name, disabled:true})
Insert cell
radios
Insert cell
viewof data = Inputs.button("Fetch Data", {value: null,
reduce: function(){
return fetchData();
}, disabled:true
})
Insert cell
function fetchTeamTourns(teamId){
var url = "https://lichess.org/api/team/"+teamId+"/arena";
//var url = "https://lichess.org/api/tournament/"+tournId+"/results";
return fetch(url).then(response=>response.text()).then(text=>convertNdjson(text)).catch(err=>{console.log(err)});
}
Insert cell
function fetchTeamStandings(tournId){
//var url = "https://lichess.org/api/team/"+teamId+"/arena";
var url = "https://lichess.org/api/tournament/"+tournId+"/teams";
return fetch(url).then(response=>response.json()).catch(err=>{console.log(err)});
}
Insert cell
function fetchTournStandings(tournId){
var url = "https://lichess.org/api/tournament/"+tournId+"/results";
return fetch(url).then(response=>response.text()).then(text=>convertNdjson(text)).catch(err=>{console.log(err)});
}
Insert cell
async function fetchData(){
if(iptTeamId!=""){
//all tournaments listed
const allTeamTourns = await fetchTeamTourns(iptTeamId);
//only arenas from the Blitz Liga
const ligaArenas = allTeamTourns.filter(d=> d.createdBy=="jeffforever" && d.status==30 && d.fullName.indexOf("Lichess Liga")>0);

//team standings and crown spots
const ligaStandings = await getAllLigas(ligaArenas)

//Sorting so it can zip in correct order
ligaArenas.sort((a,b)=>a.id-b.id);
ligaStandings.sort((a,b)=>a.id-b.id);
const zipped = d3.zip(ligaArenas,ligaStandings); //zipped on corresponding indexes
const dataLigas = zipped.map(d=>{return{...d[0], ...d[1]} } ); //merging
//const standings = await fetchTournStandings(inputIds.tourn);
//const team = standings.filter(d=>d.team==inputIds.team);
// const ok = await getAllGames(team, inputIds.tourn);
dataLigas.sort((a,b)=>{a.startsAt-b.startsAt})
return dataLigas;
}
else{return false;}
}
Insert cell
dataArenas[0].teams[0]
Insert cell
async function getAllLigas(ligas){
var teamStandUrl = "https://lichess.org/api/tournament/{id}/teams";
var selection = ligas.slice(0,21); //Can't make more than 20 requests at a time - bottleneck
var urls = selection.map(d=>teamStandUrl.replace("{id}",d.id));
var manyPromises = urls.map(url=>
fetch(url).then(function(response){
if (!response.ok) { // create error object and reject if not a 2xx response code
let err = new Error("HTTP status code: " + response.status);
err.response = response;
err.status = response.status;
throw err;
}
return response.json();
}).catch(function(err){
console.log("Error fetching the Team Standings")
console.log(err);
})
);
var ligasTeamStandings = await Promise.all(manyPromises);
return ligasTeamStandings;
}
Insert cell
allTeamTourns = fetchTeamTourns(iptTeamId)
Insert cell
ligaArenas = allTeamTourns.filter(d=> d.createdBy=="jeffforever" && d.status==30 && d.fullName.indexOf("Lichess Liga")>0)
Insert cell
ligaArenas.slice(0,50)
Insert cell
dataArenas = await getAllLigas(ligaArenas)
Insert cell
Plot.plot({
x:{tickRotate:-20, width:700},
marks: [
Plot.tickX(ligaArenas, {x: "id", fy:d=>"tourns"}),
Plot.tickX(dataArenas, {x: d=>d?d.id:0, fy:d=>"data"})
]
})
Insert cell
new Date( ligaArenas.find(d=>d.id=="5YTGvfh3").startsAt )
Insert cell
dataL = {
ligaArenas.sort((a,b)=>a.id-b.id);
dataArenas.sort((a,b)=>a.id-b.id);
let zipped = d3.zip(ligaArenas, dataArenas);
if( zipped.every(d=>d[0].id==d[1].id) ){
let merged = zipped.map(d=>{return {...d[0], ...d[1]}});
wrangleData(merged)
return merged;
}
else{
//error in zipping the 2 arrays of data correctly
return false;
}
}
Insert cell
dataL[0]
Insert cell
viewof selection = Inputs.table(dataL, {
columns: [
"id", "fullName", "nbPlayers", "liga",
"ligaN", "time.long", "timeControl","startsAt","status"
]
})
Insert cell
selection
Insert cell
function convertNdjson(text){return text.match(/.+/g).map(JSON.parse)}
Insert cell
Insert cell
Insert cell
Insert cell
{ let max = d3.max(dataL, d=>d3.max(d.teams,k=>k.score));
/*

#7625a2
Favorite color

#f37712
*/
let domainLigas = d3.extent(dataL, d=>+d.ligaN);
let colorsLigas = d3.quantize(d3.interpolateHcl("#f3a012" ,"#7625a2"), (domainLigas[1]-domainLigas[0])+1 );
let scaleLigas = d3.scaleOrdinal().domain(d3.range(domainLigas[0],domainLigas[1]+1,1)).range(colorsLigas);

let domainLeaders = d3.extent(dataL, d=>+d.teamBattle.nbLeaders);
//let colorsLeaders = d3.quantize(d3.interpolateHcl("#d874b6" ,"#9a2374"), (domainLeaders[1]-domainLeaders[0])+1 ); //purple
let colorsLeaders = d3.quantize(d3.interpolateHcl("#903c74" ,"#560b3e"), (domainLeaders[1]-domainLeaders[0])+1 );
let scaleLeaders = d3.scaleOrdinal().domain(d3.range(domainLeaders[0],domainLeaders[1]+1,1)).range(colorsLeaders);
//#b27ba0 "#caa0bd" ,"#934d7d"
dataL.sort( (a,b)=>a.startAt-b.startAt );
let order = dataL.map(d=>d.time.short);
let opctRelProm = 0.46;
return Plot.plot({
marginBottom:60,
marginTop:68,
insetBottom:6,
insetLeft:4,
width:1000,
height:640,
x:{tickRotate:0, domain:order, reverse:true, tickSize:0},
fx: {grid: true},
marks: [

//Plot.frame(dataL,{stroke:d=> d.timeControl}),

Plot.tickY(dataL, {x:d=>d.time.short, y:max, dy:-58, stroke:d=>scaleLigas(+d.ligaN), strokeWidth:7}),
//Plot.tickY(dataL, {x:d=>d.time.short, y:max, dy:idy, stroke:d=>scaleLeaders(d.teamBattle.nbLeaders), strokeWidth:10}),
//Text Liga Division number
/*
Plot.barY(dataL, {x:d=>d.time.short, y1:max ,y2:max-26, dy:-54, stroke:d=>scaleLigas(+d.ligaN),
//fill:d=>scaleLeaders(d.teamBattle.nbLeaders),
strokeWidth:2, r:2,
}),
*/
Plot.text(dataL, {x:d=>d.time.short,y:max, dy:-44, text: d=>d.fullName.split(" ").slice(-1), fontSize:17, fontWeight:"bold", fill:d=>scaleLigas(+d.ligaN) }),
//Plot.text(dataL, {x:d=>d.time.short,y:max, dy:-28, text: d=>d.teamBattle.nbLeaders+" players", fontSize:8 }),

//Number Leaders
Plot.text(dataL, {x:d=>d.time.short,y:max, dy:-22, fontSize:11, //fill:"white", strokeWidth:8,fontWeight:"bold",
//stroke:d=>scaleLeaders(d.teamBattle.nbLeaders),
//fontWeight:d=>d.teamBattle.nbLeaders*100,
text: d=>d.teamBattle.nbLeaders+"👤"
//text: d=>""+d.teamBattle.nbLeaders+"", fontWeight:"bold", fontSize:9,
}),

//Time Control background and text
Plot.barY(dataL, {x:d=>d.time.short,y1:0 ,y2:max, fill:d=>d.timeControl, strokeWidth:1, stroke:d=>(d.timeControl==rdHighlight?"gray":"none"), strokeOpacity:1, fillOpacity:d=>(d.timeControl==rdHighlight?0.1:0.08)
//opacity:0.08
}),
//Plot.barY(dataL, {x:d=>d.time.short,y1:0 ,y2:max, fill:d=>d.timeControl, opacity:0.02 }),
Plot.text(dataL, {x:d=>d.time.short,y1:0 ,y:max, dy:-6, fill:d=>d.timeControl, text: d=>d.timeControl}),
//Division Promotion
Plot.barY(dataL, {x:d=>d.time.short, y1:d=>d.teams.find(e=>e.rank==1).score ,y2:d=>d.teams.find(e=>e.rank==3).score, stroke:"black",
strokeWidth:0.4, r:1,
//fx:d=>d.timeControl,
fill:"green", opacity:opctRelProm }),
//Division Staying
Plot.barY(dataL, {x:d=>d.time.short, y1:d=>d.teams.find(e=>e.rank==3).score ,y2:d=>d.teams.find(e=>e.rank==8).score, stroke:"none",
strokeWidth:0.4,
//fx:d=>d.timeControl,
fill:"lightgray",opacity:opctRelProm }),
//Division Relegation
Plot.barY(dataL, {x:d=>d.time.short, y1:d=>d.teams.find(e=>e.rank==8).score ,y2:d=>d.teams.find(e=>e.rank==d.teams.length).score, stroke:"black",
strokeWidth:0.4, r:1,
//fx:d=>d.timeControl,
fill:"red",opacity:opctRelProm }),
//Transparent Overlay for link
Plot.barY(dataL, {x:d=>d.time.short, y1:d=>d.teams.find(e=>e.rank==1).score ,y2:d=>d.teams.find(e=>e.rank==d.teams.length).score,
//fx:d=>d.timeControl,
fill:"transparent", stroke:"none", href:d=>"https://lichess.org/tournament/"+d.id, target:"_blank", }),

Plot.ruleY([200], {strokeDasharray:"3 8", strokeWidth:0.4, opacity:0.6}),
//Tick for the Teams Rank
Plot.tickY(dataL, {x:d=>d.time.short, y:d=>d.teams.find(e=>e.id==iptTeamId).score ,
//fx:d=>d.timeControl,
stroke:"black", strokeWidth:1 }),
//Marker (dot) for the Teams Rank
Plot.link(dataL, {x:d=>d.time.short, y:d=>d.teams.find(e=>e.id==iptTeamId).score, //dx:-18,
marker:"dot",
//fx:d=>d.timeControl,
stroke:"black", strokeWidth:1.2 }),
//Text Team rank - points
Plot.text(dataL, {x:d=>d.time.short, y:d=>d.teams.find(e=>e.id==iptTeamId).score ,
//fx:d=>d.timeControl,
stroke:"black", fill:"black", stroke:"white", dy:9, dx:3, strokeWidth:0, fontSize:10, fontStyle:"italic",
text:d=>{let team = d.teams.find(e=>e.id==iptTeamId);
return team.score+" pts"} }),
//Text Team rank - #X
Plot.text(dataL, {x:d=>d.time.short, y:d=>d.teams.find(e=>e.id==iptTeamId).score ,
//fx:d=>d.timeControl,
stroke:"black", fill:"black", stroke:"white", dy:-8, dx:-12, strokeWidth:2, fontSize:15, fontWeight:"bold", fontStyle:"italic",
text:d=>{let team = d.teams.find(e=>e.id==iptTeamId);
return team.rank+"."} }),
]
})
}
Insert cell
dataL.map(d=>d.teams[9])
Insert cell
d3.extent(dataL, d=>+d.liga.slice(0,-1))
Insert cell
qtzColors = d3.quantize(d3.interpolateViridis,3)
Insert cell
domainLigas = d3.extent(dataL, d=>+d.ligaN)
Insert cell
domainLigas[1]-domainLigas[0]
Insert cell
//dataArenas.map(d=>d.teams.map(e=>{e.idTourn=d.id; return e;}))
Insert cell
Insert cell
function wrangleData(_data){
const formatTime = d3.utcFormat("%b %d, %Y");
//formatTime(new Date()); // "Jan 31, 2023"
_data.forEach( function(d){
d.time = {short: d3.utcFormat("%b %d")(d.startsAt),
long: d3.utcFormat("%b %d, %Y")(d.startsAt),
d: d3.utcFormat("%d")(d.startsAt),
m: d3.utcFormat("%B")(d.startsAt),
y: d3.utcFormat("%Y")(d.startsAt),
}
formatTime( new Date(d.startsAt) );
d.timeControl = Math.round(d.clock.limit/60)+"m"+ ( d.clock.increment==0? "" : "+"+d.clock.increment+"s" );
d.liga = d.fullName.split(" ").slice(-1)[0];
d.ligaN = +d.fullName.split(" ").slice(-1)[0].slice(0,-1);
})
}
Insert cell
function getLigaDivision(d){return +d.fullName.split(" ").slice(-1)[0].slice(0,-1) }
Insert cell
selectLigas = [{name:"latest", funct:function(ligas){ }},
{name:"top", function: function(ligas){d3.max(ligas,e=>getLigaDivision(e) ); return undefined;} },
{name:"selection", function:function(ligas){return ;} }
]
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