Skip to content

Instantly share code, notes, and snippets.

@tachycline
Last active February 5, 2016 14:43
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 tachycline/4aae734c7726c53fbec0 to your computer and use it in GitHub Desktop.
Save tachycline/4aae734c7726c53fbec0 to your computer and use it in GitHub Desktop.
Electric field visualization

Electric Field visualization

This is another in my series of attempts to port physlets to d3. This one allows you to see the electric field in the neighborhood of a charge. Actually, all of the internal machinery is there to have multiple charges, but it was making the display too cluttered.

I'd like to add buttons to add and remove charges, but I don't have time to do that right at the moment.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Charges with Forces</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
body {
background-color: #dfc27d;
}
svg {
background-color: white;
}
circle.dragging {
fill: yellow;
stroke: black;
}
div#control {
padding: 10px;
background-color: white;
}
</style></head>
<body>
<form>
<!-- <label for="ncharges">Number of Charges</label>
<input type="range" id="ncharges" min=3 max=25 value=10></br>
<label for="fscale">Force scale</label>
<input type="range" id="fscale" min=3 max=25 value=10><br> -->
<input type="checkbox" name="decorations" value="colors">Color
charges by sign <br>
<input type="checkbox" name="decorations" value="size"> Size charges
by magnitude </br>
<label for="xval">x:</label>
<span id="xval"> </span><br>
<label for="yval">y:</label>
<span id="yval"> </span><br>
<label for="qval">q:</label>
<span id="qval"> </span><br>
<label for="force">Force:</label>
<span id="force"> </span><br>
</form>
<script type="text/javascript">
var width=950;
var height=500;
resolution = 50;
nx = width/resolution;
ny = height/resolution;
points = []
for (i=0; i<nx; ++i) {
col = []
for (j=0; j<ny; ++j) {
col.push({x:i*resolution, y:j*resolution});
}
col.forEach(function(entry) {points.push(entry)});
}
var grid = {points:points,
};
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
// scales
var simscalex = d3.scale.linear().domain([0, width]).range([0, 0.01]);
var simscaley = d3.scale.linear().domain([0,height]).range([0, 0.01*height/width]);
minsep = simscalex(width/20)
var forcescalex = d3.scale.linear().domain([0,1/(minsep*minsep)]).range([0,width/15]);
var forcescaley = d3.scale.linear().domain([0,1/(minsep*minsep)]).range([0,width/15]);
var rscale = d3.scale.linear().domain([0,2.5]).range([3,10])
charges = [];
ncharges = 1;
for (index=0; index < ncharges; ++index) {
x = Math.random()*width;
y = Math.random()*height;
q = Math.random()*5 - 2.5;
charges.push({x:x, y:y, q:q});
}
function field_at(x,y) {
var fieldx = 0;
var fieldy = 0;
var k = 9.0e-1
charges.forEach(function(entry) {
dx = simscalex(x - entry.x);
dy = simscaley(y - entry.y);
f = -k*q/(dx*dx + dy*dy);
theta = Math.atan2(dy,dx);
fieldx = fieldx + f*Math.cos(theta)
fieldy = fieldy + f*Math.sin(theta)
});
return [fieldx, fieldy];
};
function force_on(num) {
netforcex = 0;
netforcey = 0;
var xval = charges[num].x;
var yval = charges[num].y;
var testcharge = charges[num].q;
for (index = 0; index < charges.length; ++index) {
if (index != num) {
attract = charges[index].q*testcharge/Math.abs(charges[index].q*testcharge);
dx = simscalex(charges[index].x - xval);
dy = simscaley(charges[index].y - yval);
f = Math.abs(testcharge*charges[index].q/(dx*dx + dy*dy));
theta = Math.atan2(dy,dx);
netforcex = netforcex - attract*f*Math.cos(theta);
netforcey = netforcey - attract*f*Math.sin(theta);
};
};
return [netforcex, netforcey];
};
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
var chargegroup = svg.append("g").attr("id", "chargegroup");
var fieldgroup = svg.append("g").attr("id", "fieldgroup");
d3.select("g#fieldgroup")
.selectAll("g")
.data(points)
.enter()
.append("g")
.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"});
d3.select("g#chargegroup")
.selectAll("g")
.data(charges)
.enter()
.append("g")
.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"})
.call(drag);
d3.selectAll("g#fieldgroup g")
.append("circle")
.attr("cx",0)
.attr("cy",0)
.attr("r", 0.5)
.attr("fill", "#222222");
d3.selectAll("g#fieldgroup g").append("path")
.attr("d", function(d,i) {
thefield = field_at(d.x, d.y);
pathdata = arrowpath(forcescalex(thefield[0]), forcescaley(thefield[1]));
return pathdata;})
.attr("stroke", "#000000")
.attr("fill", "#000000");
d3.selectAll("g#chargegroup g")
.append("circle")
.attr("cx",0)
.attr("cy",0)
.attr("r", 5)
.attr("fill", "#008837");
// d3.selectAll("g#chargegroup g").append("path")
// .attr("d", function(d,i) {
// theforce = force_on(i);
// pathdata = arrowpath(forcescalex(theforce[0]), forcescaley(theforce[1]));
// return pathdata;})
// .attr("stroke", "#000000")
// .attr("fill", "#000000");
function arrowpath(dx, dy) {
var theta = Math.atan2(dy, dx);
var theta2 = theta + 0.9*Math.PI;
var theta3 = theta - 0.9*Math.PI;
tiplength = 10;
pathdata = "M " + dx + " " + dy;
x2 = dx+tiplength*Math.cos(theta2);
x3 = dx+tiplength*Math.cos(theta3);
y2 = dy+tiplength*Math.sin(theta2);
y3 = dy+tiplength*Math.sin(theta3);
pathdata = pathdata + " L " + x2 + " " + y2;
pathdata = pathdata + " L " + x3 + " " + y3;
pathdata = pathdata + " L " + dx + " " + dy;
pathdata = pathdata + " L 0 0";
return pathdata
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
// update the control area
d3.select("span#xval").text(d.x);
d3.select("span#yval").text(d.y);
d3.select("span#qval").text(d.q);
}
function dragged(d,i) {
d.x = d3.event.x;
d.y = d3.event.y;
myforce = force_on(i);
// update the control area
d3.select("span#xval").text(d.x);
d3.select("span#yval").text(d.y);
d3.select("span#qval").text(d.q);
d3.select("span#force").text("("+[myforce[0],myforce[1]]+")");
d3.select(this)
.attr("transform", function(d) {return "translate(" +
d.x + "," + d.y + ")"});
//update all the paths
d3.selectAll('g#chargegroup g path')
.attr("d", function(d, i) {theforce = force_on(i);
pathdata = arrowpath(forcescalex(theforce[0]), forcescaley(theforce[1]));
return pathdata});
d3.selectAll('g#fieldgroup g path')
.attr("d", function(d, i) {thefield = field_at(d.x, d.y);
pathdata = arrowpath(forcescalex(thefield[0]), forcescaley(thefield[1]));
return pathdata});
}
function dragended(d) {
d3.select(this).classed("dragging", false);
// clear the control area
d3.select("span#xval").text("");
d3.select("span#yval").text("");
d3.select("span#qval").text("");
d3.select("span#force").text("");
}
// add checkbox behavior
d3.select("input[value=colors]").on("click", function() {checked=d3.select("input[value=colors]").property("checked");
if (checked) {
d3.selectAll("g#chargegroup g circle").attr("fill", function(d) {if (d.q > 0) {
return "#2c7bb6";
}
else {
return "#d7191c";
} }) }
else {
d3.selectAll("svg g g circle").attr("fill", "#008837")}
});
d3.select("input[value=size]").on("click", function() {checked=d3.select("input[value=size]").property("checked");
if (checked) {
d3.selectAll("g#chargegroup g circle").attr("r", function(d) {return rscale(Math.abs(d.q))}) }
else { d3.selectAll("svg g g circle").attr("r", 5)}
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment