Edit in JSFiddle

if window.angular_poc_loaded then return
window.angular_poc_loaded = true

do ->
    
    doc = document; warn = window.warn ?= (a...) -> console.log a...
    loadTemplate = (sel) -> doc.importNode doc.querySelector(sel).content,true
    loadFirst = null

    fns = []

    run = -> if fn = fns.shift() then fn run

    runScript = (code,scope) ->
      Function(CoffeeScript.compile(code,bare:true)).apply(scope)

    runScriptLater = (code,scope) ->
      fns.push (cb) -> runScript code, scope; cb()

    loadScript = (src,scope) ->
      fns.push (cb) ->
        r = new window.XMLHttpRequest
        r.open('GET',src, true)
        r.overrideMimeType('text/plain')
        r.onreadystatechange = -> if 4 == r.readyState
          if r.status in [0,200] then runScript r.responseText, scope
          else throw new Error "Script not loaded" + src
          cb()
        r.send()

    applyTemplate = (tpl,trg,content) ->
      trg = $(trg).append loadTemplate tpl
      for t in trg.find("content[select]")
        select = $(t).attr "select"
        match  = $(content).find(select)
        $(t).replaceWith match.contents() if match.length > 0
        match.remove()
      trg.find("content").replaceWith content if content?
      scope = document:trg
      for t in trg.find("script[type='text/coffeescript']")
        src = $(t).attr "src"
        warn src:src
        if src == "" or not src? then runScriptLater $(t).text(), scope
        else                          loadScript src, scope
      run()
      trg

    do ->
      tabs = $("tabs")
      apps = $("app")
      tpl  = "linedemo>template"
      apps.each ->
        name = $(@).attr("name"); autostart = $(@).attr "autostart"
        console.log name, tabs
        id = @id; warn {name,id,autostart}
        tab = applyTemplate tpl, $("<tab>"), app = @
        tab.find(".tab-button").click fn = ->
          warn "loading app",name,app_tpl = "##{id}>template"
          applyTemplate app_tpl,app

        $(tabs).append tab
        setTimeout fn,1 if autostart?

    warn "app tabs and first app loaded"

    window.SVGCOLORS =
      aliceblue: "rgb(240, 248, 255)"
      antiquewhite: "rgb(250, 235, 215)"
      aqua: "rgb( 0, 255, 255)"
      aquamarine: "rgb(127, 255, 212)"
      azure: "rgb(240, 255, 255)"
      beige: "rgb(245, 245, 220)"
      bisque: "rgb(255, 228, 196)"
      black: "rgb( 0, 0, 0)"
      blanchedalmond: "rgb(255, 235, 205)"
      blue: "rgb( 0, 0, 255)"
      blueviolet: "rgb(138, 43, 226)"
      brown: "rgb(165, 42, 42)"
      burlywood: "rgb(222, 184, 135)"
      cadetblue: "rgb( 95, 158, 160)"
      chartreuse: "rgb(127, 255, 0)"
      chocolate: "rgb(210, 105, 30)"
      coral: "rgb(255, 127, 80)"
      cornflowerblue: "rgb(100, 149, 237)"
      cornsilk: "rgb(255, 248, 220)"
      crimson: "rgb(220, 20, 60)"
      cyan: "rgb( 0, 255, 255)"
      darkblue: "rgb( 0, 0, 139)"
      darkcyan: "rgb( 0, 139, 139)"
      darkgoldenrod: "rgb(184, 134, 11)"
      darkgray: "rgb(169, 169, 169)"
      darkgreen: "rgb( 0, 100, 0)"
      darkgrey: "rgb(169, 169, 169)"
      darkkhaki: "rgb(189, 183, 107)"
      darkmagenta: "rgb(139, 0, 139)"
      darkolivegreen: "rgb( 85, 107, 47)"
      darkorange: "rgb(255, 140, 0)"
      darkorchid: "rgb(153, 50, 204)"
      darkred: "rgb(139, 0, 0)"
      darksalmon: "rgb(233, 150, 122)"
      darkseagreen: "rgb(143, 188, 143)"
      darkslateblue: "rgb( 72, 61, 139)"
      darkslategray: "rgb( 47, 79, 79)"
      darkslategrey: "rgb( 47, 79, 79)"
      darkturquoise: "rgb( 0, 206, 209)"
      darkviolet: "rgb(148, 0, 211)"
      deeppink: "rgb(255, 20, 147)"
      deepskyblue: "rgb( 0, 191, 255)"
      dimgray: "rgb(105, 105, 105)"
      dimgrey: "rgb(105, 105, 105)"
      dodgerblue: "rgb( 30, 144, 255)"
      firebrick: "rgb(178, 34, 34)"
      floralwhite: "rgb(255, 250, 240)"
      forestgreen: "rgb( 34, 139, 34)"
      fuchsia: "rgb(255, 0, 255)"
      gainsboro: "rgb(220, 220, 220)"
      ghostwhite: "rgb(248, 248, 255)"
      gold: "rgb(255, 215, 0)"
      goldenrod: "rgb(218, 165, 32)"
      gray: "rgb(128, 128, 128)"
      grey: "rgb(128, 128, 128)"
      green: "rgb( 0, 128, 0)"
      greenyellow: "rgb(173, 255, 47)"
      honeydew: "rgb(240, 255, 240)"
      hotpink: "rgb(255, 105, 180)"
      indianred: "rgb(205, 92, 92)"
      indigo: "rgb( 75, 0, 130)"
      ivory: "rgb(255, 255, 240)"
      khaki: "rgb(240, 230, 140)"
      lavender: "rgb(230, 230, 250)"
      lavenderblush: "rgb(255, 240, 245)"
      lawngreen: "rgb(124, 252, 0)"
      lemonchiffon: "rgb(255, 250, 205)"
      lightblue: "rgb(173, 216, 230)"
      lightcoral: "rgb(240, 128, 128)"
      lightcyan: "rgb(224, 255, 255)"
      lightgoldenrodyellow: "rgb(250, 250, 210)"
      lightgray: "rgb(211, 211, 211)"
      lightgreen: "rgb(144, 238, 144)"
      lightgrey: "rgb(211, 211, 211)"
      lightpink: "rgb(255, 182, 193)"
      lightsalmon: "rgb(255, 160, 122)"
      lightseagreen: "rgb( 32, 178, 170)"
      lightskyblue: "rgb(135, 206, 250)"
      lightslategray: "rgb(119, 136, 153)"
      lightslategrey: "rgb(119, 136, 153)"
      lightsteelblue: "rgb(176, 196, 222)"
      lightyellow: "rgb(255, 255, 224)"
      lime: "rgb( 0, 255, 0)"
      limegreen: "rgb( 50, 205, 50)"
      linen: "rgb(250, 240, 230)"
      magenta: "rgb(255, 0, 255)"
      maroon: "rgb(128, 0, 0)"
      mediumaquamarine: "rgb(102, 205, 170)"
      mediumblue: "rgb( 0, 0, 205)"
      mediumorchid: "rgb(186, 85, 211)"
      mediumpurple: "rgb(147, 112, 219)"
      mediumseagreen: "rgb( 60, 179, 113)"
      mediumslateblue: "rgb(123, 104, 238)"
      mediumspringgreen: "rgb( 0, 250, 154)"
      mediumturquoise: "rgb( 72, 209, 204)"
      mediumvioletred: "rgb(199, 21, 133)"
      midnightblue: "rgb( 25, 25, 112)"
      mintcream: "rgb(245, 255, 250)"
      mistyrose: "rgb(255, 228, 225)"
      moccasin: "rgb(255, 228, 181)"
      navajowhite: "rgb(255, 222, 173)"
      navy: "rgb( 0, 0, 128)"
      oldlace: "rgb(253, 245, 230)"
      olive: "rgb(128, 128, 0)"
      olivedrab: "rgb(107, 142, 35)"
      orange: "rgb(255, 165, 0)"
      orangered: "rgb(255, 69, 0)"
      orchid: "rgb(218, 112, 214)"
      palegoldenrod: "rgb(238, 232, 170)"
      palegreen: "rgb(152, 251, 152)"
      paleturquoise: "rgb(175, 238, 238)"
      palevioletred: "rgb(219, 112, 147)"
      papayawhip: "rgb(255, 239, 213)"
      peachpuff: "rgb(255, 218, 185)"
      peru: "rgb(205, 133, 63)"
      pink: "rgb(255, 192, 203)"
      plum: "rgb(221, 160, 221)"
      powderblue: "rgb(176, 224, 230)"
      purple: "rgb(128, 0, 128)"
      red: "rgb(255, 0, 0)"
      rosybrown: "rgb(188, 143, 143)"
      royalblue: "rgb( 65, 105, 225)"
      saddlebrown: "rgb(139, 69, 19)"
      salmon: "rgb(250, 128, 114)"
      sandybrown: "rgb(244, 164, 96)"
      seagreen: "rgb( 46, 139, 87)"
      seashell: "rgb(255, 245, 238)"
      sienna: "rgb(160, 82, 45)"
      silver: "rgb(192, 192, 192)"
      skyblue: "rgb(135, 206, 235)"
      slateblue: "rgb(106, 90, 205)"
      slategray: "rgb(112, 128, 144)"
      slategrey: "rgb(112, 128, 144)"
      snow: "rgb(255, 250, 250)"
      springgreen: "rgb( 0, 255, 127)"
      steelblue: "rgb( 70, 130, 180)"
      tan: "rgb(210, 180, 140)"
      teal: "rgb( 0, 128, 128)"
      thistle: "rgb(216, 191, 216)"
      tomato: "rgb(255, 99, 71)"
      turquoise: "rgb( 64, 224, 208)"
      violet: "rgb(238, 130, 238)"
      wheat: "rgb(245, 222, 179)"
      white: "rgb(255, 255, 255)"
      whitesmoke: "rgb(245, 245, 245)"
      yellow: "rgb(255, 255, 0)"
      yellowgreen: "rgb(154, 205, 50)"

