Removed the zooming property and added back the tooltip
This commit is contained in:
parent
1c3d2ce38c
commit
ebad26fa8b
1 changed files with 89 additions and 515 deletions
|
|
@ -1,402 +1,4 @@
|
|||
/* Generate the concentration plot using d3 library. */
|
||||
function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses, exposed_presence_intervals, short_range_intervals) {
|
||||
|
||||
var time_format = d3.timeFormat('%H:%M');
|
||||
|
||||
var data_for_graphs = {
|
||||
'concentrations': [],
|
||||
'cumulative_doses': [],
|
||||
}
|
||||
times.map((time, index) => data_for_graphs.concentrations.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': concentrations[index]}));
|
||||
times.map((time, index) => data_for_graphs.cumulative_doses.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': cumulative_doses[index]}));
|
||||
|
||||
// Add main SVG element
|
||||
var plot_div = document.getElementById(svg_id);
|
||||
var vis = d3.select(plot_div).append('svg');
|
||||
|
||||
// H:M time format for x axis.
|
||||
xRange = d3.scaleTime().domain([data_for_graphs.concentrations[0].hour, data_for_graphs.concentrations[data_for_graphs.concentrations.length - 1].hour]),
|
||||
xTimeRange = d3.scaleLinear().domain([data_for_graphs.concentrations[0].time, data_for_graphs.concentrations[data_for_graphs.concentrations.length - 1].time]),
|
||||
bisecHour = d3.bisector((d) => { return d.hour; }).left,
|
||||
|
||||
yRange = d3.scaleLinear().domain([0., Math.max(...concentrations)]),
|
||||
yCumulativeRange = d3.scaleLinear().domain([0., Math.max(...cumulative_doses)*1.1]),
|
||||
|
||||
xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)),
|
||||
yAxis = d3.axisLeft(yRange).ticks(4),
|
||||
yCumulativeAxis = d3.axisRight(yCumulativeRange).ticks(4);
|
||||
|
||||
// Line representing the mean concentration.
|
||||
var lineFunc = d3.line();
|
||||
var draw_line = vis.append('svg:path')
|
||||
.attr('stroke', '#1f77b4')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('fill', 'none');
|
||||
|
||||
var lineCumulative = d3.line();
|
||||
var draw_cumulative_line = vis.append('svg:path')
|
||||
.attr('stroke', '#1f77b4')
|
||||
.attr('stroke-width', 2)
|
||||
.style("stroke-dasharray", "5 5")
|
||||
.attr('fill', 'none');
|
||||
|
||||
// Area representing the presence of exposed person(s).
|
||||
var exposedArea = {};
|
||||
var drawArea = {};
|
||||
exposed_presence_intervals.forEach((b, index) => {
|
||||
exposedArea[index] = d3.area();
|
||||
drawArea[index] = vis.append('svg:path')
|
||||
.attr('fill', '#1f77b4')
|
||||
.attr('fill-opacity', '0.1');
|
||||
});
|
||||
|
||||
// Area representing the short range interaction(s).
|
||||
var shortRangeArea = {};
|
||||
var drawShortRangeArea = {};
|
||||
short_range_intervals.forEach((b, index) => {
|
||||
shortRangeArea[index] = d3.area();
|
||||
drawShortRangeArea[index] = vis.append('svg:path')
|
||||
.attr('fill', '#1f00b4')
|
||||
.attr('fill-opacity', '0.1');
|
||||
});
|
||||
|
||||
// X axis declaration.
|
||||
var xAxisEl = vis.append('svg:g')
|
||||
.attr('class', 'x axis');
|
||||
|
||||
// X axis label.
|
||||
var xAxisLabelEl = vis.append('text')
|
||||
.attr('class', 'x label')
|
||||
.attr('fill', 'black')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text('Time of day')
|
||||
|
||||
// Y axis declaration.
|
||||
var yAxisEl = vis.append('svg:g')
|
||||
.attr('class', 'y axis');
|
||||
|
||||
// Y axis label.
|
||||
var yAxisLabelEl = vis.append('svg:text')
|
||||
.attr('class', 'y label')
|
||||
.attr('fill', 'black')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text('Mean concentration (virions/m³)');
|
||||
|
||||
// Y cumulative concentration axis declaration.
|
||||
var yAxisCumEl = vis.append('svg:g')
|
||||
.attr('class', 'y axis')
|
||||
.style('font-size', 14)
|
||||
.style("stroke-dasharray", "5 5");
|
||||
|
||||
// Y cumulated concentration axis label.
|
||||
var yAxisCumLabelEl = vis.append('svg:text')
|
||||
.attr('class', 'y label')
|
||||
.attr('fill', 'black')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text('Mean cumulative dose (infectious virus)');
|
||||
|
||||
// Legend for the plot elements - line and area.
|
||||
var legendLineIcon = vis.append('rect')
|
||||
.attr('width', 20)
|
||||
.attr('height', 3)
|
||||
.style('fill', '#1f77b4');
|
||||
|
||||
var legendCumulativeIcon = vis.append('line')
|
||||
.style("stroke-dasharray", "5 5") //dashed array for line
|
||||
.attr('stroke-width', '2')
|
||||
.style("stroke", '#1f77b4');
|
||||
|
||||
var legendAreaIcon = vis.append('rect')
|
||||
.attr('width', 20)
|
||||
.attr('height', 15)
|
||||
.attr('fill', '#1f77b4')
|
||||
.attr('fill-opacity', '0.1');
|
||||
|
||||
var legendShortRangeAreaIcon = vis.append('rect')
|
||||
.attr('width', 20)
|
||||
.attr('height', 15)
|
||||
.attr('fill', '#1f00b4')
|
||||
.attr('fill-opacity', '0.1');
|
||||
|
||||
var legendLineText = vis.append('text')
|
||||
.text('Mean concentration')
|
||||
.style('font-size', '15px')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
var legendCumutiveText = vis.append('text')
|
||||
.text('Cumulative dose')
|
||||
.style('font-size', '15px')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
var legendAreaText = vis.append('text')
|
||||
.text('Presence of exposed person(s)')
|
||||
.style('font-size', '15px')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
var legendShortRangeText = vis.append('text')
|
||||
.text('Short range interaction(s)')
|
||||
.style('font-size', '15px')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
// Legend bounding
|
||||
var legendBBox = vis.append('rect')
|
||||
.attr('width', 255)
|
||||
.attr('height', 90)
|
||||
.attr('stroke', 'lightgrey')
|
||||
.attr('stroke-width', '2')
|
||||
.attr('rx', '5px')
|
||||
.attr('ry', '5px')
|
||||
.attr('stroke-linejoin', 'round')
|
||||
.attr('fill', 'none');
|
||||
|
||||
// Tooltip.
|
||||
var focus = {}, tooltip_rect = {}, tooltip_time = {}, tooltip_concentration = {}, toolBox = {};
|
||||
for (const [concentration, data] of Object.entries(data_for_graphs)) {
|
||||
|
||||
focus[concentration] = vis.append('svg:g')
|
||||
.style('display', 'none');
|
||||
|
||||
focus[concentration].append('circle')
|
||||
.attr('r', 3);
|
||||
|
||||
tooltip_rect[concentration] = focus[concentration].append('rect')
|
||||
.attr('fill', 'white')
|
||||
.attr('stroke', '#000')
|
||||
.attr('width', 85)
|
||||
.attr('height', 50)
|
||||
.attr('x', 10)
|
||||
.attr('y', -22)
|
||||
.attr('rx', 4)
|
||||
.attr('ry', 4);
|
||||
|
||||
tooltip_time[concentration] = focus[concentration].append('text')
|
||||
.attr('id', 'tooltip-time')
|
||||
.attr('x', 18)
|
||||
.attr('y', -2);
|
||||
|
||||
tooltip_concentration[concentration] = focus[concentration].append('text')
|
||||
.attr('id', 'tooltip-concentration')
|
||||
.attr('x', 18)
|
||||
.attr('y', 18);
|
||||
|
||||
toolBox[concentration] = vis.append('rect')
|
||||
.attr('fill', 'none')
|
||||
.attr('pointer-events', 'all')
|
||||
.on('mouseover', () => { for (const [concentration, data] of Object.entries(focus)) focus[concentration].style('display', null); })
|
||||
.on('mouseout', () => { for (const [concentration, data] of Object.entries(focus)) focus[concentration].style('display', 'none'); })
|
||||
.on('mousemove', mousemove);
|
||||
}
|
||||
|
||||
var graph_width;
|
||||
var graph_height;
|
||||
|
||||
function redraw() {
|
||||
|
||||
// Define width and height according to the screen size.
|
||||
var div_width = plot_div.clientWidth;
|
||||
var div_height = plot_div.clientHeight;
|
||||
graph_width = div_width;
|
||||
graph_height = div_height
|
||||
if (div_width >= 900) { // For screens with width > 900px legend can be on the graph's right side.
|
||||
var margins = { top: 30, right: 20, bottom: 50, left: 60 };
|
||||
div_width = 900;
|
||||
graph_width = div_width * (2/3);
|
||||
const svg_margins = {'margin-left': '0rem', 'margin-top': '0rem'};
|
||||
Object.entries(svg_margins).forEach(([prop,val]) => vis.style(prop,val));
|
||||
}
|
||||
else {
|
||||
var margins = { top: 30, right: 20, bottom: 50, left: 40 };
|
||||
div_width = div_width * 1.1
|
||||
graph_width = div_width * .9;
|
||||
graph_height = div_height * 0.65; // On mobile screen sizes we want the legend to be on the bottom of the graph.
|
||||
const svg_margins = {'margin-left': '-1rem', 'margin-top': '3rem'};
|
||||
Object.entries(svg_margins).forEach(([prop,val]) => vis.style(prop,val));
|
||||
};
|
||||
|
||||
// Use the extracted size to set the size of the SVG element.
|
||||
vis.attr("width", div_width)
|
||||
.attr('height', div_height);
|
||||
|
||||
// SVG components according to the width and height.
|
||||
|
||||
// Axis ranges.
|
||||
xRange.range([margins.left, graph_width - margins.right]);
|
||||
xTimeRange.range([margins.left, graph_width - margins.right]);
|
||||
yRange.range([graph_height - margins.bottom, margins.top]);
|
||||
yCumulativeRange.range([graph_height - margins.bottom, margins.top]);
|
||||
|
||||
// Line.
|
||||
lineFunc.defined(d => !isNaN(d.concentration))
|
||||
.x(d => xTimeRange(d.time))
|
||||
.y(d => yRange(d.concentration));
|
||||
draw_line.attr("d", lineFunc(data_for_graphs.concentrations));
|
||||
|
||||
// Cumulative line.
|
||||
lineCumulative.defined(d => !isNaN(d.concentration))
|
||||
.x(d => xTimeRange(d.time))
|
||||
.y(d => yCumulativeRange(d.concentration));
|
||||
draw_cumulative_line.attr("d", lineCumulative(data_for_graphs.cumulative_doses));
|
||||
|
||||
// Area.
|
||||
exposed_presence_intervals.forEach((b, index) => {
|
||||
exposedArea[index].x(d => xTimeRange(d.time))
|
||||
.y0(graph_height - margins.bottom)
|
||||
.y1(d => yRange(d.concentration));
|
||||
|
||||
drawArea[index].attr('d', exposedArea[index](data_for_graphs.concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
|
||||
// Short Range Area.
|
||||
short_range_intervals.forEach((b, index) => {
|
||||
shortRangeArea[index].x(d => xTimeRange(d.time))
|
||||
.y0(graph_height - margins.bottom)
|
||||
.y1(d => yRange(d.concentration));
|
||||
|
||||
drawShortRangeArea[index].attr('d', shortRangeArea[index](data_for_graphs.concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})))
|
||||
})
|
||||
|
||||
// Axis.
|
||||
var xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d));
|
||||
var yAxis = d3.axisLeft(yRange);
|
||||
|
||||
xAxisEl.attr('transform', 'translate(0,' + (graph_height - margins.bottom) + ')')
|
||||
.call(xAxis);
|
||||
xAxisLabelEl.attr('x', (graph_width + margins.right) / 2)
|
||||
.attr('y', graph_height * 0.97);
|
||||
|
||||
yAxisEl.attr('transform', 'translate(' + margins.left + ',0)').call(yAxis);
|
||||
yAxisLabelEl.attr('x', (graph_height * 0.9 + margins.bottom) / 2)
|
||||
.attr('y', (graph_height + margins.left) * 0.9)
|
||||
.attr('transform', 'rotate(-90, 0,' + graph_height + ')');
|
||||
|
||||
yAxisCumEl.attr('transform', 'translate(' + (graph_width - margins.right) + ',0)').call(yCumulativeAxis);
|
||||
yAxisCumLabelEl.attr('transform', 'rotate(-90, 0,' + graph_height + ')')
|
||||
.attr('x', (graph_height + margins.bottom) / 2);
|
||||
|
||||
if (plot_div.clientWidth >= 900) {
|
||||
yAxisCumLabelEl.attr('transform', 'rotate(-90, 0,' + graph_height + ')')
|
||||
.attr('x', (graph_height + margins.bottom) / 2)
|
||||
.attr('y', 1.71 * graph_width);
|
||||
}
|
||||
else {
|
||||
yAxisCumLabelEl.attr('transform', 'rotate(-90, 0,' + graph_height + ')')
|
||||
.attr('x', (graph_height + margins.bottom * 0.55) / 2)
|
||||
.attr('y', graph_width + 290);
|
||||
}
|
||||
|
||||
// Legend on right side.
|
||||
const size = 20;
|
||||
if (plot_div.clientWidth >= 900) {
|
||||
legendLineIcon.attr('x', graph_width + size * 2.5)
|
||||
.attr('y', margins.top + size);
|
||||
legendLineText.attr('x', graph_width + 4 * size)
|
||||
.attr('y', margins.top + size);
|
||||
|
||||
legendCumulativeIcon.attr("x1", graph_width + size + 30)
|
||||
.attr("x2", graph_width + 2 * size + 32)
|
||||
.attr("y1", 3.5 * size)
|
||||
.attr("y2", 3.5 * size);
|
||||
legendCumutiveText.attr('x', graph_width + 2.5 * size + 30)
|
||||
.attr('y', margins.top + 2 * size);
|
||||
|
||||
legendAreaIcon.attr('x', graph_width + size * 2.5)
|
||||
.attr('y', margins.top + 2.6 * size);
|
||||
legendAreaText.attr('x', graph_width + 4 * size)
|
||||
.attr('y', margins.top + 3 * size);
|
||||
|
||||
legendShortRangeAreaIcon.attr('x', graph_width + size * 2.5)
|
||||
.attr('y', margins.top + 3.6 * size);
|
||||
legendShortRangeText.attr('x', graph_width + 4 * size)
|
||||
.attr('y', margins.top + 4 * size);
|
||||
|
||||
legendBBox.attr('x', graph_width * 1.07)
|
||||
.attr('y', margins.top * 1.2);
|
||||
}
|
||||
// Legend on the bottom.
|
||||
else {
|
||||
legendLineIcon.attr('x', size * 0.5)
|
||||
.attr('y', graph_height * 1.05);
|
||||
legendLineText.attr('x', 2 * size)
|
||||
.attr('y', graph_height * 1.05);
|
||||
|
||||
legendCumulativeIcon.attr("x1", size * 0.5)
|
||||
.attr("x2", size * 1.55)
|
||||
.attr("y1", graph_height * 1.05 + size)
|
||||
.attr("y2", graph_height * 1.05 + size);
|
||||
legendCumutiveText.attr('x', 2 * size)
|
||||
.attr('y', graph_height + 1.65 * size);
|
||||
|
||||
legendAreaIcon.attr('x', size * 0.50)
|
||||
.attr('y', graph_height * 1.09 + size);
|
||||
legendAreaText.attr('x', 2 * size)
|
||||
.attr('y', graph_height + 2.6 * size);
|
||||
|
||||
legendShortRangeAreaIcon.attr('x', size * 0.50)
|
||||
.attr('y', graph_height * 1.175 + size);
|
||||
legendShortRangeText.attr('x', 2 * size)
|
||||
.attr('y', graph_height + 3.65 * size);
|
||||
|
||||
legendBBox.attr('x', 1)
|
||||
.attr('y', graph_height);
|
||||
}
|
||||
|
||||
// ToolBox.
|
||||
for (const [concentration, data] of Object.entries(data_for_graphs)) {
|
||||
toolBox[concentration].attr('width', graph_width - margins.right)
|
||||
.attr('height', graph_height);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw for the first time to initialize.
|
||||
redraw();
|
||||
|
||||
function mousemove() {
|
||||
for (const [scenario, data] of Object.entries(data_for_graphs)) {
|
||||
if (d3.pointer(event)[0] < graph_width / 2) {
|
||||
tooltip_rect[scenario].attr('x', 10)
|
||||
tooltip_time[scenario].attr('x', 18)
|
||||
tooltip_concentration[scenario].attr('x', 18);
|
||||
}
|
||||
else {
|
||||
tooltip_rect[scenario].attr('x', -90)
|
||||
tooltip_time[scenario].attr('x', -82)
|
||||
tooltip_concentration[scenario].attr('x', -82)
|
||||
}
|
||||
}
|
||||
// Concentration line
|
||||
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||
i = bisecHour(data_for_graphs.concentrations, x0, 1),
|
||||
d0 = data_for_graphs.concentrations[i - 1],
|
||||
d1 = data_for_graphs.concentrations[i];
|
||||
if (d1) {
|
||||
var d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
||||
focus.concentrations.attr('transform', 'translate(' + xRange(d.hour) + ',' + yRange(d.concentration) + ')');
|
||||
focus.concentrations.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||
focus.concentrations.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
||||
}
|
||||
// Cumulative line
|
||||
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||
i = bisecHour(data_for_graphs.cumulative_doses, x0, 1),
|
||||
d0 = data_for_graphs.cumulative_doses[i - 1],
|
||||
d1 = data_for_graphs.cumulative_doses[i];
|
||||
if (d1 && d1.concentration) {
|
||||
var d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
||||
focus.cumulative_doses.attr('transform', 'translate(' + xRange(d.hour) + ',' + yCumulativeRange(d.concentration) + ')');
|
||||
focus.cumulative_doses.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||
focus.cumulative_doses.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
// Redraw based on the new size whenever the browser window is resized.
|
||||
window.addEventListener("resize", redraw);
|
||||
|
||||
|
||||
}
|
||||
|
||||
function draw_plot(svg_id, times, concentrations, short_range_concentrations, cumulative_doses) {
|
||||
|
||||
// Used for controlling the short range interactions
|
||||
|
|
@ -406,11 +8,9 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
|
||||
var data_for_graphs = {
|
||||
'concentrations': [],
|
||||
'short_range_concentrations': [],
|
||||
'cumulative_doses': [],
|
||||
}
|
||||
times.map((time, index) => data_for_graphs.concentrations.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': concentrations[index]}));
|
||||
times.map((time, index) => data_for_graphs.short_range_concentrations.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': short_range_concentrations[index]}));
|
||||
times.map((time, index) => data_for_graphs.concentrations.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': short_range_concentrations[index]}));
|
||||
times.map((time, index) => data_for_graphs.cumulative_doses.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': cumulative_doses[index]}));
|
||||
|
||||
// Add main SVG element
|
||||
|
|
@ -526,10 +126,7 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
|
||||
// Line representing the mean concentration.
|
||||
var lineFunc = d3.line();
|
||||
var draw_line = vis.append('g')
|
||||
.attr('clip-path', 'url(#clip)');
|
||||
draw_line.append('svg:path')
|
||||
.attr('class', 'line')
|
||||
var draw_line = vis.append('svg:path')
|
||||
.attr('stroke', '#1f77b4')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('fill', 'none');
|
||||
|
|
@ -547,7 +144,7 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
var drawArea = {};
|
||||
exposed_presence_intervals.forEach((b, index) => {
|
||||
exposedArea[index] = d3.area();
|
||||
drawArea[index] = draw_line.append('svg:path')
|
||||
drawArea[index] = vis.append('svg:path')
|
||||
.attr('fill', '#1f77b4')
|
||||
.attr('fill-opacity', '0.1');
|
||||
});
|
||||
|
|
@ -557,18 +154,45 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
var drawShortRangeArea = {};
|
||||
short_range_intervals.forEach((b, index) => {
|
||||
shortRangeArea[index] = d3.area();
|
||||
drawShortRangeArea[index] = draw_line.append('svg:path')
|
||||
drawShortRangeArea[index] = vis.append('svg:path')
|
||||
.attr('fill', '#1f00b4')
|
||||
.attr('fill-opacity', '0.1');
|
||||
});
|
||||
|
||||
var clip = vis.append("defs").append("svg:clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("svg:rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 30);
|
||||
// Tooltip.
|
||||
var focus = {}, tooltip_rect = {}, tooltip_time = {}, tooltip_concentration = {}, toolBox = {};
|
||||
for (const [concentration, data] of Object.entries(data_for_graphs)) {
|
||||
|
||||
var brush = d3.brushY();
|
||||
focus[concentration] = vis.append('svg:g')
|
||||
.style('display', 'none');
|
||||
|
||||
focus[concentration].append('circle')
|
||||
.attr('r', 3);
|
||||
|
||||
tooltip_rect[concentration] = focus[concentration].append('rect')
|
||||
.attr('fill', 'white')
|
||||
.attr('stroke', '#000')
|
||||
.attr('width', 85)
|
||||
.attr('height', 50)
|
||||
.attr('x', 10)
|
||||
.attr('y', -22)
|
||||
.attr('rx', 4)
|
||||
.attr('ry', 4);
|
||||
|
||||
tooltip_time[concentration] = focus[concentration].append('text')
|
||||
.attr('id', 'tooltip-time')
|
||||
.attr('x', 18)
|
||||
.attr('y', -2);
|
||||
|
||||
tooltip_concentration[concentration] = focus[concentration].append('text')
|
||||
.attr('id', 'tooltip-concentration')
|
||||
.attr('x', 18)
|
||||
.attr('y', 18);
|
||||
|
||||
toolBox[concentration] = vis.append('rect')
|
||||
.attr('fill', 'none')
|
||||
.attr('pointer-events', 'all');
|
||||
}
|
||||
|
||||
function update_concentration_plot(data) {
|
||||
yRange.domain([0., Math.max(...data)]);
|
||||
|
|
@ -577,12 +201,11 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
lineFunc.defined(d => !isNaN(d.concentration))
|
||||
.x(d => xTimeRange(d.time))
|
||||
.y(d => yRange(d.concentration));
|
||||
draw_line.select('.line')
|
||||
.enter()
|
||||
.merge(draw_line.select('.line'))
|
||||
draw_line.enter()
|
||||
.merge(draw_line)
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.attr("d", lineFunc(data_for_graphs.short_range_concentrations));
|
||||
.attr("d", lineFunc(data_for_graphs.concentrations));
|
||||
|
||||
// Area.
|
||||
exposed_presence_intervals.forEach((b, index) => {
|
||||
|
|
@ -590,7 +213,7 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
.y0(graph_height - 50)
|
||||
.y1(d => yRange(d.concentration)
|
||||
);
|
||||
drawArea[index].transition().duration(1000).attr('d', exposedArea[index](data_for_graphs.short_range_concentrations.filter(d => {
|
||||
drawArea[index].transition().duration(1000).attr('d', exposedArea[index](data_for_graphs.concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
|
|
@ -601,67 +224,54 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
.y0(graph_height - 50)
|
||||
.y1(d => yRange(d.concentration));
|
||||
|
||||
drawShortRangeArea[index].transition().duration(1000).attr('d', shortRangeArea[index](data_for_graphs.short_range_concentrations.filter(d => {
|
||||
drawShortRangeArea[index].transition().duration(1000).attr('d', shortRangeArea[index](data_for_graphs.concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
|
||||
brush.on("end", updateChart); // Each time the brush selection changes, trigger the 'updateChart' function
|
||||
|
||||
// Brushing
|
||||
draw_line.append("svg:g")
|
||||
.attr("class", "brush")
|
||||
.call(brush);
|
||||
// Tooltip.
|
||||
for (const [concentration, data] of Object.entries(data_for_graphs)) {
|
||||
toolBox[concentration]
|
||||
.on('mouseover', () => { for (const [concentration, data] of Object.entries(focus)) focus[concentration].style('display', null); })
|
||||
.on('mouseout', () => { for (const [concentration, data] of Object.entries(focus)) focus[concentration].style('display', 'none'); })
|
||||
.on('mousemove', mousemove);
|
||||
}
|
||||
|
||||
// A function that set idleTimeOut to null
|
||||
var idleTimeout
|
||||
function idled() { idleTimeout = null; }
|
||||
|
||||
// A function that updates the chart for given boundaries
|
||||
function updateChart(event,d) {
|
||||
// What are the selected boundaries?
|
||||
extent = event.selection
|
||||
|
||||
// If no selection, back to initial coordinate. Otherwise, update Y axis domain
|
||||
if(!extent) {
|
||||
if (!idleTimeout) return idleTimeout = setTimeout(idled, 350); // This allows to wait a little bit
|
||||
}
|
||||
else {
|
||||
yRange.domain([ yRange.invert(extent[1]), yRange.invert(extent[0]) ])
|
||||
draw_line.select(".brush").call(brush.move, null) // This remove the grey brush area as soon as the selection has been done
|
||||
function mousemove() {
|
||||
for (const [scenario, data] of Object.entries(data_for_graphs)) {
|
||||
if (d3.pointer(event)[0] < graph_width / 2) {
|
||||
tooltip_rect[scenario].attr('x', 10)
|
||||
tooltip_time[scenario].attr('x', 18)
|
||||
tooltip_concentration[scenario].attr('x', 18);
|
||||
}
|
||||
else {
|
||||
tooltip_rect[scenario].attr('x', -90)
|
||||
tooltip_time[scenario].attr('x', -82)
|
||||
tooltip_concentration[scenario].attr('x', -82)
|
||||
}
|
||||
}
|
||||
// Concentration line
|
||||
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||
i = bisecHour(data_for_graphs.concentrations, x0, 1),
|
||||
d0 = data_for_graphs.concentrations[i - 1],
|
||||
d1 = data_for_graphs.concentrations[i];
|
||||
if (d1) {
|
||||
var d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
||||
focus.concentrations.attr('transform', 'translate(' + xRange(d.hour) + ',' + yRange(d.concentration) + ')');
|
||||
focus.concentrations.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||
focus.concentrations.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
||||
}
|
||||
// Cumulative line
|
||||
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||
i = bisecHour(data_for_graphs.cumulative_doses, x0, 1),
|
||||
d0 = data_for_graphs.cumulative_doses[i - 1],
|
||||
d1 = data_for_graphs.cumulative_doses[i];
|
||||
if (d1 && d1.concentration) {
|
||||
var d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
||||
focus.cumulative_doses.attr('transform', 'translate(' + xRange(d.hour) + ',' + yCumulativeRange(d.concentration) + ')');
|
||||
focus.cumulative_doses.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||
focus.cumulative_doses.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
||||
}
|
||||
|
||||
// Update axis and line position
|
||||
yAxisEl.transition().duration(1000).call(d3.axisLeft(yRange))
|
||||
lineFunc.defined(d => !isNaN(d.concentration))
|
||||
.x(d => xTimeRange(d.time))
|
||||
.y(d => yRange(d.concentration));
|
||||
draw_line
|
||||
.select('.line')
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.attr("d", lineFunc(data_for_graphs.short_range_concentrations));
|
||||
|
||||
// Area.
|
||||
exposed_presence_intervals.forEach((b, index) => {
|
||||
exposedArea[index].x(d => xTimeRange(d.time))
|
||||
.y0(graph_height - 50)
|
||||
.y1(d => yRange(d.concentration));
|
||||
drawArea[index].transition().duration(1000).attr('d', exposedArea[index](data_for_graphs.short_range_concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
|
||||
// Short Range Area.
|
||||
short_range_intervals.forEach((b, index) => {
|
||||
shortRangeArea[index].x(d => xTimeRange(d.time))
|
||||
.y0(graph_height - 50)
|
||||
.y1(d => yRange(d.concentration));
|
||||
|
||||
drawShortRangeArea[index].transition().duration(1000).attr('d', shortRangeArea[index](data_for_graphs.short_range_concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -694,13 +304,6 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
|
||||
// SVG components according to the width and height.
|
||||
|
||||
// clipPath: everything out of this area won't be drawn.
|
||||
clip.attr("width", graph_width - margins.right)
|
||||
.attr("height", graph_height - margins.top - margins.bottom);
|
||||
|
||||
// Add brushing
|
||||
brush.extent([[margins.left, margins.top],[graph_width - margins.right, graph_height - margins.bottom ]]);
|
||||
|
||||
// Axis ranges.
|
||||
xRange.range([margins.left, graph_width - margins.right]);
|
||||
xTimeRange.range([margins.left, graph_width - margins.right]);
|
||||
|
|
@ -796,6 +399,12 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
.attr('y', graph_height);
|
||||
}
|
||||
|
||||
// ToolBox.
|
||||
for (const [concentration, data] of Object.entries(data_for_graphs)) {
|
||||
toolBox[concentration].attr('width', graph_width - margins.right)
|
||||
.attr('height', graph_height);
|
||||
}
|
||||
|
||||
// Cumulative line.
|
||||
lineCumulative.defined(d => !isNaN(d.concentration))
|
||||
.x(d => xTimeRange(d.time))
|
||||
|
|
@ -818,41 +427,6 @@ function draw_plot(svg_id, times, concentrations, short_range_concentrations, cu
|
|||
});
|
||||
}
|
||||
|
||||
// If user double click, reinitialize the chart
|
||||
vis.on("dblclick",function(){
|
||||
if (button_full_exposure || button_long_exposure) {
|
||||
button_full_exposure.disabled = true;
|
||||
button_long_exposure.disabled = false;
|
||||
}
|
||||
yRange.domain([0., Math.max(...short_range_concentrations)])
|
||||
yAxisEl.transition().call(d3.axisLeft(yRange))
|
||||
lineFunc.defined(d => !isNaN(d.concentration))
|
||||
.x(d => xTimeRange(d.time))
|
||||
.y(d => yRange(d.concentration));
|
||||
draw_line
|
||||
.select('.line')
|
||||
.transition()
|
||||
.attr("d", lineFunc(data_for_graphs.short_range_concentrations));
|
||||
exposed_presence_intervals.forEach((b, index) => {
|
||||
exposedArea[index].x(d => xTimeRange(d.time))
|
||||
.y0(graph_height - 50)
|
||||
.y1(d => yRange(d.concentration)
|
||||
);
|
||||
drawArea[index].transition().duration(1000).attr('d', exposedArea[index](data_for_graphs.short_range_concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
short_range_intervals.forEach((b, index) => {
|
||||
shortRangeArea[index].x(d => xTimeRange(d.time))
|
||||
.y0(graph_height - 50)
|
||||
.y1(d => yRange(d.concentration));
|
||||
|
||||
drawShortRangeArea[index].transition().duration(1000).attr('d', shortRangeArea[index](data_for_graphs.short_range_concentrations.filter(d => {
|
||||
return d.time >= b[0] && d.time <= b[1]
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
// Draw for the first time to initialize.
|
||||
redraw();
|
||||
update_concentration_plot(short_range_concentrations);
|
||||
|
|
|
|||
Loading…
Reference in a new issue