Point Cloud in JavaScript
How to make D3.js-based Point Cloud plots in Plotly.js.
New to Plotly?
Plotly is a free and open-source graphing library for JavaScript. We recommend you read our Getting Started guide for the latest installation or upgrade instructions, then move on to our Plotly Fundamentals tutorials or dive straight in to some Basic Charts tutorials.
var myPlot = document.getElementById('myDiv');
var xy = new Float32Array([1,2,3,4,5,6,0,4]);
data = [{ xy: xy, type: 'pointcloud' }];
layout = { };
Plotly.newPlot('myDiv', data, layout);
var trace1 = {
type: "pointcloud",
mode: "markers",
marker: {
sizemin: 0.5,
sizemax: 100,
arearatio: 0,
color: "rgba(255, 0, 0, 0.6)"
},
x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
y: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
}
var trace2 = {
type: "pointcloud",
mode: "markers",
marker: {
sizemin: 0.5,
sizemax: 100,
arearatio: 0,
color: "rgba(0, 0, 255, 0.9)",
opacity: 0.8,
blend: true
},
opacity: 0.7,
x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
y: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
var trace3 = {
type: "pointcloud",
mode: "markers",
marker: {
sizemin: 0.5,
sizemax: 100,
border: {
color: "rgb(0, 0, 0)",
arearatio: 0.7071
},
color: "green",
opacity: 0.8,
blend: true
},
opacity: 0.7,
x: [3, 4.5, 6],
y: [9, 9, 9]
}
var data = [trace1, trace2,trace3];
var layout = {
title: {
text: "Basic Point Cloud"
},
xaxis: {
type: "linear",
range: [
-2.501411175139456,
43.340777299865266],
autorange: true
},
yaxis: {
type: "linear",
range: [4,6],
autorange: true
},
height: 598,
width: 1080,
autosize: true,
showlegend: false
}
Plotly.newPlot('myDiv', data, layout);
var graphDiv = document.getElementById("myDiv")
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var pointCount = 1e6
var scaleX = 2000
var scaleY = 1000
var speed = 0.3
// some non-uniform distribution
function pseudogaussian() {return (Math.random() + Math.random() + Math.random() + Math.random() + Math.random() + Math.random()) - 3}
// dataset xy array generator
function gaussian(sd) {
var result = new Float32Array(2 * pointCount)
var f = 20 / 360 * 2 * Math.PI
var sin = Math.sin(f)
var cos = Math.cos(f)
var i, x, y
for(i = 0; i < pointCount; i++) {
x = scaleX * pseudogaussian() * sd
y = scaleY * pseudogaussian() * sd * 0.75
result[i * 2] = x * cos - y * sin + scaleX * 0.5
result[i * 2 + 1] = x * sin + y * cos + scaleY * 0.5
}
return result
}
// generate initial dataset
var geom = gaussian(1/5)
var plotData = {
data: [
{
type: 'pointcloud',
marker: {
sizemin: 0.05,
sizemax: 30,
color: 'darkblue',
opacity: 1,
blend: true
},
opacity: 1,
xy: geom, // instead of separate x and y arrays
indices: new Int32Array(pointCount).map(function(d, i) {return i;}),
xbounds: [0, scaleX],
ybounds: [0, scaleY]
}
],
layout: {
title: {
text: 'Point Cloud - updating 1 million points in every single frame'
},
autosize: false,
width: 1000,
height: 600,
hovermode: false,
dragmode: "pan"
}
}
function reds (imageData) {
// uses the red channel for simplicity
var result = []
var data = imageData.data
var width = imageData.width
var height = imageData.height
var i, j
for(j =0; j < height; j++)
for(i = 0; i < width; i++)
if(data[4 * (i + width * j)])
result.push([i, j])
return result
}
function fillGeom(pixels, width, height) {
var result = new Float32Array(2 * pointCount)
var i
var pixel
var pixLength = pixels.length
for(i = 0; i < pointCount; i++) {
pixel = pixels[i % pixLength] // recycling and jittering points
result[2 * i] = scaleX * (pixel[0] + Math.random()) / width
result[2 * i + 1] = scaleY * (1 - (pixel[1] + Math.random()) / height)
}
return result
}
function recurrenceRelationGeom(target, geom, speed, maxVelo, fraction) {
// non-one fraction is for glitch effects
var i, ii, diff
var geomPointCount = geom.length
var changedCount = Math.round(geomPointCount * fraction)
var from = Math.floor(Math.random() * geomPointCount)
var to = from + changedCount
for(ii = from; ii < to; ii++) {
i = ii % geomPointCount
diff = speed * (target[i] - geom[i])
geom[i] += Math.min(maxVelo, diff) // capping for glitch effect
}
}
function clearCanvas(ctx, width, height) {
ctx.fillStyle = "black"
ctx.fillRect(0, 0, width, height)
ctx.fillStyle = "red"
}
function plotlyTextGeom(ctx, width, height) {
clearCanvas(ctx, width, height)
ctx.font = "bold 260px 'Open sans',verdana,arial,sans-serif"
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText("Plotly", width / 2, height / 2)
var imageData = ctx.getImageData(0, 0, width, height)
var filledPixels = reds(imageData)
return fillGeom(filledPixels, width, height)
}
function pointcloudTextGeom(ctx, width, height) {
clearCanvas(ctx, width, height)
ctx.font = "bold 240px 'Open sans',verdana,arial,sans-serif"
ctx.textAlign = "center"
ctx.textBaseline = "alphabetic"
ctx.fillText("Point", width / 2, height / 2)
ctx.textBaseline = "hanging"
ctx.fillText("Cloud", width / 2, height / 2)
var imageData = ctx.getImageData(0, 0, width, height)
var filledPixels = reds(imageData)
return fillGeom(filledPixels, width, height)
}
function oneMillionTextGeom(ctx, width, height) {
clearCanvas(ctx, width, height)
ctx.font = "bold 144px 'Open sans',verdana,arial,sans-serif"
ctx.textAlign = "center"
ctx.textBaseline = "bottom"
ctx.fillText("1 million", width / 2, height / 2)
ctx.textBaseline = "top"
ctx.fillText("live points", width / 2, height / 2)
var imageData = ctx.getImageData(0, 0, width, height)
var filledPixels = reds(imageData)
return fillGeom(filledPixels, width, height)
}
function initializeCanvas(plotArea) {
canvas.style.left = plotArea.left + "px"
canvas.style.top = plotArea.top + "px"
canvas.setAttribute("width", plotArea.width)
canvas.setAttribute("height", plotArea.height)
}
// 'Open sans',verdana,arial,sans-serif
Plotly.newPlot('myDiv', plotData.data, plotData.layout).then(function() {
var plotArea = document.querySelector('.gl-container div').getBoundingClientRect()
var width = plotArea.width
var height = plotArea.height
initializeCanvas(plotArea)
var targetPlotly = {
geom: plotlyTextGeom(ctx, width, height),
color: "blue",
speed: 2 - speed,
maxVelo: Infinity,
fraction: 1
}
var targetPointcloud = {
geom: pointcloudTextGeom(ctx, width, height),
color: "darkgreen",
speed: speed,
maxVelo: 100,
fraction: 0.6
}
var targetOneMillion = {
geom: oneMillionTextGeom(ctx, width, height),
color: "darkpurple",
speed: speed,
maxVelo: 200,
fraction: 0.6
}
var targetGaussian = {
geom: gaussian(1/5),
color: "darkblue",
speed: 5 * speed,
maxVelo: 50,
fraction: 1
}
var targetGaussian2 = {
geom: gaussian(100),
color: "darkblue",
speed: 0.2 * speed,
maxVelo: 1000,
fraction: 1
}
var currentIndex = 0
var targets = [targetPlotly, targetPointcloud, targetOneMillion, targetGaussian, targetGaussian2]
window.setInterval(function() {
currentIndex = (currentIndex + 1) % targets.length
}, 3000)
function getIndex() {
return currentIndex
}
window.requestAnimationFrame(function refresh() {
var target = targets[getIndex()]
recurrenceRelationGeom(target.geom, geom, target.speed, target.maxVelo, target.fraction)
Plotly.restyle('myDiv', {marker:{color: target.color}/*,xy: geom*/}, 0) // /*no need to include xy: geom*/
window.requestAnimationFrame(refresh)
})
})