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