Generation of the Rossler attractor and visualization of the solution of the system on the 3 planes.
This is the SVG version, compare to the canvas 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 SVG version, compare to the canvas version.
Visualization of other attractors:
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
.drawingDot { | |
opacity: 0.; | |
fill: #ccc; | |
stroke: black; | |
r: 3px; | |
} | |
.dotTrack { | |
fill: none; | |
stroke: url("#linear-gradient"); | |
} | |
.axisLabel { | |
font-family: sans-serif; | |
font-size: 20; | |
} | |
</style> | |
<svg width="960" height="500"></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script> | |
const svg = d3.select("svg") | |
//Append linear gradient in svg defs | |
const defs = svg.append("defs"); | |
const linearGradient = defs.append("linearGradient") | |
.attr("id", "linear-gradient"); | |
//gradient color scale | |
const gradientScale = d3.scaleLinear() | |
.range(["#2c7bb6", "#00ccbc"]); | |
//Append multiple color stops evenly spaced | |
linearGradient.selectAll("stop") | |
.data( gradientScale.range() ) | |
.enter().append("stop") | |
.attr("offset", function(d,i) { return i/(gradientScale.range().length-1); }) | |
.attr("stop-color", function(d) { return d; }); | |
//append svg marker for axes arrows | |
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'); | |
//initial conditions and parameters for Rossler system | |
let x = 0, | |
y = z = 1, | |
t = 0.05, | |
iter = 0, | |
max_iter = 5000; | |
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); | |
//initial dataPoint | |
let dataPoint = new Vertex(x, y, z) | |
///////////////////////////////////////////////// | |
//MAIN VIZ | |
//drawing dot and dot track | |
const gTrackMain = svg.append("g") | |
.attr("class", "gTrack") | |
.attr("transform", | |
`translate(500, 400)`) | |
//follow the dot! | |
const dotMain = gTrackMain.append("circle") | |
.attr("class", "drawingDot"); | |
//the track itself | |
const dotTrackMain = gTrackMain.append("path") | |
.attr("class", "dotTrack") | |
.attr("stroke-width", 0.5); | |
//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? | |
//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'] | |
//add paths for axes | |
const axes = gTrackMain.selectAll(".axisMain") | |
.data(axeSystemMain) | |
.enter() | |
.append("g") | |
.attr("class", "axisMain") | |
axes.append("path") | |
.attr("d", d => lineGenerator(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 | |
//drawing dot and dot track | |
const gTrackXY = svg.append("g") | |
.attr("class", "gTrack") | |
.attr("transform", | |
`translate(100, 100)`) | |
//follow the dot! | |
const dotXY = gTrackXY.append("circle") | |
.attr("class", "drawingDot"); | |
//the track itself | |
const dotTrackXY = gTrackXY.append("path") | |
.attr("class", "dotTrack") | |
.attr("stroke-width", 0.5); | |
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 => lineGenerator(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 | |
//drawing dot and dot track | |
const gTrackXZ = svg.append("g") | |
.attr("class", "gTrack") | |
.attr("transform", | |
`translate(100, 400)`) | |
//follow the dot! | |
const dotXZ = gTrackXZ.append("circle") | |
.attr("class", "drawingDot"); | |
//the track itself | |
const dotTrackXZ = gTrackXZ.append("path") | |
.attr("class", "dotTrack") | |
.attr("stroke-width", 0.5); | |
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 => lineGenerator(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 | |
//drawing dot and dot track | |
const gTrackYZ = svg.append("g") | |
.attr("class", "gTrack") | |
.attr("transform", | |
`translate(300, 100)`) | |
//follow the dot! | |
const dotYZ = gTrackYZ.append("circle") | |
.attr("class", "drawingDot"); | |
//the track itself | |
const dotTrackYZ = gTrackYZ.append("path") | |
.attr("class", "dotTrack") | |
.attr("stroke-width", 0.5); | |
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 => lineGenerator(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(callbackMain, callbackXZ, callbackXY, callbackYZ) { | |
//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) | |
callbackMain(rotatedPointMain); | |
callbackXZ(dataPointXZ); | |
callbackXY(dataPointXY); | |
callbackYZ(dataPointYZ); | |
} | |
//callback functions to draw the different planes | |
function drawMain(dataPoint, project = currentProjection) { | |
//add latest point projection to data | |
dataMain.push(project(dataPoint, scaleFactor)) | |
//update visualization of dot and dotTrack | |
dotTrackMain | |
.attr("d", lineGenerator(dataMain)) | |
dotMain | |
.attr("transform", `translate(${dataMain[dataMain.length-1].x}, ${dataMain[dataMain.length-1].y})`) | |
} | |
function drawXZ(dataPoint, project = orthoProjectXZ) { | |
//add latest point projection to data | |
dataXZ.push(project(dataPoint, 8)) | |
//update visualization of dot and dotTrack | |
dotTrackXZ | |
.attr("d", lineGenerator(dataXZ)) | |
dotXZ | |
.attr("transform", `translate(${dataXZ[dataXZ.length-1].x}, ${dataXZ[dataXZ.length-1].y})`) | |
} | |
function drawXY(dataPoint, project = orthoProjectXY) { | |
//add latest point projection to data | |
dataXY.push(project(dataPoint, 8)) | |
//update visualization of dot and dotTrack | |
dotTrackXY | |
.attr("d", lineGenerator(dataXY)) | |
dotXY | |
.attr("transform", `translate(${dataXY[dataXY.length-1].x}, ${dataXY[dataXY.length-1].y})`) | |
} | |
function drawYZ(dataPoint, project = orthoProjectYZ) { | |
//add latest point projection to data | |
dataYZ.push(project(dataPoint, 8)) | |
//update visualization of dot and dotTrack | |
dotTrackYZ | |
.attr("d", lineGenerator(dataYZ)) | |
dotYZ | |
.attr("transform", `translate(${dataYZ[dataYZ.length-1].x}, ${dataYZ[dataYZ.length-1].y})`) | |
} | |
//orthographic projections for each axe | |
function orthoProjectXZ(M, zoom = 1) { | |
return new Vertex2D(zoom * M.x, zoom * - M.z); | |
} | |
function orthoProjectXY(M, zoom = 1) { | |
return new Vertex2D(zoom * M.x, zoom * - M.y); | |
} | |
function orthoProjectYZ(M, zoom = 1) { | |
return new Vertex2D(zoom * M.z, zoom * - M.y); | |
} | |
//other projection in the work | |
// function perspectiveProject(M, zoom = 1) { | |
// let d = 200; | |
// let r = d / - M.y; | |
// return new Vertex2D(zoom * (r * M.x), zoom * (r * M.z)); | |
// } | |
// 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) | |
}, 10); | |
</script> |