plotly.js

The open source JavaScript graphing library that powers Plotly

Show Sidebar Hide Sidebar
Plotly.js

Point Cloud in plotly.js

How to make D3.js-based Point Cloud plots in Plotly.js.

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: "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("graphDiv")
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: '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.plot(graphDiv, 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(graphDiv, {marker:{color: target.color}/*,xy: geom*/}, 0) // /*no need to include xy: geom*/
    window.requestAnimationFrame(refresh)
  })
})
Still need help?
Contact Us

For guaranteed 24 hour response turnarounds, upgrade to a Developer Support Plan.