Experiment 1: Trace Scroller
Sections in this Article:
Scrolling Using Translate
In this example a live trace is rendered by adding points just off screen then scrolling them into view by applying an svg translate transform on a timer tick. For each tick of the timer, the entire graph is moved 1 unit to the left. When points are scrolled off screen, they are removed from the svg. This vastly improves the rendering performance. For more pleasing aesthetics, 'graph paper' is rendered as a background by drawing a very, very long rectangle with a ‘graph paper’ fill. The 'graph paper' fill is achieved by creating a custom pattern and selecting this as the fill for the rectangle.
Ultimately, if left long enough, the graph paper would fully scroll off to the left and eventually [not sure how many years], the translate function will reach an upper limit and either overflow or wrap around [this hasn’t been explored].
Referring to the performance monitor in Google’s Chrome, it can clearly be seen how efficient this solution is with the scripting time being minimal; a huge improvement on the previous example both in terms of scripting time and rendering and painting! This is possible as the ‘scrolling’ has been offloaded to the hardware and with any reasonable GPU; it will make short work of it!
Code
1"use strict"; 2class SVGScrollerTranslate extends HTMLElement { 3 4 // reference to the shadow DOM 5 _shadow = null; 6 7 // cache references to elements 8 _graph = null; 9 _pane1 = null; 10 _pointsCh1 = null; 11 _pointsCh2 = null; 12 13 _x = 0; // scroll position 14 _ticks = 0; // count of timer ticks 15 _timeId = null; // Id of the timer 16 17 constructor() { 18 super(); 19 } 20 21 connectedCallback() { 22 this._shadow = this.attachShadow({mode: 'open'}); 23 this.render(); 24 25 this._timeId = window.setInterval( () => { 26 // how many points between each logical division 27 let divisions=10; 28 29 // this can probably be optimised without setting the attribute! 30 this._pane1.setAttribute("transform", 'translate('+(this._x/divisions)+' 0)'); 31 32 // time position (x axis) - svg is 100 units wide, starting at 100 adds points to the right side which will then be scrolled into view 33 let chx = 100+(this._ticks/divisions); 34 35 // add some data - channel 1 36 let ch1y = 35+(Math.sin(this._ticks/(30))*12)+(Math.sin(this._ticks/(22*1))*9)+2+(Math.cos(this._ticks/(22*2))*7); 37 let point1 = this._graph.createSVGPoint(); 38 point1.x = chx; 39 point1.y = ch1y; 40 this._pointsCh1.appendItem(point1); 41 42 // add some data - channel 2 43 let ch2y = 35+(Math.cos(this._ticks/50)* 15); 44 let point2 = this._graph.createSVGPoint(); 45 point2.x = chx; 46 point2.y = ch2y; 47 this._pointsCh2.appendItem(point2); 48 49 // start removing points as soon as we have a screen width; this keeps the performance over time! 50 if(this._pointsCh1.length > (200*divisions)) this._pointsCh1.removeItem(0); 51 if(this._pointsCh2.length > (200*divisions)) this._pointsCh2.removeItem(0); 52 53 this._ticks++; // just a count of how many times the timer has fired! 54 this._x--; // scroll left; there will be an upper (or lower limit) 55 56 }, 10 ); // timer interval 57 } 58 59 render() { 60 if(!this._shadow) return; // not ready yet, don't render 61 62 let html=""; 63 64 html+='<style>:host { width: 100%; display: inline-block; text-align: center; }</style>'; 65 html+='<svg style="border: solid silver 1px;" width="100%" height="360px" preserveAspectRatio="none" id="graph" viewbox="0 10 100 60">'; 66 67 // defs - create a grid pattern which will be used to make the axis 68 html+=''; 69 html+='<defs>'; 70 html+=' <pattern id="pattern1"'; 71 html+=' x="10" y="10" width="20" height="20"'; 72 html+=' patternUnits="userSpaceOnUse" >'; 73 74 for(let i=1; i<21; i+=2) { 75 let stroke = (i==1||i==11) ? "0.05" : "0.02"; 76 html+=' <line x1="'+i+'" y1="0" x2="'+i+'" y2="20" style="stroke-width: '+stroke+'; stroke: #8080ff"; />'; 77 html+=' <line y1="'+i+'" x1="0" y2="'+i+'" x2="20" style="stroke-width: '+stroke+'; stroke: #8080ff"; />'; 78 } 79 html+=' </pattern>'; 80 html+='</defs>'; 81 82 // create a graphic group, this will be translated to produce the scrolling 83 html+=' <g id="pane1" transform="translate(0 0)">'; 84 85 // render a rectangle filled with the grid pattern 86 html+=' <rect x="0" y="0" width="1000000" height="100" style="stroke: none; fill: url(#pattern1);" />'; 87 88 // create two polyline for each channel - these will be populated with points later 89 html+=' <polyline id="channel1" style="fill:none;stroke:blue;stroke-width:0.2;opacity:0.7;"/>'; 90 html+=' <polyline id="channel2" style="fill:none;stroke:red;stroke-width:0.5;opacity:0.7;"/>'; 91 92 html+=' </g">'; 93 html+='</svg>'; 94 95 // cache references to objecs 96 this._shadow.innerHTML = html; 97 this._graph = this._shadow.querySelector("#graph"); 98 this._pane1 = this._graph.querySelector("#pane1"); 99 this._pointsCh1 = this._graph.querySelector("#channel1").points; 100 this._pointsCh2 = this._graph.querySelector("#channel2").points; 101 } 102} 103 104customElements.define('svg-scroller-translate', SVGScrollerTranslate ); 105
Continue on reading to Conclusions...