Compare commits
2 commits
master
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0a83146a | ||
|
|
ff9d115596 |
2 changed files with 110 additions and 86 deletions
|
|
@ -1,62 +1,52 @@
|
||||||
/* Generate the concentration plot using d3 library. */
|
/* Generate the concentration plot using d3 library. */
|
||||||
function draw_concentration_plot(svg_id, times, concentrations, exposed_presence_intervals) {
|
function draw_concentration_plot(svg_id, times, concentrations, exposed_presence_intervals) {
|
||||||
var visBoundingBox = d3.select(svg_id)
|
|
||||||
.node()
|
|
||||||
.getBoundingClientRect();
|
|
||||||
|
|
||||||
var time_format = d3.timeFormat('%H:%M');
|
var time_format = d3.timeFormat('%H:%M');
|
||||||
|
|
||||||
var data = []
|
var data = []
|
||||||
times.map((time, index) => data.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': concentrations[index] }))
|
times.map((time, index) => data.push({'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': concentrations[index] }))
|
||||||
|
|
||||||
var vis = d3.select(svg_id),
|
var plot_div = document.getElementById(svg_id);
|
||||||
width = visBoundingBox.width - 300,
|
|
||||||
height = visBoundingBox.height,
|
var height = plot_div.clientHeight;
|
||||||
margins = { top: 30, right: 20, bottom: 50, left: 50 },
|
var width; // To be defined later
|
||||||
|
|
||||||
|
var vis = d3.select(plot_div).append('svg').attr('height', height);
|
||||||
|
|
||||||
|
var margins = { top: 30, right: 20, bottom: 50, left: 50 };
|
||||||
|
|
||||||
// H:M time format for x axis.
|
// H:M time format for x axis.
|
||||||
xRange = d3.scaleTime().range([margins.left, width - margins.right]).domain([data[0].hour, data[data.length - 1].hour]),
|
var xRange = d3.scaleTime().domain([data[0].hour, data[data.length - 1].hour]);
|
||||||
xTimeRange = d3.scaleLinear().range([margins.left, width - margins.right]).domain([data[0].time, data[data.length - 1].time]),
|
var xTimeRange = d3.scaleLinear().domain([data[0].time, data[data.length - 1].time]);
|
||||||
bisecHour = d3.bisector((d) => { return d.hour; }).left,
|
var bisecHour = d3.bisector((d) => { return d.hour; }).left;
|
||||||
|
|
||||||
yRange = d3.scaleLinear().range([height - margins.bottom, margins.top]).domain([0., Math.max(...concentrations)]),
|
var yRange = d3.scaleLinear().range([height - margins.bottom, margins.top]).domain([0., Math.max(...concentrations)]);
|
||||||
|
var yAxis = d3.axisLeft(yRange);
|
||||||
xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)),
|
|
||||||
yAxis = d3.axisLeft(yRange);
|
|
||||||
|
|
||||||
// Plot tittle.
|
|
||||||
vis.append('svg:foreignObject')
|
|
||||||
.attr("background-color", "transparent")
|
|
||||||
.attr('width', width)
|
|
||||||
.attr('height', margins.top)
|
|
||||||
.style('text-align', 'center')
|
|
||||||
.html('<b>Mean concentration of virions</b>');
|
|
||||||
|
|
||||||
// Line representing the mean concentration.
|
// Line representing the mean concentration.
|
||||||
var lineFunc = d3.line()
|
var lineFunc = d3.line();
|
||||||
.defined(d => !isNaN(d.concentration))
|
var draw_line = vis.append('svg:path')
|
||||||
.x(d => xTimeRange(d.time))
|
|
||||||
.y(d => yRange(d.concentration))
|
|
||||||
.curve(d3.curveBasis);
|
|
||||||
|
|
||||||
vis.append('svg:path')
|
|
||||||
.attr('d', lineFunc(data))
|
|
||||||
.attr('stroke', '#1f77b4')
|
.attr('stroke', '#1f77b4')
|
||||||
.attr('stroke-width', 2)
|
.attr('stroke-width', 2)
|
||||||
.attr('fill', 'none');
|
.attr('fill', 'none');
|
||||||
|
|
||||||
|
// Plot tittle.
|
||||||
|
var plotTitleEl = vis.append('svg:foreignObject')
|
||||||
|
.attr("background-color", "transparent")
|
||||||
|
.attr('height', margins.top)
|
||||||
|
.style('text-align', 'center')
|
||||||
|
.html('<b>Mean concentration of virions</b>');
|
||||||
|
|
||||||
// X axis declaration.
|
// X axis declaration.
|
||||||
vis.append('svg:g')
|
var xAxisEl = vis.append('svg:g')
|
||||||
.attr('class', 'x axis')
|
.attr('class', 'x axis')
|
||||||
.attr('transform', 'translate(0,' + (height - margins.bottom) + ')')
|
.attr('transform', 'translate(0,' + (height - margins.bottom) + ')');
|
||||||
.call(xAxis);
|
|
||||||
|
|
||||||
// X axis label.
|
// X axis label.
|
||||||
vis.append('text')
|
var xAxisLabelEl = vis.append('text')
|
||||||
.attr('class', 'x label')
|
.attr('class', 'x label')
|
||||||
.attr('fill', 'black')
|
.attr('fill', 'black')
|
||||||
.attr('text-anchor', 'middle')
|
.attr('text-anchor', 'middle')
|
||||||
.attr('x', (width + margins.right) / 2)
|
|
||||||
.attr('y', height * 0.97)
|
.attr('y', height * 0.97)
|
||||||
.text('Time of day')
|
.text('Time of day')
|
||||||
|
|
||||||
|
|
@ -77,63 +67,41 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
.text('Mean concentration (virions/m³)');
|
.text('Mean concentration (virions/m³)');
|
||||||
|
|
||||||
// Area representing the presence of exposed person(s).
|
// Area representing the presence of exposed person(s).
|
||||||
exposed_presence_intervals.forEach(b => {
|
var exposedArea = {};
|
||||||
var curveFunc = d3.area()
|
var drawArea = {};
|
||||||
.x(d => xTimeRange(d.time))
|
exposed_presence_intervals.forEach((b, index) => {
|
||||||
.y0(height - margins.bottom)
|
exposedArea[index] = d3.area();
|
||||||
.y1(d => yRange(d.concentration));
|
drawArea[index] = vis.append('svg:path')
|
||||||
|
|
||||||
vis.append('svg:path')
|
|
||||||
.attr('d', curveFunc(data.filter(d => {
|
|
||||||
return d.time >= b[0] && d.time <= b[1]
|
|
||||||
})))
|
|
||||||
.attr('fill', '#1f77b4')
|
.attr('fill', '#1f77b4')
|
||||||
.attr('fill-opacity', '0.1');
|
.attr('fill-opacity', '0.1');
|
||||||
})
|
});
|
||||||
|
|
||||||
// Legend for the plot elements - line and area.
|
// Legend for the plot elements - line and area.
|
||||||
var size = 20
|
var size = 20
|
||||||
vis.append('rect')
|
var legendLineIcon = vis.append('rect')
|
||||||
.attr('x', width + size)
|
|
||||||
.attr('y', margins.top + size)
|
.attr('y', margins.top + size)
|
||||||
.attr('width', 20)
|
.attr('width', 20)
|
||||||
.attr('height', 3)
|
.attr('height', 3)
|
||||||
.style('fill', '#1f77b4');
|
.style('fill', '#1f77b4');
|
||||||
|
|
||||||
vis.append('rect')
|
// var legendAreaIcon = vis.append('rect')
|
||||||
.attr('x', width + size)
|
// .attr('y', 3 * size)
|
||||||
.attr('y', 3 * size)
|
// .attr('width', 20)
|
||||||
.attr('width', 20)
|
// .attr('height', 20)
|
||||||
.attr('height', 20)
|
// .attr('fill', '#1f77b4')
|
||||||
.attr('fill', '#1f77b4')
|
// .attr('fill-opacity', '0.1');
|
||||||
.attr('fill-opacity', '0.1');
|
|
||||||
|
|
||||||
vis.append('text')
|
// var legendLineText = vis.append('text')
|
||||||
.attr('x', width + 3 * size)
|
// .attr('y', margins.top + size)
|
||||||
.attr('y', margins.top + size)
|
// .text('Mean concentration')
|
||||||
.text('Mean concentration')
|
// .style('font-size', '15px')
|
||||||
.style('font-size', '15px')
|
// .attr('alignment-baseline', 'central');
|
||||||
.attr('alignment-baseline', 'central');
|
|
||||||
|
|
||||||
vis.append('text')
|
// var legendAreaText = vis.append('text')
|
||||||
.attr('x', width + 3 * size)
|
// .attr('y', margins.top + 2 * size)
|
||||||
.attr('y', margins.top + 2 * size)
|
// .text('Presence of exposed person(s)')
|
||||||
.text('Presence of exposed person(s)')
|
// .style('font-size', '15px')
|
||||||
.style('font-size', '15px')
|
// .attr('alignment-baseline', 'central');
|
||||||
.attr('alignment-baseline', 'central');
|
|
||||||
|
|
||||||
// Legend bounding box.
|
|
||||||
vis.append('rect')
|
|
||||||
.attr('width', 275)
|
|
||||||
.attr('height', 50)
|
|
||||||
.attr('x', width * 1.005)
|
|
||||||
.attr('y', margins.top + 5)
|
|
||||||
.attr('stroke', 'lightgrey')
|
|
||||||
.attr('stroke-width', '2')
|
|
||||||
.attr('rx', '5px')
|
|
||||||
.attr('ry', '5px')
|
|
||||||
.attr('stroke-linejoin', 'round')
|
|
||||||
.attr('fill', 'none');
|
|
||||||
|
|
||||||
// Tooltip.
|
// Tooltip.
|
||||||
var focus = vis.append('svg:g')
|
var focus = vis.append('svg:g')
|
||||||
|
|
@ -162,15 +130,65 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
.attr('x', 18)
|
.attr('x', 18)
|
||||||
.attr('y', 18);
|
.attr('y', 18);
|
||||||
|
|
||||||
vis.append('rect')
|
var toolBox = vis.append('rect')
|
||||||
.attr('fill', 'none')
|
.attr('fill', 'none')
|
||||||
.attr('pointer-events', 'all')
|
.attr('pointer-events', 'all')
|
||||||
.attr('width', width - margins.right)
|
|
||||||
.attr('height', height)
|
.attr('height', height)
|
||||||
.on('mouseover', () => { focus.style('display', null); })
|
.on('mouseover', () => { focus.style('display', null); })
|
||||||
.on('mouseout', () => { focus.style('display', 'none'); })
|
.on('mouseout', () => { focus.style('display', 'none'); })
|
||||||
.on('mousemove', mousemove);
|
.on('mousemove', mousemove);
|
||||||
|
|
||||||
|
function redraw() {
|
||||||
|
|
||||||
|
// Extract the width and height.
|
||||||
|
var width = plot_div.clientWidth;
|
||||||
|
if (width >= 900) width = 900;
|
||||||
|
|
||||||
|
// Use the extracted size to set the size of the SVG element.
|
||||||
|
vis.attr("width", width);
|
||||||
|
// Reduce width so that legend elements can be rendered
|
||||||
|
//width = width * 0.66;
|
||||||
|
|
||||||
|
// Redefine the variables according to the new clientWidth.
|
||||||
|
xRange.range([margins.left, width - margins.right]);
|
||||||
|
xTimeRange.range([margins.left, width - margins.right]);
|
||||||
|
|
||||||
|
lineFunc.defined(d => !isNaN(d.concentration))
|
||||||
|
.x(d => xTimeRange(d.time))
|
||||||
|
.y(d => yRange(d.concentration));
|
||||||
|
draw_line.attr("d", lineFunc(data));
|
||||||
|
|
||||||
|
plotTitleEl.attr('width', width);
|
||||||
|
|
||||||
|
var xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d));
|
||||||
|
|
||||||
|
xAxisEl.call(xAxis);
|
||||||
|
xAxisLabelEl.attr('x', (width + margins.right) / 2);
|
||||||
|
|
||||||
|
exposed_presence_intervals.forEach((b, index) => {
|
||||||
|
exposedArea[index].x(d => xTimeRange(d.time))
|
||||||
|
.y0(height - margins.bottom)
|
||||||
|
.y1(d => yRange(d.concentration));
|
||||||
|
|
||||||
|
drawArea[index].attr('d', exposedArea[index](data.filter(d => {
|
||||||
|
return d.time >= b[0] && d.time <= b[1]
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
|
legendLineIcon.attr('x', width + size);
|
||||||
|
|
||||||
|
// legendAreaIcon.attr('x', width + size);
|
||||||
|
|
||||||
|
// legendLineText.attr('x', width + 3 * size);
|
||||||
|
|
||||||
|
// legendAreaText.attr('x', width + 3 * size);
|
||||||
|
|
||||||
|
toolBox.attr('width', width - margins.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw for the first time to initialize.
|
||||||
|
redraw();
|
||||||
|
|
||||||
function mousemove() {
|
function mousemove() {
|
||||||
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||||
i = bisecHour(data, x0, 1),
|
i = bisecHour(data, x0, 1),
|
||||||
|
|
@ -181,4 +199,9 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
focus.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
focus.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||||
focus.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
focus.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);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -86,12 +86,13 @@
|
||||||
{% endblock report_summary_footnote %}
|
{% endblock report_summary_footnote %}
|
||||||
</div>
|
</div>
|
||||||
<p id="section1">* The results are based on the parameters and assumptions published in the CERN Open Report <a href="https://cds.cern.ch/record/2756083"> CERN-OPEN-2021-004</a>.</p>
|
<p id="section1">* The results are based on the parameters and assumptions published in the CERN Open Report <a href="https://cds.cern.ch/record/2756083"> CERN-OPEN-2021-004</a>.</p>
|
||||||
<svg id="result_plot" width="900" height="400"></svg>
|
|
||||||
|
<div id="result_plot" style="height: 400px"></div>
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
var times = {{times}}
|
var times = {{times}}
|
||||||
var concentrations = {{concentrations}}
|
var concentrations = {{concentrations}}
|
||||||
var exposed_presence_intervals = {{exposed_presence_intervals}}
|
var exposed_presence_intervals = {{exposed_presence_intervals}}
|
||||||
draw_concentration_plot("#result_plot", times, concentrations, exposed_presence_intervals);
|
draw_concentration_plot("result_plot", times, concentrations, exposed_presence_intervals);
|
||||||
</script>
|
</script>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue