Commit 17c52617f831a8b117dba01261bb7b1b4d8fd351

Authored by Goutte
1 parent b500e561

Make the orbits plot dynamic. (finally!)

1 -1.0.0-beta  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +1.0.0-rc3
3 \ No newline at end of file 3 \ No newline at end of file
@@ -102,7 +102,7 @@ targets: @@ -102,7 +102,7 @@ targets:
102 - type: 'planet' 102 - type: 'planet'
103 slug: 'earth' 103 slug: 'earth'
104 name: 'Earth' 104 name: 'Earth'
105 - title: 'Earth' 105 + title: 'Earth (Coming NEXT!)'
106 orbit: 106 orbit:
107 models: 107 models:
108 - slug: 'earth_orb_all' 108 - slug: 'earth_orb_all'
web/static/js/swapp.js
@@ -132,22 +132,29 @@ @@ -132,22 +132,29 @@
132 return results$; 132 return results$;
133 }; 133 };
134 SpaceWeather.prototype.enableTarget = function(target_slug){ 134 SpaceWeather.prototype.enableTarget = function(target_slug){
135 - var this$ = this; 135 + var ref$, this$ = this;
136 this.time_series.forEach(function(ts){ 136 this.time_series.forEach(function(ts){
137 if (ts.target.slug === target_slug && this$.parameters[ts.parameter].active) { 137 if (ts.target.slug === target_slug && this$.parameters[ts.parameter].active) {
138 return ts.show(); 138 return ts.show();
139 } 139 }
140 }); 140 });
141 this.targets[target_slug].active = true; 141 this.targets[target_slug].active = true;
  142 + if ((ref$ = this.orbits) != null) {
  143 + ref$.showOrbiter(target_slug);
  144 + }
142 return this; 145 return this;
143 }; 146 };
144 SpaceWeather.prototype.disableTarget = function(target_slug){ 147 SpaceWeather.prototype.disableTarget = function(target_slug){
  148 + var ref$;
145 this.time_series.forEach(function(ts){ 149 this.time_series.forEach(function(ts){
146 if (ts.target.slug === target_slug) { 150 if (ts.target.slug === target_slug) {
147 return ts.hide(); 151 return ts.hide();
148 } 152 }
149 }); 153 });
150 this.targets[target_slug].active = false; 154 this.targets[target_slug].active = false;
  155 + if ((ref$ = this.orbits) != null) {
  156 + ref$.hideOrbiter(target_slug);
  157 + }
151 return this; 158 return this;
152 }; 159 };
153 SpaceWeather.prototype.resize = function(){ 160 SpaceWeather.prototype.resize = function(){
@@ -706,9 +713,10 @@ @@ -706,9 +713,10 @@
706 this.data = {}; 713 this.data = {};
707 this.orbiters = {}; 714 this.orbiters = {};
708 this.orbitersElements = {}; 715 this.orbitersElements = {};
709 - this.extremum = 1;  
710 - this.xScale = d3.scaleLinear().domain([-1 * this.extremum, this.extremum]);  
711 - this.yScale = d3.scaleLinear().domain([this.extremum, -1 * this.extremum]); 716 + this.orbitersExtrema = {};
  717 + this.lastOrbiterData = {};
  718 + this.xScale = d3.scaleLinear().domain([-1, 1]);
  719 + this.yScale = d3.scaleLinear().domain([1, -1]);
712 this.xAxis = d3.axisBottom().ticks(10); 720 this.xAxis = d3.axisBottom().ticks(10);
713 this.yAxis = d3.axisLeft().ticks(10); 721 this.yAxis = d3.axisLeft().ticks(10);
714 this.svg = d3.select(this.container).append('svg'); 722 this.svg = d3.select(this.container).append('svg');
@@ -738,11 +746,6 @@ @@ -738,11 +746,6 @@
738 if (slug in this.orbitersElements) { 746 if (slug in this.orbitersElements) {
739 throw new Error("Second init of " + slug); 747 throw new Error("Second init of " + slug);
740 } 748 }
741 - this.extremum = Math.max(this.extremum, 1.11 * d3.max(data, function(d){  
742 - return Math.max(Math.abs(d.x), Math.abs(d.y));  
743 - }));  
744 - this.xScale = d3.scaleLinear().domain([-1 * this.extremum, this.extremum]);  
745 - this.yScale = d3.scaleLinear().domain([-1 * this.extremum, this.extremum]);  
746 orbit_ellipse = this.plotWrapper.append("svg:ellipse").classed('orbit orbit_ellipse', true); 749 orbit_ellipse = this.plotWrapper.append("svg:ellipse").classed('orbit orbit_ellipse', true);
747 orbiter = this.plotWrapper.append("svg:image").attr('xlink:href', config['img']).attr('width', '32px').attr('height', '32px'); 750 orbiter = this.plotWrapper.append("svg:image").attr('xlink:href', config['img']).attr('width', '32px').attr('height', '32px');
748 orbiter.append('svg:title').text(config.name); 751 orbiter.append('svg:title').text(config.name);
@@ -752,65 +755,122 @@ @@ -752,65 +755,122 @@
752 return this$.yScale(d.x); 755 return this$.yScale(d.x);
753 }); 756 });
754 orbit_section = this.plotWrapper.append('path').datum(data).classed('orbit orbit_section', true); 757 orbit_section = this.plotWrapper.append('path').datum(data).classed('orbit orbit_section', true);
755 - this.orbiters[slug] = config;  
756 this.data[slug] = data; 758 this.data[slug] = data;
  759 + this.orbiters[slug] = config;
757 this.orbitersElements[slug] = { 760 this.orbitersElements[slug] = {
758 orbiter: orbiter, 761 orbiter: orbiter,
759 orbit_ellipse: orbit_ellipse, 762 orbit_ellipse: orbit_ellipse,
760 orbit_section: orbit_section, 763 orbit_section: orbit_section,
761 orbit_line: orbit_line 764 orbit_line: orbit_line
762 }; 765 };
763 - this.resize(); 766 + this.orbitersExtrema[slug] = d3.max(data, function(d){
  767 + return Math.max(Math.abs(d.x), Math.abs(d.y));
  768 + });
764 $(this.svg.node()).show(); 769 $(this.svg.node()).show();
  770 + this.resize(true);
765 return this; 771 return this;
766 }; 772 };
  773 + Orbits.prototype.showOrbiter = function(slug){
  774 + this.orbiters[slug].hidden = false;
  775 + this.orbitersElements[slug].orbiter.style("display", null);
  776 + this.orbitersElements[slug].orbit_ellipse.style("display", null);
  777 + this.orbitersElements[slug].orbit_section.style("display", null);
  778 + return this.resize(true);
  779 + };
  780 + Orbits.prototype.hideOrbiter = function(slug){
  781 + this.orbiters[slug].hidden = true;
  782 + this.orbitersElements[slug].orbiter.style("display", "none");
  783 + this.orbitersElements[slug].orbit_ellipse.style("display", "none");
  784 + this.orbitersElements[slug].orbit_section.style("display", "none");
  785 + return this.resize(true);
  786 + };
767 Orbits.prototype.clear = function(){ 787 Orbits.prototype.clear = function(){
768 return $(this.svg.node()).remove(); 788 return $(this.svg.node()).remove();
769 }; 789 };
770 - Orbits.prototype.resize = function(){  
771 - var width, height, slug, ref$, config; 790 + Orbits.prototype.resize = function(animate){
  791 + var width, height, extremum, s, o, slug, ref$, config, t, t1, this$ = this;
  792 + animate == null && (animate = false);
772 width = Math.ceil($(this.container).width() - this.margin.left - this.margin.right); 793 width = Math.ceil($(this.container).width() - this.margin.left - this.margin.right);
773 height = Math.ceil(1.0 * width); 794 height = Math.ceil(1.0 * width);
774 console.debug("Resizing orbits : " + width + " × " + height + "…"); 795 console.debug("Resizing orbits : " + width + " × " + height + "…");
  796 + extremum = 1.1 * d3.max((function(){
  797 + var ref$, results$ = [];
  798 + for (s in ref$ = this.orbiters) {
  799 + o = ref$[s];
  800 + if (!o.hidden) {
  801 + results$.push(s);
  802 + }
  803 + }
  804 + return results$;
  805 + }.call(this)), function(d){
  806 + return this$.orbitersExtrema[d];
  807 + });
  808 + this.xScale = d3.scaleLinear().domain([-1 * extremum, extremum]);
  809 + this.yScale = d3.scaleLinear().domain([extremum, -1 * extremum]);
775 this.xScale.range([0, width]); 810 this.xScale.range([0, width]);
776 - this.yScale.range([0, height]); 811 + this.yScale.range([height, 0]);
777 this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom); 812 this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom);
778 this.sun.attr("x", width / 2 - 16).attr("y", height / 2 - 16); 813 this.sun.attr("x", width / 2 - 16).attr("y", height / 2 - 16);
779 for (slug in ref$ = this.orbiters) { 814 for (slug in ref$ = this.orbiters) {
780 config = ref$[slug]; 815 config = ref$[slug];
781 - this.resizeOrbiter(slug, config, width, height); 816 + this.resizeOrbiter(slug, config, width, height, animate);
782 } 817 }
783 this.xAxis.scale(this.xScale); 818 this.xAxis.scale(this.xScale);
784 this.yAxis.scale(this.yScale); 819 this.yAxis.scale(this.yScale);
785 - this.svg.select('.x.axis').attr('transform', 'translate(0,' + height + ')').call(this.xAxis);  
786 - this.svg.select('.y.axis').call(this.yAxis); 820 + this.svg.select('.x.axis').attr('transform', 'translate(0,' + height + ')');
  821 + if (animate) {
  822 + t = this.svg.transition().duration(750);
  823 + t1 = this.svg.transition().duration(4750);
  824 + this.svg.select('.x.axis').transition(t).call(this.xAxis);
  825 + this.svg.select('.y.axis').transition(t).call(this.yAxis);
  826 + } else {
  827 + this.svg.select('.x.axis').call(this.xAxis);
  828 + this.svg.select('.y.axis').call(this.yAxis);
  829 + }
787 this.xAxisTitle.attr("x", width / 2).attr("y", 37); 830 this.xAxisTitle.attr("x", width / 2).attr("y", 37);
788 this.yAxisTitle.attr("x", -1 * height / 2).attr("y", -30); 831 this.yAxisTitle.attr("x", -1 * height / 2).attr("y", -30);
789 return this; 832 return this;
790 }; 833 };
791 - Orbits.prototype.resizeOrbiter = function(slug, config, width, height){  
792 - var el, a, b, c, cx, cy, data; 834 + Orbits.prototype.resizeOrbiter = function(slug, config, width, height, animate){
  835 + var tt, el, orbit_section, t, a, b, c, cx, cy, orbit_ellipse;
  836 + animate == null && (animate = false);
793 console.debug("Resizing orbit of " + slug + "…"); 837 console.debug("Resizing orbit of " + slug + "…");
  838 + tt = this.svg.transition().duration(750);
794 el = this.orbitersElements[slug]; 839 el = this.orbitersElements[slug];
795 - el['orbit_section'].attr('d', el['orbit_line']); 840 + orbit_section = el['orbit_section'];
  841 + if (animate) {
  842 + t = this.svg.transition().duration(750);
  843 + orbit_section = orbit_section.transition(tt);
  844 + }
  845 + orbit_section.attr('d', el['orbit_line']);
796 a = config['orbit']['a']; 846 a = config['orbit']['a'];
797 b = config['orbit']['b']; 847 b = config['orbit']['b'];
798 c = Math.sqrt(a * a - b * b); 848 c = Math.sqrt(a * a - b * b);
799 cx = width / 2 - c; 849 cx = width / 2 - c;
800 cy = height / 2; 850 cy = height / 2;
801 - el['orbit_ellipse'].attr('cx', cx).attr('cy', cy).attr('rx', this.xScale(a) - this.xScale(0)).attr('ry', this.yScale(b) - this.yScale(0));  
802 - data = this.data[slug];  
803 - el['orbiter'].attr('x', this.xScale(data[data.length - 1].y) - 16);  
804 - el['orbiter'].attr('y', this.yScale(data[data.length - 1].x) - 16); 851 + orbit_ellipse = el['orbit_ellipse'];
  852 + if (animate) {
  853 + t = this.svg.transition().duration(750);
  854 + orbit_ellipse = orbit_ellipse.transition(t);
  855 + }
  856 + orbit_ellipse.attr('cx', cx).attr('cy', cy).attr('rx', this.xScale(a) - this.xScale(0)).attr('ry', this.yScale(b) - this.yScale(0));
  857 + this.repositionOrbiter(slug, null, true);
805 return this; 858 return this;
806 }; 859 };
807 - Orbits.prototype.repositionOrbiter = function(slug, datum){  
808 - var data, el; 860 + Orbits.prototype.repositionOrbiter = function(slug, datum, animate){
  861 + var data, el, t;
  862 + animate == null && (animate = false);
809 data = this.data[slug]; 863 data = this.data[slug];
  864 + datum == null && (datum = this.lastOrbiterData[slug]);
810 datum == null && (datum = data[data.length - 1]); 865 datum == null && (datum = data[data.length - 1]);
811 - el = this.orbitersElements[slug];  
812 - el['orbiter'].attr('x', this.xScale(datum.y) - 16);  
813 - el['orbiter'].attr('y', this.yScale(datum.x) - 16); 866 + this.lastOrbiterData[slug] = datum;
  867 + el = this.orbitersElements[slug]['orbiter'];
  868 + if (animate) {
  869 + t = this.svg.transition().duration(750);
  870 + el = el.transition(t);
  871 + }
  872 + el.attr('x', this.xScale(datum.y) - 16);
  873 + el.attr('y', this.yScale(datum.x) - 16);
814 return this; 874 return this;
815 }; 875 };
816 Orbits.prototype.bisectDate = d3.bisector(function(d){ 876 Orbits.prototype.bisectDate = d3.bisector(function(d){
web/static/js/swapp.ls
@@ -128,11 +128,13 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE @@ -128,11 +128,13 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE
128 enableTarget: (target_slug) -> 128 enableTarget: (target_slug) ->
129 @time_series.forEach((ts) ~> ts.show() if ts.target.slug == target_slug && @parameters[ts.parameter].active) 129 @time_series.forEach((ts) ~> ts.show() if ts.target.slug == target_slug && @parameters[ts.parameter].active)
130 @targets[target_slug].active = true 130 @targets[target_slug].active = true
  131 + @orbits?.showOrbiter target_slug
131 this 132 this
132 133
133 disableTarget: (target_slug) -> 134 disableTarget: (target_slug) ->
134 @time_series.forEach((ts) -> ts.hide() if ts.target.slug == target_slug) 135 @time_series.forEach((ts) -> ts.hide() if ts.target.slug == target_slug)
135 @targets[target_slug].active = false 136 @targets[target_slug].active = false
  137 + @orbits?.hideOrbiter target_slug
136 this 138 this
137 139
138 resize: -> 140 resize: ->
@@ -656,10 +658,11 @@ export class Orbits @@ -656,10 +658,11 @@ export class Orbits
656 658
657 @data = {} # slug => HEE array 659 @data = {} # slug => HEE array
658 @orbiters = {} # slug => config 660 @orbiters = {} # slug => config
659 - @orbitersElements = {}  
660 - @extremum = 1  
661 - @xScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])  
662 - @yScale = d3.scaleLinear().domain([@extremum, -1 * @extremum]) 661 + @orbitersElements = {} # see initOrbiter
  662 + @orbitersExtrema = {} # slug => local extrema
  663 + @lastOrbiterData = {} # slug => most recently used datum for position
  664 + @xScale = d3.scaleLinear().domain([-1, 1])
  665 + @yScale = d3.scaleLinear().domain([1, -1])
663 666
664 @xAxis = d3.axisBottom().ticks(10) 667 @xAxis = d3.axisBottom().ticks(10)
665 @yAxis = d3.axisLeft().ticks(10) 668 @yAxis = d3.axisLeft().ticks(10)
@@ -700,12 +703,6 @@ export class Orbits @@ -700,12 +703,6 @@ export class Orbits
700 console.info "Initializing orbit of #{config.name}…" 703 console.info "Initializing orbit of #{config.name}…"
701 if slug of @orbitersElements then throw new Error("Second init of #{slug}") 704 if slug of @orbitersElements then throw new Error("Second init of #{slug}")
702 705
703 - @extremum = Math.max(@extremum, 1.11 * d3.max(data, (d) ->  
704 - Math.max(Math.abs(d.x), Math.abs(d.y))  
705 - ))  
706 - @xScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])  
707 - @yScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])  
708 -  
709 # The order is important, as it will define the default z-order 706 # The order is important, as it will define the default z-order
710 orbit_ellipse = @plotWrapper.append("svg:ellipse") 707 orbit_ellipse = @plotWrapper.append("svg:ellipse")
711 .classed('orbit orbit_ellipse', true) 708 .classed('orbit orbit_ellipse', true)
@@ -722,31 +719,54 @@ export class Orbits @@ -722,31 +719,54 @@ export class Orbits
722 .datum(data) 719 .datum(data)
723 .classed('orbit orbit_section', true) 720 .classed('orbit orbit_section', true)
724 721
725 - @orbiters[slug] = config  
726 @data[slug] = data 722 @data[slug] = data
  723 + @orbiters[slug] = config
727 @orbitersElements[slug] = 724 @orbitersElements[slug] =
728 orbiter: orbiter 725 orbiter: orbiter
729 orbit_ellipse: orbit_ellipse 726 orbit_ellipse: orbit_ellipse
730 orbit_section: orbit_section 727 orbit_section: orbit_section
731 orbit_line: orbit_line 728 orbit_line: orbit_line
732 -  
733 - @resize() 729 + @orbitersExtrema[slug] = d3.max(data, (d) ->
  730 + Math.max(Math.abs(d.x), Math.abs(d.y))
  731 + )
734 732
735 $(@svg.node()).show(); 733 $(@svg.node()).show();
736 734
  735 + @resize(true)
  736 +
737 this 737 this
738 738
  739 + showOrbiter: (slug) ->
  740 + @orbiters[slug].hidden = false
  741 + @orbitersElements[slug].orbiter.style("display", null)
  742 + @orbitersElements[slug].orbit_ellipse.style("display", null)
  743 + @orbitersElements[slug].orbit_section.style("display", null)
  744 + @resize(true)
  745 +
  746 + hideOrbiter: (slug) ->
  747 + @orbiters[slug].hidden = true
  748 + @orbitersElements[slug].orbiter.style("display", "none")
  749 + @orbitersElements[slug].orbit_ellipse.style("display", "none")
  750 + @orbitersElements[slug].orbit_section.style("display", "none")
  751 + @resize(true)
  752 +
739 clear: -> 753 clear: ->
740 $(@svg.node()).remove() 754 $(@svg.node()).remove()
741 755
742 - resize: -> 756 + resize: (animate = false) ->
743 width = Math.ceil($(@container).width() - @margin.left - @margin.right) 757 width = Math.ceil($(@container).width() - @margin.left - @margin.right)
744 height = Math.ceil(1.0 * width) 758 height = Math.ceil(1.0 * width)
745 759
746 console.debug("Resizing orbits : #{width} × #{height}…") 760 console.debug("Resizing orbits : #{width} × #{height}…")
747 761
  762 + extremum = 1.1 * d3.max([s for s, o of @orbiters when not o.hidden], (d) ~>
  763 + @orbitersExtrema[d]
  764 + )
  765 + @xScale = d3.scaleLinear().domain([-1 * extremum, extremum])
  766 + @yScale = d3.scaleLinear().domain([extremum, -1 * extremum])
  767 +
748 @xScale.range([0, width]) 768 @xScale.range([0, width])
749 - @yScale.range([0, height]) 769 + @yScale.range([height, 0])
750 770
751 @svg.attr('width', width + @margin.right + @margin.left) 771 @svg.attr('width', width + @margin.right + @margin.left)
752 .attr('height', height + @margin.top + @margin.bottom) 772 .attr('height', height + @margin.top + @margin.bottom)
@@ -754,17 +774,20 @@ export class Orbits @@ -754,17 +774,20 @@ export class Orbits
754 @sun.attr("x", width / 2 - 16).attr("y", height / 2 - 16) 774 @sun.attr("x", width / 2 - 16).attr("y", height / 2 - 16)
755 775
756 for slug, config of @orbiters 776 for slug, config of @orbiters
757 - @resizeOrbiter(slug, config, width, height) 777 + @resizeOrbiter(slug, config, width, height, animate)
758 778
759 @xAxis.scale(@xScale) 779 @xAxis.scale(@xScale)
760 @yAxis.scale(@yScale) 780 @yAxis.scale(@yScale)
761 781
762 - @svg.select('.x.axis')  
763 - .attr('transform', 'translate(0,' + height + ')')  
764 - .call(@xAxis)  
765 -  
766 - @svg.select('.y.axis')  
767 - .call(@yAxis) 782 + @svg.select('.x.axis').attr('transform', 'translate(0,' + height + ')')
  783 + if animate
  784 + t = @svg.transition().duration(750)
  785 + t1 = @svg.transition().duration(4750)
  786 + @svg.select('.x.axis').transition(t).call(@xAxis);
  787 + @svg.select('.y.axis').transition(t).call(@yAxis);
  788 + else
  789 + @svg.select('.x.axis').call(@xAxis)
  790 + @svg.select('.y.axis').call(@yAxis)
