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> |