luma.gl and deck.gl

luma.gl and deck.gl for Data Visualization

luma.gl (MIT, 9.4.2, github.com/visgl/luma.gl (https://github.com/visgl/luma.gl 2,483 ), about 2,500 stars) is a GPU toolkit with WebGL 2 and WebGPU back ends behind one API of devices, buffers, textures and models. Most developers meet it through deck.gl 492,297 (MIT, 9.4.0, github.com/visgl/deck.gl (https://github.com/visgl/deck.gl 14,614 ), about 14,600 stars), the large-data visualization framework begun at Uber and now part of vis.gl, an OpenJS Foundation project. You describe layers (points, arcs, hexagon bins, columns, GeoJSON, 3D tiles) over data arrays, and deck.gl builds the instanced buffers and shaders, handles picking and animates transitions. Its standalone bundle draws BookNest's ratings as 3D columns:

BookNest's ratings as a deck.gl ColumnLayer in an orbit viewHTML
<div id="chart" style="position:relative;width:600px;height:190px;background:#ece8de"></div>
<script src="https://cdn.jsdelivr.net/npm/deck.gl@9.4.0/dist.min.js"></script>
<script src="webgl-helper.js"></script>
<script>
const books = [4.6, 4.3, 4.8, 4.1, 4.5, 4.4].map((rating, i) =>
  ({ rating, i, color: GLH.rgb(GLH.covers[i]).map((c) => c * 255) }));
new deck.Deck({
  parent: document.getElementById('chart'), width: 600, height: 190,
  views: new deck.OrbitView({ fovy: 30 }),                  // no map: plain 3D coordinates
  initialViewState: { target: [0, 0, 22], rotationX: 12, rotationOrbit: -10, zoom: 1.5 },
  layers: [new deck.ColumnLayer({
    id: 'ratings', data: books, coordinateSystem: deck.COORDINATE_SYSTEM.CARTESIAN,
    getPosition: (d) => [(d.i - 2.5) * 30, 0], getElevation: (d) => (d.rating - 3.5) * 40,
    getFillColor: (d) => d.color, radius: 10, diskResolution: 24, extruded: true,
  })],
  onAfterRender: () => { window.__done = true; },
});
</script>
Browser output of Listing 3.160
Browser output of 160

Heights are ratings above 3.5 stars, so Salt and Saffron's 4.8 stands tallest. With a MapView and longitudes and latitudes, the same layer stands on a map, usually over MapLibre 34,367 (MapLibre GL for Vector Maps). The page logged two luma.gl warnings about uniform-block reflection falling back to its own std140 layout (UBO Layout), then rendered correctly.

BookNest's ratings as a deck.gl ColumnLayer in an orbit view, one extruded column per bookHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  #chart { position: relative; width: 100%; max-width: 600px; height: 300px; background: #ece8de; }
  .names { display: flex; max-width: 600px; text-align: center; font: 10px monospace; }
  .names div { flex: 1; padding: 3px 1px; }
  p { max-width: 600px; margin: 2px 6px; font: 11px monospace; }
</style>
<div id="chart"></div>
<div class="names" id="names"></div>
<p id="out"></p>
<!-- 9.3.11 rather than 9.4.0: 9.4.0 lost its WebGL context about 2 s in on the test GPU -->
<script src="https://cdn.jsdelivr.net/npm/deck.gl@9.3.11/dist.min.js"></script>
<script>
const books = [['The Quiet Harbor', 4.6, '#1e5f8c'], ['Small Steps', 4.3, '#5b3f99'], ["Clockmaker's Paradox", 4.1, '#e09a10'],
  ['Salt and Saffron', 4.8, '#3f7d3a'], ['Quiet Harbor (audio)', 4.5, '#b5452e'], ['Saffron (2nd ed.)', 4.4, '#2a9d8f']]
  .map(([title, rating, hex], i) => ({ title, rating, i, color: [1, 3, 5].map((k) => parseInt(hex.slice(k, k + 2), 16)) }));
document.getElementById('names').innerHTML = books.map((b) => `<div>${b.title}<br><b>${b.rating}</b></div>`).join('');
new deck.Deck({
  parent: document.getElementById('chart'), width: '100%', height: 300,
  views: new deck.OrbitView({ fovy: 30 }),                     // no map: plain 3D coordinates
  initialViewState: { target: [0, 0, 20], rotationX: 14, rotationOrbit: -12, zoom: 1.6 },
  controller: true,                                            // drag to orbit
  layers: [
    new deck.ColumnLayer({
      id: 'ratings', data: books, coordinateSystem: deck.COORDINATE_SYSTEM.CARTESIAN,
      getPosition: (d) => [(d.i - 2.5) * 30, 0], getElevation: (d) => (d.rating - 3.5) * 40,
      getFillColor: (d) => d.color, radius: 10, diskResolution: 24, extruded: true,
      pickable: true, autoHighlight: true,
    }),
    new deck.ColumnLayer({                                     // a flat floor under the columns
      id: 'floor', data: [0], coordinateSystem: deck.COORDINATE_SYSTEM.CARTESIAN,
      getPosition: () => [0, 0], getElevation: 0.5, getFillColor: [200, 192, 178], radius: 110,
      diskResolution: 4, angle: 45, extruded: true,
    }),
  ],
  getTooltip: ({ object }) => object?.title && `${object.title}: ${object.rating} stars`,
  onLoad: () => { document.getElementById('out').textContent = `deck.gl ${deck.VERSION} | heights are ratings above 3.5 stars`; },
});
</script>