Skip to content

Instantly share code, notes, and snippets.

@gcalmettes
Last active September 30, 2017 17:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gcalmettes/fd80abba670527a9736d69486e557735 to your computer and use it in GitHub Desktop.
Save gcalmettes/fd80abba670527a9736d69486e557735 to your computer and use it in GitHub Desktop.
Canvas Rossler attractor
license: gpl-3.0
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment