math = function() { var π = Math.PI, ε = 1e-6, math = { version: "0.0.1" }, math_radians = π / 180, math_degrees = 180 / π, math_document = document, math_window = window; math.util = {}; math.util.concat = function(a, b) { var x = []; for (var i = 0; i < a.length; i++) x.push(a[i]); for (var i = 0; i < b.length; i++) x.push(b[i]); return x; }; math.psd = {}; math.psd.collapse = function(P) { var d = numeric.dim(P); if (d[0] != d[1]) throw Error("P must be square"); var V = []; for (var i = 0; i < d[0]; i++) { for (var j = i; j < d[1]; j++) { V.push(P[i][j]); } } return V; }; math.psd.dilate = function(V) { var d = numeric.dim(V); if (d.length != 1) throw Error("Cannot dilate a matrix"); var n = -.5 + Math.sqrt(.5 * .5 + 2 * d[0]); var P = []; k = 0; for (var i = 0; i < n; i++) P[i] = []; for (var i = 0; i < n; i++) { for (var j = i; j < n; j++) { P[i][j] = V[k]; if (i != j) P[j][i] = V[k]; k++; } } return P; }; math.quaternion = {}; math.quaternion.q = function q(w, x, y, z) { this.w = w; this.x = x; this.y = y; this.z = z; }; math.quaternion.q.prototype.add = function(_) { return new math.quaternion.q(this.w + _.w, this.x + _.x, this.y + _.y, this.z + _.z); }; math.quaternion.q.prototype.sub = function(_) { return new math.quaternion.q(this.w - _.w, this.x - _.x, this.y - _.y, this.z - _.z); }; math.quaternion.q.prototype.dot = function(_) { return this.w * _.w + this.x * _.x + this.y * _.y + this.z * _.z; }; math.quaternion.q.prototype.mul = function(_) { var a1 = this.w, b1 = this.x, c1 = this.y, d1 = this.z; if (typeof _ == "object") { var a2 = _.w, b2 = _.x, c2 = _.y, d2 = _.z; return new math.quaternion.q(a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2, a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2, a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2, a1 * d2 + b1 * c2 - c1 * b2 + d1 * a2); } else if (typeof _ == "number") return new math.quaternion.q(a1 * _, b1 * _, c1 * _, d1 * _); }; math.quaternion.q.prototype.conj = function() { return new math.quaternion.q(this.w, -this.x, -this.y, -this.z); }; math.quaternion.q.prototype.norm = function() { return numeric.norm2([ this.w, this.x, this.y, this.z ]); }; math.quaternion.q.prototype.yaw = function() { return Math.atan2(2 * (this.w * this.z + this.x * this.y), 1 - 2 * (this.y * this.y + this.z * this.z)); }; math.quaternion.q.prototype.pitch = function() { return Math.asin(2 * (this.w * this.y - this.z * this.x)); }; math.quaternion.q.prototype.roll = function() { return Math.atan2(2 * (this.w * this.x + this.y * this.z), 1 - 2 * (this.x * this.x + this.y * this.y)); }; math.quaternion.q.prototype.dcm = function() { var w = this.w, x = this.x, y = this.y, z = this.z; return [ [ w * w + x * x - y * y - z * z, 2 * (x * y - w * z), 2 * (x * z + w * y) ], [ 2 * (y * x + w * z), w * w - x * x + y * y - z * z, 2 * (y * z - w * x) ], [ 2 * (z * x - w * y), 2 * (w * x + y * z), w * w - x * x - y * y + z * z ] ]; }; math.quaternion.q.prototype.vec = function() { return [ this.w, this.x, this.y, this.z ]; }; math.quaternion.identity = function() { return new math.quaternion.q(1, 0, 0, 0); }; math.quaternion.rotateX = function(theta) { return new math.quaternion.q(Math.cos(theta / 2), Math.sin(theta / 2), 0, 0); }; math.quaternion.rotateY = function(theta) { return new math.quaternion.q(Math.cos(theta / 2), 0, Math.sin(theta / 2), 0); }; math.quaternion.rotateZ = function(theta) { return new math.quaternion.q(Math.cos(theta / 2), 0, 0, Math.sin(theta / 2)); }; math.quaternion.rotate = function(v, theta) { if (v.length != 3) throw new Error("math.quaternion.rotate defined for R3"); var n = Math.sin(theta / 2) / numeric.norm2(v); return new math.quaternion.q(Math.cos(theta / 2), v[0] * n, v[1] * n, v[2] * n); }; math.quaternion.euler = function(yaw, pitch, roll) { temp1 = math.quaternion.rotateZ(yaw); temp2 = temp1.mul(math.quaternion.rotateY(pitch)); return temp2.mul(math.quaternion.rotateX(roll)); }; math.quaternion.permuteZXY = function() { return new math.quaternion.q(.5, .5, .5, .5); }; math.quaternion.permuteYZX = function() { return new math.quaternion.q(.5, -.5, -.5, -.5); }; math.quaternion.swapXYflipZ = function() { return new math.quaternion.q(0, Math.sqrt(2) / 2, Math.sqrt(2) / 2, 0); }; math.quaternion.swapXZflipY = function() { return new math.quaternion.q(0, Math.sqrt(2) / 2, 0, Math.sqrt(2) / 2); }; math.quaternion.swapYZflipX = function() { return new math.quaternion.q(0, 0, Math.sqrt(2) / 2, Math.sqrt(2) / 2); }; math.quaternion.slerp = function(q1, q2, u) { var theta = Math.acos(q1.dot(q2)); var st = Math.sin(theta); var su = Math.sin(u * theta); var suu = Math.sin((1 - u) * theta); var a1 = q1.mul(suu / st); var a2 = q2.mul(su / st); var b = a1.add(a2); return b.mul(1 / b.norm()); }; math.models = {}; math.models.poly = function(order, n) { var J, ns = (order + 1) * n; function derivative(t, x) { calcJ(t, x); return numeric.dot(J, x); } function calcJ(t, x) { if (x.length != ns) throw Error("Wrong number of states"); J = numeric.rep([ ns, ns ], 0); var I = numeric.identity(ns - 1); numeric.setBlock(J, [ 0, n ], [ ns - n - 1, ns - 1 ], I); } derivative.jacobian = function(t, x) { if (arguments.length == 2) calcJ(t, x); return J; }; derivative.numStates = function(_) { if (!arguments.length) return ns; ns = _; return derivative; }; return derivative; }; math.models.meas = {}; math.models.meas.simple = function(m, ns) { var J; function to(t, x) { calcJ(t, x); return numeric.dot(J, x); } function calcJ(t, x) { if (!ns) ns = x.length; J = numeric.rep([ m, ns ], 0); numeric.setBlock(J, [ 0, 0 ], [ m - 1, m - 1 ], numeric.identity(m)); } to.jacobian = function(t, x) { if (arguments.length == 2) calcJ(t, x); return J; }; return to; }; math.state = function(t, x, P) { var extra, flattened; s = {}; s.t = t; s.x = x; s.P = P; s.flatten = function() { var xout = []; for (var i in x) xout.push(x[i]); var pvec = math.psd.collapse(P); for (var i in pvec) xout.push(pvec[i]); s.flattened = xout; return xout; }; s.meta = function(_) { if (!arguments.length) return extra; extra = _; return s; }; return s; }; math.state.dilate = function(t, vec) { if (arguments.length == 1) { vec = t; t = 0; } var n = 1; switch (vec.length) { case 2: n = 1; break; case 5: n = 2; break; case 9: n = 3; break; } var x = numeric.getBlock(vec, [ 0 ], [ n - 1 ]); var P = math.psd.dilate(numeric.getBlock(vec, [ n ], [ vec.length - 1 ])); return new math.state(t, x, P); }; math.kalman = {}; math.kalman.dynamics = function(f, q) { return function(t, xP) { var state = math.state.dilate(t, xP); var x = state.x; var P = state.P; var xd = f(t, x); var F = f.jacobian(); var Ft = numeric.transpose(F); var Pd = numeric.add(numeric.dot(F, P), numeric.dot(P, Ft)); if (q) var Pd = numeric.add(Pd, q(t, x)); var p = math.psd.collapse(Pd); for (var i = 0; i < p.length; i++) xd.push(p[i]); return xd; }; }; math.kalman.predict = function(f, q) { return function(state, time_to) { if (state.t == time_to.t) return state; var vec = state.flatten(), fdyn = math.kalman.dynamics(f, q); var sol = numeric.dopri(state.t, time_to, vec, fdyn); var n = sol.x.length - 1; return math.state.dilate(time_to, sol.y[n]); }; }; math.kalman.update = function(h) { return function(state, meas) { var t = state.t, x = state.x, P = state.P, z = meas.z, R = meas.R; if (!R.length) R = [ [ R ] ]; var zp = h(t, x), H = h.jacobian(), Ht = numeric.transpose(H), Rp = numeric.dot(H, numeric.dot(P, Ht)); var S = numeric.add(Rp, R); var K = numeric.dot(numeric.dot(P, Ht), numeric.inv(S)); var innovation = numeric.sub(z, zp); var xE = numeric.add(x, numeric.dot(K, innovation)); var I = numeric.identity(x.length), kh = numeric.dot(K, H), ikh = numeric.sub(I, kh), ikht = numeric.transpose(ikh), krk = numeric.dot(K, numeric.dot(R, numeric.transpose(K))); var Pe = numeric.add(numeric.dot(ikh, numeric.dot(P, ikht)), krk); var s = new math.state(t, xE, Pe); return s.meta({ Gain: K, innovation: innovation, S: S }); }; }; math.geo = {}; math.geo.earth = math_geo_earth; function math_geo_earth(earth_type) { var Re = 6378137, Rp = 6356752.3142, μ = 3986004418e5, μA = 35e7, ω = 72921151467e-15, f = 1 / 298.257223563, ε = .08181919084262149, J2 = .0010826299890519; if (arguments.length == 1) { type = earth_type.split(" "); type.forEach(function(t) { switch (t) { case "wgs84": break; case "spherical": Re = 6371e3; f = 0; ε = 0; J2 = 0; break; case "inertial": ω = 0; break; } }); } var model = function(state) { var Ω = [ [ 0, -ω, 0 ], [ ω, 0, 0 ], [ 0, 0, 0 ] ]; var ΩΩ = [ [ -ω * ω, 0, 0 ], [ 0, -ω * ω, 0 ], [ 0, 0, 0 ] ]; var r = state.slice(0, 3); var v = state.slice(3, 6); var R = Math.sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2]), RSq = R * R, ReSq = Re * Re, ur = [ r[0] / R, r[1] / R, r[2] / R ], up = [ 0, 0, 1 ], Cc = r[2] / R, CcSq = Cc * Cc; var g = [ 0, 0, 0 ]; for (var i = 0; i < 3; i++) { g[i] = -μ / RSq * (ur[i] - 1.5 * (J2 * ReSq / RSq) * ((5 * CcSq - 1) * ur[i] - 2 * Cc * up[i])); } var a = [ 0, 0, 0 ]; var coriolis = [ 0, 0, 0 ]; var centrifugal = [ 0, 0, 0 ]; for (var i = 0; i < 3; i++) { if (v.length == 3) { for (var j = 0; j < 3; j++) { coriolis[i] += -2 * Ω[i][j] * v[j]; centrifugal[i] += -ΩΩ[i][j] * r[j]; } } a[i] = g[i] + coriolis[i] + centrifugal[i]; } return a; }; model.toGeodetic = function(position) { var x = position[0], y = position[1], z = position[2], xSq = x * x, ySq = y * y; var eSq = ε * ε, r = Math.sqrt(xSq + ySq), b = Re * Math.sqrt(1 - eSq); var lon = Math.atan2(y, x); if (ε == 0) { lat = Math.atan2(z, r); H = Math.sqrt(x * x + y * y + z * z) - Re; } else { var l = eSq / 2, lSq = l * l, m = Math.pow(r / Re, 2), n = Math.pow((1 - eSq) * z / b, 2), i = -(2 * lSq + m + n) / 2, k = lSq * (lSq - m - n), q = Math.pow(m + n - 4 * lSq, 3) / 216 + m * n * lSq, D = Math.sqrt((2 * q - m * n * lSq) * m * n * lSq), beta = i / 3 - Math.pow(q + D, 1 / 3) - Math.pow(q - D, 1 / 3); var sign = 0; if (m - n > 0) { sign = 1; } else if (m - n < 0) { sign = -1; } var t = Math.sqrt(Math.sqrt(beta * beta - k) - (beta + i) / 2) - sign * Math.sqrt((beta - i) / 2); var r0 = r / (t + l); var z0 = (1 - eSq) * z / (t - l); var lat = Math.atan(z0 / ((1 - eSq) * r0)); sign = 0; if (t - 1 + l > 0) { sign = 1; } else if (t - 1 + l < 0) { sign = -1; } var H = sign * Math.sqrt((r - r0) * (r - r0) + (z - z0) * (z - z0)); } return [ lat * math_degrees, lon * math_degrees, H ]; }; model.toCartesian = function(gc) { var lat = gc[0] * math_radians, lon = gc[1] * math_radians, alt = gc[2], slat = Math.sin(lat), clat = Math.cos(lat); var R = Re / Math.sqrt(1 - ε * ε * slat * slat); var x = (R + alt) * clat * Math.cos(lon); var y = (R + alt) * clat * Math.sin(lon); var z = (R + alt - ε * ε * R) * slat; return [ x, y, z ]; }; model.semimajor_axis = Re; model.semiminor_axis = Rp; model.eccentricity = ε; model.flattening = f; model.rotation_rate = ω; model.GM = μ; model.GM_atm = μA; model.J2 = J2; return model; } math.geo.orbital = math_geo_orbital; function math_geo_orbital() { var earth = math.geo.earth("spherical inertial"), Re = earth.semimajor_axis, μ = earth.GM; var γ = 45 * math_radians; function model(launch, impact) { var R0 = earth.toCartesian(launch), r0 = numeric.norm2(R0), ur0 = numeric.mul(R0, 1 / r0); var Rf = earth.toCartesian(impact), rf = numeric.norm2(Rf), urf = numeric.mul(Rf, 1 / rf); var φ = Math.acos(numeric.dot(ur0, urf)), V = Math.sqrt(μ * (1 - Math.cos(φ)) / (r0 * Math.cos(γ) * (r0 * Math.cos(γ) / Re - Math.cos(φ + γ)))); var tempK = cross(ur0, urf), K = numeric.mul(tempK, 1 / numeric.norm2(tempK)); var q = math.quaternion.rotate(K, Math.PI / 2 - γ), qv = new math.quaternion.q(0, ur0[0], ur0[1], ur0[2]), rotR = q.mul(qv.mul(q.conj())).vec().slice(1, 4), urotR = numeric.mul(rotR, 1 / numeric.norm2(rotR)), velocity = numeric.mul(urotR, V); return [ 0, 0, 0, 0, 0, 0 ].map(function(d, i) { if (i < 3) return R0[i]; else return velocity[i - 3]; }); } model.flightTime = function(r, φ, V) { var cg = Math.cos(γ), sg = Math.sin(γ), tg = Math.tan(γ), cp = Math.cos(φ), sp = Math.sin(φ), cgp = Math.cos(γ + φ), cot = 1 / Math.tan(φ / 2), λ = r * V * V / μ; var first_term = (tg * (1 - cp) + (1 - λ) * sp) / ((2 - λ) * ((1 - cp) / (λ * cg * cg) + cgp / cg)), sec_term = 2 * cg / (λ * Math.pow(2 / λ - 1, 1.5)) * Math.atan2(Math.sqrt(2 / λ - 1), cg * cot - sg); return r / (V * cg) * (first_term + sec_term); }; function cross(a, b) { return [ b[2] * a[1] - b[1] * a[2], b[0] * a[2] - b[2] * a[0], b[1] * a[0] - b[0] * a[1] ]; } model.velocity = function(_) { if (!arguments.length) return V; V = _; return model; }; model.angle = function(_) { if (!arguments.length) return γ; γ = _; return model; }; return model; } math.radar = {}; math.radar.header = math_radar_header; function math_radar_header() { var time = 0, nf = -30, f0 = 2424e5, bw = 1335e5, location = [ 0, 0 ], numberSamples = Math.pow(2, 15), numberChannels = 1269; var header = {}; header.origin = function() { var elements = [], l = 3e8 / f0; for (var i = 0; i < numberChannels; i++) elements.push({ x: (i - (numberChannels - 1) / 2) * (l / 2) + location[0], y: location[1] }); return elements; }; header.time = function(_) { if (!arguments.length) return time; time = _; return header; }; header.f0 = function(_) { if (!arguments.length) return f0; f0 = _; return header; }; header.bw = function(_) { if (!arguments.length) return bw; bw = _; return header; }; header.nf = function(_) { if (!arguments.length) return nf; nf = _; return header; }; header.loc = function(_) { if (!arguments.length) return location; location = _; return header; }; header.numberSamples = function(_) { if (!arguments.length) return numberSamples; numberSamples = _; return header; }; return header; } math.radar.pb = math_radar_pulse_builder; function math_radar_pulse_builder() { var pulse, header, inFrequencyDomain = true; function pb() { var z = numeric.rep([ header.numberSamples() ], 0); pulse = numeric.t(z, z); } pb.toTimeDomain = function() { if (!inFrequencyDomain) return pulse; inFrequencyDomain = false; return pulse.ifft(); }; pb.addNoise = function(noiseFloor) { var sigf = Math.sqrt(.5 * pulse.x.length) * Math.pow(10, noiseFloor / 20); pulse.x = pulse.x.map(function(d) { return d + jStat.randn() * sigf; }); pulse.y = pulse.y.map(function(d) { return d + jStat.randn() * sigf; }); }; pb.addPointResponse = function(state, power) { var f = numeric.linspace(header.f0() - header.bw() / 2, header.f0() + header.bw() / 2, pulse.x.length); var k = numeric.mul(4 * Math.PI / 3e8, f); var loc = header.loc(); var r = numeric.norm2(numeric.sub(state, loc)); var A = Math.pow(10, power / 20); for (var i = 0; i < pulse.x.length; i++) { pulse.x[i] += A * Math.cos(-k[i] * r); pulse.y[i] += A * Math.sin(-k[i] * r); } }; pb.pulse = function(_) { if (!arguments.length) return pulse; pulse = _; return pb; }; pb.header = function(_) { if (!arguments.length) return header; header = _; return pb; }; pb.header = function(_) { if (!arguments.length) return header; header = _; return pb; }; return pb; } math.plot = {}; math.plot.axis = math_plot_axis; function math_plot_axis() { var axis = d3.svg.axis(), scale = d3.scale.linear(), ticks = null, grid, gridStroke = function(d) { return d ? "#ccc" : "#666"; }, interp, label, labelMargin = 60, container; axis.scale(scale).orient("bottom").tickFormat(function(d) { return d; }); function chart(selection) { selection.each(function(data) { container = d3.select(this); var g = container.selectAll("g.math.axis").data([ data ]); var g1 = g.enter().append("g").attr("class", "math axis"); g1.append("g").attr("class", "the axis"); if (ticks !== null) axis.ticks(ticks); chart.draw(); }); } function draw_label() { var g = container.select("g.the.axis").selectAll("text.label").data([ 0 ]); var scale = axis.scale(); switch (axis.orient()) { case "bottom": var w = scale.range()[1]; g.enter().append("text").attr("class", "label").attr("x", w / 2).attr("y", labelMargin).attr("dx", "1em").style("text-anchor", "middle").text(label); break; case "left": g.enter().append("text").attr("class", "label").attr("transform", "rotate(-90)").attr("y", 0 - labelMargin).attr("x", 0 - scale.range()[0] / 2).attr("dy", "1em").style("text-anchor", "middle").text(label); break; } } chart.draw = function() { var g = container.select("g.the.axis"); if (interp) { g.transition().ease("linear").call(axis); } else g.call(axis); if (grid) { g.selectAll("g").each(function() { d3.select(this).append("svg:line").attr("class", "axis line").style("stroke", gridStroke).attr("x1", grid.x1).attr("x2", grid.x2).attr("y1", grid.y1).attr("y2", grid.y2); }); } draw_label(); }; chart.axis = axis; d3.rebind(chart, axis, "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat"); chart.margin = function(_) { if (!arguments.length) return labelMargin; labelMargin = _; return chart; }; chart.scale = function(_) { if (!arguments.length) return scale; scale = _; axis.scale(scale); return chart; }; chart.ticks = function(_) { if (!arguments.length) return ticks; ticks = _; return chart; }; chart.grid = function(_) { if (!arguments.length) return grid; grid = _; return chart; }; chart.label = function(_) { if (!arguments.length) return label; label = _; return chart; }; chart.interp = function(_) { if (!arguments.length) return interp; interp = _; return chart; }; return chart; } math.plot.zoom = math_plot_zoom; function math_plot_zoom() { var container; function chart(selection, f) { selection.each(function() { var xAxis = f.xAxis(); var yAxis = f.yAxis(); var width = f.width(); var height = f.height(); var margin = f.margin(); var zoomable = f.zoom(); var xyzoom = d3.behavior.zoom().x(xAxis.scale()).y(yAxis.scale()).on("zoom", zoomable ? f.draw : null); var xzoom = d3.behavior.zoom().x(xAxis.scale()).on("zoom", zoomable ? f.draw : null); var yzoom = d3.behavior.zoom().y(yAxis.scale()).on("zoom", zoomable ? f.draw : null); container = d3.select(this).append("g").attr("class", "zoom").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); container.append("svg:rect").attr("class", "zoom xy box").attr("width", width - margin.left - margin.right).attr("height", height - margin.top - margin.bottom).style("visibility", "hidden").attr("pointer-events", "all").call(xyzoom); container.append("svg:rect").attr("class", "zoom x box").attr("width", width).attr("height", margin.bottom).attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")").style("visibility", "hidden").attr("pointer-events", "all").call(xzoom); container.append("svg:rect").attr("class", "zoom y box").attr("width", margin.left).attr("height", height).attr("transform", "translate(" + -margin.left + "," + 0 + ")").style("visibility", "hidden").attr("pointer-events", "all").call(yzoom); }); } chart.update = function(f) { var xAxis = f.xAxis(); var yAxis = f.yAxis(); var zoomable = f.zoom(); var xyzoom = d3.behavior.zoom().x(xAxis.scale()).y(yAxis.scale()).on("zoom", zoomable ? f.draw : null); var xzoom = d3.behavior.zoom().x(xAxis.scale()).on("zoom", zoomable ? f.draw : null); var yzoom = d3.behavior.zoom().y(yAxis.scale()).on("zoom", zoomable ? f.draw : null); container.select("rect.zoom.xy.box").call(xyzoom); container.select("rect.zoom.x.box").call(xzoom); container.select("rect.zoom.y.box").call(yzoom); }; return chart; } math.plot.scatter = math_plot_scatter; function math_plot_scatter() { var margin = { top: 60, bottom: 80, left: 60, right: 0 }, width = 940, height = 600, xValue = function(d) { return d.x; }, yValue = function(d) { return d.y; }, cValue = function() { return "steelblue"; }, fValue = null, xAxis = math.plot.axis(), yAxis = math.plot.axis(), cScale = d3.scale.linear().domain([ 0, 1 ]).range([ "hsl(250, 100%, 50%)", "hsl(0, 100%, 50%)" ]).interpolate(d3.interpolateHsl), markerSize = function() { return 1; }, dfilter = function() { return true; }, zoom = math.plot.zoom(), zoomable = false, container; function chart(selection) { selection.each(function(data) { data = data.map(function(d, i) { if (!fValue) return [ xValue.call(data, d, i), yValue.call(data, d, i), cValue.call(data, d, i) ]; else return [ xValue.call(data, d, i), yValue.call(data, d, i), cValue.call(data, d, i), fValue.call(data, d, i) ]; }); container = d3.select(this).selectAll("svg").data([ data ]); container.enter().append("svg"); var g = container.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); g.append("defs").append("clipPath").attr("id", "clip").append("rect").attr("width", width - margin.left - margin.right).attr("height", height - margin.top - margin.bottom); g.append("svg:rect").attr("class", "border").attr("width", width - margin.left - margin.right).attr("height", height - margin.top - margin.bottom).style("stroke", "black").style("fill", "none"); g.append("g").attr("class", "x axis").attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")"); g.append("g").attr("class", "y axis"); g.append("g").attr("class", "scatter").attr("clip-path", "url(#clip)"); g.append("g").attr("class", "legend"); if (cValue) cScale.domain(d3.extent(data, function(d) { return d[2]; })); xAxis.grid({ x1: 0, x2: 0, y1: -(height - margin.top - margin.bottom), y2: 0 }).orient("bottom").tickPadding(10); yAxis.grid({ x1: width - margin.left - margin.right, x2: 0, y1: 0, y2: 0 }).orient("left").tickPadding(10); xAxis.scale().domain(d3.extent(data, function(d) { return d[0]; })).range([ 0, width - margin.left - margin.right ]); container.select("g.x.axis").call(xAxis); yAxis.scale().domain(d3.extent(data, function(d) { return d[1]; })).range([ height - margin.top - margin.bottom, 0 ]); container.select("g.y.axis").call(yAxis); container.call(zoom, chart); chart.draw(); }); return chart; } function update() { var gs = container.select("g.scatter"); var circle = gs.selectAll("circle").data(function(d) { return d; }); circle.style("fill", function(d) { return dfilter(d) ? C(d) : "none"; }).attr("cx", function(d) { return X(d); }).attr("cy", function(d) { return Y(d); }).attr("r", markerSize); circle.enter().append("svg:circle").attr("class", "points").style("fill", function(d) { return C(d); }).attr("cx", function(d) { return X(d); }).attr("cy", function(d) { return Y(d); }).attr("r", markerSize); circle.exit().remove(); } chart.draw = function() { container.select("g.x.axis").call(xAxis); container.select("g.y.axis").call(yAxis); update(); zoom.update(chart); }; function X(d) { var xScale = xAxis.scale(); return xScale(d[0]); } function Y(d) { var yScale = yAxis.scale(); return yScale(d[1]); } function C(d) { return cScale(d[2]); } chart.xaxis = xAxis; d3.rebind(chart, xAxis, "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat"); chart.yaxis = yAxis; d3.rebind(chart, yAxis, "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat"); chart.margin = function(_) { if (!arguments.length) return margin; margin.top = typeof _.top != "undefined" ? _.top : margin.top; margin.right = typeof _.right != "undefined" ? _.right : margin.right; margin.bottom = typeof _.bottom != "undefined" ? _.bottom : margin.bottom; margin.left = typeof _.left != "undefined" ? _.left : margin.left; return chart; }; chart.width = function(_) { if (!arguments.length) return width; width = _; return chart; }; chart.height = function(_) { if (!arguments.length) return height; height = _; return chart; }; chart.markersize = function(_) { if (!arguments.length) return markerSize; markerSize = _; return chart; }; chart.xAxis = function(_) { if (!arguments.length) return xAxis; xAxis = _; return chart; }; chart.yAxis = function(_) { if (!arguments.length) return yAxis; yAxis = _; return chart; }; chart.caxis = function(_) { if (!arguments.length) return cScale.domain(); cScale.domain(_); return chart; }; chart.x = function(_) { if (!arguments.length) return xValue; xValue = _; return chart; }; chart.y = function(_) { if (!arguments.length) return yValue; yValue = _; return chart; }; chart.c = function(_) { if (!arguments.length) return cValue; cValue = _; return chart; }; chart.filter = function(_, __) { if (!arguments.length) return [ fValue, dfilter ]; fValue = _; dfilter = __; return chart; }; chart.zoom = function(_) { if (!arguments.length) return zoomable; zoomable = _; return chart; }; return chart; } math.plot.line = math_plot_line; function math_plot_line() { var margin = { top: 60, bottom: 80, left: 60, right: 0 }, width = 940, height = 600, zoom = math.plot.zoom(), zoomable = false, xValue = function(d) { return d.x; }, yValue = function(d) { return d.y; }, groupValue = function(d) { return d.id; }, xAxis = math.plot.axis(), yAxis = math.plot.axis(), color = d3.scale.category20c(), lineWidth = function() { return "2pt"; }, interp = false, ylim, container; function chart(selection) { selection.each(function(data) { var flattened_data = data.map(function(d, i) { return [ xValue.call(data, d, i), yValue.call(data, d, i), groupValue.call(data, d, i) ]; }); data = d3.nest().key(function(d) { return I(d); }).entries(flattened_data); container = d3.select(this).selectAll("svg").data([ data ]); container.enter().append("svg"); var g = container.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); g.append("defs").append("clipPath").attr("id", "clip").append("rect").attr("width", width - margin.left - margin.right).attr("height", height - margin.top - margin.bottom); g.append("svg:rect").attr("class", "border").attr("width", width - margin.left - margin.right).attr("height", height - margin.top - margin.bottom).style("stroke", "black").style("fill", "none"); g.append("g").attr("class", "x axis").attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")"); g.append("g").attr("class", "y axis"); g.append("g").attr("class", "lineChart").attr("clip-path", "url(#clip)"); g.append("g").attr("class", "legend"); xAxis.grid({ x1: 0, x2: 0, y1: -(height - margin.top - margin.bottom), y2: 0 }).orient("bottom").tickPadding(10); xAxis.scale().domain(d3.extent(flattened_data, function(d) { return d[0]; })).range([ 0, width - margin.left - margin.right ]); container.select("g.x.axis").call(xAxis); yAxis.grid({ x1: width - margin.left - margin.right, x2: 0, y1: 0, y2: 0 }).orient("left").tickPadding(10); if (ylim) { yAxis.scale().domain(ylim).range([ height - margin.top - margin.bottom, 0 ]); } else { yAxis.scale().domain(d3.extent(flattened_data, function(d) { return d[1]; })).range([ height - margin.top - margin.bottom, 0 ]); } container.select("g.y.axis").call(yAxis); container.call(zoom, chart); chart.draw(); }); } chart.update = function() { var line = d3.svg.line().x(function(d) { return X(d); }).y(function(d) { return Y(d); }).interpolate("linear"); var gl = container.select("g.lineChart"); var l = gl.selectAll("path.lines").data(function(d) { return d; }); if (interp) { l.transition().ease("linear"); } l.attr("class", "lines").style("stroke", function(d) { return color(+d.key); }).style("stroke-width", lineWidth).style("fill", "none").attr("d", function(d) { return line(d.values); }); l.enter().append("svg:path").attr("class", "lines").style("stroke", function(d) { return color(+d.key); }).style("stroke-width", lineWidth).style("fill", "none").attr("d", function(d) { return line(d.values); }); if (interp) { l.exit().transition().ease("linear").remove(); } else { l.exit().remove(); } }; chart.draw = function() { container.select("g.x.axis").call(xAxis); container.select("g.y.axis").call(yAxis); chart.update(); zoom.update(chart); }; function X(d) { var xScale = xAxis.scale(); return xScale(d[0]); } function Y(d) { var yScale = yAxis.scale(); return yScale(d[1]); } function I(d) { return d[2]; } chart.xaxis = xAxis; d3.rebind(chart, xAxis, "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat"); chart.yaxis = yAxis; d3.rebind(chart, yAxis, "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat"); chart.margin = function(_) { if (!arguments.length) return margin; margin.top = typeof _.top != "undefined" ? _.top : margin.top; margin.right = typeof _.right != "undefined" ? _.right : margin.right; margin.bottom = typeof _.bottom != "undefined" ? _.bottom : margin.bottom; margin.left = typeof _.left != "undefined" ? _.left : margin.left; return chart; }; chart.width = function(_) { if (!arguments.length) return width; width = _; return chart; }; chart.height = function(_) { if (!arguments.length) return height; height = _; return chart; }; chart.xAxis = function(_) { if (!arguments.length) return xAxis; xAxis = _; return chart; }; chart.yAxis = function(_) { if (!arguments.length) return yAxis; yAxis = _; return chart; }; chart.x = function(_) { if (!arguments.length) return xValue; xValue = _; return chart; }; chart.y = function(_) { if (!arguments.length) return yValue; yValue = _; return chart; }; chart.group = function(_) { if (!arguments.length) return groupValue; groupValue = _; return chart; }; chart.linewidth = function(_) { if (!arguments.length) return lineWidth; lineWidth = _; return chart; }; chart.colorscale = function(_) { if (!arguments.length) return color; color = _; return chart; }; chart.zoom = function(_) { if (!arguments.length) return zoomable; zoomable = _; return chart; }; chart.interp = function(_) { if (!arguments.length) return interp; interp = _; return chart; }; chart.ylim = function(_) { if (!arguments.length) return ylim; ylim = _; return chart; }; return chart; } return math; }();