block by syntagmatic 8852129f7d6508d6fd67c4067f85512c

Intro to HTML5 Canvas, Øredev 2018

Full Screen

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>canvas</title>
    <meta charset="utf-8">
    <style>
      body {
        font-family: 'Futura';
        font-weight: normal;
        color: #080808;
        font-size: 20px;
      }
      h1, h2, h3, h4 {
        font-family: 'Futura';
        font-weight: normal;
      }
      h2 {
        font-size: 30px !important;;
      }
      .bottom h2 {
        margin-bottom: 0px;
      }
      .remark-code, .remark-code-line {
        font-family: 'Ubuntu Mono', Monaco, Menlo, monospace;
      }
      .remark-slide-content {
        background-size: cover;
        padding: 1em 3em !important;
      }
      .inverse {
        background-color: #1c1e19;
        color: #f4f3f2;
      }
      .inverse h1 {
        text-shadow: 0 0 8px #555;
      }
      .inverse h1, .inverse h2 {
        color: #f4f3f2;
      }
      a {
        color: #762a83;
        text-decoration: none;
      }
      code.remark-code {
        background: rgba(255,255,255,0.9) !important;
        border-radius: 3px;
        font-size: 20px;
      }
      blockquote {
        font-size: 1.6em;
        margin: 1em 0;
      }
      .hljs-attr {
        color: #e6550d !important;
      }
      .remark-slide-number {
        font-size: 13px;
      }
      .nobg {
        background: transparent;
      }
      /* Two-column layout */
      .left-column {
        color: #777;
        width: 20%;
        height: 92%;
        float: left;
        font-size: 18px;
      }
      .right-column {
        width: 76%;
        float: right;
      }

    </style>
  </head>
  <body>
    <textarea id="source">

class: center, middle

# canvas

&nbsp;

<span style="color: #777;">an intro to web graphics with html5</span>

&nbsp;

Kai Chang<br/>
<a href="//bl.ocks.org/syntagmatic">bl.ocks.org/syntagmatic</a><br/>

---

class: bottom

background-image: url(julia.png)

## <a href="//bl.ocks.org/syntagmatic/3736720/">Julia Set</a>

---

class: bottom

<a href="https://archive.nytimes.com/www.nytimes.com/interactive/2012/11/11/sunday-review/counties-moving.html"><img src="nyt-wind.gif"/ width="90%" ></a>


---

## Exoplanets

<a href="https://bl.ocks.org/syntagmatic/482706e0638c67836d94b20f0cb37122/"><img src="exoplanets.png"/ width="110%" style="margin-left: -7%;"></a>

Extrasolar planets, their discovery method and characteristics in parallel coordinates.

Data from the NASA Exoplanet Archive.

---

background-image: url(monitor-small.jpg)

---

background-image: url(monitor-big.png)

---

background-image: url(monitor-big-dark.png)

class: middle, inverse

<blockquote>The electric light is pure information.<br/>It is a medium without a message, as it were, unless it is used to spell out some verbal ad or name.
</blockquote>
Marshall McLuhan

---

background-image: url(eye-cells.jpg)
background-size: contain

---

class: bottom, right

background-image: url(eye-brain.jpg)
background-size: contain

<a href="//open.lib.umn.edu/intropsyc/">open.lib.umn.edu/intropsyc</a>

---

class: middle, inverse

<blockquote>The canvas is the door to another dimension.<br/>The paintbrush is the key.
</blockquote>
Luhraw

---

class: middle, inverse

<blockquote>The <span style="color: #00E218;">HTML5</span> canvas is the door to another dimension.<br/><span style="text-decoration: line-through; color: #888;">The paintbrush</span> <span style="color: #00E218;">Javascript</span> is the key.
</blockquote>
Kai

---

## Canvas

* Used for drawing graphics in the browser
* Pixel-based (bitmap of rgba image data)
* Procedural, immediate Mode
* This talk is about the 2D Context
* There is also a 3D WebGL Context

## JavaScript

* Call methods on the Canvas context
* Load, parse, draw data
* Data structures: Arrays, Objects, Strings, Numbers
* Functions for data transformation, drawing subroutines

## Applications

* Generative art
* Data visualization
* Web-based games
* Drawing and image editing apps

---

## Can I use?

<a href="https://caniuse.com/#feat=canvas"><img src="caniuse.png" width=800/></a>

---

