// TO DO
//
// Improve tooltip
// Fix resize event listener
function appendOrSelect(parent, type, className) {
return parent.selectAll(`.${className}`)._groups[0][0] ? parent.select(`.${className}`) : parent.append(type).attr('class', className);
}
function createChartLayers(parent) {
let chartLayer = appendOrSelect(parent, 'g', 'chart-layer')
let axisLayer = appendOrSelect(chartLayer, 'g', 'axis-layer')
let axisX = appendOrSelect(axisLayer, 'g', 'axis-x')
let axisY = appendOrSelect(axisLayer, 'g', 'axis-y')
let titleLayer = appendOrSelect(chartLayer, 'g', 'title-layer')
let dataLayer = appendOrSelect(chartLayer, 'g', 'data-layer')
let hoverLayer = appendOrSelect(chartLayer, 'g', 'hover-layer')
}
function createChartSettings(parent, dataArray, pad) {
[topPad, rightPad, bottomPad, leftPad] = pad;
let width = Number(parent.style('width').split('px')[0]);
let height = Number(parent.style('height').split('px')[0]);
let numDays = dataArray.length;
let barWidth = (width - leftPad - rightPad)/numDays;
return {
width: width,
height: height,
numDays: numDays,
barWidth: barWidth,
pad: {
top: topPad,
right: rightPad,
bottom: bottomPad,
left: leftPad
}
}
}
//Scale functions
function getMaxSum(dataArray, metricArray) {
let maxTotal = 0
dataArray.forEach(day => {
let dayTotal = 0;
metricArray[2].forEach(metric => {
dayTotal += day[metricArray[0]][metricArray[1]][metric]
})
maxTotal = Math.max(dayTotal, maxTotal)
})
return maxTotal;
}
function getYScale(chart, max) {
return d3.scaleLinear()
.domain([0, Math.max(max*1.05, 1)])
.range([chart.height - chart.pad.bottom, chart.pad.top])
}
function getXScale(chart, data) {
return d3.scaleLinear()
.domain([0, chart.numDays-1])
.range([chart.pad.left, chart.width - chart.pad.right])
}
function getTimeScale(chart, data) {
let timeRange = d3.extent(data, d => d.date);
let before = timeRange[0];
before.setHours(0,0,0)
let after = timeRange[1];
after.setHours(0,0,0)
return d3.scaleTime().domain([before, after]).range([chart.pad.left, chart.width - chart.pad.right])
}
//Line chart functions
function getLine(xScale, yScale, zero=false) {
return d3.line().x((d, i) => xScale(i)).y((d, i) => yScale(zero ? 0 : d[1]))
}
//Hover dot functions
function createDots(fullData, layer, xScale, yScale, areaColorScale, lineColorScale, dotColorScale) {
fullData.forEach((stack, index) => {
let D = layer.selectAll(`.circle-${index}`).data(stack.data)
let Dplus = D.enter().append('circle');
Dplus.append()
Dplus.attr('class', `circle-${index}`)
.attr('dot', (d, i) => i)
.attr('cx', (d, i) => xScale(i))
.attr('cy', d => yScale(0))
.attr('r', 5)
.attr('fill', dotColorScale(fullData[index].key))
.attr('stroke', lineColorScale(fullData[index].key))
.attr('stroke-width', 1)
.attr('opacity', 0)
.transition().duration(1000)
.attr('cy', d => yScale(d[1]))
})
}
function updateDots(fullData, layer, xScale, yScale, areaColorScale, lineColorScale, dotColorScale) {
layer.selectAll('*').remove()
fullData.forEach((stack, index) => {
let D = layer.selectAll(`.circle-${index}`).data(stack.data)
D.exit().remove()
D.attr('class', `circle-${index}`)
.attr('cx', (d, i) => xScale(i))
.attr('fill', areaColorScale(fullData[index].key))
.attr('stroke', lineColorScale(fullData[index].key))
.attr('opacity', 0.3)
.transition().duration(1000)
.attr('cy', d => yScale(d[1]))
D.enter().append('circle')
.attr('class', `circle-${index}`)
.attr('dot', (d, i) => i)
.attr('cx', (d, i) => xScale(i))
.attr('cy', d => yScale(0))
.attr('r', 5)
.attr('fill', dotColorScale(fullData[index].key))
.attr('stroke', lineColorScale(fullData[index].key))
.attr('stroke-width', 1)
.attr('opacity', 0)
.transition().duration(1000)
.attr('cy', d => yScale(d[1]))
})
}
function enterLegend(parent, data, chart, areaColorScale, lineColorScale) {
parent.selectAll('*').remove();
let newData = data.slice().reverse();
let Leg = parent.selectAll('g').data(newData)
let LegLayer = Leg.enter().append('g').attr('transform', (d, i) => `translate(${chart.pad.left}, ${i*20 + chart.pad.top})`)
LegLayer.append('rect')
.attr('fill', d => areaColorScale(d))
.attr('width', 14)
.attr('height', 14)
.attr('stroke', d => lineColorScale(d))
.attr('stroke-width', 0)
.attr('rx', 2)
//.attr('shape-rendering', 'crispEdges')
LegLayer.append('text')
.text(d => d.toLowerCase().split(' ').map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' '))
.attr('x', 19)
.attr('y', 12)
.attr('font-size', 14)
.attr('font-family', `'CTVSans-Regular','CTV Sans', 'sans-serif`)
}
//Area chart functions
function getArea(xScale, yScale, zero=false) {
return d3.area().x((d, i) => xScale(i)).y0(d => yScale(zero ? 0 : d[0])).y1(d => yScale(zero ? 0 : d[1]))
}
function enterArea(selection, area, areaZero, areaColorScale) {
selection.enter().append('path')
.attr('class', 'area')
.attr('d', d => areaZero(d.data))
.attr('fill', (d, i) => areaColorScale(d.key))
.transition().duration(1000)
.attr('d', d => area(d.data))
return selection
}
function updateArea(selection, area, dur) {
selection.transition().duration(dur)
.attr('d', d => area(d.data))
}
function exitArea(selection, areaZero, dur) {
selection.exit()
.lower()
.attr('opacity', 1)
.transition().duration(1000)
.attr('d', d => areaZero(d.data))
.attr('opacity', 1)
.remove()
}
//Resize functions
function debounce(func){
var timer;
return function(event){
if(timer) clearTimeout(timer);
timer = setTimeout(func,10,event);
};
}
function addResize(parent, data, metricArray, latestDate) {
window.addEventListener('resize', debounce(() => {
resizeNewCurveChart(parent, data, metricArray, latestDate)
}) )
}
function makeButton(buttonParent, text, index, selection, parent, data, metricArray, latestDate) {
toggle = 'button-off';
if (metricArray[index] === selection) {
toggle = 'button-on';
}
if (index === 2 && metricArray[2][0] === 'cases' && text === 'Total') {
toggle = 'button-on';
}
if (index === 2 && metricArray[2][0] !== 'cases' && text === 'Breakdown') {
toggle = 'button-on';
}
buttonParent.append('button').text(text)
.on('click', d => {
metricArray[index] = selection;
if (metricArray[2][0] !== 'cases') { metricArray[2] = ['deaths', 'recovered', 'active'] }
if (metricArray[1] !== 'cumulative') { metricArray[2] = metricArray[2].filter(e => e !== 'active')}
//if (metricArray[2][0] !== 'cases' && metricArray[1] !== 'cumulative') {metricArray[2] = ['deaths', 'recovered']}
if (parent === '#covid-canada-date-chart') { chartInput = metricArray }
updateNewCurveChart(parent, data, [metricArray[0], metricArray[1], metricArray[2]], latestDate)
})
.attr('class',`chart-button ${toggle}`)
}
function checkDate(date, latestDate) {
let d1 = new Date(date)
let d2 = new Date(latestDate)
let dayDiff = (d2.setHours(0,0,0,0)-d1.setHours(0,0,0,0))/(1000*3600*24);
//let check = date.setHours(0,0,0,0) == latestDate.setHours(0,0,0,0);
//console.log(date, latestDate, check)
return dayDiff;
}
//Constants
const op = 0.55; let ds = -10; let dl = -10;
const colorCategories = ['cases', 'deaths', 'recovered', 'active']
const areaColors = [`#fadca2`,`#a1a0a0`, `#efb88f`, `#76c1cf`]
const dotColors = [`#ffeecf`,`#d4d4d4`, `#ffe3cf`, `#bdf3fc`]
const lineColors = [`rgb(244, 187, 63)`,`hsla(0, 0%, ${32.5+dl}%, 1`, `hsla(27, 74.2%,${52.9+dl}%, 1)`, `hsla(191, 71.6%,${38.6+dl}%, 1)`]
const chartPadding = [10, 50, 30, 20];
const dur = 1000;
function createNewCurveChart(parent, data, metricArray, latestDate) {
let dataArray = data.tracking;
let dateAdjustment = checkDate(dataArray[dataArray.length-1].date, latestDate);
console.log(data.properties.PRENAME, dateAdjustment)
dataArray = dataArray.filter((day, i) => i > 35 + dateAdjustment)
let container = d3.select(parent);
let titleDiv = appendOrSelect(container, 'div', 'title-div')
titleDiv.text(data.properties.PRENAME);
let buttonDiv = appendOrSelect(container, 'div', 'button-div')
let buttonGroup1 = buttonDiv.append('div').attr('class', 'button-group')
let buttonGroup2 = buttonDiv.append('div').attr('class', 'button-group')
let buttonGroup3 = buttonDiv.append('div').attr('class', 'button-group')
makeButton(buttonGroup1, 'Total', 2, ['cases'], parent, data, metricArray, latestDate)
makeButton(buttonGroup1, 'Breakdown', 2, ['deaths', 'recovered', 'active'], parent, data, metricArray, latestDate)
makeButton(buttonGroup2, 'Cumulative', 1, 'cumulative', parent, data, metricArray, latestDate)
makeButton(buttonGroup2, 'New', 1, 'new', parent, data, metricArray, latestDate)
makeButton(buttonGroup2, '7-day avg', 1, 'average', parent, data, metricArray, latestDate)
makeButton(buttonGroup3, 'Raw', 0, 'raw', parent, data, metricArray, latestDate)
makeButton(buttonGroup3, '/100K', 0, 'per100k', parent, data, metricArray, latestDate)
let svg = appendOrSelect(container, 'svg', 'curve-svg')
svg.attr('width', '100%').attr('height', '250')
let chart = createChartSettings(svg, dataArray, chartPadding)
let chartLayer = appendOrSelect(svg, 'g', 'chart-layer')
let axisLayer = appendOrSelect(chartLayer, 'g', 'axis-layer')
let axisX = appendOrSelect(axisLayer, 'g', 'axis-x')
let axisY = appendOrSelect(axisLayer, 'g', 'axis-y')
let titleLayer = appendOrSelect(chartLayer, 'g', 'title-layer')
let dataLayer = appendOrSelect(chartLayer, 'g', 'data-layer')
let areaLayer = appendOrSelect(dataLayer, 'g', 'area-layer')
let lineLayer = appendOrSelect(dataLayer, 'g', 'line-layer')
let legendLayer = appendOrSelect(chartLayer, 'g', 'legend-layer')
let dotLayer = appendOrSelect(chartLayer, 'g', 'dot-layer')
let hoverLayer = appendOrSelect(chartLayer, 'g', 'hover-layer')
let tooltipLayer = appendOrSelect(chartLayer, 'g', 'tooltip-layer')
let max = getMaxSum(dataArray, metricArray)
let yScale = getYScale(chart, max);
let xScale = getXScale(chart);
let areaColorScale = d3.scaleOrdinal().domain(colorCategories).range(areaColors);
let lineColorScale = d3.scaleOrdinal().domain(colorCategories).range(lineColors);
let dotColorScale = d3.scaleOrdinal().domain(colorCategories).range(dotColors);
let timeScale = getTimeScale(chart, dataArray)
let xAxis = d3.axisBottom().scale(timeScale)
axisX.call(xAxis).attr('transform', `translate(0, ${chart.height - chart.pad.bottom})`)
.attr('font-family', `'CTVSans-Regular','CTV Sans', 'sans-serif`)
let yAxis = d3.axisRight().scale(yScale).ticks(6)
axisY.call(yAxis).attr('transform', `translate(${chart.width - chart.pad.right}, 0)`)
.attr('font-family', `'CTVSans-Regular','CTV Sans', 'sans-serif`)
let breakdown = [];
metricArray[2].forEach(metric => {
breakdown.push([metricArray[0], metricArray[1], metric])
})
const stack = d3.stack()
.keys(breakdown)
.value((d, key) => d[key[0]][key[1]][key[2]])
const stackedValues = stack(dataArray);
let stackArray = []
stackedValues.forEach((stack, i) => {
let key = metricArray[2][i];
stackArray.push({
key: key,
data: stack
})
})
let area = getArea(xScale, yScale);
let areaZero = getArea(xScale, yScale, true)
let line = getLine(xScale, yScale)
let lineZero = getLine(xScale, yScale, true)
let A = areaLayer.selectAll('.area').data(stackArray, d => d.key)
enterArea(A, area, areaZero, areaColorScale)
let L = lineLayer.selectAll('.line').data(stackArray, d => d.key)
L.enter().append('path')
.attr('class', 'line')
.attr('d', d => lineZero(d.data))
.attr('stroke', (d, i) => lineColorScale(d.key))
.attr('stroke-width', 1)
.attr('fill', 'none')
.transition().duration(1000)
.attr('d', d => line(d.data))
let H = hoverLayer.selectAll('rect').data(dataArray)
function toolTip(d, i) {
tooltipLayer.selectAll('*').remove();
dotLayer.selectAll(`[dot="${i}"]`).attr('opacity', 1)
let x = d3.mouse(this)[0]
let y = d3.mouse(this)[1]
let newData = metricArray[2].slice().reverse();
newData.forEach((metric, j) => {
tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 10 + j*15 - 0.5*metricArray[2].length*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.text(`${metric}: ${d[metricArray[0]][metricArray[1]][metric]}`)
})
}
H.enter().append('rect')
.attr('x', (d, i) => xScale(i) - (xScale(i+1) - xScale(i))/2)
.attr('y', yScale(max*1.05))
.attr('width', (d, i) => xScale(i+1) - xScale(i))
.attr('height', yScale(0))
.attr('shape-rendering', 'crispEdges')
.attr('opacity', 0)
.on('mousemove', function(d,i) {
tooltipLayer.selectAll('*').remove();
dotLayer.selectAll(`[dot="${i}"]`).attr('opacity', 1)
let x = d3.mouse(this)[0]
let y = d3.mouse(this)[1]
let newData = metricArray[2].slice().reverse();
let rect = tooltipLayer.append('rect')
.attr('fill', 'white').attr('stroke', '#444')
.attr('shape-rendering', 'crispEdges')
.style('pointer-events', 'none')
let dateText = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 8 + - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${d.date.toLocaleDateString()}`)
let tipWidth = dateText.node().getComputedTextLength();
let tipHeight = 15;
newData.forEach((metric, j) => {
let text = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 10 + (j+1)*15 - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${metric}: ${d[metricArray[0]][metricArray[1]][metric].toLocaleString()}`)
tipWidth = Math.max(tipWidth, text.node().getComputedTextLength());
tipHeight += 15;
})
rect
.attr('width', tipWidth + 10).attr('height', tipHeight + 10)
.attr('x', (x < chart.width/2 ? x + 25 + tipWidth : x - 15) - tipWidth - 5).attr('y', y - (tipHeight + 10)/2 -2.5)
})
.on('mouseout', function(d,i) {
dotLayer.selectAll(`[dot]`).attr('opacity', 0)
tooltipLayer.selectAll('*').remove();
})
createDots(stackArray, dotLayer, xScale, yScale, areaColorScale, lineColorScale, dotColorScale)
enterLegend(legendLayer, metricArray[2], chart, areaColorScale, lineColorScale)
addResize(parent, data, metricArray, latestDate)
}
function updateNewCurveChart(parent, data, metricArray, latestDate) {
let dataArray = data.tracking;
let dateAdjustment = checkDate(dataArray[dataArray.length-1].date, latestDate);
dataArray = dataArray.filter((day, i) => i > 35 - dateAdjustment)
let container = d3.select(parent);
let titleDiv = appendOrSelect(container, 'div', 'title-div')
titleDiv.text(data.properties.PRENAME);
let buttonDiv = appendOrSelect(container, 'div', 'button-div')
buttonDiv.selectAll('*').remove()
let buttonGroup1 = buttonDiv.append('div').attr('class', 'button-group')
let buttonGroup2 = buttonDiv.append('div').attr('class', 'button-group')
let buttonGroup3 = buttonDiv.append('div').attr('class', 'button-group')
makeButton(buttonGroup1, 'Total', 2, ['cases'], parent, data, metricArray, latestDate)
makeButton(buttonGroup1, 'Breakdown', 2, ['deaths', 'recovered', 'active'], parent, data, metricArray, latestDate)
makeButton(buttonGroup2, 'Cumulative', 1, 'cumulative', parent, data, metricArray, latestDate)
makeButton(buttonGroup2, 'New', 1, 'new', parent, data, metricArray, latestDate)
makeButton(buttonGroup2, '7-day avg', 1, 'average', parent, data, metricArray, latestDate)
makeButton(buttonGroup3, 'Raw', 0, 'raw', parent, data, metricArray, latestDate)
makeButton(buttonGroup3, '/100K', 0, 'per100k', parent, data, metricArray, latestDate)
let svg = appendOrSelect(container, 'svg', 'curve-svg')
//svg.on('click', d => updateNewCurveChart(parent, data, ['total']))
let chart = createChartSettings(svg, dataArray, chartPadding)
let chartLayer = appendOrSelect(svg, 'g', 'chart-layer')
let axisLayer = appendOrSelect(chartLayer, 'g', 'axis-layer')
let axisX = appendOrSelect(axisLayer, 'g', 'axis-x')
let axisY = appendOrSelect(axisLayer, 'g', 'axis-y')
let titleLayer = appendOrSelect(chartLayer, 'g', 'title-layer')
let dataLayer = appendOrSelect(chartLayer, 'g', 'data-layer')
let areaLayer = appendOrSelect(dataLayer, 'g', 'area-layer')
let lineLayer = appendOrSelect(dataLayer, 'g', 'line-layer')
let legendLayer = appendOrSelect(chartLayer, 'g', 'legend-layer')
let dotLayer = appendOrSelect(chartLayer, 'g', 'dot-layer')
let hoverLayer = appendOrSelect(chartLayer, 'g', 'hover-layer')
let tooltipLayer = appendOrSelect(chartLayer, 'g', 'tooltip-layer')
let max = getMaxSum(dataArray, metricArray)
let yScale = getYScale(chart, max);
let xScale = getXScale(chart);
let areaColorScale = d3.scaleOrdinal().domain(colorCategories).range(areaColors);
let lineColorScale = d3.scaleOrdinal().domain(colorCategories).range(lineColors);
let dotColorScale = d3.scaleOrdinal().domain(colorCategories).range(dotColors);
let timeScale = getTimeScale(chart, dataArray)
let xAxis = d3.axisBottom().scale(timeScale)
axisX.transition().duration(1000).call(xAxis)
let yAxis = d3.axisRight().scale(yScale)
axisY.transition().duration(1000).call(yAxis)
let breakdown = [];
metricArray[2].forEach(metric => {
breakdown.push([metricArray[0], metricArray[1], metric])
})
const stack = d3.stack()
.keys(breakdown)
.value((d, key) => d[key[0]][key[1]][key[2]])
const stackedValues = stack(dataArray);
let stackArray = []
stackedValues.forEach((stack, i) => {
let key = metricArray[2][i];
stackArray.push({
key: key,
data: stack
})
})
let area = getArea(xScale, yScale);
let areaZero = getArea(xScale, yScale, true)
let line = getLine(xScale, yScale)
let lineZero = getLine(xScale, yScale, true)
let A = areaLayer.selectAll('.area').data(stackArray, d => d.key)
exitArea(A, areaZero, dur)
updateArea(A, area, dur)
enterArea(A, area, areaZero, areaColorScale)
let L = lineLayer.selectAll('.line').data(stackArray, d => d.key)
L.exit()
.lower()
.attr('opacity', 1)
.transition().duration(1000)
.attr('d', d => lineZero(d.data))
.attr('opacity', 0)
.remove()
L.transition().duration(1000)
.attr('d', d => line(d.data))
L.enter().append('path')
.attr('class', 'line')
.attr('d', d => lineZero(d.data))
.attr('stroke', (d, i) => lineColorScale(d.key))
.attr('stroke-width', 1)
.attr('fill', 'none')
.attr('opacity', 1)
.transition().duration(1000)
.attr('d', d => line(d.data))
.attr('opacity', 1)
let H = hoverLayer.selectAll('rect').data(dataArray)
H.exit().remove()
H.attr('x', (d, i) => xScale(i) - (xScale(i+1) - xScale(i))/2)
.attr('y', yScale(max*1.05))
.attr('width', (d, i) => xScale(i+1) - xScale(i))
.attr('height', yScale(0))
.on('mousemove', function(d,i) {
tooltipLayer.selectAll('*').remove();
dotLayer.selectAll(`[dot="${i}"]`).attr('opacity', 1)
let x = d3.mouse(this)[0]
let y = d3.mouse(this)[1]
let newData = metricArray[2].slice().reverse();
let rect = tooltipLayer.append('rect')
.attr('fill', 'white').attr('stroke', '#444')
.attr('shape-rendering', 'crispEdges')
.style('pointer-events', 'none')
let dateText = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 8 + - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${d.date.toLocaleDateString()}`)
let tipWidth = dateText.node().getComputedTextLength();
let tipHeight = 15;
newData.forEach((metric, j) => {
let text = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 10 + (j+1)*15 - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${metric}: ${d[metricArray[0]][metricArray[1]][metric].toLocaleString()}`)
tipWidth = Math.max(tipWidth, text.node().getComputedTextLength());
tipHeight += 15;
})
rect
.attr('width', tipWidth + 10).attr('height', tipHeight + 10)
.attr('x', (x < chart.width/2 ? x + 25 + tipWidth : x - 15) - tipWidth - 5).attr('y', y - (tipHeight + 10)/2 -2.5)
})
.on('mouseout', function(d,i) {
dotLayer.selectAll(`[dot]`).attr('opacity', 0)
tooltipLayer.selectAll('*').remove();
})
H.enter().append('rect')
.attr('x', (d, i) => xScale(i) - (xScale(i+1) - xScale(i))/2)
.attr('y', yScale(max*1.05))
.attr('width', (d, i) => xScale(i+1) - xScale(i))
.attr('height', yScale(0))
.attr('shape-rendering', 'crispEdges')
.attr('opacity', 0)
.on('mousemove', function(d,i) {
tooltipLayer.selectAll('*').remove();
dotLayer.selectAll(`[dot="${i}"]`).attr('opacity', 1)
let x = d3.mouse(this)[0]
let y = d3.mouse(this)[1]
let newData = metricArray[2].slice().reverse();
let rect = tooltipLayer.append('rect')
.attr('fill', 'white').attr('stroke', '#444')
.attr('shape-rendering', 'crispEdges')
.style('pointer-events', 'none')
let dateText = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 8 + - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${d.date.toLocaleDateString()}`)
let tipWidth = dateText.node().getComputedTextLength();
let tipHeight = 15;
newData.forEach((metric, j) => {
let text = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 10 + (j+1)*15 - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${metric}: ${d[metricArray[0]][metricArray[1]][metric].toLocaleString()}`)
tipWidth = Math.max(tipWidth, text.node().getComputedTextLength());
tipHeight += 15;
})
rect
.attr('width', tipWidth + 10).attr('height', tipHeight + 10)
.attr('x', (x < chart.width/2 ? x + 25 + tipWidth : x - 15) - tipWidth - 5).attr('y', y - (tipHeight + 10)/2 -2.5)
})
.on('mouseout', function(d,i) {
dotLayer.selectAll(`[dot]`).attr('opacity', 0)
tooltipLayer.selectAll('*').remove();
})
updateDots(stackArray, dotLayer, xScale, yScale, areaColorScale, lineColorScale, dotColorScale)
enterLegend(legendLayer, metricArray[2], chart, areaColorScale, lineColorScale)
addResize(parent, data, metricArray, latestDate)
}
function resizeNewCurveChart(parent, data, metricArray, latestDate) {
let dataArray = data.tracking;
let dateAdjustment = checkDate(dataArray[dataArray.length-1].date, latestDate);
dataArray = dataArray.filter((day, i) => i > 35 + dateAdjustment)
let container = d3.select(parent);
let svg = appendOrSelect(container, 'svg', 'curve-svg')
let chart = createChartSettings(svg, dataArray, chartPadding)
let chartLayer = appendOrSelect(svg, 'g', 'chart-layer')
let axisLayer = appendOrSelect(chartLayer, 'g', 'axis-layer')
let axisX = appendOrSelect(axisLayer, 'g', 'axis-x')
let axisY = appendOrSelect(axisLayer, 'g', 'axis-y')
let titleLayer = appendOrSelect(chartLayer, 'g', 'title-layer')
let dataLayer = appendOrSelect(chartLayer, 'g', 'data-layer')
let areaLayer = appendOrSelect(dataLayer, 'g', 'area-layer')
let lineLayer = appendOrSelect(dataLayer, 'g', 'line-layer')
let legendLayer = appendOrSelect(chartLayer, 'g', 'legend-layer')
let dotLayer = appendOrSelect(chartLayer, 'g', 'dot-layer')
let hoverLayer = appendOrSelect(chartLayer, 'g', 'hover-layer')
let tooltipLayer = appendOrSelect(chartLayer, 'g', 'tooltip-layer')
let areaColorScale = d3.scaleOrdinal().domain(colorCategories).range(areaColors);
let lineColorScale = d3.scaleOrdinal().domain(colorCategories).range(lineColors);
let dotColorScale = d3.scaleOrdinal().domain(colorCategories).range(dotColors);
let max = getMaxSum(dataArray, metricArray)
let yScale = getYScale(chart, max);
let xScale = getXScale(chart);
let timeScale = getTimeScale(chart, dataArray)
let xAxis = d3.axisBottom().scale(timeScale)
axisX.call(xAxis).attr('transform', `translate(0, ${chart.height - chart.pad.bottom})`)
let yAxis = d3.axisRight().scale(yScale)
axisY.call(yAxis).attr('transform', `translate(${chart.width - chart.pad.right}, 0)`)
let breakdown = [];
metricArray[2].forEach(metric => {
breakdown.push([metricArray[0], metricArray[1], metric])
})
const stack = d3.stack()
.keys(breakdown)
.value((d, key) => d[key[0]][key[1]][key[2]])
const stackedValues = stack(dataArray);
let stackArray = []
stackedValues.forEach((stack, i) => {
let key = metricArray[2][i];
stackArray.push({
key: key,
data: stack
})
})
let area = getArea(xScale, yScale);
let line = getLine(xScale, yScale)
let A = areaLayer.selectAll('.area').data(stackArray, d => d.key)
updateArea(A, area, 0)
let L = lineLayer.selectAll('.line').data(stackArray, d => d.key).attr('d', d => line(d.data))
let H = hoverLayer.selectAll('rect').data(dataArray)
H.attr('x', (d, i) => xScale(i) - (xScale(i+1) - xScale(i))/2)
.attr('y', yScale(max*1.05))
.attr('width', (d, i) => xScale(i+1) - xScale(i))
.attr('height', yScale(0))
.on('mousemove', function(d,i) {
tooltipLayer.selectAll('*').remove();
dotLayer.selectAll(`[dot="${i}"]`).attr('opacity', 1)
let x = d3.mouse(this)[0]
let y = d3.mouse(this)[1]
let newData = metricArray[2].slice().reverse();
let rect = tooltipLayer.append('rect')
.attr('fill', 'white').attr('stroke', '#444')
.attr('shape-rendering', 'crispEdges')
.style('pointer-events', 'none')
let dateText = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 8 + - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${d.date.toLocaleDateString()}`)
let tipWidth = dateText.node().getComputedTextLength();
let tipHeight = 15;
newData.forEach((metric, j) => {
let text = tooltipLayer.append('text')
.attr('x', x < chart.width/2 ? x + 25 : x - 15)
.attr('y', y + 10 + (j+1)*15 - 0.5*(metricArray[2].length+1)*15)
.attr('text-anchor', x < chart.width/2 ? 'start' : 'end')
.style('pointer-events', 'none')
.html(`${metric}: ${d[metricArray[0]][metricArray[1]][metric].toLocaleString()}`)
tipWidth = Math.max(tipWidth, text.node().getComputedTextLength());
tipHeight += 15;
})
rect
.attr('width', tipWidth + 10).attr('height', tipHeight + 10)
.attr('x', (x < chart.width/2 ? x + 25 + tipWidth : x - 15) - tipWidth - 5).attr('y', y - (tipHeight + 10)/2 -2.5)
})
.on('mouseout', function(d,i) {
dotLayer.selectAll(`[dot]`).attr('opacity', 0)
tooltipLayer.selectAll('*').remove();
})
updateDots(stackArray, dotLayer, xScale, yScale, areaColorScale, lineColorScale, dotColorScale)
}