768 791
769 @xAxisTitle.attr("x", width / 2) 792 @xAxisTitle.attr("x", width / 2)
770 .attr("y", 37) 793 .attr("y", 37)
@@ -773,35 +796,49 @@ export class Orbits @@ -773,35 +796,49 @@ export class Orbits
773 796
774 this 797 this
775 798
776 - resizeOrbiter: (slug, config, width, height) -> 799 + resizeOrbiter: (slug, config, width, height, animate = false) ->
777 console.debug("Resizing orbit of #{slug}…") 800 console.debug("Resizing orbit of #{slug}…")
778 801
  802 + tt = @svg.transition().duration(750)
779 el = @orbitersElements[slug] 803 el = @orbitersElements[slug]
780 - el['orbit_section'].attr('d', el['orbit_line']) 804 + orbit_section = el['orbit_section']
  805 + if animate
  806 + t = @svg.transition().duration(750)
  807 + orbit_section = orbit_section.transition(tt)
  808 + orbit_section.attr('d', el['orbit_line'])
781 809
782 a = config['orbit']['a'] 810 a = config['orbit']['a']
783 b = config['orbit']['b'] 811 b = config['orbit']['b']
784 c = Math.sqrt(a*a - b*b) 812 c = Math.sqrt(a*a - b*b)
785 cx = (width / 2) - c 813 cx = (width / 2) - c
786 cy = (height / 2) 814 cy = (height / 2)
787 - el['orbit_ellipse'].attr('cx', cx).attr('cy', cy) 815 +
  816 + orbit_ellipse = el['orbit_ellipse']
  817 + if animate
  818 + t = @svg.transition().duration(750)
  819 + orbit_ellipse = orbit_ellipse.transition(t)
  820 + # These ellipses ain't worth much
  821 + # Maybe a simple circle whose radius is the mean radius of the orbit ?
  822 + orbit_ellipse.attr('cx', cx).attr('cy', cy)
788 .attr('rx', @xScale(a) - @xScale(0)) 823 .attr('rx', @xScale(a) - @xScale(0))
789 .attr('ry', @yScale(b) - @yScale(0)) 824 .attr('ry', @yScale(b) - @yScale(0))
790 # .attr('transform', 'rotate(66,'+(cx+c)+', '+cy+')') 825 # .attr('transform', 'rotate(66,'+(cx+c)+', '+cy+')')
791 826
792 - data = @data[slug]  
793 -  
794 - el['orbiter'].attr('x', @xScale(data[data.length - 1].y) - 16)  
795 - el['orbiter'].attr('y', @yScale(data[data.length - 1].x) - 16) 827 + @repositionOrbiter(slug, null, true)
796 828
797 this 829 this
798 830
799 - repositionOrbiter: (slug, datum) -> 831 + repositionOrbiter: (slug, datum, animate = false) ->
800 data = @data[slug] 832 data = @data[slug]
  833 + datum ?= @lastOrbiterData[slug]
801 datum ?= data[data.length - 1] 834 datum ?= data[data.length - 1]
802 - el = @orbitersElements[slug]  
803 - el['orbiter'].attr('x', @xScale(datum.y) - 16)  
804 - el['orbiter'].attr('y', @yScale(datum.x) - 16) 835 + @lastOrbiterData[slug] = datum
  836 + el = @orbitersElements[slug]['orbiter']
  837 + if animate
  838 + t = @svg.transition().duration(750)
  839 + el = el.transition(t)
  840 + el.attr('x', @xScale(datum.y) - 16)
  841 + el.attr('y', @yScale(datum.x) - 16)
805 this 842 this
806 843
807 bisectDate: d3.bisector((d) -> d.t).left 844 bisectDate: d3.bisector((d) -> d.t).left