Public
Edited
Jan 17, 2023
Insert cell
Insert cell
Insert cell
// ECV = Essential Climate Variable
ecvs=[
{name:"aerosol",group:"atmospheric",links:["soil moisture","ozone","land cover","ocean colour","sea surface temperature","cloud","fire"]},
{name:"cloud",group:"atmospheric",links:["soil moisture","aerosol"]},
{name:"carbon\ndioxide",group:"atmospheric",links:["soil moisture","methane","land cover","fire"]},
{name:"methane",group:"atmospheric",links:["soil moisture","carbon dioxide","fire"]},
{name:"ozone",group:"atmospheric",links:["ocean colour","aerosol","fire"]},
{name:"fire",group:"terrestrial",links:["soil moisture","carbon dioxide","methane","ozone","aerosol","land cover","aerosol"]},
{name:"soil\nmoisture",group:"terrestrial",links:["sea level","carbon dioxide","methane","land cover","sea surface temperature","cloud","aerosol","fire"]},
{name:"land\ncover",group:"terrestrial",links:["soil moisture","carbon dioxide","aerosol","fire"]},
{name:"glaciers",group:"terrestrial",links:["glaciers","sea level","land cover"]},
{name:"ice sheets",group:"terrestrial",links:[]}, // No links!?!
{name:"sea\nsurface\ntemperature",group:"oceanic",links:["soil moisture","sea level","ocean colour","cloud","aerosol","sea ice"]},
{name:"ocean colour",group:"oceanic",links:["sea level","ozone","sea surface temperature","aerosol"]},
{name:"sea level",group:"oceanic",links:["soil moisture","glaciers","ocean colour","sea surface temperature","sea ice"]},
{name:"sea ice",group:"oceanic",links:["sea level","sea surface temperature"]}
]
Insert cell
groupcolours=[{oceanic:'#007D8A',terrestrial:'#FF8F1C',atmospheric:'#009CDE'}][0]
Insert cell
Insert cell
//preload=html`<link rel="preload" as="font" href="${fonturl}" type="font/woff2" crossorigin="anonymous">` // Not sure this is needed.
Insert cell
Insert cell
chords = {
document.head.innerHTML+=styling;
const width=512;
const height=512;
var canvas=DOM.canvas(width, height);
var ctx=canvas.getContext('2d');
let frame;

// Precompute label centres
var ecvxys=[];
for (var i=0;i<ecvs.length;i++) {
const ecv=ecvs[i];
const a=(2.0*Math.PI*(1+i))/(ecvs.length+1);
const dx= 0.4*width*Math.sin(a);
const dy= -0.4*height*Math.cos(a);
const x=width*0.5+dx;
const y=height*0.5+dy;
ecvxys.push(
[x,y]
);
}

// Names with "\n" line breaks replaced by " "
var ecvnames=[];
for (var i=0;i<ecvs.length;i++) {
ecvnames.push(ecvs[i].name.replaceAll("\n"," "));
}

// Lookup from ecvname to its index
var ecvindices={};
for (var i=0;i<ecvs.length;i++) {
ecvindices[ecvnames[i]]=i;
}
// Precompute enumerated links
var ecvlinks=[];
for (var i=0;i<ecvs.length;i++) {
var row=[];
console.log(ecvs[i].links);
for (var j=0;j<ecvs[i].links.length;j++) {
row.push(ecvindices[ecvs[i].links[j]]);
}
ecvlinks.push(row);
}
console.log(ecvlinks);
var nearestecv= -2; // -1 means near centre

var ecvstar=
function tick() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height); // Draw a filled rectangle

//console.log('Tick: nearest is '+(ecvs[nearestecv].name));
for (var i=0;i<ecvs.length;i++) {
const ecv=ecvs[i];

const active=(i==nearestecv);
for (var j=0;j<ecvlinks[i].length;j++) {

const t=ecvlinks[i][j];

const x0=width*0.5+0.8*(ecvxys[i][0]-0.5*width);
const y0=height*0.5+0.8*(ecvxys[i][1]-0.5*height)

const x1=width*0.5+0.8*(ecvxys[t][0]-0.5*width);
const y1=height*0.5+0.8*(ecvxys[t][1]-0.5*height);

if (active || nearestecv==-1) {
var gradient = ctx.createLinearGradient(x0,y0,x1,y1);
gradient.addColorStop(0,groupcolours[ecvs[i].group]);
gradient.addColorStop(1,groupcolours[ecvs[t].group]);
ctx.strokeStyle=gradient;
ctx.lineWidth=2;
} else {
ctx.strokeStyle='#eeeeee44'; // A bit of transparency to make it really faint
ctx.lineWidth=1;
}

ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.quadraticCurveTo(width*0.5,height*0.5,x1,y1);
ctx.stroke();
}

if (active) {
ctx.font='16px notesstyle-boldtfregular';
} else {
ctx.font='14px notesstyle-boldtfregular';
}
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle='#444444'; // Text colour
const lines=ecv.name.split('\n');
if (lines.length==1) {
ctx.fillText(ecv.name,ecvxys[i][0],ecvxys[i][1]);
} else {
const lineHeight=ctx.measureText('M').width*1.25 // Doesn't measure height!
for (var line=0;line<lines.length;line++) {
ctx.fillText(lines[line],ecvxys[i][0],ecvxys[i][1]-lineHeight*(0.5*lines.length-line));
}
}
}

frame = requestAnimationFrame(tick);
}
tick();

function sqr(x) {return x*x;}
canvas.onmousemove = function(e) {
const rect = canvas.getBoundingClientRect();
const ex = e.clientX - rect.left;
const ey = e.clientY - rect.top;
nearestecv= -1;
var nearestd=4.0*Math.sqrt(sqr(ex-0.5*width)+sqr(ey-0.5*height));
for (var i=0;i<ecvs.length;i++) {
const x=ecvxys[i][0];
const y=ecvxys[i][1];
const d=Math.sqrt(sqr(x-ex)+sqr(y-ey));
if (d<nearestd) {
nearestecv=i;
nearestd=d;
}
//console.log('Nearest is '+(ecvs[nearestecv].name));
}
}
invalidation.then(
() => cancelAnimationFrame(frame)
);
return canvas;
}

Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more