Generation of the Rossler attractor and visualization of the solution of the system on the 3 planes.
This is the canvas version (except for the axes, so d3-axis could be used). Compare to the SVG version
Visualization of other attractors:
| license: gpl-3.0 |
Generation of the Rossler attractor and visualization of the solution of the system on the 3 planes.
This is the canvas version (except for the axes, so d3-axis could be used). Compare to the SVG version
Visualization of other attractors:
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| canvas, svg { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| } | |
| .axisLabel { | |
| font-family: sans-serif; | |
| font-size: 20; | |
| } | |
| </style> | |
| <canvas width="960" height="500"></canvas> | |
| <svg width="960" height="500"></svg> | |
| <script src="https://d3js.org/d3.v4.min.js"></script> | |
| <script> | |
| const canvas = document.querySelector("canvas"), | |
| context = canvas.getContext("2d"); | |
| const svg = d3.select("svg") | |
| // //Append marker in svg defs | |
| const defs = svg.append("defs"); | |
| const marker = defs.append('marker') | |
| .attr("id", "arrow") | |
| .attr("refX", 12) | |
| .attr("refY", 6) | |
| .attr("markerUnits", 'userSpaceOnUse') | |
| .attr("markerWidth", 12) | |
| .attr("markerHeight", 18) | |
| .attr("orient", 'auto') | |
| .append('path') | |
| .attr("d", 'M 0 0 12 6 0 12 3 6'); | |
| //canvas linear gradient | |
| const canvasGradient = context.createLinearGradient(20, 0, 960, 0); | |
| canvasGradient.addColorStop(0, '#d7191c'); | |
| canvasGradient.addColorStop(0.25, '#f29e2e'); | |
| canvasGradient.addColorStop(0.50, '#d7191c'); | |
| canvasGradient.addColorStop(0.75, '#f29e2e'); | |
| canvasGradient.addColorStop(1, '#d7191c'); | |
| //initial conditions and parameters for Rossler system | |
| let x = 0, | |
| y = z = 1, | |
| t = 0.05, | |
| iter = 0, | |
| max_iter = 10000; | |
| const rosslerParameters = { | |
| a: 0.2, | |
| b: 0.2, | |
| c: 5.7 | |
| } | |
| //scale the rossler attractor | |
| const scaleFactor = 14 | |
| //store the iterations of the solution of Rossler | |
| let dataMain = [], | |
| dataXY = [], | |
| dataXZ = [], | |
| dataYZ = []; | |
| //To store spatial coordinates | |
| const Vertex = function(x, y, z) { | |
| this.x = parseFloat(x); | |
| this.y = parseFloat(y); | |
| this.z = parseFloat(z); | |
| }; | |
| const Vertex2D = function(x, y) { | |
| this.x = parseFloat(x); | |
| this.y = parseFloat(y); | |
| }; | |
| //takes (x,y) coordinates | |
| const lineGenerator = d3.line() | |
| .x(d => d.x) | |
| .y(d => d.y) | |
| .curve(d3.curveCardinal) | |
| .context(context); | |
| const lineGeneratorAxes = d3.line() | |
| .x(d => d.x) | |
| .y(d => d.y) | |
| .curve(d3.curveBasis) | |
| //initial dataPoint | |
| let dataPoint = new Vertex(x, y, z) | |
| const dxMain = 500, | |
| dyMain = 400, | |
| dxXZ = 100, | |
| dyXZ = 400, | |
| dxXY = 100, | |
| dyXY = 100, | |
| dxYZ = 300, | |
| dyYZ = 100; | |
| //rotation parameters for main viz | |
| const rotTheta = 20 * - Math.PI / 180, //z axis rotation (20deg) | |
| rotPhi = 20 * Math.PI / 180, //y axis rotation (20deg) | |
| centerOrigin = {x: 0, y: 0, z: 0},//rotation origin | |
| currentProjection = orthoProjectXZ //add other projections later? | |
| // ///////////////////////////////////////////////// | |
| // //MAIN VIZ axes | |
| const gTrackMain = svg.append("g") | |
| .attr("class", "gTrack") | |
| .attr("transform", | |
| `translate(500, 400)`) | |
| //axes vertex and projection of axes | |
| let axeCenter = new Vertex(-150, -150, 0) | |
| rotate(axeCenter, centerOrigin, rotTheta, rotPhi) | |
| let axeX = new Vertex(150, axeCenter.y, axeCenter.z) | |
| rotate(axeX, centerOrigin, rotTheta, rotPhi) | |
| let axeY = new Vertex(axeCenter.x, 400, axeCenter.z) | |
| rotate(axeY, centerOrigin, rotTheta, rotPhi) | |
| let axeZ = new Vertex(axeCenter.x, axeCenter.y, 200) | |
| rotate(axeZ, centerOrigin, rotTheta, rotPhi) | |
| const axeSystemMain = [ | |
| [currentProjection(axeCenter), currentProjection(axeX)], //x axis line | |
| [currentProjection(axeCenter), currentProjection(axeY)], //y axis line | |
| [currentProjection(axeCenter), currentProjection(axeZ)] //z axis line | |
| ] | |
| const axisLabels = ['x', 'y', 'z'] | |
| const axes = gTrackMain.selectAll(".axisMain") | |
| .data(axeSystemMain) | |
| .enter() | |
| .append("g") | |
| .attr("class", "axisMain") | |
| axes.append("path") | |
| .attr("d", d => lineGeneratorAxes(d)) | |
| .attr("stroke", "black") | |
| .attr("marker-end", "url(#arrow)") | |
| axes.append("text") | |
| .attr("class", "axisLabel") | |
| .attr("x", d => d[1].x + 5) | |
| .attr("y", d => d[1].y - 2) | |
| .text((d,i) => axisLabels[i]) | |
| ///////////////////////////////////////////////// | |
| //XY plane axes | |
| const gTrackXY = svg.append("g") | |
| .attr("class", "gTrack") | |
| .attr("transform", | |
| `translate(${dxXY}, ${dyXY})`) | |
| axesXY = gTrackXY.append("g") | |
| .attr("transform", "translate(-85, 95)") | |
| .selectAll(".axisXY") | |
| .data([{line: [{x: 0, y: 0}, {x: 180, y: 0}], label: "x"}, {line: [{x: 0, y: 0}, {x: 0, y: -170}], label: "y"}]) | |
| .enter() | |
| axesXY.append("path") | |
| .attr("class", "axisXY") | |
| .attr("d", d => lineGeneratorAxes(d.line)) | |
| .attr("stroke", "black") | |
| .attr("marker-end", "url(#arrow)") | |
| axesXY.append("text") | |
| .attr("class", "axisLabel") | |
| .attr("x", d => d.line[1].x + 5) | |
| .attr("y", d => d.line[1].y + 5) | |
| .text(d => d.label) | |
| ///////////////////////////////////////////////// | |
| //XZ plane axes | |
| const gTrackXZ = svg.append("g") | |
| .attr("class", "gTrack") | |
| .attr("transform", | |
| `translate(${dxXZ}, ${dyXZ})`) | |
| axesXZ = gTrackXZ.append("g") | |
| .attr("transform", "translate(-85, 25)") | |
| .selectAll(".axisXZ") | |
| .data([{line: [{x: 0, y: 0}, {x: 180, y: 0}], label: "x"}, {line: [{x: 0, y: 0}, {x: 0, y: -180}], label: "z"}]) | |
| .enter() | |
| axesXZ.append("path") | |
| .attr("class", "axisXZ") | |
| .attr("d", d => lineGeneratorAxes(d.line)) | |
| .attr("stroke", "black") | |
| .attr("marker-end", "url(#arrow)") | |
| axesXZ.append("text") | |
| .attr("class", "axisLabel") | |
| .attr("x", d => d.line[1].x + 5) | |
| .attr("y", d => d.line[1].y + 5) | |
| .text(d => d.label) | |
| ///////////////////////////////////////////////// | |
| //YZ plane axes | |
| const gTrackYZ = svg.append("g") | |
| .attr("class", "gTrack") | |
| .attr("transform", | |
| `translate(${dxYZ}, ${dyYZ})`) | |
| axesYZ = gTrackYZ.append("g") | |
| .attr("transform", "translate(-25, 95)") | |
| .selectAll(".axisYZ") | |
| .data([{line: [{x: 0, y: 0}, {x: 180, y: 0}], label: "z"}, {line: [{x: 0, y: 0}, {x: 0, y: -170}], label: "y"}]) | |
| .enter() | |
| axesYZ.append("path") | |
| .attr("class", "axisXY") | |
| .attr("d", d => lineGeneratorAxes(d.line)) | |
| .attr("stroke", "black") | |
| .attr("marker-end", "url(#arrow)") | |
| axesYZ.append("text") | |
| .attr("class", "axisLabel") | |
| .attr("x", d => d.line[1].x + 5) | |
| .attr("y", d => d.line[1].y + 5) | |
| .text(d => d.label) | |
| ///////////////////////////////////////// | |
| function rossler(callback) { | |
| //update dataPoint position | |
| dataPoint.x += t * (- dataPoint.y - dataPoint.z) | |
| dataPoint.y += t * (dataPoint.x + rosslerParameters.a * dataPoint.y) | |
| dataPoint.z += t * (rosslerParameters.b + dataPoint.z * (dataPoint.x - rosslerParameters.c)); | |
| //apply rotation/transformation Main viz | |
| rotatedPointMain = new Vertex(dataPoint.x, dataPoint.y, dataPoint.z) | |
| rotate(rotatedPointMain, centerOrigin, rotTheta, rotPhi) | |
| //apply rotation/transformation XZ plane | |
| dataPointXZ = new Vertex(dataPoint.x, dataPoint.y, dataPoint.z) | |
| //apply rotation/transformation XY plane | |
| dataPointXY = new Vertex(dataPoint.x, dataPoint.y, dataPoint.z) | |
| //apply rotation/transformation XY plane | |
| dataPointYZ = new Vertex(dataPoint.x, dataPoint.y, dataPoint.z) | |
| callback(rotatedPointMain, dataPointXY, dataPointXZ, dataPointYZ); | |
| } | |
| //callback functions to draw the different planes | |
| function draw(dataPointMain, dataPointXY, dataPointXZ, dataPointYZ, project = currentProjection) { | |
| //add latest point projection to data | |
| dataMain.push(project(dataPointMain, dxMain, dyMain, scaleFactor)) | |
| dataXY.push(orthoProjectXY(dataPointXY, dxXY, dyXY, 8)) | |
| dataXZ.push(orthoProjectXZ(dataPointXZ, dxXZ, dyXZ, 8)) | |
| dataYZ.push(orthoProjectYZ(dataPointYZ, dxYZ, dyYZ, 8)) | |
| //Main | |
| context.clearRect(0, 0, 960, 500); | |
| context.beginPath(); | |
| lineGenerator(dataMain); | |
| context.lineWidth = 0.5; | |
| context.strokeStyle = canvasGradient; | |
| context.stroke(); | |
| //dot | |
| context.beginPath(); | |
| context.arc(dataMain[dataMain.length-1].x, dataMain[dataMain.length-1].y, 3, 0, 2*Math.PI); | |
| context.fillStyle = 'rgba(225,225,225,0.3)'; | |
| context.fill() | |
| context.strokeStyle = 'gray'; | |
| context.stroke(); | |
| //XY | |
| context.beginPath(); | |
| lineGenerator(dataXY); | |
| context.lineWidth = 0.5; | |
| context.strokeStyle = canvasGradient; | |
| context.stroke(); | |
| //dot | |
| context.beginPath(); | |
| context.arc(dataXY[dataXY.length-1].x, dataXY[dataXY.length-1].y, 3, 0, 2*Math.PI); | |
| context.fillStyle = 'rgba(225,225,225,0.3)'; | |
| context.fill() | |
| context.strokeStyle = 'gray'; | |
| context.stroke(); | |
| //XZ | |
| context.beginPath(); | |
| lineGenerator(dataXZ); | |
| context.lineWidth = 0.5; | |
| context.strokeStyle = canvasGradient; | |
| context.stroke(); | |
| //dot | |
| context.beginPath(); | |
| context.arc(dataXZ[dataXZ.length-1].x, dataXZ[dataXZ.length-1].y, 3, 0, 2*Math.PI); | |
| context.fillStyle = 'rgba(225,225,225,0.3)'; | |
| context.fill() | |
| context.strokeStyle = 'gray'; | |
| context.stroke(); | |
| //YZ | |
| context.beginPath(); | |
| lineGenerator(dataYZ); | |
| context.lineWidth = 0.5; | |
| context.strokeStyle = canvasGradient; | |
| context.stroke(); | |
| //dot | |
| context.beginPath(); | |
| context.arc(dataYZ[dataYZ.length-1].x, dataYZ[dataYZ.length-1].y, 3, 0, 2*Math.PI); | |
| context.fillStyle = 'rgba(225,225,225,0.3)'; | |
| context.fill() | |
| context.strokeStyle = 'gray'; | |
| context.stroke(); | |
| } | |
| //orthographic projections for each axe | |
| function orthoProjectXZ(M, dx=0, dy=0, zoom = 1) { | |
| return new Vertex2D(dx + zoom * M.x, dy + zoom * - M.z); | |
| } | |
| function orthoProjectXY(M, dx=0, dy=0, zoom = 1) { | |
| return new Vertex2D(dx + zoom * M.x, dy + zoom * - M.y); | |
| } | |
| function orthoProjectYZ(M, dx=0, dy=0, zoom = 1) { | |
| return new Vertex2D(dx + zoom * M.z, dy + zoom * - M.y); | |
| } | |
| // Rotate a vertice | |
| function rotate(M, center, theta, phi) { | |
| // Rotation matrix coefficients | |
| const ct = Math.cos(theta); | |
| const st = Math.sin(theta); | |
| const cp = Math.cos(phi); | |
| const sp = Math.sin(phi); | |
| // Rotation | |
| const x = M.x - center.x; | |
| const y = M.y - center.y; | |
| const z = M.z - center.z; | |
| //update/mutate current vertice | |
| M.x = ct * x - st * cp * y + st * sp * z + center.x; | |
| M.y = st * x + ct * cp * y - ct * sp * z + center.y; | |
| M.z = sp * y + cp * z + center.z; | |
| } | |
| let timeInterval = d3.interval(function() { | |
| iter++; //time increment | |
| if (iter >= max_iter) timeInterval.stop(); | |
| // rossler(drawMain, drawXZ, drawXY, drawYZ) | |
| rossler(draw) | |
| }, 10); | |
| </script> |