Published
Edited
Sep 1, 2020
Importers
Insert cell
Insert cell
Insert cell
Insert cell
html`<div
id="blankpage"
style="
height:500px;
width:${width};"
></div>`
Insert cell
planetdata=d3.csv('https://exoplanetarchive.ipac.caltech.edu/cgi-bin/nstedAPI/nph-nstedAPI?table=exoplanets&select=pl_bmassj,pl_radj,pl_bmassn,pl_bmassjerr1,pl_bmassjerr2,pl_eqt&order=dec')
Insert cell
exoplanets={
var masses=[],radii=[],teqs=[]
for (var i=0;i<planetdata.length;i++){
if ((planetdata[i].pl_bmassjerr1>0) & (planetdata[i].pl_bmassjerr2<0) & (planetdata[i].pl_eqt>0)){
masses.push(parseFloat(planetdata[i].pl_bmassj))
radii.push(parseFloat(planetdata[i].pl_radj))
teqs.push(0.1*parseFloat(planetdata[i].pl_eqt))
}
}
return [masses,radii,teqs]
}
Insert cell
aplot={
var plot = new newplot(480,480,20,20,'aplot') // a plot 300x300 pixels, offset (50,50) from top left corner, called 'aplot'
// set x and y ranges
plot.update_xlim(1e-4,1e2,'log')
plot.update_ylim(3e-2,3e0,'log')
plot.clear() // clear current canvas
plot.axes('x','y') // draw x and y axes
var cols=[]
var areas=[]
for (var i=0; i<exoplanets[2].length; i++){
areas.push(40*exoplanets[1][i])
cols.push(d3.interpolateSpectral(exoplanets[2][i]/200))
}
const ss1={'alpha':0.8, 'col':cols, 'area':50}
plot.scatter(exoplanets[0],exoplanets[1],ss1)
var crange=[0,200]
var cBar=cbar(480,20,20,0,d3.interpolateSpectral,crange,'T_eq')
return plot
}
Insert cell
function newplot(w,h,left,top,name,parent='#blankpage'){
this.left=left // position on page relative to top left (pixels)
this.top=top
this.w=w // size of plot (pixels)
this.h=h
this.xlim=[0,1] // extent of data coords
this.xscale='linear'
this.xlabel=''
this.ylim=[0,1]
this.yscale='linear'
this.ylabel=''
this.xax = d3.scaleLinear().domain([0,1]).range([0,this.w])
this.yax = d3.scaleLinear().domain([0,1]).range([this.h,0])
var canvas=d3.select('#'+name)
if (canvas.empty()){
var div=d3.select('#blankpage')
canvas=div.append('canvas')
}
canvas.attr('width',this.w).attr('height',this.h).attr('id',name)
.style('left',this.left+'px').style('top',this.top+'px').style('position','absolute')
var context = canvas.node().getContext('2d');
this.clear=function(alpha=1){ // currently drawing white (optionally transulucent) square over plot each time
this.context.fillStyle = 'rgba(255, 255, 255, '+alpha+')';
this.context.fillRect(0, 0, this.w, this.h);
this.context.fillStyle = 'rgba(0, 0, 0, 1)';
}
this.update_xlim=function(x0,x1,scale){
this.xlim=[x0,x1]
this.xscale=scale
if (scale=='linear'){ this.xax=d3.scaleLinear().domain([x0,x1]).range([0,this.w]) }
else if (scale=='log'){ this.xax=d3.scaleLog().domain([x0,x1]).range([0,this.w]) }
}
this.update_ylim=function(y0,y1,scale){
this.ylim=[y0,y1]
this.yscale=scale
if (scale=='linear'){ this.yax=d3.scaleLinear().domain([y0,y1]).range([this.h,0]) }
else if (scale=='log'){ this.yax=d3.scaleLog().domain([y0,y1]).range([this.h,0]) }
}
this.canvas=canvas
this.context=context
this.line=lineplot
this.scatter=scatterplot
this.shape=shapeplot
this.axes=draw_axes
}
Insert cell
cbar=function(w,h,left,top,cMap,range,name,scale='linear'){
var plot = new newplot(w,h,left,top,name+'cbar')
plot.update_xlim(0,256,'linear')
plot.update_ylim(0,1,'linear') // hack to not plot any ticks on y-axis
var ints=Array(256).fill().map((x,i)=>i)
var ctx=plot.context
var xax=plot.xax, yax= plot.yax
ctx.save()
for (var i=0;i<256;i++){
ctx.fillStyle=cMap((i+0.5)/256)
ctx.strokeStyle=cMap((i+0.5)/256)
ctx.fillRect(xax(i),yax(0),xax(1),yax(1)-yax(0))
ctx.strokeRect(xax(i),yax(0),xax(1),yax(1)-yax(0))
}
ctx.restore()
plot.update_xlim(range[0],range[1],scale)
plot.axes(name,'',[3,0])
return plot
}
Insert cell
lineplot=function(xs,ys,linestyle={c:'black'}){
// plot options
var ctx = this.canvas.node().getContext('2d')
ctx.save();
if(linestyle.hasOwnProperty('c')) { ctx.fillStyle = linestyle.c }
if(linestyle.hasOwnProperty('alpha')) { ctx.globalAlpha = linestyle.alpha }
if(linestyle.hasOwnProperty('lw')) { ctx.lineWidth = linestyle.lw }
// drawing line
const xax=this.xax, yax=this.yax
const nPoints=xs.length
ctx.beginPath()
ctx.moveTo(xax(xs[0]),yax(ys[0]))
for (var i=1;i<nPoints;i++){
ctx.lineTo(xax(xs[i]),yax(ys[i]))
}
ctx.stroke()
// resetting plot options
ctx.restore();
}
Insert cell
scatterplot=function(xs,ys,scatterstyle={shape:'circle', area:100, col:'cadetblue', edgecol:'black', alpha:1, edgealpha:1}){
var ctx = this.canvas.node().getContext('2d')
ctx.save();
const nPoints=xs.length
// for each property of the scatter creates an array for each element
var shapes=[], areas=[], cols=[], edgecols=[], alphas=[], edgealphas=[]
// shape (defaults to square)
if (scatterstyle.hasOwnProperty('shape')){
if (Array.isArray(scatterstyle.shape)) { shapes=scatterstyle.shape }
else { shapes=Array(nPoints).fill(scatterstyle.shape) }
} else { shapes=Array(nPoints).fill('circle') }
// area (defaults to 100 pixels)
if (scatterstyle.hasOwnProperty('area')){
if (Array.isArray(scatterstyle.area)) { areas=scatterstyle.area }
else { areas=Array(nPoints).fill(scatterstyle.area) }
} else { areas=Array(nPoints).fill(100) }
// fill colour (defaults to cadetblue)
if (scatterstyle.hasOwnProperty('col')){
if (Array.isArray(scatterstyle.col)) { cols=scatterstyle.col }
else { cols=Array(nPoints).fill(scatterstyle.col) }
} else { cols=Array(nPoints).fill('cadetblue') }
// edge colour (defaults to black)
if (scatterstyle.hasOwnProperty('edgecol')){
if (Array.isArray(scatterstyle.edgecol)) { edgecols=scatterstyle.edgecol }
else { edgecols=Array(nPoints).fill(scatterstyle.edgecol) }
} else { edgecols=Array(nPoints).fill(100) }
// alpha (defaults to 1)
if (scatterstyle.hasOwnProperty('alpha')){
if (Array.isArray(scatterstyle.alpha)) { alphas=scatterstyle.alpha }
else { alphas=Array(nPoints).fill(scatterstyle.alpha) }
} else { alphas=Array(nPoints).fill(1) }
// edgealpha (defaults to 1)
if (scatterstyle.hasOwnProperty('edgealpha')){
if (Array.isArray(scatterstyle.edgealpha)) { edgealphas=scatterstyle.edgealpha }
else { edgealphas=Array(nPoints).fill(scatterstyle.edgealpha) }
} else { edgealphas=Array(nPoints).fill(1) }
const xax=this.xax, yax=this.yax
for (var i=0;i<nPoints;i++){
ctx.fillStyle=cols[i]
ctx.strokeStyle=edgecols[i]
ctx.globalAlpha=alphas[i]
if (shapes[i]=='circle'){
ctx.beginPath();
ctx.arc(xax(xs[i]), yax(ys[i]), Math.sqrt(areas[i]/Math.PI), 0, 2 * Math.PI);
ctx.fill();
ctx.globalAlpha=edgealphas[i];
ctx.stroke();
}
if (shapes[i]=='square'){
ctx.fillRect(xax(xs[i])-0.5*Math.sqrt(areas[i]), yax(ys[i])-0.5*Math.sqrt(areas[i]), Math.sqrt(areas[i]), Math.sqrt(areas[i]));
ctx.globalAlpha=edgealphas[i]
ctx.strokeRect(xax(xs[i])-0.5*Math.sqrt(areas[i]), yax(ys[i])-0.5*Math.sqrt(areas[i]), Math.sqrt(areas[i]), Math.sqrt(areas[i]));
}
if (Array.isArray(shapes[i])){ // in theory can define an arbitrary shape as a list of x,y positions - untested
ctx.beginPath();
for (var j; j<shapes[i].size; j++){
ctx.moveTo(shapes[i][j][0],shapes[i][j][1]);
ctx.fill();
ctx.globalAlpha=edgealphas[i];
ctx.stroke();
}
}
}
ctx.restore();
}
Insert cell
shapeplot=function(xs,ys,shapestyle={col:'cadetblue', alpha:1}){
// plot options
var ctx = this.canvas.node().getContext('2d')
ctx.save();
if(shapestyle.hasOwnProperty('col')) { ctx.fillStyle = shapestyle.col }
if(shapestyle.hasOwnProperty('alpha')) { ctx.globalAlpha = shapestyle.alpha }
// drawing shape
const xax=this.xax, yax=this.yax
const nPoints=xs.length
ctx.beginPath()
ctx.moveTo(xax(xs[0]),yax(ys[0]))
for (var i=1;i<nPoints;i++){
ctx.lineTo(xax(xs[i]),yax(ys[i]))
}
ctx.fill()
// resetting plot options
ctx.restore();
}
Insert cell
draw_axes=function(xlabel,ylabel,precision=[2,2]){
// n.b. linear axes should work fine - log axes are more hopeful...
var ctx = this.canvas.node().getContext('2d')
ctx.font = '8px sans-serif';
ctx.strokeStyle = 'black'
var dx,dy,x0,y0
x0=this.xlim[0]
y0=this.ylim[0]
if (this.xscale=='linear'){dx=this.xlim[1]-this.xlim[0]}
if (this.yscale=='linear'){dy=this.ylim[1]-this.ylim[0]}
var DeltaX,DeltaY
var xsigma=Math.floor(dx*Math.pow(10,1-Math.floor(Math.log10(dx)))) // should be first 2 sig. figs of dx
var ysigma=Math.floor(dy*Math.pow(10,1-Math.floor(Math.log10(dy))))
if (xsigma>50){DeltaX=10*Math.pow(10,-1+Math.floor(Math.log10(dx)))}
else if (xsigma>30){DeltaX=5*Math.pow(10,-1+Math.floor(Math.log10(dx)))}
else{DeltaX=2*Math.pow(10,-1+Math.floor(Math.log10(dx)))}
if (ysigma>50){DeltaY=10*Math.pow(10,-1+Math.floor(Math.log10(dy)))}
else if (ysigma>30){DeltaY=5*Math.pow(10,-1+Math.floor(Math.log10(dy)))}
else{DeltaY=2*Math.pow(10,-1+Math.floor(Math.log10(dy)))}
var tick
if (this.xscale=='linear'){
for (var i = 0; i < Math.floor(dx/DeltaX); i++) {
tick=Math.floor(x0/DeltaX)*DeltaX + (i+1)*DeltaX
ctx.beginPath();
ctx.moveTo(this.xax(tick),this.yax(this.ylim[0]));
ctx.lineTo(this.xax(tick),this.yax(this.ylim[0]+dy/100));
ctx.stroke();
ctx.fillText(Number.parseFloat(tick).toPrecision(precision[0]),
this.xax(tick+dx/200),this.yax(this.ylim[0]+dy/100))
}
}
if (this.xscale=='log'){
var smallPow=Math.floor(Math.log10(this.xlim[0]))
var smallVal=(Math.floor(0.99999*x0/(10**smallPow))+1)
var largePow=Math.floor(Math.log10(this.xlim[1]))
var largeVal=(Math.floor(1.00001*this.xlim[1]/(10**largePow))+1)
var lower,upper
var tickHeight
var tickLabel
for (var i=smallPow; i<largePow+1;i++){
if (i==smallVal){lower=smallVal}
else{ lower=1 }
if (i==largeVal){upper=largeVal}
else{ upper=9 }
for (var j=lower;j<upper+1;j++){
ctx.beginPath();
ctx.moveTo(this.xax(j*(10**i)),this.yax(this.ylim[0]));
if (j!=1){
tickHeight=10**(0.995*Math.log10(this.ylim[0])+0.005*Math.log10(this.ylim[1]))
ctx.lineTo(this.xax(j*(10**i)),this.yax(tickHeight))
ctx.stroke()
}
if (j==1){
tickHeight=10**(0.99*Math.log10(this.ylim[0])+0.01*Math.log10(this.ylim[1]))
ctx.lineTo(this.xax(j*(10**i)),this.yax(tickHeight))
ctx.stroke()
ctx.fillText('10^'+i,this.xax(1.05*j*(10**i)),this.yax(tickHeight))
}
}
}
}
if (this.yscale=='linear'){
for (var i = 0; i < Math.floor(dy/DeltaY)+1; i++) {
if (precision[1]>0){
tick=Math.floor(y0/DeltaY)*DeltaY + (i+1)*DeltaY
ctx.beginPath();
ctx.moveTo(this.xax(this.xlim[0]),this.yax(tick));
ctx.lineTo(this.xax(this.xlim[0]+dx/100),this.yax(tick));
ctx.stroke();
ctx.fillText(Number.parseFloat(tick).toPrecision(precision[1]),
this.xax(this.xlim[0]+dx/100),this.yax(tick+dy/200))
}
}
}
if (this.yscale=='log'){
var smallPow=Math.floor(Math.log10(this.ylim[0]))
var smallVal=(Math.floor(0.99999*y0/(10**smallPow))+1)
var largePow=Math.floor(Math.log10(this.ylim[1]))
var largeVal=(Math.floor(1.00001*this.ylim[1]/(10**largePow))+1)
var lower,upper
var tickHeight
var tickLabel
for (var i=smallPow; i<largePow+1;i++){
if (i==smallVal){lower=smallVal}
else{ lower=1 }
if (i==largeVal){upper=largeVal}
else{ upper=9 }
for (var j=lower;j<upper+1;j++){
ctx.beginPath();
ctx.moveTo(this.xax(this.xlim[0]),this.yax(j*(10**i)));
if (j!=1){
tickHeight=10**(0.995*Math.log10(this.xlim[0])+0.005*Math.log10(this.xlim[1]))
ctx.lineTo(this.xax(tickHeight),this.yax(j*(10**i)))
ctx.stroke()
}
if (j==1){
tickHeight=10**(0.99*Math.log10(this.xlim[0])+0.01*Math.log10(this.xlim[1]))
ctx.lineTo(this.xax(tickHeight),this.yax(j*(10**i)))
ctx.stroke()
ctx.fillText('10^'+i,this.xax(tickHeight),this.yax(1.05*j*(10**i)))
}
}
}
}
ctx.textAlign = "end";
ctx.font = 'bold 12px sans-serif';
ctx.fillStyle='black'
ctx.fillText(xlabel,this.xax(this.xlim[1])-8,this.yax(this.ylim[0])-10)
ctx.textAlign = "start";
ctx.fillText(ylabel,this.xax(this.xlim[0])+8,this.yax(this.ylim[1])+10)
ctx.font = '8px sans-serif';
ctx.beginPath();
ctx.moveTo(this.xax(this.xlim[0]),this.yax(this.ylim[0]));
ctx.lineTo(this.xax(this.xlim[1]),this.yax(this.ylim[0]));
ctx.stroke();
ctx.beginPath();
ctx.moveTo(this.xax(this.xlim[0]),this.yax(this.ylim[0]));
ctx.lineTo(this.xax(this.xlim[0]),this.yax(this.ylim[1]));
ctx.stroke();
}
Insert cell
function toArray(list,property,type='float'){ // from a list of objects extracts an array of one particular property
const length=list.length
var array=[]
for (var i=0;i<length;i++){
if (type=='float'){array.push(parseFloat(list[i][property]))}
if (type=='str'){array.push(list[i][property])}
if (type=='int'){array.push(parseInt(list[i][property]))}
}
return array
}
Insert cell
toArray(planetdata,'pl_bmassjerr1')
Insert cell
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