Public
Edited
Mar 3
Insert cell
Insert cell
svg = {
const svg = d3.select(DOM.svg(width + margin.left + margin.right, height + margin.top + margin.bottom));
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);

// const svgRect = svg.append('rect')
// .attr('x', 0)
// .attr('y', 0)
// .attr('width', width + (margin.left) + margin.right)
// .attr('height', height + (margin.top) + margin.bottom)
// .attr('fill', 'none')
// .attr('stroke', 'lime')
// ;
// const outline = g.append('rect')
// .attr('x', 0)
// .attr('y', 0)
// .attr('width', width )
// .attr('height', height)
// .attr('fill', 'none')
// .attr('stroke', 'hotpink');

// add observed Dst
const dst = g.selectAll('.observed')
.data(realtimeDstData).enter()
.append('rect')
.attr('class', 'observed')
.style('stroke', 'white')
.style('stroke-width', 1.5)
.style('fill', d => {
return d ? dstColor(d.value) : 'silver';
})
.attr('x', (d, i) => i * recWidth)
.attr('y', height)
.attr('width', recWidth)
.attr('height', recHeight)
const dstLables = g.selectAll('.dstLables')
.data(realtimeDstData).enter()
.append('text')
.attr('class', 'observed')
.text( d => {
return d?.value || undefined
})
.attr('x', (d,i) => recWidth/2 + i * recWidth)
.attr('y', height + (recHeight/1.3))
.style('text-anchor', 'middle')
.style('fill', 'black')
.style('stroke', 'none');
const dstTitle = g.append('text')
.text('Observed Dst')
.attr('x', -5)
.attr('y', height + (recHeight/1.3))
.style('text-anchor', 'end')
.style('fill', 'navy')
.style('stroke', 'none');

// const dstLegend;
// const differenceLegend;
const timeAxisTitle = g.append('text')
.text('Time (UTC)')
.attr('x', -5)
.attr('y', height + (recHeight * 1.8))
.style('text-anchor', 'end')
.style('fill', 'black')
.style('stroke', 'none');
// Add time labels
const timeArray = [...reverseDataArray]
const reverseModelRuns = [...modelRuns].reverse();
const timeAxis = g.selectAll('.time-label')
.data( [...reverseModelRuns] )
.join('text')
.text((d, i) => {
const previousIndex = reverseModelRuns[ i - 1 ]
const momentTime = moment.utc( d);
const differentDay = previousIndex?.length ? (previousIndex && moment.utc(previousIndex).format('DD') != momentTime.format('DD') ) : true;
return differentDay ?
momentTime.format('YYYY-MM-DD HH') : momentTime.format('HH')
})
.attr('class', 'time-label')
.attr('x', (d, i) => (recWidth) + (recWidth * i))
.attr('y', (recHeight * 1.3) + height)
.attr('transform', (d,i) => {
const previousIndex = reverseModelRuns[ i - 1 ]
const momentTime = moment.utc( d);
const differentDay = previousIndex?.length ? (previousIndex && moment.utc(previousIndex).format('DD') != momentTime.format('DD') ) : true;
return differentDay ? `rotate(-90,${(recWidth) + (recWidth * i)}, ${(recHeight * 1.3) + height})` : `translate(0,10)`
})
.style('text-anchor', 'end')
.style('fill', 'black')
.style('stroke', 'none');

const nowLineGroup = g.append('g');
const nowPosition = xScale( Date.now() );
const nowLine =
nowLineGroup.append('line')
.attr('x1', nowPosition)
.attr('x2', nowPosition)
.attr('y1', 0)
.attr('y2', height)
.attr('stroke', 'cyan')

