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 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()
}