<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet'>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div class="lineWrapper">
<div class="ControlsWrapper">
<div>Controls</div>
<div class="slidersWrapper">
<div class="slider-container">
<span class="slider-label">Start</span>
<input type="range" id="startSlider" class="slider" min="0" max="3" value="0.5" step="0.01">
<span id="startValue">0.5</span>
</div>
<div class="slider-container">
<span class="slider-label">Rotations</span>
<input type="range" id="rotSlider" class="slider" min="-30" max="30" value="1.5" step="0.001">
<span id="rotValue">1.5</span>
</div>
<div class="slider-container">
<span class="slider-label">Gamma</span>
<input type="range" id="gammaSlider" class="slider" min="0" max="3" value="1" step="0.01">
<span id="gammaValue">1</span>
</div>
<div class="slider-container">
<span class="slider-label">Min Sat</span>
<input type="range" id="minSatSlider" class="slider" min="0" max="2" value="1.2" step="0.01">
<span id="minSatValue">1.2</span>
</div>
<div class="slider-container">
<span class="slider-label">Max Sat</span>
<input type="range" id="maxSatSlider" class="slider" min="0" max="2" value="1.2" step="0.01">
<span id="maxSatValue">1.2</span>
</div>
<div class="slider-container">
<span class="slider-label">Min Lightness</span>
<input type="range" id="minLightSlider" class="slider" min="0" max="1" value="0" step="0.01">
<span id="minLightValue">0</span>
</div>
<div class="slider-container">
<span class="slider-label">Max Lightness</span>
<input type="range" id="maxLightSlider" class="slider" min="0" max="1" value="1" step="0.01">
<span id="maxLightValue">1</span>
</div>
</div>
</div>
<div class="lineCanvasWrapper">
<div>RGB Curves</div>
<canvas id="lineCanvas" width="300" height="300"></canvas>
</div>
</div>
<!-- <div id="d3-container"></div> -->
<!-- <div id="plotly-container" style="width:600px;height:400px;"></div> -->
<div id="plotly-plot" style="display:none;"></div>
<div class="my3dWrapper">
<div id="helix3dPlot"></div>
</div>
<div class="my3dWrapper">
<div id="line3dPlot"></div>
</div>
<div id="ellipseCanvasWrapper">
<canvas id="ellipseCanvas" width="600" height="300"></canvas>
</div>
<div class="helixGradientWrapper">
<canvas id="helixGradient"></canvas>
</div>
body {
font-family: "Raleway";
font-size: 0.8em;
}
.ControlsWrapper {
width: 350px;
}
.slidersWrapper {
border: 1px solid red;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
}
.slider-container {
margin-bottom: 10px;
font-size: 1em;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: row;
flex-wrap: nowrap;
}
.slider-container span:last-child {
font-family: "Courier New", Courier, monospace;
margin: 0 1em;
font-size: 1.1em;
}
.slider-label {
width: 100px;
display: inline-block;
}
.slider {
width: 180px;
}
.my3dWrapper {
display: flex;
justify-content: flex-start;
align-items: center;
/* width: fit-content; */
flex-wrap: wrap;
border: 1px solid red;
}
.lineWrapper {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: row;
flex-wrap: wrap;
align-content: center;
}
#plotly-plot {
border: 1px solid cyan;
}
#myCanvas {
border: 1px solid red;
}
#ellipseCanvas {
border: 1px solid pink;
}
#lineCanvas {
border: 1px solid blue;
}
#helixGradient {
width: 100%;
height: 100%;
}
.helixGradientWrapper {
margin: 15px 0;
width: 1000px;
height: 40px;
border: 1px solid grey;
}
input[type="range"] {
cursor: pointer;
}
// Adding event listeners to sliders when the window loads
window.onload = function () {
handleSliderChange();
document
.getElementById("startSlider")
.addEventListener("input", handleSliderChange);
document
.getElementById("rotSlider")
.addEventListener("input", handleSliderChange);
document
.getElementById("gammaSlider")
.addEventListener("input", handleSliderChange);
document
.getElementById("minSatSlider")
.addEventListener("input", handleSliderChange);
document
.getElementById("maxSatSlider")
.addEventListener("input", handleSliderChange);
document
.getElementById("minLightSlider")
.addEventListener("input", handleSliderChange);
document
.getElementById("maxLightSlider")
.addEventListener("input", handleSliderChange);
};
let rgb;
var currentLayout = {}; // Global variable to store the current layout
let sizePoint = 4;
// var currentCameraSettings = null; // Initialize with null
var currentCameraSettings = {
up: {
x: 0,
y: 0,
z: 1
},
center: {
x: 0,
y: 0,
z: 0
},
eye: {
x: 1.6,
y: -1.6,
z: 1.2
},
projection: {
type: "perspective"
}
}; // Initialize with null
// Function to handle slider changes
function handleSliderChange() {
var start = parseFloat(document.getElementById("startSlider").value);
var rot = parseFloat(document.getElementById("rotSlider").value);
var gamma = parseFloat(document.getElementById("gammaSlider").value);
var minSat = parseFloat(document.getElementById("minSatSlider").value);
var maxSat = parseFloat(document.getElementById("maxSatSlider").value);
var minLight = parseFloat(document.getElementById("minLightSlider").value);
var maxLight = parseFloat(document.getElementById("maxLightSlider").value);
document.getElementById("startValue").textContent = start.toFixed(2);
document.getElementById("rotValue").textContent = rot.toFixed(2);
document.getElementById("gammaValue").textContent = gamma.toFixed(2);
document.getElementById("minSatValue").textContent = minSat.toFixed(2);
document.getElementById("maxSatValue").textContent = maxSat.toFixed(2);
document.getElementById("minLightValue").textContent = minLight.toFixed(2);
document.getElementById("maxLightValue").textContent = maxLight.toFixed(2);
let myLen = 256 * 1 * Math.abs(rot);
if (myLen >= 256) myLen = 256;
console.log("myLen :", myLen);
// Call a function to update your visualization with these values
// updateVisualization(start, rot, gamma, minSat, maxSat, minLight, maxLight);
rgb = cubehelix(start, rot, gamma, minSat, maxSat, minLight, maxLight, myLen);
updatePlotlyVisualization(rgb);
// update3DScatterPlot(rgb);
generate3DScatterPlot(rgb);
// drawLines_2(rgb)
}
// Function to clear the canvas
function updatePlotlyVisualization(rgb) {
// Get both canvases and their contexts
const ellipseCanvas = document.getElementById("ellipseCanvas");
const lineCanvas = document.getElementById("lineCanvas");
const ctx = ellipseCanvas.getContext("2d");
const lineCtx = lineCanvas.getContext("2d");
const helixGradientCanvas = document.getElementById("helixGradient");
const helixGradientCtx = helixGradientCanvas.getContext("2d");
const width = ellipseCanvas.width;
const height = ellipseCanvas.height;
const lineWidth = lineCanvas.width;
const lineHeight = lineCanvas.height;
const ellipseWidth = ellipseCanvas.width;
const ellipseHeight = ellipseCanvas.height;
// Function to clear a canvas
function clearCanvas(canvasCtx) {
canvasCtx.clearRect(0, 0, canvasCtx.canvas.width, canvasCtx.canvas.height);
}
// XY Plot experimental
function tracePlotXY() {
var trace1 = {
x: [], // x coordinates for circles
y: [], // y coordinates for circles
mode: "markers",
marker: {
size: [],
color: []
}
};
for (var i = 0; i <= 255; i++) {
// Calculating values for circles
var xCircle = map(i, 0, 255, 300, 1000); // Adjust the mapping as needed
var yCircle = map(rgb.g[i], 0, 255, 0, 500); // Adjust the mapping as needed
var size = map(rgb.b[i], 0, 255, 1, 20);
var color = `rgb(${rgb.r[i]}, ${rgb.g[i]}, ${rgb.b[i]})`;
trace1.x.push(xCircle);
trace1.y.push(yCircle);
trace1.marker.size.push(size);
trace1.marker.color.push(color);
}
var data = [trace1];
var layout = {
title: "RGB Visualization",
xaxis: { title: "X Axis" },
yaxis: { title: "Y Axis" }
};
Plotly.newPlot("plotly-plot", data, layout, { responsive: true });
}
// Function to draw ellipses
function drawEllipses() {
clearCanvas(ctx);
for (let i = 0; i <= 255; i++) {
let sz = map(rgb.b[i], 0, 255, 1, 20);
let x = map(rgb.r[i], 0, 255, 0, ellipseWidth);
let y = map(rgb.g[i], 0, 255, ellipseHeight, 0);
ctx.fillStyle = `rgb(${rgb.r[i]}, ${rgb.g[i]}, ${rgb.b[i]})`;
ctx.beginPath();
ctx.ellipse(x, y, sz, sz, 0, 0, 2 * Math.PI);
ctx.fill();
}
}
// Function to draw lines in the separate canvas
function drawLines() {
clearCanvas(lineCtx);
for (let i = 1; i <= 255; i++) {
drawLineComponent(lineCtx, rgb.r, "r", i);
drawLineComponent(lineCtx, rgb.g, "g", i);
drawLineComponent(lineCtx, rgb.b, "b", i);
}
}
function drawLines_2(rgb) {
clearCanvas(lineCtx);
for (let i = 1; i <= 255; i++) {
drawLineComponent(lineCtx, rgb.r, "r", i);
drawLineComponent(lineCtx, rgb.g, "g", i);
drawLineComponent(lineCtx, rgb.b, "b", i);
//!
// fill(rgb.r[i], rgb.g[i], rgb.b[i]);
}
var traceR = {
// x: new Array(rgb.r.length).fill(0), // Zero array for R
x: Array.from({ length: rgb.r.length }, (_, i) => i), // Array of indices
y: rgb.r,
// y: rgb.r, // Red values
z: rgb.r,
mode: "lines",
type: "scatter3d",
line: {
color: "red",
width: 4
},
name: "Red"
};
var traceG = {
y: rgb.g, // Green values
x: Array.from({ length: rgb.g.length }, (_, i) => i), // Array of indices
z: rgb.g, // Green values
mode: "lines",
type: "scatter3d",
line: {
color: "green",
width: 4
},
name: "Green"
};
var traceB = {
x: Array.from({ length: rgb.b.length }, (_, i) => i), // Array of indices
y: rgb.b,
z: rgb.b, // Blue values
mode: "lines",
type: "scatter3d",
line: {
color: "blue",
width: 4
},
name: "Blue"
};
var data = [traceR, traceG, traceB];
var layout = {
title: "RGB Lines Visualization",
autosize: true,
margin: {
l: 0,
r: 0,
b: 0,
t: 30
},
scene: {
camera: {
eye: {
x: 0,
y: -2,
z: 0
},
up: {
x: 0,
y: 0,
z: 1
},
center: {
x: 0,
y: 0,
z: 0
},
projection: {
type: "orthographic"
}
},
aspectmode: "cube",
xaxis: {
title: "R",
range: [0, 255],
dtick: 255 / 3
},
yaxis: {
title: "B",
range: [0, 255],
dtick: 255 / 3
},
zaxis: {
title: "G",
range: [0, 255],
dtick: 255 / 3
}
}
};
// fill(rgb.r[i], rgb.g[i], rgb.b[i]);
//
Plotly.newPlot("line3dPlot", data, layout, { responsive: true });
}
function drawLineComponent(ctx, componentArray, component, index) {
ctx.beginPath();
if (component === "r") {
ctx.strokeStyle = `rgb(${componentArray[index]}, 0, 0)`;
} else if (component === "g") {
ctx.strokeStyle = `rgb(0, ${componentArray[index]}, 0)`;
} else {
// 'b'
ctx.strokeStyle = `rgb(0, 0, ${componentArray[index]})`;
}
ctx.moveTo(
map(index - 1, 0, 255, 0, lineWidth),
map(componentArray[index - 1], 0, 255, lineHeight, 0)
);
ctx.lineTo(
map(index, 0, 255, 0, lineWidth),
map(componentArray[index], 0, 255, lineHeight, 0)
);
ctx.stroke();
}
function drawGradient(rgb) {
const canvas = document.getElementById("helixGradient");
const ctx = canvas.getContext("2d");
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Create a linear gradient
// The gradient goes from left (0) to right (canvas width)
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
// Add color stops to the gradient
for (let i = 0; i < rgb.r.length; i++) {
const color = `rgb(${rgb.r[i]}, ${rgb.g[i]}, ${rgb.b[i]})`;
const position = i / (rgb.r.length - 1); // Normalize position to range from 0 to 1
gradient.addColorStop(position, color);
}
// Fill the rectangle with the gradient
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Call the draw functions
tracePlotXY();
drawEllipses();
drawLines();
drawLines_2(rgb);
drawGradient(rgb);
}
// Call this function with the rgb data to update the 3D plot
function generate3DScatterPlot(rgb) {
var rot = parseFloat(document.getElementById("rotSlider").value);
console.log(rot >= 8 ? Math.abs((sizePoint / rot) * 10) : sizePoint);
var trace = {
x: rgb.r, // X coordinates (Red values)
y: rgb.g, // Y coordinates (Green values)
z: rgb.b, // Z coordinates (Blue values)
mode: "markers",
type: "scatter3d",
marker: {
// size: 5
size: rot >= 8 ? Math.abs((sizePoint / rot) * 10) : sizePoint,
color: rgb.r.map((r, idx) => `rgb(${r}, ${rgb.g[idx]}, ${rgb.b[idx]})`),
opacity: 0.8
}
};
var data = [trace];
var layout = {
title: "Cubehelix Color Scheme",
scene: {
xaxis: { title: "R" },
yaxis: { title: "G" },
zaxis: { title: "B" },
camera: currentCameraSettings
}
};
Plotly.newPlot("helix3dPlot", data, layout, { responsive: true });
// Update the global camera settings whenever the layout changes
document
.getElementById("helix3dPlot")
.on("plotly_relayout", function (eventData) {
if (eventData["scene.camera"]) {
currentCameraSettings = eventData["scene.camera"];
console.log("currentCameraSettings :", currentCameraSettings);
}
});
}
// Helper function for mapping values (similar to p5.js map function)
function map(value, start1, stop1, start2, stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}
// cubehelix NOT using p5
function cubehelix(
start,
rot,
gamma,
minSat,
maxSat,
minLight,
maxLight,
nlev
) {
var r = [];
var g = [];
var b = [];
// var nlev = 256;
var nlo = 0;
var nhi = 0;
for (var i = 0; i < nlev; i++) {
var fract = linearMap(i, 0, nlev, minLight, maxLight);
var angle = 2 * Math.PI * (start / 3.0 + rot * fract + 1.0);
fract = Math.pow(fract, gamma);
var satar = linearMap(i, 0, 255, minSat, maxSat);
var amp = (satar * fract * (1 - fract)) / 2.0;
var r1 =
fract + amp * (-0.14861 * Math.cos(angle) + 1.78277 * Math.sin(angle));
var g1 =
fract + amp * (-0.29227 * Math.cos(angle) - 0.90649 * Math.sin(angle));
var b1 = fract + amp * (1.97294 * Math.cos(angle));
r1 = clamp(r1, 0, 1);
g1 = clamp(g1, 0, 1);
b1 = clamp(b1, 0, 1);
r.push(255 * r1);
g.push(255 * g1);
b.push(255 * b1);
}
return { r, g, b };
}
function linearMap(value, start1, stop1, start2, stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}
function clamp(value, min, max) {
if (value < min) return min;
else if (value > max) return max;
else return value;
}
This Pen doesn't use any external CSS resources.