.left-column[
## A Blank Canvas
]
.right-column[
We need a canvas element to draw on:

```html
<canvas id='painting' width=960 height=500></canvas>
```

And a canvas context to render to:

```html
<script type='text/javascript'>

  var canvas = document.getElementById('painting')
  var context = canvas.getContext('2d')

  // draw on pixels here

</script>
```
]

---

background-image: url(rects.png)

.left-column[
## Rectangles

Rect arguments:

* x
* y
* width
* height
]
.right-column[
```html
<script type='text/javascript'>

  var canvas = document.getElementById('painting')
  var context = canvas.getContext('2d')

  context.fillStyle = "mediumseagreen"
  context.fillRect(500,20,200,600)

  context.fillStyle = "darkorchid"
  context.fillRect(200,200,600,60)

  context.fillStyle = "deepskyblue"
  context.fillRect(400,180,60,300)

</script>
```
]

---

.left-column[
## Lines

Move a cursor around with moveTo and lineTo
]
.right-column[
```javascript
context.strokeStyle = "#f5b"
context.lineWidth = 3

context.beginPath()
context.moveTo(150,250)
context.lineTo(200,100)
context.lineTo(250,175)
context.lineTo(300,100)
context.lineTo(350,250)
context.stroke()
```

&nbsp;

<img src="lines.png" width=200 />
]

---

.left-column[
## Lines

Close shapes with closePath()
]
.right-column[
```javascript
context.strokeStyle = "#5738FF"
context.lineWidth = 3

context.beginPath()
context.moveTo(150,250)
context.lineTo(200,100)
context.lineTo(250,175)
context.lineTo(300,100)
context.lineTo(350,250)
context.closePath()
context.stroke()
```

&nbsp;

<img src="lines-closed.png" width=200 />
]
---

.left-column[
## Shapes

The same as lines, just using fill instead of stroke
]
.right-column[
```javascript
context.fillStyle = "#15673D"

context.beginPath()
context.moveTo(150,250)
context.lineTo(200,100)
context.lineTo(250,175)
context.lineTo(300,100)
context.lineTo(350,250)
context.closePath()
context.fill()
```

&nbsp;

<img src="lines-filled.png" width=200 />
]

---

background-image: url(circs.png)

.left-column[
## Circles

Arc arguments:

* x
* y
* r
* start angle
* end angle
]
.right-column[
```javascript
context.fillStyle = "crimson"
context.beginPath()
context.arc(800,250,450,0,2*Math.PI)
context.fill()

context.fillStyle = "steelblue"
context.beginPath()
context.arc(50,250,50,0,2*Math.PI)
context.fill()

```
]


---

.left-column[
## Creating words
]
.right-column[
```javascript
function circle(x,y,radius,color) {
  context.fillStyle = color
  context.beginPath()
  context.arc(x,y,radius,0,2*Math.PI)
  context.fill()
}
```
]

---

background-image: url(circs.png)

.left-column[
## Creating words

Filling circles (again)
]
.right-column[
```javascript
circle(800,250,450,"crimson")
circle(800,250,450,"steelblue")


function circle(x,y,radius,color) {
  context.fillStyle = color
  context.beginPath()
  context.arc(x,y,radius,0,2*Math.PI)
  context.fill()
}
```
]

---

.left-column[
## Animation

&nbsp;

<img src="canvas-animation.gif" width=140 />
]
.right-column[
```html
<script src="https://d3js.org/d3.v4.js"></script>
<script>

d3.timer(function() {
  // redraw all the things!
})

</script>
```
]

---

.left-column[
## Animation

&nbsp;

<img src="canvas-animation.gif" width=140 />
]
.right-column[
```javascript
var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

// center the animation
context.translate(canvas.width/2, canvas.height/2)

d3.timer(function(t) {
  var x = Math.cos(t/2000*Math.PI) * 120
  var y = Math.sin(t/2000*Math.PI) * 120
  var color = "hsl(" + (Math.floor(t/30)%360) + ",80%,60%)"

  circle(x,y,40,color)
})

function circle(x,y,radius,color) {
  context.fillStyle = color
  context.beginPath()
  context.arc(x,y,radius,0,2*Math.PI)
  context.fill()
}

```
]

---

