|
<!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> |