Skip to content

Instantly share code, notes, and snippets.

@gcalmettes
Last active March 7, 2023 22:01
Show Gist options
  • Save gcalmettes/2097c8e3e077bbd1b30dce3dbb93ac58 to your computer and use it in GitHub Desktop.
Save gcalmettes/2097c8e3e077bbd1b30dce3dbb93ac58 to your computer and use it in GitHub Desktop.
Orthographic projections of the Rossler attractor
license: gpl-3.0
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment