Polare Darstellung mit D3 animieren
Trotz grossem Aufwand überzeugt das polare Plotly-Diagramm nicht wirklich.
Also habe ich mich mit D3 nochmals an die Arbeit gemacht, und das Resultat sieht eindeutig besser aus. Damit die beiden vergleichbar sind, ist der Stil ziemlich ähnlich.
Die Ladezeit der beiden Seiten ist nicht zu vergleichen, die D3-Version ist sehr viel schneller als Plotly. Die Animation läuft ebenfalls deutlich schneller, und die Geschwindigkeit liesse sich noch steigern.
Das Programm dazu
Der Code ist um einiges länger als bei der Python-Version, und auch nicht so einfach zu schreiben:
const months = [ { "name": "Jan", "angle": 0 }, { "name": "Feb", "angle": 30.57534247 }, { "name": "Mär", "angle": 58.19178082 }, { "name": "Apr", "angle": 88.76712329 }, { "name": "Mai", "angle": 118.3561644 }, { "name": "Jun", "angle": 148.9315068 }, { "name": "Jul", "angle": 178.5205479 }, { "name": "Aug", "angle": 209.0958904 }, { "name": "Sep", "angle": 239.6712329 }, { "name": "Okt", "angle": 269.2602740 }, { "name": "Nov", "angle": 299.8356164 }, { "name": "Dez", "angle": 329.4246575 } ] ; d3.json( "polar.json" ).then( data => { const svg = d3.select( "#polar" ) ; const rscale = d3.scaleLinear().domain( [0,75] ).range( [0,300] ) ; const line = d3.lineRadial().radius( d => rscale(d.value) ).angle( (_,i) => i / 365 * 2 * Math.PI ) ; let end = 2 ; let auto = true ; const group = svg.append( "g" ) .attr( "transform", "translate(330,330)" ) ; const gr = group.selectAll( "g" ) .data( [15,30,45,60,75] ) .enter() .append( "g" ) ; const ga = group.append( "g" ) .selectAll( "g" ) .data( months ) .enter() .append( "g" ) .attr( "transform", d => `rotate(${d.angle-90})` ) ; gr.append( "circle" ) .attr( "r", d => rscale(d) ) .attr( "fill", "none" ) .attr( "stroke", "rgb(64,64,64)" ) ; gr.append( "text" ) .attr( "fill", "white" ) .attr( "transform", "rotate(15)" ) .attr( "y", d => -rscale(d) - 5 ) .attr( "text-anchor", "middle" ) .text( d => d ) ; ga.append( "text" ) .attr( "x", "305" ) .attr( "fill", "white" ) .attr( "text-anchor", "middle" ) .attr( "transform", d => `rotate(${90},305,0)` ) .text( d => d.name ) ; ga.append( "line" ) .attr( "x2", "300" ) .attr( "fill", "none" ) .attr( "stroke", "rgb(64,64,64)" ) ; group.append( "path" ) .attr( "d", line(data.slice(0,2)) ) .attr( "fill", "none" ) ; const tooltip = group.append( "g" ) .style( "display", "none" ) ; tooltip.append( "rect" ) .attr( "width", 156 ) .attr( "height", 50 ) .attr( "rx", 4 ) .attr( "ry", 4 ) ; tooltip.append( "text" ) .attr( "id", "upper-date" ) .attr( "fill", "black" ) .attr( "x", 10 ) .attr( "y", 30 ) ; tooltip.append( "text" ) .attr( "id", "upper-value" ) .attr( "fill", "black" ) .attr( "x", 112 ) .attr( "y", 30 ) ; tooltip.append( "text" ) .attr( "id", "lower-date" ) .attr( "fill", "black" ) .attr( "x", 10 ) .attr( "y", 39 ) ; tooltip.append( "text" ) .attr( "id", "lower-value" ) .attr( "fill", "black" ) .attr( "x", 112 ) .attr( "y", 39 ) ; function updateTooltip( pos ) { const index = Math.round( (Math.atan2( -pos[0], pos[1] ) + Math.PI) / (2 * Math.PI) * 365 ) ; const value = rscale.invert( Math.sqrt(pos[0]*pos[0]+pos[1]*pos[1]) ) ; if( end > index ) { tooltip.select( "#upper-date" ).text( data[index].day ) ; tooltip.select( "#upper-value" ).text( data[index].value.toFixed(1) ) ; } if( end > index + 365 ) { tooltip.select( "#upper-date").attr( "y", 21 ) ; tooltip.select( "#upper-value").attr( "y", 21 ) ; tooltip.select( "#lower-date" ).style( "display", null ).text( data[index+365].day ) ; tooltip.select( "#lower-value" ).style( "display", null ).text( data[index+365].value.toFixed(1) ) ; } else { tooltip.select( "#upper-date").attr( "y", 32 ) ; tooltip.select( "#upper-value").attr( "y", 32 ) ; tooltip.select( "#lower-date" ).style( "display", "none" ) ; tooltip.select( "#lower-value" ).style( "display", "none" ) ; } if( (end < index) || (value < 32) || (value > 72) ) { tooltip.style( "display", "none" ) ; } else { tooltip.attr( "transform", `translate(${pos[0]-78},${pos[1]-53})` ) ; tooltip.style( "display", null ) ; } } group.on( "mouseover", () => tooltip.style("display",null) ) ; group.on( "mouseout", () => tooltip.style("display","none") ) ; group.on( "mousemove", function() { updateTooltip(d3.mouse(this)) ; } ) ; function updateView( end ) { document.querySelector( "#day" ).textContent = data[end].day ; group.select( "path" ) .attr( "d", line(data.slice(0,end+1)) ) .attr( "fill", "none" ) .attr( "stroke", "rgb(240,249,33)" ) ; } function update() { if( auto && (end < data.length) ) { document.querySelector( "#slider" ).value = end ; updateView( end++ ) ; setTimeout( update, 50 ) ; } } function sliderInput() { auto = false ; end = +this.value ; updateView( end ) ; } update() ; document.querySelector( "#slider" ).addEventListener( "input", sliderInput ) ; }) ;
Die Daten
JavaScript und JSON gehören zusammen, und deshalb sind die Daten als JSON abgelegt. Irgendwelche seltsamen Tricks wie bei Plotly sind nicht nötig:
[{"day":"01.01.2018","value":33.0264791275622}, {"day":"02.01.2018","value":50.23564716870469}, {"day":"03.01.2018","value":53.15636351184351}, {"day":"04.01.2018","value":53.3859972224156}, {"day":"05.01.2018","value":54.71566858468588}, . .