regl-scatter2d
Version:
Scatter2d plot built with regl
317 lines (250 loc) • 7.05 kB
JavaScript
'use strict'
require('enable-mobile')
const createScatter = require('./')
const panZoom = require('pan-zoom')
const fps = require('fps-indicator')({ css: `padding: 1.4rem` })
const random = require('gauss-random')
const rgba = require('color-rgba')
const nanoraf = require('nanoraf')
const palettes = require('nice-color-palettes')
const sdf = require('bitmap-sdf')
const parsePath = require('parse-svg-path')
const drawPath = require('draw-svg-path')
const normalizePath = require('normalize-svg-coords')
const pathBounds = require('svg-path-bounds')
const isSvgPath = require('is-svg-path')
const t = require('tape')
const regl = require('regl')({
extensions: ['OES_element_index_uint']
})
t('unsnapped elements render')
t('snapped elements render')
t('multimarker multipass render')
t('too many colors render')
t('palette colors render')
t('single color render')
t('precision')
t('1e6 points')
t('marker size')
t('circle size')
t('multipass rendering')
t('single point')
t('no-boundaries')
t('cluster with external buffer')
//create square test sdf image
let w = 200, h = 200
let dist = new Array(w*h)
for (let i = 0; i < w; i++) {
for (let j = 0; j < h; j++) {
if (i > j) {
if (i < h - j) {
dist[j*w + i] = j/(h/2)
}
else {
dist[j*w + i] = 1 - (i-w/2)/(w/2)
}
}
else {
if (i < h - j) {
dist[j*w + i] = i/(w/2)
}
else {
dist[j*w + i] = 1 - (j-h/2)/(h/2)
}
}
}
}
function show (arr) {
let dim = Math.sqrt(arr.length)
let cnv = document.body.appendChild(document.createElement('canvas'))
let ctx = cnv.getContext('2d')
let w = cnv.width = dim
let h = cnv.height = dim
let iData = new ImageData(w, h)
let data = iData.data
for (let i = 0; i < w; i++) {
for (let j = 0; j < h; j++) {
data[i*w*4 + j*4 + 0] = arr[i*w + j] * 255
data[i*w*4 + j*4 + 1] = arr[i*w + j] * 255
data[i*w*4 + j*4 + 2] = arr[i*w + j] * 255
data[i*w*4 + j*4 + 3] = 255
}
}
ctx.putImageData(iData, 0, 0)
}
let N = 1e5
let ratio = window.innerWidth / window.innerHeight
let range = [-10 * ratio, -10, 10 * ratio, 10]
let colors = palettes[Math.floor(Math.random() * palettes.length)]
let markers = [ null /*, dist*/ ]
let passes = markers.length
let scatter = createScatter(regl)
console.time(1)
scatter.update(...Array(passes).fill(null).map((x, i) => {
var pos = generate(N)
// var pos = [
// [0,0.75,0.5,0.85,1,0.75,1.25,null,1.5,0.85,1.75,null,2,0.75,2.5,0.85,3,0.75],
// [0,0.5,1,0.6,2,0.5,2.5,null,3,0.5]
// ][i]
return {
positions: pos,
// positions: [0,0, 1,1, 2,2, 3,3, 4,4, 5,5, 6,6, 7,7],
// size: Array(pos.length).fill(15).map(x => Math.random() * x/2 + x/2),
// size: [2, 5, 9, 14, 20, 27, 35, 44, 54],
size: 8,
opacity: .15,
// color: ['red', 'green', 'blue', 'black', 'red', 'red', 'red', 'gray'],
// color: Array(N).fill(0).map(() => colors[Math.floor(Math.random() * colors.length)]),
color: 'rgba(0, 0, 255, .5)',
// color: Array(N * 4).fill(0).map(Math.random),
// borderColor: 'rgba(0, 255, 0, .5)',
// marker: getCharSdf('©'),
// marker: [null, null, null, null, dist, dist, dist, dist],
//marker: markers[i],
// marker: Array(N).fill(0).map(() => markers[Math.floor(Math.random() * markers.length)]),
range: range,
// borderSize: 3,
// borderColor: Array(N).fill(0).map(() => colors[Math.floor(Math.random() * colors.length)]),
snap: true,
// viewport: [0,100,300,300]
}
}))
scatter.draw()
// scatter.draw([[0, 2, 4, 6]])
// scatter.draw([[1, 3, 5, 7]])
console.timeEnd(1)
// setTimeout(() => {
// scatter.regl.clear({color: true})
// scatter.draw([[0], [0]])
// }, 500)
//interactions
let prev = null
var frame = nanoraf(passes => {
scatter(...passes)
})
let cnv = document.body.querySelectorAll('canvas')[1]
panZoom(cnv, e => {
//FIXME: panzoom fails working right on ipad
let w = cnv.offsetWidth
let h = cnv.offsetHeight
let rx = e.x / w
let ry = e.y / h
let xrange = range[2] - range[0],
yrange = range[3] - range[1]
if (e.dz) {
let dz = e.dz / w
range[0] -= rx * xrange * dz
range[2] += (1 - rx) * xrange * dz
range[1] -= (1 - ry) * yrange * dz
range[3] += ry * yrange * dz
}
range[0] -= xrange * e.dx / w
range[2] -= xrange * e.dx / w
range[1] += yrange * e.dy / h
range[3] += yrange * e.dy / h
let state = Array(passes).fill({range})
frame(state, prev)
prev = state
})
function generate(N) {
var positions = new Float64Array(2 * N)
for(var i=0; i<2*N; ++i) {
positions[i] = random() * 3
}
// for (var i = 0; i < N; i++) {
// positions[i * 2] = i;
// positions[i * 2 + 1] = Math.random();
// }
return positions
}
window.addEventListener('resize', scatter)
function getCharSdf(char, size) {
if (!size) size = 200
let cnv = document.createElement('canvas')
let ctx = cnv.getContext('2d')
let w = cnv.width = size
let h = cnv.height = size
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, w, h)
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.font = size/2 + 'px sans-serif'
ctx.fillStyle = 'white'
ctx.fillText(char, size/2, size/2)
let data = sdf(ctx, {
cutoff: .48,
radius: size/2
})
// show(data)
return data
}
//return bitmap sdf data from any argument
function getSDF(arg, markerSize) {
let size = markerSize * 2
let w = canvas.width = size * 2
let h = canvas.height = size * 2
let cutoff = 1
let radius = size/2
let data
//FIXME: replace with render-svg or rasterize-svg module or so
//svg path or utf character
if (typeof arg === 'string') {
arg = arg.trim()
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, w, h)
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'
ctx.lineWidth = 1
//svg path
if (isSvgPath(arg)) {
let path = normalizePath({
path: arg,
viewBox: pathBounds(arg),
min: 0,
max: size
})
//FIXME: make this good
ctx.translate(size, size)
//if canvas svg paths api is available
if (global.Path2D) {
let path2d = new Path2D(path)
ctx.fill(path2d)
ctx.stroke(path2d)
}
//fallback to bezier-curves
else {
let segments = parsePath(path)
drawPath(ctx, segments)
ctx.fill()
ctx.stroke()
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
//plain character
else {
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.font = size + 'px sans-serif'
ctx.fillText(arg, size, size)
}
data = sdf(ctx, {
cutoff: cutoff,
radius: radius
})
}
//direct sdf data
else if (Array.isArray(arg)) {
data = arg
}
//image data, pixels, canvas, array
else {
data = sdf(arg, {
cutoff: cutoff,
radius: radius,
width: w,
height: h
})
}
// show(data, arg)
return data
}