const nowLable = nowLineGroup.append('text')
.text('current time (UTC)')
.attr('x', nowPosition - 3 )
.attr('y', height / 2)
.attr('transform', 'rotate(-90, ' + (nowPosition - 3 ) + ', ' + height / 2 + ')')
.style('text-anchor', 'end')
.style('fill', 'teal')
.style('stroke', 'none');
// Add row labels
const rowLabels = g.selectAll('.row-label')
.data( dataArray ).join('text')
// .append('text')
.text((d,i) => d.length ? moment.utc(d[0][0]).format('YYYY-MM-DD HH') : 'no model data')
// .attr('transform', 'translate(0,' + recHeight * 2 + ')')
.attr('class', 'row-label')
.attr('x', (d,i) => recWidth * (mapRows - i - 1.3))
.attr('y', (d,i) => recHeight + (i * recHeight))
.attr('dy', -recHeight/4)
.style('fill', 'gray')
.style('stroke', 'none')
.style('text-anchor', 'end');
// Draw each rectangle
const rectangles = g.selectAll('.cell')
.data( points ).enter()
.append('rect')
.attr('class', 'cell')
.style('stroke', 'white')
.style('stroke-width', 1.5)
.style('fill', d => d.value ? isNaN(d.difference) ? dstColor(d.value) : color(d.difference) : 'silver' )
.attr('x', d => d.x)
.attr('y', d => d.y)
.attr('width', recWidth)
.attr('height', recHeight);
// Add difference value to rectangles
const values = g.selectAll('.value')
.data( points ).enter()
.append('text')
.text(d => isNaN(d.difference) ? d.value : d.difference )
.attr('class', 'value')
.attr('x', d => d.x.toFixed(0))
.attr('dx', recWidth/2)
.attr('y', d => d.y)
.attr('dy', recHeight/1.3)
.style('fill', d => isNaN(d.difference) ? 'black' : 'black')
.style('stroke', 'white')
.style('stroke-width', 0.1)
.style('text-anchor', 'middle')
// .on('mouseover', hover)
// .on('mouseout', leave);
// Mouseover a value
function hover(d) {
rectangles.style( 'fill-opacity', h => !isNaN(d.difference) && d.time === h[0].time ? 1 : 0.6)
.style('stroke-width', h => !isNaN(d.difference) && d.time === h[0].time ? 5 : 1.5);
modelRuns.style( 'fill', m => d.time === m.time ? 'goldenrod' : 'black');
d3.select(this)
.transition().duration(10)
.text( d => d.value)
.style('fill', 'goldenrod')
.style('cursor', 'default')
}

