block by gka cc00d2a0a3b09cba43850ca8012a5328

regl-hurricane-II

Full Screen

To dev:

yarn && yarn run start

Edit script.js and the page will update without a full reload.

Trying to remake Hurricane Irma Is One of the Strongest Storms In History with regl using line examples and rreusser’s demo. See my first attempt for an example of just drawing lines to different points.

I’m not sure if converting to the ndarray is necessary.

Hurricane data only includes storms through 2016 and hasn’t been cleaned.

forked from 1wheel‘s block: regl-hurricane-II

_script.js

try { window.regltick.cancel() } catch(e){}
console.clear()

var data, byStorm

// parse hurricane data
d3.loadData('hurdat2.csv', (err, res) => {
  data = res[0]

  data.forEach((d, i) => {
    if (d.Longitude.includes('E')){
      d.Longitude = -d.Longitude.replace('E', '') + ''
    }
    if (d.Latitude.includes('S')){
      d.Latitude = -d.Latitude.replace('S', '') + ''
    }

    d.i = i
  })

  byStorm = d3.nestBy(data, d => d.Serial)
  byStorm.forEach(storm => {

    storm.forEach((d, i) => {
      d.maxWind = +d['Max wind']
      d.day = i/4 // assumes data points a day 
      d.prev = i ? storm[i - 1] : null;
    })

    storm.maxWind = d3.max(storm, d => d.maxWind)
    storm.forEach(d => {
      d.color = storm.maxWind > 136 ? 1 : .1
    })

    // https://en.wikipedia.org/wiki/Accumulated_cyclone_energy
    var ace = 0
    storm.forEach(d => {
      ace += d.maxWind*d.maxWind/10000
      d.ace = ace
    })
    storm.ace = ace
  })

  byStorm = _.sortBy(byStorm, d => -d.maxWind)

  // calc positions for each step
  var projection = d3
    .geoConicConformal()
    .parallels([18 + 2 / 60, 18 + 26 / 60])
    .rotate([60 + 26 / 60, 13 - 50 / 60])
    .fitExtent([[-.7, .5], [0, .6]], {
      'type': 'LineString', 'coordinates': [[-95.2, 0.5], [-60.1, 8.5]]
    })


  var x = d3.scaleLinear().domain([0, 16]).range([1, -1])

  var windSpeedY = d3.scaleLinear().domain([40, 190]).range([-.6, 1])
  var aceScaleY = d3.scaleLinear().domain([0, 90]).range([-.6, 1])

  data.forEach(d => {
    // position
    d.pos0 = projection([-d.Longitude.replace('W', ''), d.Latitude.replace('N', '')])

    // lon v. wind speed
    d.pos1 = [d.pos0[0], windSpeedY(d.maxWind)]

    // day v. wind speed
    d.pos2 = [x(d.day), windSpeedY(d.maxWind)]

    // day v. ACE
    d.pos3 = [x(d.day), aceScaleY(d.ace)]
  })




  window.regl ? run(null, regl) : reglLib({onDone: run})
})



function run(err, regl){
  window.regl = regl

  // regl.clear({color: [0, 0, 0, 1], depth: 1})

  var n = data.length


  elements = byStorm
    .map(d => d.filter(d => d.prev).map(d => [d.prev.i, d.i]))
    .filter(d => d.length)
  elements = _.flatten(elements)



  var datasets = []
  var curIndex = 0
  var lastSwitchTime = 0
  var switchInterval = 2
  var switchDuration = 1


  function createDatasets(){
    // datasets = ['pos0', 'pos1', 'pos2', 'pos3'].map(str => 
    datasets = ['pos3', 'pos0', 'pos1', 'pos2'].map(str => 
      regl.buffer(vectorFill(ndarray([], [data.length, 2]), i => data[i][str]))
    )
  }
  createDatasets()
  var colors = linspace(ndarray([], [n]), 1, 0)



  var drawPoints = regl({
    vert: `
      precision mediump float;
      attribute vec2 xy0, xy1;
      attribute float basis;
      varying float c;
      uniform float interp;      
      void main() {
        c = basis;
        vec2 pos = mix(xy0, xy1, interp);
        gl_Position = vec4(pos, 0, 1);
      }`,

    frag: `
      precision mediump float;
      varying float c;
      void main() {
        gl_FragColor = vec4(c, 0, c, c + .1);
      }`,
    lineWidth: 1,
    attributes: {
      xy0: () => datasets[curIndex % datasets.length],
      xy1: () => datasets[(curIndex + 1) % datasets.length],
      basis: data.map(d => d.color),
    },
    uniforms: {
      interp: (ctx, props) => Math.max(0, Math.min(1, props.interp))
    },

    elements: regl.elements({
      data: elements,
      type: 'uint16',
      usage: 'static',
      primitive: 'lines'
    }),
  })


  window.regltick = regl.frame(({time}) => {
    // Check how long it's been since the last switch, and cycle the buffers
    // and reset the timer if it's time for a switch:
    if ((time - lastSwitchTime) > switchInterval) {
      lastSwitchTime = time
      curIndex++
    }

    drawPoints({interp: ease((time - lastSwitchTime) / switchDuration)})
  })


}

index.html

<!DOCTYPE html>
<html>
<head>
<body></body>

<script src='d3+_.js'></script>
<script src='lib-build.js'></script>
<script src='_script.js'></script>
</html>

lib-src.js

var libs = {
  glsl: require('glslify'),
  linspace: require('ndarray-linspace'),
  vectorFill: require('ndarray-vector-fill'),
  ndarray: require('ndarray'),
  ease: require('eases/cubic-in-out'),
  reglLib: require('regl'),
}

var glsl = require('glslify')
libs.glslViridis = glsl`
#pragma glslify: colormap = require(glsl-colormap/viridis)
`


for (key in libs) window[key] = libs[key]

package.json

{
  "version": "1.0.0",
  "description": "watchify and hot-server",
  "scripts": {
    "start": "watchify -t glslify lib-src.js -o lib-build.js & hot-server"
  },
  "dependencies": {
    "browserify": "^14.1.0",
    "budo": "^9.4.7",
    "control-panel": "^1.2.0",
    "d3": "^4.10.2",
    "eases": "^1.0.8",
    "es2040": "^1.2.5",
    "fail-nicely": "^2.0.0",
    "github-cornerify": "^1.0.7",
    "glsl-colormap": "^1.0.1",
    "glsl-noise": "^0.0.0",
    "glslify": "^6.0.1",
    "hot-server": "^0.0.11",
    "indexhtmlify": "^1.3.1",
    "metadataify": "^1.0.3",
    "ndarray": "^1.0.18",
    "ndarray-linspace": "^2.0.3",
    "ndarray-vector-fill": "^1.0.0",
    "regl": "^1.3.0",
    "standard": "^9.0.1",
    "uglify-js": "^2.8.13",
    "watchify": "^3.9.0"
  }
}