.left-column[
## Animation

&nbsp;

&nbsp;

Use clearRect to erase previously drawn areas of the canvas

&nbsp;

<img src="canvas-animation-2.gif" width=140 />
]
.right-column[
```javascript
var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

// center the animation
context.translate(canvas.width/2, canvas.height/2)

d3.timer(function(t) {
  context.clearRect(-canvas.width/2,
                    -canvas.height/2,
                     canvas.width,
                     canvas.height)

  var x = Math.cos(t/2000*Math.PI) * 120
  var y = Math.sin(t/2000*Math.PI) * 120
  var color = "hsl(" + (Math.floor(t/30)%360) + ",80%,60%)"

  circle(x,y,40,color)
})

function circle(x,y,radius,color) {
  context.fillStyle = color
  context.beginPath()
  context.arc(x,y,radius,0,2*Math.PI)
  context.fill()
}

```
]
---

background-image: url(boids.gif)

.left-column[
## Particle Trails

Clearing the canvas by filling a transparent rect can be used to create a whimsical trail effect
]
.right-column[
```javascript
d3.timer(function() {
  context.fillStyle = 'rgba(255,255,255,0.3)'
  context.fillRect(0, 0, canvas.width, canvas.height)

  // redraw all the things!
})
```
]

---

.left-column[
## That's right, Particle Trails!

And torus wrapping!
]
.right-column[
<a href="trails.html"><img src="trails.gif" width=360 /></a>
<a href="trails-neon.html"><img src="trails-neon.gif" width=360 /></a>
<a href="compositing.html"><img src="bubbles.gif" width=360 /></a>
<a href="compositing-2.html"><img src="bubbles-dark.gif" width=360 /></a>
]

---

.left-column[
## Bar Chart

<div style="height:17px">&nbsp;</div>
Set up the canvas
<div style="height:36px">&nbsp;</div>
Make some data
<div style="height:15px">&nbsp;</div>
Color pink
<div style="height:15px">&nbsp;</div>
Draw the bars
<div style="height:38px">&nbsp;</div>
These "magic numbers" shape and scale the chart

&nbsp;
<img src="barchart.png" width=100 />

]
.right-column[
```html
<canvas id='painting' width=100 height=500></canvas>

<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

var data = [1, 1, 2, 3, 5, 8, 13, 21, 34]

context.fillStyle = "#f5b"

data.forEach(drawBar)

function drawBar(d,i) {
  var x = 10*i
  var y = 190
  var width = 8
  var height = -5*d
  context.fillRect(x,y,width,height)
}
</script>
```
]

---
class: center

.left-column[
## Compositing 

Changes the way colors combine when overlapping shapes are rendered.
]
.right-column[
<img src="source-over.gif" width=130/>
```javascript
  context.globalCompositeOperation = "source-over"  // default
```

<img src="multiply.gif" width=130/>
```javascript
  context.globalCompositeOperation = "multiply"
```

<img src="screen.gif" width=130/>
```javascript
  context.globalCompositeOperation = "screen"
```
]

---

class: bottom
background-image: url(infant-mortality.png)
background-size: contain

<a href="//bl.ocks.org/syntagmatic/623a3221d3e694f85967d83082fd4a77">SVG version</a> ·
<a href="//bl.ocks.org/syntagmatic/cb9a7441f79908c630207383b194a701">Canvas version</a>

---

background-image: url(choro.png)
background-size: contain

## Mapmaking

```javascript
var projection = d3.geoAlbersUsa()

var path = d3.geoPath()
	.projection(projection)
	.context(context)

var color = d3.scaleThreshold()
  .range(["#fde0dd", "#fcc5c0",
          "#fa9fb5", "#f768a1",
          "#dd3497", "#ae017e",
          "#7a0177","#49006a"])
  .domain([250, 500, 750, 1000, 1250, 1500, 1750, 2000])

context.strokeStyle = "#fff"
context.lineWidth = 0.3

countGeojson.forEach(function(d) {
	context.fillStyle = color(d["Crude Rate"])
	context.beginPath()
	path(d)
	context.fill()
	context.stroke()
})
```

---

background-image: url(science-sats.gif)

# Science Satellites

```javascript
d3.timer(function(elapsed) {
  // run time at 151x speed
  var time = new Date(now.getTime() + 150*elapsed)

  data.forEach(function(d) {
    plotSatellite(d, time)
  })
})


function plotSatellite(d, time) {
  var position = satellite.propagate(sat, time).position
  var groundPosition = satellite.geodetic(position, time)

  drawSat(sat, groundPosition)
}

```

Note: this bit is pseudo-code.

<a href="https://bl.ocks.org/syntagmatic/e494e2c3d7c797e41e48838f005f731f">Click here for a live version</a>.