// Mouseout function
function leave(d) {
rectangles.style( 'fill-opacity', 0.6)
.style('stroke-width', 1.5);
modelRuns.style( 'fill', 'black');
d3.select(this)
.transition().duration(500)
.style('fill-opacity', 0.6)
.style('fill', d => d.difference ? 'black' : 'goldenrod' )
.text( d => isNaN(d.difference) ? d.value : d.difference)
}
return svg.node()
}
Insert cell
Insert cell
Insert cell
pointsCompactLayout = {
const colArray = Array.from(Array(mapColumns).keys()); // 6 predictions
const rowArray = Array.from(Array(mapRows).keys()); // 24 hours
const pointsArray = rowArray.reduce( ( aggregator, row ) => {
colArray.forEach( col => {
const colOffset = mapRows - row -1;
const colWidth = recWidth;
const rowHeight = recHeight;
const x = (col * colWidth) + (colOffset * colWidth);
const rowOffset = (col);
console.log({ rowOffset });
const y = (height - (colArray.length * rowHeight) + (rowOffset * rowHeight));
// Note: due to a bug in the API, at hour 00:00, this will be one hour earlier
const timestamp = dataArray[row].length ? dataArray[row][col][0] : undefined;
// Note: due to a bug in the API, it will not find the dstInfo for hour 00:00
const dstInfo = (realtimeDstData.find( dst => dst?.time === timestamp ));
const dstValue = dstInfo?.value;
const modelValue = dataArray[row].length ? dataArray[row][col + 1][1].toFixed(0) : undefined;
const difference = dstValue - modelValue;
aggregator.push( {
x: x,
y: y,
time: timestamp,
difference: difference, //results in NaN for hour 00
value: modelValue
} );
});
return aggregator;
}, []);
return pointsArray;
}
Insert cell
Insert cell
// width = Math.ceil(mapColumns * hexRadius * SQRT3 + hexRadius)
width = (recWidth * (mapRows + mapColumns - 1));
Insert cell
// height = Math.ceil(mapRows * 1.5 * hexRadius + 0.5 * hexRadius)
height = recHeight * mapRows
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
recWidth = 25
Insert cell
recHeight = 20
Insert cell
margin = ({
left: recWidth * 5,
right: recWidth,
top: recHeight * 2,
bottom: recHeight * 7
})
Insert cell
Insert cell
modelRuns = await Array.from(Array(mapRows).keys()).map( value => moment.utc( mostRecentModelRunData ).clone().subtract( value, 'hours').format('YYYY-MM-DDTHH:00:00'))
Insert cell
mostRecentModelRun = (await fetch('https://livedst-api.dev.swx-trec.com/livedst_latest')).json()
Insert cell
mostRecentModelRunData = mostRecentModelRun.predictions.data[0][0]
Insert cell
dstRequest = (await fetch('https://lasp.colorado.edu/space-weather-portal/latis/dap/kyoto_dst_index_service.jsond?time,dst&time%3E%3D' + modelRuns[0] )).json()
Insert cell
dstData = dstRequest.kyoto_dst_index_service.data.reduce( (aggregator, data ) => {
aggregator[data[0]] = data[1];
return aggregator;
}, {})
Insert cell
realtimeDstData = [...reverseDataArray].reduce((aggregator, data ) => {
if (data.length) {
aggregator.push({
time: data[0][0],
value: data[0][1]
})
} else {
aggregator.push(null)
}
// aggregator[data[0][0]] = data[0][1];
return aggregator;
}, [])
Insert cell
dataArray = [ getValuesOnly(thing0), getValuesOnly(thing1), getValuesOnly(thing2),getValuesOnly(thing3), getValuesOnly(thing4), getValuesOnly(thing5), getValuesOnly(thing6), getValuesOnly(thing7), getValuesOnly(thing8), getValuesOnly(thing9), getValuesOnly(thing10), getValuesOnly(thing11), getValuesOnly(thing12), getValuesOnly(thing13), getValuesOnly(thing14), getValuesOnly(thing15), getValuesOnly(thing16), getValuesOnly(thing17), getValuesOnly(thing18), getValuesOnly(thing19), getValuesOnly(thing20), getValuesOnly(thing21), getValuesOnly(thing22), getValuesOnly(thing23) ]
Insert cell
Insert cell
newDataArray = [ getValuesOnly(thing0)]
Insert cell
Insert cell
Insert cell
thing1 = (await dataPromises[1]).json();
Insert cell
thing2 = (await dataPromises[2]).json();
Insert cell
thing3 = (await dataPromises[3]).json();
Insert cell
thing4 = (await dataPromises[4]).json();
Insert cell
thing5 = (await dataPromises[5]).json();
Insert cell
thing6 = (await dataPromises[6]).json();
Insert cell
thing7 = (await dataPromises[7]).json();
Insert cell
thing8 = (await dataPromises[8]).json();
Insert cell
thing9 = (await dataPromises[9]).json();
Insert cell
thing10 = (await dataPromises[10]).json();
Insert cell
thing11 = (await dataPromises[11]).json();
Insert cell
thing12 = (await dataPromises[12]).json();
Insert cell
thing13 = (await dataPromises[13]).json();
Insert cell
thing14 = (await dataPromises[14]).json();
Insert cell
thing15 = (await dataPromises[15]).json();
Insert cell
thing16 = (await dataPromises[16]).json();
Insert cell
thing17 = (await dataPromises[17]).json();
Insert cell
thing18 = (await dataPromises[18]).json();
Insert cell
thing19 = (await dataPromises[19]).json();
Insert cell
thing20 = (await dataPromises[20]).json();
Insert cell
thing21 = (await dataPromises[21]).json();
Insert cell
thing22 = (await dataPromises[22]).json();
Insert cell
thing23 = (await dataPromises[23]).json();
Insert cell
Insert cell
getValuesOnly = (response) => response.predictions.data;
Insert cell
Insert cell
Insert cell
Insert cell
// color = d3.scaleDiverging([-20, 0, 20], t => d3.interpolateRdBu(1 - t))
color = d3.scaleLinear([-20, -10, 0, 10, 20], ['firebrick', 'goldenrod', 'palegreen', 'goldenrod', 'firebrick'])
Insert cell
dstColor = d3.scaleLinear([-150, 50], ['navy', 'white'])
Insert cell
// TODO: Fix this? do I need to take out the earliest date or what this just some glitch? Should be a 29 item array, check back another day
availableDates = Array.from(dataArray.reduce( (aggregator, data, index) => {
if ( data.length ) {
aggregator.add( data[0][0])
if ( index === 0 ) {
data.forEach( element => aggregator.add(element[0]));
}
}
return aggregator;
}, new Set())).sort().reverse();
Insert cell
trimmedAvailableDates = availableDates.length > 29 ? [...availableDates].slice(0, -1) : [...availableDates]
Insert cell
Insert cell
dates = d3.extent(availableDates)
Insert cell
xScale = d3.scaleLinear( d3.extent(availableDates), [0, width])
Insert cell
Insert cell
moment = require('moment');
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