Skip to content

Instantly share code, notes, and snippets.

@tachycline
Last active January 8, 2016 17:25
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/9210910 to your computer and use it in GitHub Desktop.
Save tachycline/9210910 to your computer and use it in GitHub Desktop.
Interactive Charges

Interactive forces on charges

We've used physlets for a number of years to teach concepts related to electric forces, fields, and potentials. Simulations like these are an excellent way to cope with the fact that the experiments we could do for this collection of material don't deal with individual charges, and you can't see fields or potentials directly.

I'm not, however, enthusiastic about the fact that they're written in Java. When physlets originated, it was the most cross-platform, web-delivered solution, but technology has moved quite a bit since then. I wanted to see if I could duplicate some (or perhaps eventually all) of the functionality of physlets for electricity and magnetism using javascript in the browser. This is my first stab.

Charges can be color coded (red are positive, blue are negative) and sized according to the magnitude of the charge. The arrows show the magnitude and direction of the force. Charges are draggable, and forces update on the fly.

<!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=960;
var height=500;
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/5]);
var forcescaley = d3.scale.linear().domain([0,1/(minsep*minsep)]).range([0,height/5]);
var rscale = d3.scale.linear().domain([0,2.5]).range([3,10])
charges = [];
ncharges = 10;
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) {
return 0;
};
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", "field");
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("svg g g")
.append("circle")
.attr("cx",0)
.attr("cy",0)
.attr("r", 5)
.attr("fill", "#008837");
d3.selectAll("svg g 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('svg g g path')
.attr("d", function(d, i) {theforce = force_on(i);
pathdata = arrowpath(forcescalex(theforce[0]), forcescaley(theforce[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("svg g 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("svg g 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