---

## Places to Learn More

<a href="https://www.w3schools.com/html/html5_canvas.asp">w3Schools Canvas Tutorial</a>

<a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">MDN Canvas API</a>

<a href="https://flaviocopes.com/canvas/">The HTML5 Canvas Guide</a>

<a href="https://joshondesign.com/p/books/canvasdeepdive/toc.html">Josh on Design Canvas Deep Dive</a>

<a href="https://www.visualcinnamon.com/2015/11/learnings-from-a-d3-js-addict-on-starting-with-canvas.html">Learnings from a d3.js addict starting with canvas</a>

---

class: center, middle

## Thank you Øredev

Now go draw some rects!

    </textarea>
    <script src="remark-latest.min.js">
    </script>
    <script>
      var slideshow = remark.create({
        highlightStyle: 'color-brewer',
        ratio: "16:10"
      });
    </script>
    <script src="https://d3js.org/d3.v4.js"></script>
  </body>
</html>

animation.html

<style>
body {
  width: 1000px;
  margin: 0 auto;
  padding: 0;
}
</style>
<canvas id='painting' width=740 height=740></canvas>
<script src="d3.v4.js"></script>
<script>
var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

// center the animation
context.translate(canvas.width/2, canvas.height/2)

d3.timer(function(t) {
  context.clearRect(-canvas.width/2, -canvas.height/2, canvas.width, canvas.height)

  var x = Math.cos(t / 2000 * Math.PI) * 120
  var y = Math.sin(t / 2000 * Math.PI) * 120
  var color = "hsl(" + (Math.floor(t/30) % 360) + ",80%,60%)"

  circle(x,y,40,color)
})

function circle(x,y,radius,color) {
  context.fillStyle = color
  context.beginPath()
  context.arc(x,y,radius,0,2*Math.PI)
  context.fill()
}
</script>

barchart.html

<canvas id='painting' width=120 height=500></canvas>
<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

var data = [1, 1, 2, 3, 5, 8, 13, 21, 34]

context.fillStyle = "#f5b"
data.forEach(drawBar)

function drawBar(d,i) {
  var x = 12*i;
  var y = 200;
  var width = 10;
  var height = -5*d;
  context.fillRect(x,y,width,height);
}
</script>

color-wheels.html

<style>
body {
  width: 1000px;
  margin: 0 auto;
  padding: 0;
}
</style>
<canvas id='painting' width=440 height=440></canvas>
<script src="d3.v4.js"></script>
<script>
var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

context.globalCompositeOperation = "multiply";

// center the animation
context.translate(canvas.width/2, canvas.height/2)

d3.timer(function(t) {
  context.clearRect(-canvas.width/2, -canvas.height/2, canvas.width, canvas.height)

  d3.range(3).forEach(function(d) {
    d = d + 0.0002 * t

    var x = Math.cos(d / 1.5 * Math.PI) * 70
    var y = Math.sin(d / 1.5 * Math.PI) * 70
    var color = "hsl(" + (d*120) + ",80%,60%)"

    circle(x,y,100,color)
  })
})

function circle(x,y,radius,color) {
  context.fillStyle = color
  context.beginPath()
  context.arc(x,y,radius,0,2*Math.PI)
  context.fill()
}
</script>

compositing-2.html

<style>
body {
  background: #000;
  width: 1000px;
  margin: 0 auto;
  padding: 0;
}
</style>
<canvas id='painting' width=1000 height=740></canvas>
<script src="d3.v4.js"></script>
<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')
context.globalCompositeOperation = "screen"

var particles = d3.range(150).map(function(d) {
  return {
    speed: 0.3 + 0.3 * Math.random(),
    size: 10 + 25*Math.random(),
    angle: 2 * Math.PI * Math.random(),
    x: canvas.width * Math.random(),
    y: canvas.height * Math.random(),
    fill: "hsl(" + Math.floor(360*Math.random()) + ",80%,50%)"
  }
});

function drawDot(d) {
  context.fillStyle = d.fill
  context.beginPath()
  context.arc(d.x,d.y,d.size,0,2*Math.PI)
  context.fill()
}

d3.timer(function(t) {
  particles.forEach(function(d) {
    d.x = d.x + Math.cos(d.angle) * d.speed
    d.y = d.y + Math.sin(d.angle) * d.speed
    
    // put particles on a torus
    if (d.x < 0) d.x = canvas.width
    if (d.y < 0) d.y = canvas.height
    if (d.x > canvas.width) d.x = 0
    if (d.y > canvas.height) d.y = 0
  })

  context.clearRect(0, 0, canvas.width, canvas.height)

  particles.forEach(drawDot);
})

</script>

compositing.html

<style>
body {
  width: 1000px;
  margin: 0 auto;
  padding: 0;
}
</style>
<canvas id='painting' width=1000 height=740></canvas>
<script src="d3.v4.js"></script>
<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')
context.globalCompositeOperation = "screen"

var particles = d3.range(150).map(function(d) {
  return {
    speed: 0.2 + 0.2 * Math.random(),
    size: 10 + 25*Math.random(),
    angle: 2 * Math.PI * Math.random(),
    x: canvas.width * Math.random(),
    y: canvas.height * Math.random(),
    fill: "hsl(" + Math.floor(360*Math.random()) + ",80%,60%)"
  }
});

function drawDot(d) {
  context.fillStyle = d.fill
  context.beginPath()
  context.arc(d.x,d.y,d.size,0,2*Math.PI)
  context.fill()
}

d3.timer(function(t) {
  particles.forEach(function(d) {
    d.x = d.x + Math.cos(d.angle) * d.speed
    d.y = d.y + Math.sin(d.angle) * d.speed
    
    // put particles on a torus
    if (d.x < 0) d.x = canvas.width
    if (d.y < 0) d.y = canvas.height
    if (d.x > canvas.width) d.x = 0
    if (d.y > canvas.height) d.y = 0
  })

  context.clearRect(0, 0, canvas.width, canvas.height)

  particles.forEach(drawDot);
})

</script>

lines.html

<canvas id='painting' width=960 height=500></canvas>
<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

context.fillStyle = "#15673D"

context.beginPath()
context.moveTo(150,250)
context.lineTo(200,100)
context.lineTo(250,175)
context.lineTo(300,100)
context.lineTo(350,250)
context.closePath()
context.fill()
</script>

trails-neon.html

<style>
body {
  background: #000;
  width: 1000px;
  margin: 0 auto;
  padding: 0;
}
</style>
<canvas id='painting' width=1000 height=740></canvas>
<script src="d3.v4.js"></script>
<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

var particles = d3.range(500).map(function(d) {
  return {
    speed: 1 + Math.random(),
    angle: 2 * Math.PI * Math.random(),
    x: canvas.width * Math.random(),
    y: canvas.height * Math.random(),
    fill: "hsl(" + Math.floor(360*Math.random()) + ",80%,60%)"
  }
});

function drawDot(d) {
  context.fillStyle = d.fill
  context.beginPath()
  context.arc(d.x,d.y,5,0,2*Math.PI)
  context.fill()
}

d3.timer(function(t) {
  particles.forEach(function(d) {
    d.x = d.x + Math.cos(d.angle) * d.speed
    d.y = d.y + Math.sin(d.angle) * d.speed
    
    // put particles on a torus
    if (d.x < 0) d.x = canvas.width
    if (d.y < 0) d.y = canvas.height
    if (d.x > canvas.width) d.x = 0
    if (d.y > canvas.height) d.y = 0
  })

  context.fillStyle = 'rgba(0,0,0,0.05)'
  context.fillRect(0, 0, canvas.width, canvas.height)

  particles.forEach(drawDot);
})

</script>

trails.html

<style>
body {
  background: #000;
  width: 1000px;
  margin: 0 auto;
  padding: 0;
}
</style>
<canvas id='painting' width=1000 height=740></canvas>
<script src="d3.v4.js"></script>
<script>

var canvas = document.getElementById('painting')
var context = canvas.getContext('2d')

var particles = d3.range(10000).map(function(d) {
  return {
    speed: 1 + Math.random(),
    angle: 2 * Math.PI * Math.random(),
    x: canvas.width * Math.random(),
    y: canvas.height * Math.random(),
  }
});

function drawDot(d) {
  context.fillRect(d.x,d.y,2,2)
}

d3.timer(function(t) {
  context.fillStyle = 'rgba(0,0,0,0.8)'
  context.fillRect(0, 0, canvas.width, canvas.height)

  particles.forEach(function(d) {
    d.x = d.x + Math.cos(d.angle) * d.speed
    d.y = d.y + Math.sin(d.angle) * d.speed
  })

  context.fillStyle = "#f5b"
  particles.forEach(drawDot)
})

</script>