<!DOCTYPE html> <html> <head> <title></title> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .axis.y .tick line { stroke: #999999; stroke: rgba(0,0,0,0.075); } .axis.x .tick:nth-of-type(2n+1) text { display: none; } .line { fill: none; stroke-width: 1px; } .line.semimajor { stroke: steelblue; } .line.semimajor.prediction { stroke: steelblue; stroke-dasharray: 4, 4; } .line.apoapsis, .line.periapsis { opacity: 0.6; } .line.apoapsis { stroke: red; } .line.periapsis { stroke: green; } .legend text { font: 14px sans-serif; } .dots circle { fill: none; stroke: steelblue; } .dots line { fill: none; stroke: steelblue; stroke-width: 1.5px; opacity: 0.6; } .overlay { fill: none; pointer-events: all; } .focus line { fill: none; stroke: red; stroke-width: 1px; stroke-opacity: .5; shape-rendering: crispEdges; } .focus text { font-size: 14px; } </style> </head> <body> <script src="http://d3js.org/d3.v3.js"></script> <script src="simple_statistics.js"></script> <script> var margin = {top: 10, right: 60, bottom: 125, left: 40}, width = 1000 - margin.left - margin.right, height = 600 - margin.top - margin.bottom; var parseDate = d3.time.format("%Y-%m-%d").parse; var x = d3.time.scale() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom") .ticks(d3.time.day, 1); var yAxis = d3.svg.axis() .scale(y) .orient("left") .innerTickSize(-width) .tickPadding(8); var lineSemimajor = d3.svg.line() .interpolate('basis') .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.a); }); var linePeriapsis = d3.svg.line() .interpolate('basis') .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.apoapsis); }); var lineApoapsis = d3.svg.line() .interpolate('basis') .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.periapsis); }); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); svg.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); var diagram = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var legend = svg.append("g") .attr("class", "legend") .attr("transform", "translate(" + (width - 100) + ",17)"); var lSemiMajor = legend.append("g") .attr("transform", "translate(0,0)") lSemiMajor.append("rect") .attr("width", 12) .attr("height", 12) .attr("fill", "steelblue"); lSemiMajor.append("text") .attr("y", 11) .attr("x", 16) .text("Semi-Major axis"); var lApoapsis = legend.append("g") .attr("transform", "translate(0,20)") lApoapsis.append("rect") .attr("width", 12) .attr("height", 12) .attr("fill", "red"); lApoapsis.append("text") .attr("y", 11) .attr("x", 16) .text("Apogee"); var lPeriapsis = legend.append("g") .attr("transform", "translate(0,40)") lPeriapsis.append("rect") .attr("width", 12) .attr("height", 12) .attr("fill", "green"); lPeriapsis.append("text") .attr("y", 11) .attr("x", 16) .text("Perigee"); var focus, data; var bisectDate = d3.bisector(function(d) { return d.date; }).left; function mousemove() { var x0 = x.invert(d3.mouse(this)[0]), i = bisectDate(data, x0, 1, data.length - 1); var d0 = data[i - 1], d1 = data[i]; x0 = x0 >= data[0].date ? x0 <= data[data.length-1].date ? x0 : data[data.length-1].date : data[0].date; var y0 = d3.interpolate(d0.a, d1.a)( d3.scale.linear().domain([d0.date, d1.date])(x0) ); focus.select("line.x").attr("transform", "translate(" + x(x0) + ",0)"); focus.select("line.y").attr("transform", "translate(0," + y(y0) + ")"); focus.select("text").attr("transform", "translate(0," + y(y0) + ")"); focus.select("text").text(y0.toFixed(1) + ' km'); } d3.json("goceReentry.json", function(error, json) { data = json.map( clean ); data.forEach( calculateAltitude ); // var predictions = predict(data); x.domain(d3.extent(data, function(d) { return d.date; })).nice(d3.time.day); y.domain([ d3.min(data, function(d) { return d.periapsis; }), d3.max(data, function(d) { return d.apoapsis; }) ]).nice(); diagram.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); diagram.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Altitude [km]"); var dots = diagram.append("g") .attr("class", "dots") .selectAll(".dot") .data(data) .enter(); // dots.append("circle") // .attr("class", "dot") // .attr("r", 2) // .attr("cx", function(d) { return x(d.date); }) // .attr("cy", function(d) { return y(d.a); }); dots.append("line") .attr("class", "cross") .attr("transform", function(d) { return "translate(" + x(d.date) + "," + y(d.a) + ")rotate(45)"; }) .attr("x1", 0) .attr("x2", 0) .attr("y1", -4) .attr("y2", +4); dots.append("line") .attr("class", "cross") .attr("transform", function(d) { return "translate(" + x(d.date) + "," + y(d.a) + ")rotate(-45)"; }) .attr("x1", 0) .attr("x2", 0) .attr("y1", -4) .attr("y2", +4); diagram.append("path") .datum(data) .attr("class", "line semimajor") .attr("clip-path", "url(#clip)") .attr("d", lineSemimajor); diagram.append("path") .datum(data) .attr("class", "line apoapsis") .attr("clip-path", "url(#clip)") .attr("d", linePeriapsis); diagram.append("path") .datum(data) .attr("class", "line periapsis") .attr("clip-path", "url(#clip)") .attr("d", lineApoapsis); // diagram.append("path") // .datum([data[data.length-1]].concat(predictions)) // .attr("class", "line semimajor prediction") // .attr("clip-path", "url(#clip)") // .attr("d", lineSemimajor); // data = data.concat(predictions) focus = diagram.append("g") .attr("class", "focus") .style("display", "none"); focus.append("line") .attr("class", "x") .attr("x1", 0) .attr("y1", 0) .attr("x2", 0) .attr("y2", height); focus.append("line") .attr("class", "y") .attr("x1", 0) .attr("y1", 0) .attr("x2", width) .attr("y2", 0); focus.append("text") .attr("x", 6) .attr("y", -12); diagram.append("rect") .attr("class", "overlay") .attr("width", width) .attr("height", height) .on("mouseover", function() { focus.style("display", null); }) .on("mouseout", function() { focus.style("display", "none"); }) .on("mousemove", mousemove); }); // function predict(data) { // var step = 2*3600*1e3, // T0 = data[data.length-1].date.getTime(); // // Mean Motion // var meanMotionData = data.filter( function(d, i) { // return (data[data.length-1].date.getTime() - d.date.getTime() < 4*24*3600*1e3); // }).map( function(d) { // return [ // d.date.getTime(), d.meanMotion // ]; // }); // var meanMotion = ss.linear_regression() // .data(meanMotionData).line(); // // Mean Motion dot // var meanMotionDotData = data.filter( function(d, i) { // return (data[data.length-1].date.getTime() - d.date.getTime() < 3*24*3600*1e3) && (d.meanMotionDot > 0); // }).map( function(d) { // return [ // d.date.getTime(), d.meanMotionDot // ]; // }); // var meanMotionDot = ss.linear_regression() // .data(meanMotionDotData).line(); // // Mean Motion dotdot // var meanMotionDotDotData = data.filter( function(d, i) { // return (data[data.length-1].date.getTime() - d.date.getTime() < 2*24*3600*1e3) && (d.meanMotionDotDot > 0); // }).map( function(d) { // return [ // d.date.getTime(), d.meanMotionDotDot // ]; // }); // var meanMotionDotDot = ss.linear_regression() // .data(meanMotionDotDotData); // var predictions = []; // for (var i = 1; i < 25; i++) { // var dT = i * step, // dT_ = dT / ( 24 * 3600 * 1e3 ); // var d = {}; // d.date = new Date( T0 + dT ); // d.eccentricity = data[data.length-1].eccentricity; // d.meanMotion = data[data.length-1].meanMotion + meanMotionDot(T0 + dT) * dT_ + 0.5 * meanMotionDotDot.line()(T0 + dT) * dT_*dT_; // calculateAltitude(d); // predictions.push(d); // if ( d.a < 100 ) break; // } // return predictions; // } function clean(d) { return { date: new Date( d.EPOCH + '.' + d.EPOCH_MICROSECONDS + 'Z' ), eccentricity: +d.ECCENTRICITY, inclination: +d.INCLINATION / 180 * Math.PI, meanMotion: +d.MEAN_MOTION, meanMotionDot: 2 * d.MEAN_MOTION_DOT, meanMotionDotDot: 6 * d.MEAN_MOTION_DDOT } } var GM = 398600.4418 * 1e9, rE = 6378.1e3, f = GM / ( 4 * Math.PI*Math.PI ); function calculateAltitude(d) { d.period = 1 / d.meanMotion * 24 * 3600; var a = Math.pow(f * d.period*d.period, 1/3); d.a = ((a - rE) * 1e-3); d.apoapsis = ((a * (1 + d.eccentricity) - rE) * 1e-3); d.periapsis = ((a * (1 - d.eccentricity) - rE) * 1e-3); } </script> </body> </html>