<linedemo unresolved>
  <template>
    <!--<button class="tab-button"><content select="name"></content></button>-->
    <content></content>
  </template>

  <tabs>
  </tabs>

  <app name="Lines" id="line-render-app" autostart>
    <!--<name>Lines!</name>-->
    <template>
      <canvas width="400" height=200></canvas>
      <controls>
        <inputs ng-enter="render">
          <label>lines:</label><input type="text" ng-model="lineCount"></input>
          <label>t<sub>max</sub>:</label><input type="text" ng-model="t_max"></input>
          <label></label><button ng-click="render">render</button>
          <label></label><button ng-click="render(,true)">render (sync)</button>
        </inputs>
        <stats>
          <label>t<sub>create</sub></label><span> = </span><span ng-text="t_create"></span><span> ms</span>
          <label>t<sub>pack</sub></label><span> = </span><span ng-text="t_pack"></span><span> ms</span>
          <label>t<sub>unpack</sub></label><span> = </span><span ng-text="t_unpack"></span><span> ms</span>
          <label>t<sub>render</sub></label><span> = </span><span ng-text="t_render"></span><span> ms</span>
        </stats>
      </controls>
      <script type="text/coffeescript">
          #imports
          {SVGCOLORS} = window

          #setup global scope
          G = this

          #setup scope for DOM interaction
          G.O = {}

          #basic utils
          G.warn     ?= (a...) -> console.debug a...
          G.$        ?= (a...) -> document.querySelectorAll a...
          G.isString ?= (s)    -> typeof s is "string"
          G.isEmpty  ?= (s)    -> (not s?) or s == ""

          #setup document root
          G.document ?= document
          if G.document == document then G.warn "You are running as root. Be careful!", G.document

          #wrap global jQuery locally
          G.$ = do -> jQuery = window.$; (o,a...) ->
            if isString o then jQuery(G.document).find o,a...
            else               jQuery o,a...

          #benchmarking fns
          G.bench = do ->
            bench_count = 0
            bench_fn = (name,fn=name,log=true) ->
              bench_count++
              name = bench_count unless G.isString name
              t1 = Date.now(); fn(); t = Date.now() - t1
              if log == true then G.warn "bench",name,":",t,"ms"
              t

          G.abench = do ->
            abench_count = 0
            abench_fn = (name,fn=name,setTime,cb,log=true) ->
              abench_count++
              name = bench_count unless G.isString name
              t1 = Date.now()
              fn (a...) ->
                setTime t = Date.now() - t1
                G.warn "bench",name,":",t,"ms"
                cb a... if cb?

          #expression parser
          G.parseValue = (v) -> #maps string to basic atoms
            switch v
              when ""      then undefined
              when "null"  then null
              when "true"  then true
              when "false" then false
              else v

          G.parseExpr = (exp) -> #converts "fn(a.b)" string to name + params
            tokens = exp.split /\(|\)/
            name   = tokens[0]
            if      tokens.length == 1 then params = []
            else if tokens.length == 3 then params = tokens[1].split(",").map G.parseValue
            else throw new Error "Bad expression: #{exp}"
            result =
              name:   name
              params: params
            #warn "parseExpr", result
            result

          #scope utils
          G.read  = (scope,name)     -> #reads properties or calls getter in scope
            if typeof scope[name] is "function" then scope[name]()
            else scope[name]

          G.write = (scope,name,val) -> #writes properties or calls setter in scope
            if typeof scope[name] is "function" then scope[name] val
            else scope[name] = val

          G.call  = (scope,name,params=[]) -> #calls a fn in scope
            if typeof scope[name] is "function" then scope[name] params...
            else throw new Error "#{name} is not a function in the scope"

          #async utils
          G.postpone = (t,fn=t) -> t = 0 if isNaN t; setTimeout fn,t

          G.async = do -> #queues and auto invokes async fns
            list = []; timo = null
            runLater = -> clearTimeout timo; timo = postpone 1, run
            run = -> if list.length > 0 then list.shift()(); runLater()
            async_fn = (fns...) -> list.push fns...; runLater()

          #import utils from global scope
          {O,warn,$,bench,abench,isString,isEmpty,
           parseExpr,read,write,call,postpone,async} = G

          class window.SvgColors
            _colors = (v for k,v of SVGCOLORS)
            constructor: ->
              i = 0
              fn = (num=i) -> _colors[i]
              fn.next = => i = (i + 1)%_colors.length; @color_fn()
              @color_fn = fn

          D =
            START_LEN: 10000
            MAX_LEN:   10e6
            T_MAX:     "10000"

          {MAX_LEN} = D

          rand  = (n=1) -> Math.random()*n
          fr    = (n) -> Math.round n
          frand = (n) -> fr rand n

          O.line_count = D.START_LEN
          O.t_max      = D.T_MAX
          O.points     = []

          O.lineCount = (n) ->
            if n?
              n = 1 * n
              O.line_count = if isNaN(n) then 0 else n
              warn "lineCount:", O.line_count, n:n
            O.line_count

          O.resetStats = -> O.t_create = O.t_pack = O.t_unpack = O.t_render = ""

          O.render = (cb,sync=false) ->
            O.packed = "[]"
            b = (name,fn) -> bench name,fn,false
            if sync then dsync = (fn) -> fn()
            else         dsync = (fn) -> async -> fn(); O.digest()
            dsync -> O.resetStats()
            dsync -> O.t_create = b "create", -> O.createPoints()
            dsync -> O.t_pack   = b "pack",   -> O.packed = JSON.stringify(data: O.points, size: O.points.length)
            dsync -> O.t_unpack = b "unpack", -> O.points = JSON.parse(O.packed).data
            draw_fn = (cb) -> O.drawLines cb,sync
            setTime = (v)  -> O.t_render = v
            done    = -> O.digest(); warn "render done", sync:sync; cb?()
            if sync then setTime b "render", draw_fn; done()
            else         async -> abench "render", draw_fn, setTime, done

          O.createPoints = ->
            {w,h} = O
            v0 = rand(h)
            len = O.line_count
            nextVal = (v0) -> Math.max 0, Math.min h, v0 + h * (rand() - rand())/100
            toPoint = (n)  -> [w*i/len + rand(), v0 = nextVal(v0)]
            warn "creatng", len, "points"
            len = MAX_LEN if len > MAX_LEN
            if len > 0
              O.points = list = []
              list.push toPoint i for i in [0...len]
              #warn i, "points created"
            O.line_count = O.points.length
            return

          O.drawLines = (cb,sync=false) ->
            {w,h,canvas,t_max} = O
            ctx = canvas.getContext "2d"
            ctx.save()
            ctx.clearRect 0,0,w,h
            #ctx.translate -0.5,-0.5 #not for rects!
            ctx.fillStyle = "black"
            len   = O.line_count
            t1    = Date.now()
            t_max = 1 * t_max
            p0    = O.points[0]
            color = new SvgColors().color_fn
            ctx.strokeStyle = color()

            fns = []
            addRenderFn = (i0,num) ->
              i1 = Math.min len-1, i0 + num
              #warn "addRenderFn", i0,i1,len
              fns.push fn = -> renderLines i0,i1

            addRenderFn i,step-1 for i in [0...len] by step=100000

            renderLines = (i0,i1) -> #bench "renderLines", ->
              if Date.now() - t1 < t_max
                #warn "renderLines", i0,i1,len
                for i in [i0..i1]
                  p = O.points[i]
                  ctx.beginPath()
                  ctx.moveTo p0[0],p0[1]
                  ctx.lineTo p[0], p[1]
                  ctx.stroke()
                  #ctx.fillRect frand(w), frand(h), 1,1
                  p0 = p
                  if i%1e3 == 0
                    ctx.strokeStyle = color.next()
                    if Date.now() - t1 > t_max
                      warn "stopped rendering after",t_max,"ms"; break

            done = ->
              warn i,"of",len,"lines rendered",sync:sync
              ctx.restore()
              cb?()

            if sync then fn() for fn in fns; done()
            else         async fns...; async done

          O.setupModel = ->
            O.canvas = $("canvas")[0]
            for e in $("input[ng-model]")
              name = $(e).attr "ng-model"
              val = read O,name if isEmpty val = $(e).val()
              write O,name,val
              $(e).change ->
                warn "change",name=$(@).attr("ng-model"), val = $(@).val()
                write O,name,val

            for e in $("[ng-enter]")
              $(e).keypress (event) -> if event.which == 13
                $(@).find("input").change()
                expr = parseExpr $(@).attr("ng-enter")
                call O, expr.name, expr.params

            for e in $("[ng-click]")
              $(e).click ->
                expr = parseExpr $(@).attr("ng-click")
                call O, expr.name, expr.params

            #populate derived and bound values
            O.digest true
            return

          O.digest = (init=false) ->
            O.w = O.canvas.width
            O.h = O.canvas.height
            for e in $("[ng-text]")
              name = $(e).attr "ng-text"
              $(e).text val = read O,name
              #warn "update text", name, val
            for e in $("input[ng-model]")
              name = $(e).attr "ng-model"
              $(e).val val = read O,name
              #warn "update val", name, val
            return

          O.setupModel()
          O.render()

          warn "vdda paper test module loaded"
      </script>
      <script type="text/coffeescript">warn "Lines app loaded"</script>
    </template>
  </app>
</linedemo>
linedemo canvas {
  border: solid 1px silver;
}
linedemo > * {
  float: left;
  clear: left;
}
linedemo controls > * {
  float: left;
}
linedemo span,linedemo input,linedemo button,linedemo p,linedemo label {
  font-family: BentonSans, Benton Sans, News Gothic MT, News Gothic, Verdana, sans;
  font-size: 10pt;
}
linedemo inputs {
  padding-left: 3px;
}
linedemo inputs > input {
  float: left;
}
linedemo inputs > label {
  margin-top: 8px;
  float: left;
  clear: left;
  min-width: 35px;
}
linedemo inputs > button {
  float: left;
}
linedemo stats {
  padding-left: 5px;
  border: solid 1px silver;
}
linedemo stats > label {
  clear: left;
}
stats > * {
  float: left;
}
linedemo stats > span {
  white-space: pre;
}
linedemo label {
  display: block;
  color: gray;
  float: left;
  min-width: 50px;
}
linedemo input,linedemo button,linedemo ul {
  padding: 1px;
  margin: 1px;
}
linedemo input,linedemo button {
  height: 30px;
}

External resources loaded into this fiddle: