block by 1wheel f9b9909f10ed0f01780c5338ad38bd50

conventions-layers

Full Screen

d3-jetpackā€™s d3.conventions can now create canvas and html elements. Here d3.conventions({layers: 'csd'}) makes an canvas ctx, svg and div with a shared coordinate system. Yellow shapes are drawn on canvas, cyan on svg and purple on html.

Layers are position absolutely on top of each other in the order listed in the layer string. To create an svg with two canvas elements on top:

var {layers: [svg, bg_ctx, fg_ctx]} = d3.conventions({layers: 'scc'})

Hurricane How-To describes using multiple renders for something more practical than bouncing circles.

_script.js

console.clear()

var sel = d3.select('body').html('')

var c = d3.conventions({sel, layers: 'csd', margin: {left: 30, bottom: 40}})

var xMax = Math.floor(c.width/2)
c.x.domain([0, xMax])
c.y.domain([0, 100])
d3.drawAxis(c)

var [ctx, svg, div] = c.layers

svg.select('.y').append('text')
  .text('ms per frame')
  .at({textAnchor: 'start', x: -25, y: 15, fill: '#000'})

var points = d3.range(150)
  .map(d => [Math.random()*c.width, Math.random()*c.height])
points.forEach(d => {d.vx = 0, d.vy = 0})

var r = 6

var svgCircles = svg.appendMany('circle', points)
  .translate(d => d)
  .at({r, fill: '#0ff', stroke: '#0ff', fillOpacity: .2})

var divCircles = div.appendMany('div', points)
  .translate(d => d)
  .st({
    position: 'absolute',
    width: r*2,
    height: r*2,
    left: -r, 
    top: -r,
    borderRadius: r*2 + 'px',
    background: 'rgba(255, 0, 255, .2)',
    backgroundOpacity: .2,
    border: '1px solid #f0f'
  })

var i = 0
var prevT = 0
if (window.timer) timer.stop()
timer = d3.timer(t => {
  i++
  var dt = t - prevT
  prevT = t

  var color = ['#0ff', '#f0f', '#ff0'][i % 3]
  var x = c.x(i)
  var y = c.y(dt)

  if (i > xMax){
    i = i - xMax
    c.sel.selectAll('.time-bar').remove()
  }

  points.forEach(d => {
    d.vx = d3.clamp(-3, d.vx + Math.random()*.6 - .6*.5, 3)
    d.vy = d3.clamp(-3, d.vy + Math.random()*.6 - .6*.5, 3)

    d[0] += d.vx
    d[1] += d.vy

    if (d[0] < 0 || d[0] > c.width)  d.vx = -d.vx
    if (d[1] < 0 || d[1] > c.height) d.vy = -d.vy
  })

  if        (i % 3 == 0){
    svgCircles.translate(d => d)
    svg.append('path.time-bar')
      .at({d: `M ${x} ${c.height} V ${y}`, stroke: color})
  } else if (i % 3 == 1){
    divCircles.translate(d => d)
    div.append('div.time-bar')
      .st({left: x, top: y, height: c.height - y})
      .st({width: 1, background: color, position: 'absolute'})
  } else if (i % 3 == 2){
    ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'
    ctx.fillRect(-c.margin.left, -c.margin.top, c.totalWidth, c.totalHeight)
    ctx.beginPath()
    ctx.fillStyle = 'rgba(255, 255, 0, .2)'
    ctx.strokeStyle = '#ff0'
    points.forEach(([x, y]) => {
      ctx.moveTo(x + r, y)
      ctx.arc(x, y, r, 0, 2 * Math.PI)
    })
    ctx.stroke()
    ctx.fill()

    ctx.strokeStyle = color
    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x, c.height)
    ctx.stroke()
  }
})








index.html

<!DOCTYPE html>
<meta charset='utf-8'>
<link rel="stylesheet" type="text/css" href="style.css">

<body>
  <div class='graph'></div>
</body>

<script src='d3+_.js'></script>
<script src='_script.js'></script>

style.css

body{
  font-family: monaco, Consolas, 'Lucida Console', monospace; 
  margin: 0px;
  background: black;
  overflow: hidden;
}

canvas{
  position: absolute;
  top: 0px;
  left: 0px;
}

.tooltip {
  top: -1000px;
  position: fixed;
  padding: 10px;
  background: rgba(255, 255, 255, .90);
  border: 1px solid lightgray;
  pointer-events: none;
}
.tooltip-hidden{
  opacity: 0;
  transition: all .3s;
  transition-delay: .1s;
}

@media (max-width: 590px){
  div.tooltip{
    bottom: -1px;
    width: calc(100%);
    left: -1px !important;
    right: -1px !important;
    top: auto !important;
    width: auto !important;
  }
}

svg{
  overflow: visible;
}

.domain{
  display: none;
}

text{
  pointer-events: none;
  fill: #fff;
  /*text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;*/
}

.time-bar{
  /*shape-rendering: crispEdges;*/
}