Skip to content

Instantly share code, notes, and snippets.

@mtaptich
Last active June 5, 2016 17:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mtaptich/d339957ec9a7324de6413ca82f820b82 to your computer and use it in GitHub Desktop.
Save mtaptich/d339957ec9a7324de6413ca82f820b82 to your computer and use it in GitHub Desktop.
Capture the Plume!

The control of contaminant migration in groundwater is a common task for environmental engineers. One way of completing this work is by installing recovery wells downstream of the chemical plume. In this visualization, I illustrate how pumping rates effect the "capture zone" of the recovery well under various groundwater flow rates. The setting assumes that the plume is contained within a confined aquifer that is 30m thick. The coordinates are a plan view of the system and the groundwater flow direction is shown via the moving circles.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
width: 1024px;
margin-top: 0;
margin: auto;
font-family: "Lato", "PT Serif", serif;
color: #222222;
padding: 0;
font-weight: 300;
line-height: 28px;
-webkit-font-smoothing: antialiased;
}
.axis path,
.axis line{
fill: none;
stroke: black;
}
.line{
fill: none;
stroke: blue;
stroke-width: 2px;
}
.tick text{
font-size: 12px;
}
.tick line{
opacity: 0.2;
}
.xbottom path.domain{
stroke:none;
}
.x text, .y text{
fill:none;
}
.plume{
fill:#9b59b6;
fill-opacity: 0.5;
stroke: #000;
stroke-width:2px;
}
.running{
fill: #2980b9;
}
</style>
<body>
<div id='graph'></div>
<div style='width:500px; margin-right:50px;'>
<div style="float:left; width:200px; margin-left:50px">
<div>GW Flow Rate (Ki):</div>
<label for="nflow">
<span id="flow-value" style='font-weight:bold'>5</span> cm/s <br>
<input style="width: 120px;" type="range" min="1" max="10" id="flow" value="3.4" step='0.1'>
</label>
</div>
<div style="float:left; width:200px;">
<div>Pumping Rate (Q):</div>
<label for="nQ">
<span id="Q-value" style='font-weight:bold'>500</span> m<sup>3</sup>/day<br>
<input style="width: 120px;" type="range" min="50" max="2000" id="Q" value="500" step='1'>
</label>
</div>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 220, bottom: 50, left: 100},
width = 650 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom,
flow = 5,
Q = 500/86400,
plume_width = 250;
var x = d3.scale.linear()
.range([0, width])
.domain([-90*3,210*3]);
var y = d3.scale.linear()
.range([height, 0])
.domain([-150*3,150*3]);
var color = d3.scale.category10();
var xAxistop = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height/2)
.outerTickSize(0)
.tickPadding(10);
var xAxisbottom = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(height/2)
.outerTickSize(0)
.tickPadding(10);
var yAxisRight = d3.svg.axis()
.scale(y)
.orient("left")
.innerTickSize(-width*.7)
.outerTickSize(0)
.tickPadding(10);
var yAxisLeft = d3.svg.axis()
.scale(y)
.orient("left")
.innerTickSize(width*.3)
.outerTickSize(0)
.tickPadding(10);
var svg = d3.select("#graph").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height/2 + ")")
.call(xAxistop)
svg.append("g")
.attr("class", "xbottom axis")
.attr("transform", "translate(0," + height/2 + ")")
.call(xAxisbottom)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", height/2)
.attr('dy', "2.2em")
.style("text-anchor", "end")
.text("x (e.g., longitudinal axis), meters");
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width*0.3 + ",0)")
.call(yAxisRight)
svg.append("g")
.attr("class", "yleft axis")
.attr("transform", "translate(" + width*0.3 + ",0)")
.call(yAxisLeft)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)translate(0," + -width*0.3 + ")")
.attr("y", -50)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("y, meters")
var area = svg.append('path')
.attr('class', 'plume')
svg.append('circle')
.attr('cx', x(0))
.attr('cy', y(0))
.attr('r', 3)
.style('fill','#fff')
.style('stroke','#000')
function input_data(){
var xposition = 1300 + Math.random()*50*(flow/5);
var c = svg.append('circle')
.attr('cy', (y(-plume_width) - y(plume_width))*Math.random() + y(plume_width))
.attr('cx', x(xposition))
.attr('r', (3*Math.random()) + 2)
.attr('class', 'running')
.style('fill-opacity', Math.random() + 0.3)
c.transition()
.duration((xposition-600)/(flow/100))
.attr('cx', x(600))
.ease('linear')
.transition()
.duration(100)
.style('opacity', 0)
.remove();
}
function boundary(y){
return -y / Math.tan(2*Math.PI * (flow/(100*24*3600)) * 30 * y / Q)
}
function cone(){
d = 'M'+x(boundary(0.1))+" "+y(0.1)
py = 1
px = boundary(py)
while (px <= 600) {
px = boundary(py);
d+= ' L '+x(px)+" "+y(py)+','
py+=0.1
}
d+= 'M'+x(boundary(0.1))+" "+y(0.1)
py = 1
px = boundary(py)
while (px <= 600 ) {
px = boundary(py);
d+= ' L '+x(px)+" "+y(-py)+','
py+=0.1
}
d+= ' L '+x(px)+" "+y(py)
area.attr('d', d)
}
d3.select("#flow").on("input", function() {
flow = +this.value;
d3.select('#flow-value').text(flow);
cone()
//cone()
});
d3.select("#Q").on("input", function() {
var eQ = +this.value;
Q = eQ/86400
d3.select('#Q-value').text(eQ);
cone()
//cone()
});
function pulse(){
for (var i = 0; i < 50; i++) {
input_data()
}
}
pulse()
setInterval(pulse, 1000)
setInterval(function(){ plume_width = Math.round(Math.random()*40)*10;}, 10000)
cone()
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment