├── src
├── views
│ ├── layout.coffee
│ ├── not-found.coffee
│ ├── behavior.coffee
│ ├── hex.coffee
│ ├── breadcrumbs.coffee
│ ├── db.coffee
│ ├── search.coffee
│ ├── navlist.coffee
│ ├── contributors.coffee
│ ├── index.coffee
│ ├── tree.coffee
│ ├── traffic.coffee
│ ├── main.coffee
│ ├── filter.coffee
│ ├── extensions
│ │ ├── index.coffee
│ │ └── info.coffee
│ └── parameters.coffee
├── chart
│ ├── index.coffee
│ ├── series.coffee
│ ├── bar.coffee
│ ├── gauge.coffee
│ ├── donut.coffee
│ └── stacked-percentage.coffee
├── index.coffee
├── util.coffee
├── scroll.coffee
└── location.coffee
├── robots.txt
├── img
├── checkbox.png
├── favicon.png
├── touch-icon
│ ├── 57.png
│ ├── 72.png
│ ├── 114.png
│ ├── 120.png
│ ├── 144.png
│ ├── 152.png
│ ├── 167.png
│ └── 180.png
├── referrers
│ ├── playcanvas.png
│ ├── sketchfab.png
│ └── mrdoob.svg
├── search.svg
├── hamburger.svg
└── logo.svg
├── lib
├── v0.10.1.zip
├── url-search-params.js
├── jquery.easypiechart.min.js
├── require.coffee
└── lunr.min.js
├── .gitignore
├── __init__.py
├── app-manifest.json
├── less
├── header.less
├── chart.less
├── search.less
├── index.less
├── filter.less
├── nav.less
├── main.less
└── normalize.less
├── index.html
└── res
├── sitemap.txt
└── sitemap-ssl.txt
/src/views/layout.coffee:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/img/checkbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/checkbox.png
--------------------------------------------------------------------------------
/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/favicon.png
--------------------------------------------------------------------------------
/lib/v0.10.1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/lib/v0.10.1.zip
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | google*
3 | script.js
4 | script.js.json
5 | style.css
6 | style.css.json
7 |
--------------------------------------------------------------------------------
/img/touch-icon/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/57.png
--------------------------------------------------------------------------------
/img/touch-icon/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/72.png
--------------------------------------------------------------------------------
/img/touch-icon/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/114.png
--------------------------------------------------------------------------------
/img/touch-icon/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/120.png
--------------------------------------------------------------------------------
/img/touch-icon/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/144.png
--------------------------------------------------------------------------------
/img/touch-icon/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/152.png
--------------------------------------------------------------------------------
/img/touch-icon/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/167.png
--------------------------------------------------------------------------------
/img/touch-icon/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/touch-icon/180.png
--------------------------------------------------------------------------------
/img/referrers/playcanvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/referrers/playcanvas.png
--------------------------------------------------------------------------------
/img/referrers/sketchfab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emackey/webglstats-site/master/img/referrers/sketchfab.png
--------------------------------------------------------------------------------
/src/chart/index.coffee:
--------------------------------------------------------------------------------
1 | exports.StackedPercentage = sys.import 'stacked-percentage'
2 | exports.Gauge = sys.import 'gauge'
3 | exports.Series = sys.import 'series'
4 | exports.Bar = sys.import 'bar'
5 | exports.Donut = sys.import 'donut'
6 |
--------------------------------------------------------------------------------
/src/views/not-found.coffee:
--------------------------------------------------------------------------------
1 | exports.index = ->
2 | widget = $('
')
3 | .appendTo('main')
4 |
5 | $('Page Not Found ')
6 | .appendTo(widget)
7 |
8 | $('''
9 | The page you requested could not be found.
10 |
''').appendTo(widget)
11 |
--------------------------------------------------------------------------------
/src/views/behavior.coffee:
--------------------------------------------------------------------------------
1 | activatables = []
2 | collapsables = []
3 |
4 | exports.activatable = (instance) ->
5 | activatables.push instance
6 |
7 | exports.collapsable = (instance) ->
8 | collapsables.push instance
9 |
10 | exports.collapse = (origin) ->
11 | for instance in collapsables
12 | if origin isnt instance
13 | instance.collapse()
14 |
15 | exports.deactivate = ->
16 | for instance in activatables
17 | instance.deactivate()
18 |
--------------------------------------------------------------------------------
/src/views/hex.coffee:
--------------------------------------------------------------------------------
1 | alphabet = '0123456789abcdef'
2 | lookup = {}
3 | for c, i in alphabet
4 | lookup[c] = i
5 |
6 | exports.encode = (data) ->
7 | string = ''
8 | for num in data
9 | a = Math.floor(num/16)
10 | b = num % 16
11 | string += alphabet[a]
12 | string += alphabet[b]
13 | return string
14 |
15 | exports.decode = (string) ->
16 | data = new Uint8Array(string.length/2)
17 | for i in [0...string.length] by 2
18 | a = lookup[string[i+0]]
19 | b = lookup[string[i+1]]
20 | data[i/2] = a*16 + b
21 | return data
22 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | import os, urlparse
2 |
3 | path = os.path.dirname(os.path.realpath(__file__))
4 |
5 | validPaths = set()
6 | for url in open(os.path.join(path, 'res/sitemap.txt')):
7 | url = url.strip()
8 | validPaths.add(urlparse.urlparse(url).path)
9 |
10 | class Site:
11 | def __init__(self, server, config, log):
12 | self.log = log
13 |
14 | def __call__(self, request, response, config):
15 | content = open(os.path.join(path, 'index.html'), 'rb').read()
16 | response['Content-Type'] = 'text/html; charset=utf-8'
17 | response.compress = True
18 | if request.path not in validPaths:
19 | response.status = '404 Not Found'
20 | return content
21 |
--------------------------------------------------------------------------------
/app-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name" : "WebGL Stats",
3 | "name" : "WebGL Stats",
4 | "display" : "fullscreen",
5 | "icons" : [
6 | {"src":"/img/touch-icon/57.png", "sizes":"57x57", "type":"image/png"},
7 | {"src":"/img/touch-icon/72.png", "sizes":"72x72", "type":"image/png"},
8 | {"src":"/img/touch-icon/114.png", "sizes":"114x114", "type":"image/png"},
9 | {"src":"/img/touch-icon/120.png", "sizes":"120x120", "type":"image/png"},
10 | {"src":"/img/touch-icon/144.png", "sizes":"144x144", "type":"image/png"},
11 | {"src":"/img/touch-icon/152.png", "sizes":"152x152", "type":"image/png"},
12 | {"src":"/img/touch-icon/167.png", "sizes":"167x167", "type":"image/png"},
13 | {"src":"/img/touch-icon/180.png", "sizes":"180x180", "type":"image/png"}
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/views/breadcrumbs.coffee:
--------------------------------------------------------------------------------
1 | exports.index = (items) ->
2 | items = items[...]
3 | items.unshift ['Home', '/']
4 |
5 | ol = $(' ')
6 | .attr('vocab', 'http://schema.org/')
7 | .attr('typeof', 'BreadcrumbList')
8 | .appendTo('main')
9 |
10 | itemList = []
11 | structuredData =
12 | '@context': 'http://schema.org',
13 | '@type': 'BreadcrumbList',
14 | itemListElement: itemList
15 |
16 | i = 1
17 | for item in items
18 | li = $(' ')
19 | .appendTo(ol)
20 |
21 | if typeof(item) == 'string'
22 | li.text(item)
23 | else
24 | $(' ')
25 | .attr('href', item[1])
26 | .text(item[0])
27 | .appendTo(li)
28 |
29 | itemList.push
30 | '@type': 'ListItem'
31 | position: i
32 | item:
33 | '@id': "//webglstats.com#{item[1]}"
34 | name: item[0]
35 |
36 | i += 1
37 |
38 | $('')
39 | .text(JSON.stringify(structuredData))
40 | .appendTo('main')
41 |
--------------------------------------------------------------------------------
/src/views/db.coffee:
--------------------------------------------------------------------------------
1 | progress = null
2 |
3 | exports.init = ->
4 | progress = $('
')
5 | .appendTo('header')
6 |
7 | requested = 0
8 | completed = 0
9 | visible = false
10 | fadeOuttimeout = null
11 |
12 | fadeOut = ->
13 | visible = false
14 | progress.hide()
15 |
16 | updateProgress = ->
17 | f = completed/requested
18 | progress.width((100-f*100).toFixed(0) + '%')
19 |
20 | startRequest = ->
21 | if not visible
22 | progress.show()
23 | visible = true
24 | requested = 0
25 | completed = 0
26 |
27 | requested += 1
28 | updateProgress()
29 |
30 | if fadeOutTimeout?
31 | clearTimeout(fadeOutTimeout)
32 | fadeOutTimeout = null
33 |
34 | completeRequest = ->
35 | completed += 1
36 | updateProgress()
37 |
38 | if completed == requested
39 | if fadeOutTimeout?
40 | clearTimeout(fadeOutTimeout)
41 | fadeOutTimeout = setTimeout(fadeOut, 1000)
42 |
43 | exports.execute = ({db, query, success}) ->
44 | db ?= 'webgl1'
45 | startRequest()
46 | $.post
47 | url: "https://data.webglstats.com/#{db}",
48 | data: JSON.stringify(query)
49 | dataType: 'json'
50 | success: (result) =>
51 | completeRequest()
52 | success(result)
53 |
--------------------------------------------------------------------------------
/less/header.less:
--------------------------------------------------------------------------------
1 | header{
2 | height: @headerHeight;
3 | line-height: @headerHeight;
4 | width: 100%;
5 | background-color: @light;
6 | position: fixed;
7 | padding-left: 5px;
8 | border-bottom: 1px solid rgba(0,0,0,0.75);
9 | z-index: 1;
10 |
11 | > span.updated{
12 | position: absolute;
13 | top: 0px;
14 | right: 0px;
15 | color: rgba(0,0,0,0.5);
16 | padding-right: 5px;
17 |
18 | @media(max-width: 445px){
19 | display: none;
20 | }
21 | }
22 |
23 | img{
24 | height: 40px;
25 | vertical-align: middle;
26 | }
27 |
28 | .navtoggle{
29 | width: 20px;
30 | height: 20px;
31 | margin-left: 20px;
32 | cursor: pointer;
33 |
34 | @media(min-width: 760px){
35 | display: none;
36 | }
37 | }
38 |
39 | @keyframes progressPulse{
40 | from{
41 | background-color: #2d8fff;
42 | }
43 | to{
44 | background-color: #24ff1b;
45 | }
46 | }
47 |
48 | .progress{
49 | animation-duration: 0.25s;
50 | animation-name: progressPulse;
51 | animation-iteration-count: infinite;
52 | animation-direction: alternate;
53 |
54 | position: absolute;
55 | bottom: 0px;
56 | right: 0px;
57 | width: 100%;
58 | height: 3px;
59 | //background-color: #2d8fff;
60 | background-color: #24ff1b;
61 | visibility: visible;
62 | transition: width 0.05s;
63 | pointer-events: none;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/index.coffee:
--------------------------------------------------------------------------------
1 | navLists = []
2 | Views = sys.import 'views'
3 | db = sys.import 'views/db'
4 | util = sys.import 'util'
5 | Scroll = sys.import 'scroll'
6 | Location = sys.import 'location'
7 |
8 | app = null
9 |
10 | class Application
11 | constructor: ->
12 | @dbmeta = document.webglstats.meta
13 | @location = new Location(@)
14 | @views = new Views(@)
15 | @updateDate()
16 | @setupNavigation()
17 | @navigate(true)
18 |
19 | backendError: ->
20 | widget = $('
')
21 | .appendTo('main')
22 |
23 | $('Data Backend Maintenance ')
24 | .appendTo(widget)
25 |
26 | $('The data backend is under maintenance, please try later.
')
27 | .appendTo(widget)
28 |
29 | updateDate: ->
30 | date = document.webglstats.meta.webgl1.lastChunk
31 | [year, month, day] = date.split('-')
32 | $('header > span.updated').text('Last update: ' + util.formatDate(year, month, day))
33 |
34 | setupNavigation: ->
35 | $('.navtoggle').click ->
36 | $('body').toggleClass('sidebar')
37 |
38 | $('div.overlay').click ->
39 | $('body').removeClass('sidebar')
40 |
41 | @scroll = new Scroll($('nav > div.scroller')[0])
42 |
43 | navigate: (pageload=false) ->
44 | @views.handle(@location, pageload)
45 |
46 | load = ->
47 | app = new Application()
48 |
49 | if document.webglstats.domready and document.webglstats.meta?
50 | document.webglstats.loaded = true
51 | load()
52 | else
53 | document.webglstats.load = load
54 |
--------------------------------------------------------------------------------
/img/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
15 |
17 |
18 |
20 | image/svg+xml
21 |
23 |
24 |
25 |
26 |
27 |
30 |
33 |
39 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/less/chart.less:
--------------------------------------------------------------------------------
1 | div.stacked-percentage{
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: nowrap;
5 | justify-content: flex-start;
6 | align-items: center;
7 | align-content: center;
8 | position: relative;
9 |
10 | > table {
11 | flex-shrink: 0;
12 | flex-grow: 0;
13 | }
14 |
15 | > canvas.plot{
16 | flex-shrink: 1;
17 | flex-grow: 1;
18 | min-width: 0;
19 | }
20 |
21 | > canvas.overlay{
22 | position: absolute;
23 | top: 0px;
24 | left: 0px;
25 | width: 100%;
26 | height: 100%;
27 | pointer-events: none;
28 | }
29 |
30 | }
31 |
32 | table.data-table{
33 | margin-left: 60px;
34 | border-collapse: collapse;
35 | color: rgba(255,255,255,0.5);
36 |
37 | tr td:first-child{
38 | width: 10px;
39 | }
40 |
41 | td{
42 | border: 1px solid rgb(64,64,64);
43 | padding: 2px;
44 | text-align: right;
45 | }
46 |
47 | td.percent{
48 | width: 50px;
49 | }
50 |
51 | td.bar{
52 | width: 110px;
53 | div{
54 | width: 0px;
55 | height: 10px;
56 | background-color: #348CFF;
57 | }
58 | }
59 |
60 | thead td{
61 | font-weight: bold;
62 | text-align: center;
63 | }
64 | }
65 |
66 | div.donut{
67 | display: flex;
68 | align-items: center;
69 | justify-content: center;
70 |
71 | > canvas{
72 | margin-right: 20px;
73 | }
74 |
75 | > div{
76 | > div{
77 | border-left: 10px solid;
78 | padding-left: 5px;
79 | margin-bottom: 5px;
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/chart/series.coffee:
--------------------------------------------------------------------------------
1 | util = sys.import '/util'
2 |
3 | smooth = (size, src) ->
4 | values = src[..size]
5 | for i in [size...src.length]
6 | sum = 0
7 | for j in [0...size]
8 | sum += src[i-j]
9 | sum/=size
10 | values.push(sum)
11 | return values
12 |
13 | exports.index = class Series
14 | constructor: ->
15 | @elem = $('
')
16 |
17 | update: (items) ->
18 | if items[0].values?
19 | values = for item in items
20 | if item.total > 0
21 | item.values[1]/item.total
22 | else
23 | 0
24 | else
25 | values = for item in items
26 | item.value
27 |
28 | #values = smooth(30, values)
29 |
30 | @elem.sparkline values,
31 | type:'line'
32 | chartRangeMin:0
33 | chartRangeMax:1
34 | spotColor: false
35 | minSpotColor: false
36 | maxSpotColor: false
37 | highlightLineColor: 'rgb(255,70,21)'
38 | spotRadius: 0
39 | lineColor: 'rgba(255,255,255,0.5)'
40 | fillColor: '#348CFF'
41 | height:300
42 | width:'100%'
43 | tooltipFormatter: (sparkline, options, fields) ->
44 | x = fields.x
45 | item = items[x]
46 | if item.total?
47 | if item.total > 0
48 | value = (item.values[1]/item.total)*100
49 | else
50 | value = 0
51 | return "#{item.name} - #{value.toFixed(0)}% (#{util.formatNumber(item.values[1])}) "
52 | else
53 | return "#{item.name} - #{util.formatNumber(item.value)} "
54 |
--------------------------------------------------------------------------------
/img/hamburger.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
15 |
17 |
18 |
20 | image/svg+xml
21 |
23 |
24 |
25 |
26 |
27 |
30 |
37 |
44 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/util.coffee:
--------------------------------------------------------------------------------
1 | exports.measureHeight = (elem) ->
2 | style = elem.style
3 |
4 | origTransition = style.transition
5 | origHeight = style.height
6 |
7 | style.transition = 'none !important'
8 | style.height = 'auto'
9 | height = elem.getBoundingClientRect().height
10 |
11 | style.height = origHeight
12 | style.transition = origTransition
13 |
14 | return height
15 |
16 | exports.after = (timeout, fun) ->
17 | setTimeout(fun, timeout*1000)
18 |
19 | exports.nextFrame = (fun) ->
20 | requestAnimationFrame(fun)
21 |
22 | exports.formatNumber = (n) ->
23 | if n < 1e3
24 | return n.toFixed(0)
25 | else if n >= 1e3 and n < 1e6
26 | return (n/1e3).toFixed(1) + 'k'
27 | else if n >= 1e6 and n < 1e9
28 | return (n/1e6).toFixed(1) + 'M'
29 | else if n >= 1e9 and n < 1e12
30 | return (n/1e9).toFixed(1) + 'G'
31 | else
32 | return (n/1e12).toFixed(1) + 'T'
33 |
34 | exports.capitalize = (s) ->
35 | return s[0].toUpperCase() + s[1...]
36 |
37 | exports.versionPath = (webglVersion) ->
38 | ({webgl1:'webgl', webgl2:'webgl2'})[webglVersion]
39 |
40 | exports.versionLabel = (webglVersion) ->
41 | ({webgl1:'WebGL 1', webgl2:'WebGL 2'})[webglVersion]
42 |
43 | monthNames = [
44 | 'Jan', 'Feb', 'Mar', 'Apr',
45 | 'May', 'Jun', 'Jul', 'Aug',
46 | 'Sep', 'Oct', 'Nov', 'Dec',
47 | ]
48 | exports.formatDate = (year, month, day) ->
49 | month = parseInt(month, 10)-1
50 | return "#{day} #{monthNames[month]}. #{year}"
51 |
52 | exports.getJSON = ({url, success, error}) ->
53 | xhr = new XMLHttpRequest()
54 | xhr.open('GET', url)
55 | xhr.onload = ->
56 | data = JSON.parse(xhr.response)
57 | success(data)
58 | xhr.onerror = ->
59 | error()
60 | xhr.send()
61 |
62 | exports.iter2list = (iterator) ->
63 | result = []
64 | while 1
65 | item = iterator.next()
66 | if item?
67 | if item.done
68 | break
69 | else
70 | result.push item.value
71 | else
72 | break
73 | return result
74 |
--------------------------------------------------------------------------------
/less/search.less:
--------------------------------------------------------------------------------
1 | .placeholder(@rules){
2 | &::-moz-placeholder{
3 | @rules();
4 | }
5 | &:-ms-input-placeholder{
6 | @rules();
7 | }
8 | &::-webkit-input-placeholder{
9 | @rules();
10 | }
11 |
12 | &:placeholder{
13 | @rules();
14 | }
15 | }
16 |
17 | form.search{
18 | border: 1px solid @medium;
19 | background-color: #3e3d3b;
20 | border-radius: 14px;
21 | margin: 10px;
22 | padding: 0px 35px 0px 0px;
23 | position: relative;
24 |
25 | input{
26 | color: white;
27 | padding-left: 14px;
28 | height: 28px;
29 | outline: none;
30 | border: none;
31 | background: transparent;
32 | display: inline-block;
33 | width: 202px;
34 | border-radius: 14px 0px 0px 14px;
35 | transition: background-color 0.3s, color 0.3s;
36 |
37 | &:focus{
38 | background-color: rgb(230,230,230);
39 | color: #222;
40 | .placeholder({
41 | color: rgba(0,0,0,0.3);
42 | })
43 | }
44 |
45 | .placeholder({
46 | color: rgba(255,255,255,0.3);
47 | transition: color 0.3s;
48 | })
49 | }
50 | button{
51 | padding: 0;
52 | margin: 0;
53 | border: none;
54 | outline: none;
55 | width: 35px;
56 | height: 28px;
57 | border-radius: 0px 14px 14px 0px;
58 | background-color: white;
59 | background-image: url("/img/search.svg");
60 | background-repeat: no-repeat;
61 | opacity: 0.3;
62 |
63 | position: absolute;
64 | top: 0px;
65 | right: 0px;
66 | cursor: pointer;
67 | transition: opacity 0.3s;
68 |
69 | &:hover{
70 | opacity: 0.5;
71 | }
72 | }
73 | }
74 |
75 | main > div.search-result{
76 | display: flex;
77 |
78 | >.gauge:first-child{
79 | flex-grow: 0;
80 | flex-shrink: 0;
81 | margin-right: 10px;
82 | }
83 |
84 | a{
85 | font-size: 16px;
86 | font-weight: bold;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/chart/bar.coffee:
--------------------------------------------------------------------------------
1 | normalize = (labels, values) ->
2 |
3 | total = 0
4 | for value in values
5 | total += value
6 |
7 | newValues = []
8 | cum = 1
9 | for value, i in values
10 | label = labels[i]
11 | abs = value/total
12 | newValues.push(abs:abs, cum:cum)
13 | cum -= abs
14 |
15 | values = newValues
16 | newValues = []
17 | newLabels = []
18 | for value, i in values
19 | label = labels[i]
20 | if value.abs > 0.0001
21 | newValues.push(value)
22 | newLabels.push(label)
23 |
24 | return [newLabels, newValues]
25 |
26 | exports.index = class Bar
27 | constructor: ->
28 | @elem = @table = $('')
29 |
30 | $('Value Abs. Cum. ')
31 | .appendTo(@table)
32 |
33 | @tbody = $(' ')
34 | .appendTo(@table)
35 |
36 | update: (labels, values) ->
37 | [labels,values] = normalize(labels, values)
38 |
39 | @tbody.remove()
40 | @tbody = $(' ')
41 | .appendTo(@table)
42 |
43 | @rows = []
44 |
45 | for value, i in values
46 | label = labels[i]
47 |
48 | row = $(' ')
49 | .appendTo(@tbody)
50 |
51 | $(' ')
52 | .text(label)
53 | .appendTo(row)
54 |
55 | $(' ')
56 | .text((value.abs*100).toFixed(1) + '%')
57 | .appendTo(row)
58 |
59 | $('
')
60 | .appendTo(row)
61 | .find('div')
62 | .css('width', value.abs*100)
63 |
64 | $(' ')
65 | .text((value.cum*100).toFixed(1) + '%')
66 | .appendTo(row)
67 |
68 | $('
')
69 | .appendTo(row)
70 | .find('div')
71 | .css('width', value.cum*100)
72 |
--------------------------------------------------------------------------------
/src/views/search.coffee:
--------------------------------------------------------------------------------
1 | behavior = sys.import 'behavior'
2 | breadcrumbs = sys.import 'breadcrumbs'
3 |
4 | exports.index = class Search
5 | constructor: ->
6 | @index = lunr ->
7 | @field 'title', boost:10
8 | @field 'body'
9 | @field 'extra'
10 | @ref 'id'
11 |
12 | @entries = {}
13 |
14 | breadcrumbs: ->
15 | breadcrumbs [
16 | 'Search'
17 | ]
18 |
19 | show: (query, instant) ->
20 | @breadcrumbs()
21 |
22 | search = query.query ? ''
23 | results = @index.search(search)
24 |
25 | behavior.deactivate()
26 | behavior.collapse()
27 |
28 | widget = $('
')
29 | .appendTo('main')
30 |
31 | $('Search Results for: ')
32 | .appendTo(widget)
33 |
34 | $(' ')
35 | .appendTo(widget)
36 | .text('"' + search + '". ')
37 |
38 | $("#{results.length} results found. ")
39 | .appendTo(widget)
40 |
41 | for result in results
42 | entry = @entries[result.ref]
43 |
44 | widget = $('
')
45 | .appendTo('main')
46 |
47 | if entry.gauge?
48 | entry.gauge().appendTo(widget)
49 |
50 | text = $('
')
51 | .appendTo(widget)
52 |
53 | link = $(' ')
54 | .appendTo(text)
55 | .attr('href', result.ref)
56 | .text(entry.type + ' ' + entry.title)
57 |
58 | $('
')
59 | .appendTo(text)
60 | .append(entry.body)
61 |
62 | add: ({id,titles,body,extra,type,gauge}) ->
63 | if not (titles instanceof Array)
64 | titles = [titles]
65 |
66 | extra ?= null
67 |
68 | @entries[id] =
69 | title: titles[0]
70 | body: body
71 | type: type
72 | gauge: gauge
73 |
74 | @index.add(id:id, title:titles.join(' '), body:$('
').append(body).text(),extra:extra)
75 |
--------------------------------------------------------------------------------
/src/views/navlist.coffee:
--------------------------------------------------------------------------------
1 | behavior = sys.import 'behavior'
2 |
3 | exports.index = class NavlistExpand
4 | constructor: (id, @prefix, entries) ->
5 | behavior.activatable @
6 | behavior.collapsable @
7 | @parent = $(id)
8 | @link = @parent.find('a')
9 |
10 | @list = $('')
11 | .appendTo(@parent)
12 |
13 | @entries = {}
14 | for entry in entries
15 | @add(entry)
16 |
17 | @list.css('display', 'block')
18 | @height = @list[0].getBoundingClientRect().height
19 | @list[0].style.height = '0px'
20 | @link.on 'click', @toggle
21 |
22 | @expanded = false
23 |
24 | add: (name) ->
25 | if typeof(name) == 'string'
26 | label = name
27 | tags = []
28 | else
29 | label = name.label
30 | tags = name.tags
31 | name = name.name
32 |
33 | li = $(' ').appendTo(@list)
34 | $(' ')
35 | .appendTo(li)
36 | .text(label)
37 | .attr('href', "/#{@prefix}/#{name}")
38 |
39 | @entries[name] = li
40 |
41 | toggle: =>
42 | if @expanded
43 | @collapse()
44 | else
45 | @expand()
46 |
47 | expand: (instant=false) ->
48 | behavior.collapse(@)
49 | @parent.addClass('expanded')
50 |
51 | @expanded = true
52 | if instant
53 | @list.addClass('notransition')
54 | @list[0].style.height = @height + 'px'
55 | if instant
56 | @list[0].getBoundingClientRect()
57 | @list.removeClass('notransition')
58 |
59 | collapse: (instant=false) ->
60 | @parent.removeClass('expanded')
61 | @expanded = false
62 | if instant
63 | @list.addClass('notransition')
64 | @list[0].style.height = '0px'
65 | if instant
66 | @list[0].getBoundingClientRect()
67 | @list.removeClass('notransition')
68 |
69 | deactivate: ->
70 | for name, entry of @entries
71 | entry.removeClass('active')
72 |
73 | activate: (name, instant=false) ->
74 | behavior.deactivate()
75 | @entries[name].addClass('active')
76 | @expand(instant)
77 |
--------------------------------------------------------------------------------
/src/chart/gauge.coffee:
--------------------------------------------------------------------------------
1 | util = sys.import '/util'
2 |
3 | mix = (a, b, f) ->
4 | return Math.round(a*(1-f) + b*f).toFixed(0)
5 |
6 | colorStops = [
7 | [255,70,21]
8 | [21,216,255]
9 | [106,255,21]
10 | ]
11 |
12 | exports.index = class Gauge
13 | constructor: ({size,label}) ->
14 | size ?= 'small'
15 |
16 | step = (start, end, value) =>
17 | if isNaN(value)
18 | value = 0
19 |
20 | percent.textContent = "#{value.toFixed(0)}%"
21 |
22 | f = value / 100
23 |
24 | c0 = Math.min(Math.floor(f*2), 1)
25 | c1 = c0+1
26 | f = (f%0.5)*2
27 | c0 = colorStops[c0]
28 | c1 = colorStops[c1]
29 |
30 | r = mix(c0[0], c1[0], f)
31 | g = mix(c0[1], c1[1], f)
32 | b = mix(c0[2], c1[2], f)
33 |
34 | @chart.options.barColor = "rgb(#{r},#{g},#{b})"
35 |
36 | @elem = $('
')
37 | .addClass(size)
38 | .easyPieChart
39 | animate: 1000
40 | onStart: (start, end) =>
41 | step(null, null, start)
42 | onStep: step
43 | onStop: (start, end) =>
44 | step(null, null, end)
45 |
46 | lineWidth: 8
47 | #barColor: '#15ecff'
48 | #barColor: 'rgb(106,255,21)' #good
49 | #barColor: 'rgb(21,216,255)' #meh
50 | barColor: 'rgb(255,70,21)' #bad
51 | trackColor: 'rgba(255,255,255,0.05)'
52 | scaleColor: 'rgba(255,255,255,0.2)'
53 | size: if size=='small' then 80 else 160
54 | lineCap: 'butt'
55 |
56 | percent = $('0%
').appendTo(@elem)[0]
57 |
58 | if label?
59 | @label = $(' ')
60 | .text(label)
61 | .appendTo(@elem)
62 |
63 | @chart = @elem.data('easyPieChart')
64 | @chart.disableAnimation()
65 | @chart.update(0)
66 | @chart.enableAnimation()
67 |
68 | setLabel: (text) ->
69 | if @label?
70 | @label.text(text)
71 |
72 | update: (value) ->
73 | util.after 0.01, =>
74 | if isNaN(value)
75 | value = 0
76 | @chart.update(value)
77 |
--------------------------------------------------------------------------------
/src/chart/donut.coffee:
--------------------------------------------------------------------------------
1 | colors = [
2 | [160,0,65]
3 | [94,76,164]
4 | [44,135,191]
5 | [98,195,165]
6 | [170,222,162]
7 | [230,246,147]
8 | [255,255,188]
9 | [255,255,133]
10 | [255,175,89]
11 | [246,109,58]
12 | ]
13 |
14 | exports.index = class Donut
15 | constructor: (options={}) ->
16 | @width = options.width ? 160
17 | @height = options.height ? 160
18 |
19 | @elem = $('
')
20 |
21 | canvas = $(' ').appendTo(@elem)[0]
22 | canvas.width = @width
23 | canvas.height = @height
24 | @ctx = canvas.getContext('2d')
25 |
26 | @legend = $('
')
27 | .appendTo(@elem)
28 |
29 | update: (values) ->
30 | values.sort (a, b) -> b.label - a.label
31 | values = values.filter (entry) -> entry.value > 0
32 |
33 | @legend.empty()
34 | @ctx.clearRect(0, 0, @width, @height)
35 |
36 | total = 0
37 | for entry in values
38 | total += entry.value
39 |
40 | start = 0
41 | for entry, n in values
42 |
43 | [r,g,b] = colors[n%colors.length]
44 | color = "rgb(#{r},#{g},#{b})"
45 | end = start + entry.value/total
46 |
47 | $('
')
48 | .appendTo(@legend)
49 | .text(entry.label)
50 | .css('border-color', color)
51 |
52 | @segment(start, end, color)
53 | @separator(end)
54 | start = end
55 |
56 | separator: (pos) ->
57 | r2 = Math.min(@width, @height)/2
58 | r1 = r2*0.8
59 |
60 | a = Math.PI*2*pos - Math.PI/2
61 |
62 | cx = @width/2
63 | cy = @height/2
64 |
65 | x1 = cx + Math.cos(a)*r1
66 | y1 = cy + Math.sin(a)*r1
67 | x2 = cx + Math.cos(a)*r2
68 | y2 = cy + Math.sin(a)*r2
69 |
70 | @ctx.beginPath()
71 | @ctx.moveTo(x1,y1)
72 | @ctx.lineTo(x2,y2)
73 | @ctx.stroke()
74 |
75 | segment: (start, end, color) ->
76 | start = Math.PI*2*start - Math.PI/2
77 | end = Math.PI*2*end - Math.PI/2
78 | @ctx.fillStyle = color
79 | r2 = Math.min(@width, @height)/2
80 | r1 = r2*0.8
81 | @ctx.beginPath()
82 | @ctx.arc(@width/2, @height/2, r2, start, end, false)
83 | @ctx.arc(@width/2, @height/2, r1, end, start, true)
84 | @ctx.fill()
85 |
--------------------------------------------------------------------------------
/src/views/contributors.coffee:
--------------------------------------------------------------------------------
1 | db = sys.import 'db'
2 | util = sys.import '/util'
3 | behavior = sys.import 'behavior'
4 | breadcrumbs = sys.import 'breadcrumbs'
5 |
6 | referrers = '''
7 | delight-vr.com
8 | babylonjs.com
9 | usecubes.com
10 | glad.dav1d.de
11 | mappable.com
12 | beta.archilogic.com
13 | webglfundamentals.org
14 | stephaneginier.com
15 | urbangalaxyonline.com
16 | g-truc.net
17 | field.io
18 | zephyrosanemos.com
19 | gootechnologies.com
20 | pheelicks.com
21 | dghost.net
22 | robrowser.com
23 | yobi3d.com
24 | jweel.com
25 | ga.me
26 | minko.io
27 | stack.gl
28 | vorg.github.io/pex
29 | variable.io
30 | floorplanner.com
31 | phaser.io
32 | acko.net
33 | threejs.org
34 | voxelquest.com
35 | blend4web.com
36 | playcanvas.com
37 | clara.io
38 | mrdoob.com
39 | imvu-customer-sandbox.com
40 | sketchfab.com
41 | htwins.net
42 | illyriad.co.uk
43 | processingjs.org
44 | opensourcehacker.com
45 | cesiumjs.org
46 | codeflow.org
47 | 3dthis.com
48 | gootechnologies.com
49 | marcinignac.com
50 | turbulenz.com
51 | bfilipek.com
52 | webglreport.com
53 | wellcaffeinated.net
54 | tigraphics.blogspot.com
55 | spacegoo.com
56 | int13h.com
57 | snappymaria.com
58 | renderingpipeline.com
59 | south.im
60 | laugharne.me
61 | threedeemedia.com
62 | threejs.ru
63 | ramsol.in
64 | boxbase.org
65 | greencarbody.de
66 | geeks3d.com
67 | '''.trim().split('\n')
68 |
69 | exports.index = class Contributors
70 | constructor: ->
71 | null
72 |
73 | breadcrumbs: ->
74 | breadcrumbs [
75 | ['Contributors', '/contributors']
76 | ]
77 |
78 | show: ->
79 | behavior.deactivate()
80 | behavior.collapse(@)
81 |
82 | @breadcrumbs()
83 |
84 | widget = $('
')
85 | .appendTo('main')
86 |
87 | $('Contributors ')
88 | .appendTo(widget)
89 |
90 | $('''
91 | Without contributing sites WebGL stats would be impossible. A special thanks to:
92 |
''').appendTo(widget)
93 |
94 | list = $('')
95 | .appendTo(widget)
96 |
97 | for referrer in referrers
98 | item = $(' ')
99 | .appendTo(list)
100 |
101 | $(' ')
102 | .attr('href', 'http://' + referrer)
103 | .text(referrer)
104 | .appendTo(item)
105 |
--------------------------------------------------------------------------------
/src/views/index.coffee:
--------------------------------------------------------------------------------
1 | Parameters = sys.import 'parameters'
2 | Extensions = sys.import 'extensions'
3 | Main = sys.import 'main'
4 | Traffic = sys.import 'traffic'
5 | Contributors = sys.import 'contributors'
6 | Filter = sys.import 'filter'
7 | Search = sys.import 'search'
8 | db = sys.import 'db'
9 | notFound = sys.import 'not-found'
10 | breadcrumbs = sys.import 'breadcrumbs'
11 |
12 | exports.index = class Views
13 | constructor: (@app) ->
14 | db.init(@app.dbmeta)
15 |
16 | @search = new Search()
17 | @filter = new Filter('#filter', @app)
18 | @main = new Main(@filter, @search)
19 | @parameters = new Parameters(@filter, @search)
20 | @extensions = new Extensions(@filter, @search)
21 | @traffic = new Traffic(@filter, @search)
22 | @contributors = new Contributors()
23 |
24 | setFilter: (platforms) ->
25 | @filter.set(platforms)
26 |
27 | breadcrumbs: ->
28 | breadcrumbs []
29 |
30 | handle: (location, pageload=false) ->
31 | $('main').empty()
32 | $('body').removeClass('sidebar')
33 |
34 | switch location.path
35 | when '/'
36 | @breadcrumbs()
37 | @main.showInfo()
38 | @main.show('webgl1', false)
39 | @main.show('webgl2', false)
40 | when '/search'
41 | @search.show(location.query, pageload)
42 | when '/traffic'
43 | @traffic.show()
44 | when '/webgl'
45 | @main.show('webgl1')
46 | @extensions.overview('webgl1', pageload)
47 | when '/webgl2'
48 | @main.show('webgl2')
49 | @extensions.overview('webgl2', pageload)
50 | when '/contributors'
51 | @contributors.show()
52 | else
53 | path = location.path[1...]
54 | parts = path.split('/')
55 | webglVersion = ({webgl:'webgl1', webgl2:'webgl2'})[parts.shift()]
56 | category = parts.shift()
57 | name = parts.shift()
58 |
59 | if not webglVersion?
60 | notFound()
61 | return
62 |
63 | switch category
64 | when 'parameter' then @parameters.show(webglVersion, name, pageload)
65 | when 'extension' then @extensions.show(webglVersion, name, pageload)
66 | else
67 | notFound()
68 |
--------------------------------------------------------------------------------
/less/index.less:
--------------------------------------------------------------------------------
1 | *{
2 | box-sizing: border-box;
3 | }
4 |
5 | @import "normalize.less";
6 |
7 | ul,ol{
8 | list-style-type: none;
9 | }
10 | ul,ol,li{
11 | padding: 0;
12 | margin: 0;
13 | }
14 | a{
15 | color: inherit;
16 | text-decoration: none;
17 | outline: none;
18 | }
19 |
20 | //@light: #f5f5f5;
21 | //@medium: #9e9e9e;
22 | //@dark: #232323;
23 |
24 | //@light: #f5f5f5;
25 | //@medium: #383636;
26 | //@dark: #191818;
27 |
28 | @light: #f7f7f7;
29 | @medium: #2b333a;
30 | @dark: #13171a;
31 |
32 | @headerHeight: 60px;
33 | @sidebarWidth: 260px;
34 |
35 | .sidebarPadding(){
36 | padding-left: @sidebarWidth+5px;
37 | @media(max-width: 760px){
38 | padding-left: 5px;
39 | }
40 | }
41 |
42 | html{
43 | font-family: 'Source Sans Pro';
44 | height: 100%;
45 | }
46 |
47 | body{
48 | position: relative;
49 | background-color: @dark;
50 | color: white;
51 | min-height: 100%;
52 | overflow-x: hidden;
53 | overflow-y: scroll;
54 | }
55 |
56 | @import "header.less";
57 |
58 | .spinner{
59 | animation: jump 0.6s ease 0s infinite normal;
60 | }
61 |
62 | @keyframes jump {
63 | 0%{
64 | transform: translateY(0);
65 | }
66 | 20%{
67 | transform: translateY(0);
68 | }
69 | 40%{
70 | transform: translateY(-10px);
71 | }
72 | 50%{
73 | transform: translateY(0);
74 | }
75 | 60%{
76 | transform: translateY(-5px);
77 | }
78 | 80%{
79 | transform: translateY(0);
80 | }
81 | 100%{
82 | transform: translateY(0);
83 | }
84 | }
85 |
86 | @import "nav.less";
87 | @import "main.less";
88 | @import "chart.less";
89 | @import "search.less";
90 |
91 | footer{
92 | color: rgba(255,255,255, 0.25);
93 | position: absolute;
94 | bottom: 0px;
95 | right: 0px;
96 | height: 50px;
97 | line-height: 50px;
98 | .sidebarPadding();
99 | transition: padding-left 0.25s;
100 | width: 100%;
101 | background-color: #0c0f11;
102 |
103 | a{
104 | color: #6dbfff;
105 | }
106 | }
107 |
108 | div.jqstooltip{
109 | width: auto !important;
110 | height: auto !important;
111 | border: 1px solid rgba(255,255,255,0.5);
112 | border-radius: 3px;
113 | }
114 |
115 | .notransition{
116 | transition: none !important;
117 | }
118 |
119 | @keyframes warnPulse{
120 | from{
121 | color: #ffffff;
122 | }
123 | to{
124 | color: #ff0000;
125 | }
126 | }
127 |
128 | .warning{
129 | font-weight: bold;
130 | animation-duration: 0.25s;
131 | animation-name: warnPulse;
132 | animation-iteration-count: infinite;
133 | animation-direction: alternate;
134 | }
135 |
--------------------------------------------------------------------------------
/src/scroll.coffee:
--------------------------------------------------------------------------------
1 | exports.index = class Scroll
2 | constructor: (@scroller) ->
3 | @scroller.addEventListener 'touchstart', @onTouch
4 | @scroller.addEventListener 'touchend', @onTouch
5 | @scroller.addEventListener 'touchcancel', @onTouch
6 | @scrollbar = document.createElement('div')
7 | @style = @scrollbar.style
8 | @style.position = 'absolute'
9 | @style.opacity = '0'
10 | #@style.visibility = 'hidden'
11 | @style.transform = 'translateY(0px)'
12 | @style.transition = 'opacity 0.25s'
13 | @style.top = '0px'
14 | @style.right = '3px'
15 | @style.height = '100%'
16 | @style.backgroundColor = 'rgba(0,0,0,0.5)'
17 | @style.width = '4px'
18 | @style.borderRadius = '5px'
19 | @style.pointerEvents = 'none'
20 |
21 | @scroller.parentElement.appendChild(@scrollbar)
22 | @paddingTop = getComputedStyle(@scroller.parentElement).paddingTop
23 | @paddingTop = parseInt(@paddingTop[...@paddingTop.length-2], 10)
24 |
25 | @lastScrollTop = @scroller.scrollTop
26 | @lastScrolled = performance.now()
27 | @visible = false
28 | @touching = false
29 | @update()
30 |
31 | onTouch: (event) =>
32 | if event.touches.length > 0
33 | @touching = true
34 | else
35 | @touching = false
36 |
37 | update: =>
38 | scrollTop = @scroller.scrollTop
39 | if scrollTop == @lastScrollTop
40 | if @visible and (not @touching)
41 | if performance.now() - @lastScrolled > 200
42 | @visible = false
43 | @style.opacity = '0'
44 | else
45 | if not @visible
46 | @visible = true
47 | @style.opacity = '1'
48 |
49 | @lastScrolled = performance.now()
50 | scrollHeight = @scroller.scrollHeight
51 | height = @scroller.clientHeight
52 | possibleScroll = scrollHeight-height
53 | overflow = Math.max(
54 | Math.max(0, scrollTop) - scrollTop,
55 | Math.max(0, scrollTop - possibleScroll)
56 | )*2.2; # aesthetic scale factor
57 |
58 | scale = (height-overflow)/scrollHeight
59 | scroll = Math.min(1, Math.max(0, scrollTop/possibleScroll))
60 | offset = (1-scale)*scroll
61 |
62 | scale = scale*(height-6) # padding and to px
63 | offset = offset*(height-6)+3 # padding and to px
64 |
65 | @style.height = scale.toFixed(1)+'px' # height to preserve rounded top/bottom
66 | #@style.top = offset.toFixed(1)+'px'
67 | @style.transform = 'translateY(' + (offset+@paddingTop).toFixed(1) + 'px)'
68 | @lastScrollTop = scrollTop
69 | requestAnimationFrame @update
70 |
--------------------------------------------------------------------------------
/lib/url-search-params.js:
--------------------------------------------------------------------------------
1 | /*! (C) WebReflection Mit Style License */
2 | var URLSearchParams=URLSearchParams||function(){"use strict";function e(e){return encodeURIComponent(e).replace(i,u)}function t(e){return decodeURIComponent(e.replace(s," "))}function n(e){this[f]=Object.create(null);if(!e)return;e.charAt(0)==="?"&&(e=e.slice(1));for(var n,r,i=(e||"").split("&"),s=0,o=i.length;s
4 | result = {}
5 | for name, value of obj
6 | result[name] = value
7 | return result
8 |
9 | query2obj = (query) ->
10 | result = {}
11 | for name in util.iter2list(query.keys())
12 | result[name] = query.get(name)
13 | return result
14 |
15 | obj2query = (obj) ->
16 | query = new URLSearchParams()
17 | for name, value of obj
18 | query.set(name, value)
19 | return query
20 |
21 | equalObj = (a, b) ->
22 | if a? and not b?
23 | return false
24 |
25 | if b? and not a?
26 | return false
27 |
28 | for name, value of a
29 | if b[name] != value
30 | return false
31 |
32 | for name, value of b
33 | if a[name] != value
34 | return false
35 |
36 | return true
37 |
38 | parseQuery = (string) ->
39 | query = new URLSearchParams(string)
40 | return query2obj(query)
41 |
42 | exports.index = class Location
43 | constructor: (@app) ->
44 | @path = document.location.pathname
45 | @query = parseQuery(document.location.search)
46 | if @query?
47 | @platforms = @query.platforms
48 | delete @query.platforms
49 |
50 | window.addEventListener 'popstate', @popstate
51 | document.addEventListener 'click', @click
52 | $('form').submit @formSubmit
53 |
54 | setLocation: (path, query=null) ->
55 | if not equalObj(query, @query) or path != @path
56 | @path = path
57 | @query = query
58 | @push()
59 | @app.navigate(false)
60 |
61 | push: ->
62 | query = obj2query(@query)
63 | if @platforms?
64 | query.set('platforms', @platforms)
65 |
66 | query = query.toString().trim()
67 | if query.length > 0
68 | history.pushState(null, null, "#{@path}?#{query}")
69 | else
70 | history.pushState(null, null, @path)
71 |
72 | popstate: (event) =>
73 | path = document.location.pathname
74 | query = parseQuery(document.location.search)
75 | platforms = query.platforms ? null
76 | delete query.platforms
77 |
78 | if platforms != @platforms
79 | @platforms = platforms
80 | @app.views.setFilter(platforms)
81 |
82 | if not equalObj(query, @query) or path != @path
83 | @path = path
84 | @query = query
85 | @app.navigate(false)
86 |
87 | click: (event) =>
88 | if event.ctrlKey or event.shiftKey or event.metaKey or event.altKey
89 | return
90 |
91 | target = event.target ? event.srcElement
92 | anchor = $(target).closest('a')[0]
93 | if anchor?
94 | path = anchor.getAttribute('href')
95 | if path? and path.startsWith('/')
96 | event.preventDefault()
97 | @setLocation(path)
98 |
99 | formSubmit: (event) =>
100 | event.preventDefault()
101 |
102 | params = {}
103 | for {name,value} in $(event.target).serializeArray()
104 | params[name] = value
105 |
106 | path = $(event.target).attr('action')
107 | @setLocation(path, params)
108 |
109 | setFilter: (platforms) ->
110 | @platforms = platforms
111 | @push()
112 |
--------------------------------------------------------------------------------
/lib/jquery.easypiechart.min.js:
--------------------------------------------------------------------------------
1 | /**!
2 | * easy-pie-chart
3 | * Lightweight plugin to render simple, animated and retina optimized pie charts
4 | *
5 | * @license
6 | * @author Robert Fleischmann (http://robert-fleischmann.de)
7 | * @version 2.1.7
8 | **/
9 | !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("jquery")):b(jQuery)}(this,function(a){var b=function(a,b){var c,d=document.createElement("canvas");a.appendChild(d),"object"==typeof G_vmlCanvasManager&&G_vmlCanvasManager.initElement(d);var e=d.getContext("2d");d.width=d.height=b.size;var f=1;window.devicePixelRatio>1&&(f=window.devicePixelRatio,d.style.width=d.style.height=[b.size,"px"].join(""),d.width=d.height=b.size*f,e.scale(f,f)),e.translate(b.size/2,b.size/2),e.rotate((-0.5+b.rotate/180)*Math.PI);var g=(b.size-b.lineWidth)/2;b.scaleColor&&b.scaleLength&&(g-=b.scaleLength+2),Date.now=Date.now||function(){return+new Date};var h=function(a,b,c){c=Math.min(Math.max(-1,c||0),1);var d=0>=c?!0:!1;e.beginPath(),e.arc(0,0,g,0,2*Math.PI*c,d),e.strokeStyle=a,e.lineWidth=b,e.stroke()},i=function(){var a,c;e.lineWidth=1,e.fillStyle=b.scaleColor,e.save();for(var d=24;d>0;--d)d%6===0?(c=b.scaleLength,a=0):(c=.6*b.scaleLength,a=b.scaleLength-c),e.fillRect(-b.size/2+a,0,c,1),e.rotate(Math.PI/12);e.restore()},j=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),k=function(){b.scaleColor&&i(),b.trackColor&&h(b.trackColor,b.trackWidth||b.lineWidth,1)};this.getCanvas=function(){return d},this.getCtx=function(){return e},this.clear=function(){e.clearRect(b.size/-2,b.size/-2,b.size,b.size)},this.draw=function(a){b.scaleColor||b.trackColor?e.getImageData&&e.putImageData?c?e.putImageData(c,0,0):(k(),c=e.getImageData(0,0,b.size*f,b.size*f)):(this.clear(),k()):this.clear(),e.lineCap=b.lineCap;var d;d="function"==typeof b.barColor?b.barColor(a):b.barColor,h(d,b.lineWidth,a/100)}.bind(this),this.animate=function(a,c){var d=Date.now();b.onStart(a,c);var e=function(){var f=Math.min(Date.now()-d,b.animate.duration),g=b.easing(this,f,a,c-a,b.animate.duration);this.draw(g),b.onStep(a,c,g),f>=b.animate.duration?b.onStop(a,c):j(e)}.bind(this);j(e)}.bind(this)},c=function(a,c){var d={barColor:"#ef1e25",trackColor:"#f9f9f9",scaleColor:"#dfe0e0",scaleLength:5,lineCap:"round",lineWidth:3,trackWidth:void 0,size:110,rotate:0,animate:{duration:1e3,enabled:!0},easing:function(a,b,c,d,e){return b/=e/2,1>b?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},onStart:function(a,b){},onStep:function(a,b,c){},onStop:function(a,b){}};if("undefined"!=typeof b)d.renderer=b;else{if("undefined"==typeof SVGRenderer)throw new Error("Please load either the SVG- or the CanvasRenderer");d.renderer=SVGRenderer}var e={},f=0,g=function(){this.el=a,this.options=e;for(var b in d)d.hasOwnProperty(b)&&(e[b]=c&&"undefined"!=typeof c[b]?c[b]:d[b],"function"==typeof e[b]&&(e[b]=e[b].bind(this)));"string"==typeof e.easing&&"undefined"!=typeof jQuery&&jQuery.isFunction(jQuery.easing[e.easing])?e.easing=jQuery.easing[e.easing]:e.easing=d.easing,"number"==typeof e.animate&&(e.animate={duration:e.animate,enabled:!0}),"boolean"!=typeof e.animate||e.animate||(e.animate={duration:1e3,enabled:e.animate}),this.renderer=new e.renderer(a,e),this.renderer.draw(f),a.dataset&&a.dataset.percent?this.update(parseFloat(a.dataset.percent)):a.getAttribute&&a.getAttribute("data-percent")&&this.update(parseFloat(a.getAttribute("data-percent")))}.bind(this);this.update=function(a){return a=parseFloat(a),e.animate.enabled?this.renderer.animate(f,a):this.renderer.draw(a),f=a,this}.bind(this),this.disableAnimation=function(){return e.animate.enabled=!1,this},this.enableAnimation=function(){return e.animate.enabled=!0,this},g()};a.fn.easyPieChart=function(b){return this.each(function(){var d;a.data(this,"easyPieChart")||(d=a.extend({},b,a(this).data()),a.data(this,"easyPieChart",new c(this,d)))})}});
--------------------------------------------------------------------------------
/lib/require.coffee:
--------------------------------------------------------------------------------
1 | ### does not work as expected
2 | window.onerror = (message, url, line, column, error) ->
3 | #console.log message, url, line, column, error
4 | #console.log error.message
5 | #console.log [error.stack.toString()]
6 |
7 | lines = error.stack.split('\n')
8 | result = [lines.shift()]
9 | for line in lines
10 | result.push(line)
11 | result.push(' http://clients.codeflow.org/inzept3d/dev/src/webgl/index.coffee:3')
12 |
13 | lines = result.join('\n')
14 |
15 | console.error lines
16 | ###
17 |
18 | moduleManager =
19 | File: class File
20 | constructor: (@manager, @absPath) ->
21 | if not @manager.files[@absPath]?
22 | throw new Error("file does not exist: #{@absPath}")
23 | read: -> @manager.files[@absPath]
24 |
25 | modules: {}
26 | files: {}
27 | module: (name, closure) ->
28 | @modules[name] =
29 | closure: closure
30 | instance: null
31 |
32 | text: (name, content) ->
33 | @files[name] = content
34 |
35 | index: ->
36 | @getLocation()
37 | @import('/index')
38 |
39 | getLocation: ->
40 | if self.document?
41 | scripts = document.getElementsByTagName 'script'
42 | script = scripts[scripts.length-1]
43 | @script = script.src
44 | else
45 | @script = self.location.href
46 |
47 | @location = @script[...@script.lastIndexOf('/')+1]
48 |
49 | abspath: (fromName, pathName) ->
50 | if pathName == '.'
51 | pathName = ''
52 |
53 | baseName = fromName.split('/')
54 | baseName.pop()
55 | baseName = baseName.join('/')
56 |
57 | if pathName[0] == '/'
58 | return pathName
59 | else
60 | path = pathName.split '/'
61 | if baseName == '/'
62 | base = ['']
63 | else
64 | base = baseName.split '/'
65 |
66 | while base.length > 0 and path.length > 0 and path[0] == '..'
67 | base.pop()
68 | path.shift()
69 |
70 | if base.length == 0 || path.length == 0 || base[0] != ''
71 | throw new Error("Invalid path: #{base.join '/'}/#{path.join '/'}")
72 | return "#{base.join('/')}/#{path.join('/')}"
73 |
74 | import: (moduleName) ->
75 | if moduleName?
76 | module = @modules[moduleName]
77 |
78 | if module == undefined
79 | module = @modules[moduleName+'/index']
80 | if module?
81 | moduleName = moduleName+'/index'
82 | else
83 | throw new Error('Module not found: ' + moduleName)
84 |
85 | if module.instance == null
86 | require = (requirePath) =>
87 | path = @abspath(moduleName, requirePath)
88 | return @import(path)
89 |
90 | exports = {}
91 | sys = {
92 | script: @script
93 | location: @location
94 | import: (requirePath) =>
95 | path = @abspath(moduleName, requirePath)
96 | return @import(path)
97 | file: (path) =>
98 | path = @abspath(moduleName, path)
99 | return new @File(@, path)
100 | File: File
101 | }
102 | module.closure(exports, sys)
103 | if exports.index?
104 | module.instance = exports.index
105 | else
106 | module.instance = exports
107 |
108 | return module.instance
109 | else
110 | throw new Error('no module name provided')
111 |
--------------------------------------------------------------------------------
/img/referrers/mrdoob.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
42 |
44 |
47 |
52 |
53 |
55 |
56 |
58 |
59 | image/svg+xml
60 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/less/filter.less:
--------------------------------------------------------------------------------
1 | > div.filter{
2 | transition: height 0.4s;
3 | display: none;
4 |
5 | overflow: hidden;
6 | background-color: #3e3d3b;
7 |
8 | .option{
9 | > label{
10 | margin-right: 10px;
11 | }
12 | margin-bottom: 5px;
13 | }
14 |
15 | .radio{
16 | display: inline-flex;
17 |
18 | >*{
19 | background-color: rgba(255,255,255,0.1);
20 | font-size: 14px;
21 | border: 1px solid @dark;
22 | border-radius: 0px;
23 | margin: 0;
24 | cursor: pointer;
25 | position: relative;
26 | top: 0px;
27 | left: 0px;
28 | outline: 0;
29 | padding: 2px 6px 3px 6px;
30 |
31 | &:hover{
32 | background-color: rgba(255,255,255,0.3);
33 | }
34 | &:active{
35 | //background-color: rgba(0,0,0,0.1);
36 | background-color: #c57f00;
37 | color: black;
38 | }
39 | &:first-child{
40 | border-radius: 10px 0px 0px 10px;
41 | padding: 2px 6px 3px 8px;
42 | }
43 | &:not(:last-child){
44 | border-right: none;
45 | }
46 | &:last-child{
47 | border-radius: 0px 10px 10px 0px;
48 | padding: 2px 8px 3px 6px;
49 | }
50 | &.active{
51 | //background-color: rgba(0,0,0,0.1);
52 | background-color: #c57f00;
53 | color: black;
54 | }
55 | }
56 | }
57 |
58 | > div{ // content
59 | border-top: 1px solid rgb(32,32,32);
60 | padding-top: 10px;
61 | padding-bottom: 10px;
62 | padding-left: 5px;
63 |
64 | > div.tree{
65 | div{
66 | border: 1px solid rgb(32,32,32);
67 | margin: 1px;
68 | margin-top: 2px;
69 | margin-right: 5px;
70 | padding: 1px;
71 | padding-left: 35px;
72 | position: relative;
73 | background-color: rgba(255,255,255,0.1);
74 | font-size: 14px;
75 |
76 | > span.arrow{
77 | position: absolute;
78 | left: 6px;
79 | top: 50%;
80 | transform: translateY(-50%) rotate(-90deg);
81 | //transition: transform 0.2s;
82 |
83 | display: inline-block;
84 | width: 0px;
85 | height: 0px;
86 | border-top: 5px solid transparent;
87 | border-bottom: 5px solid transparent;
88 | border-right: 5px solid white;
89 | }
90 |
91 | &.expanded > span.arrow{
92 | transform: translateY(-50%) rotate(-90deg);
93 | }
94 |
95 | &.collapsed > span.arrow{
96 | transform: translateY(-50%) rotate(-180deg);
97 | }
98 |
99 | > span.checkbox{
100 | position: absolute;
101 | display: inline-block;
102 | left: 18px;
103 | top: 50%;
104 | width: 12px;
105 | height: 12px;
106 | // border: 1px solid rgba(255,255,255,0.5);
107 | background-image: url("/img/checkbox.png");
108 |
109 | &.unchecked{
110 | background-position: left 0px top 0px; // empty
111 | }
112 | &.checked{
113 | background-position: left 12px top 0px; // checked
114 | }
115 | &.semichecked{
116 | background-position: left 24px top 0px; // semi
117 | }
118 |
119 | transform: translateY(-50%);
120 | }
121 | }
122 |
123 | li{
124 | margin-left: 12px;
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/less/nav.less:
--------------------------------------------------------------------------------
1 | body > nav{
2 | width: @sidebarWidth;
3 | transition: width 0.25s;
4 |
5 | @media(max-width: 760px){
6 | width: 0px;
7 | }
8 | }
9 |
10 | body > div.overlay{
11 | height: 100%;
12 | width: 100%;
13 | background-color: transparent;
14 | position: absolute;
15 | top: 0px;
16 | left: 0px;
17 | visibility: hidden;
18 | }
19 |
20 | body.sidebar{
21 | > div.overlay{
22 | visibility: visible;
23 | }
24 |
25 | > nav{
26 | width: @sidebarWidth;
27 |
28 | @media(max-width: 760px){
29 | box-shadow: 10px 0px 20px 0px rgba(0,0,0,0.5);
30 | }
31 | }
32 | }
33 |
34 | body > nav{
35 | position: fixed;
36 | top: 0px;
37 | left: 0px;
38 |
39 | height: 100%;
40 | padding-top: @headerHeight;
41 | background-color: @medium;
42 | z-index: 1;
43 |
44 | overflow: hidden;
45 |
46 | div.scroller{
47 | width: @sidebarWidth+20px;
48 | height: 100%;
49 | overflow-x: hidden;
50 | overflow-y: scroll;
51 | -webkit-overflow-scrolling: touch; /* momentum scroll mobile, works in safari, default on chrome */
52 | scroll-behavior: smooth; /* seems to have no effect but is what I want */
53 | }
54 |
55 | div.content{
56 | margin-bottom: 80px;
57 | width: @sidebarWidth;
58 |
59 | > div{
60 | border-bottom: 1px solid rgb(32,32,32);
61 |
62 | &.expanded > a > span.arrow{
63 | transform: translateY(-50%) rotate(-90deg);
64 | }
65 |
66 | &.sub > a{
67 | padding-left: 25px;
68 | }
69 |
70 | > a{
71 | display: block;
72 | padding-left: 5px;
73 | height: 50px;
74 | line-height: 50px;
75 | position: relative;
76 | cursor: pointer;
77 |
78 | transition: background-color 0.3s;
79 |
80 | &:hover{
81 | background-color: #3e4852;
82 | }
83 |
84 | > span.indicator{
85 | padding: 2px 8px 2px 8px;
86 | border-radius: 3px;
87 | margin-left: 20px;
88 | color: black;
89 | //background-color: @dark;
90 | background-color: #ff9a1b;
91 | display: none;
92 | }
93 |
94 | > span.arrow{
95 | position: absolute;
96 | right: 6px;
97 | top: 50%;
98 | transform: translateY(-50%) rotate(0deg);
99 | transition: transform 0.2s;
100 |
101 | display: inline-block;
102 | width: 0px;
103 | height: 0px;
104 | border-top: 5px solid transparent;
105 | border-bottom: 5px solid transparent;
106 | border-right: 5px solid white;
107 | }
108 | }
109 |
110 | @import "filter.less";
111 |
112 | > ul{
113 | transition: height 0.4s;
114 | display: none;
115 |
116 | overflow: hidden;
117 | background-color: #3e3d3b;
118 | > li{
119 | transition: border-width 0.25s;
120 | border-right: 0px solid #c57f00;
121 | &.active{
122 | border-right: 5px solid #c57f00;
123 | }
124 | > a{
125 | display: block;
126 | font-size: 12px;
127 | padding-left: 10px;
128 | padding-top: 9px;
129 | padding-bottom: 9px;
130 | border-top: 1px solid rgb(32,32,32);
131 | cursor: pointer;
132 |
133 | transition: background-color 0.3s;
134 |
135 | &:hover{
136 | background-color: #545351;
137 | }
138 | }
139 | }
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/views/tree.coffee:
--------------------------------------------------------------------------------
1 | exports.index = class Node
2 | constructor: ({name, @container, @parent, @checkChange, @expanded}) ->
3 | @checkChange ?= ->
4 | @expanded ?= true
5 |
6 | if name?
7 | @item = $('
')
8 | .text(name)
9 | .appendTo(@container)
10 | .click(@toggleExpand)
11 |
12 | @checkbox = $(' ')
13 | .appendTo(@item)
14 | .click(@toggleCheck)
15 |
16 | @children = []
17 | @setStatus 'checked'
18 |
19 | toggleExpand: =>
20 | if @expanded
21 | @collapse()
22 | else
23 | @expand()
24 |
25 | collapse: ->
26 | @expanded = false
27 | if @item?
28 | @item.removeClass('expanded').addClass('collapsed')
29 | if @list?
30 | @list.hide()
31 |
32 | expand: ->
33 | @expanded = true
34 | if @item?
35 | @item.removeClass('collapsed').addClass('expanded')
36 | if @list?
37 | @list.show()
38 |
39 | toggleCheck: (event) =>
40 | event.preventDefault()
41 | event.stopPropagation()
42 |
43 | if @status == 'checked'
44 | @uncheck()
45 | else
46 | @check()
47 |
48 | if @parent?
49 | @parent.updateCheck()
50 | else
51 | @checkChange()
52 |
53 | check: ->
54 | @setStatus 'checked'
55 |
56 | for child in @children
57 | child.check()
58 |
59 | uncheck: ->
60 | @setStatus 'unchecked'
61 |
62 | for child in @children
63 | child.uncheck()
64 |
65 | updateCheck: ->
66 | allChecked = true
67 | noneChecked = true
68 |
69 | for child in @children
70 | if child.status == 'checked' or child.status == 'semichecked'
71 | noneChecked = false
72 |
73 | if child.status != 'checked'
74 | allChecked = false
75 |
76 | if allChecked
77 | @setStatus 'checked'
78 | else if noneChecked
79 | @setStatus 'unchecked'
80 | else
81 | @setStatus 'semichecked'
82 |
83 | if @parent?
84 | @parent.updateCheck()
85 |
86 | @checkChange()
87 |
88 | updateStatus: ->
89 | if @children? and @children.length > 0
90 | allChecked = true
91 | noneChecked = true
92 |
93 | for child in @children
94 | status = child.updateStatus()
95 | if status == 'checked' or child.status == 'semichecked'
96 | noneChecked = false
97 |
98 | if status != 'checked'
99 | allChecked = false
100 |
101 | if allChecked
102 | @setStatus 'checked'
103 | else if noneChecked
104 | @setStatus 'unchecked'
105 | else
106 | @setStatus 'semichecked'
107 |
108 | return @status
109 |
110 | setStatus: (@status) ->
111 | if @checkbox?
112 | @checkbox
113 | .removeClass('unchecked')
114 | .removeClass('semichecked')
115 | .removeClass('checked')
116 | .addClass(@status)
117 |
118 | add: (name, expanded=true) ->
119 | if not @list?
120 | @list = $('')
121 | .appendTo(@container)
122 |
123 | if @expanded
124 | if @item?
125 | @item.addClass('expanded')
126 | else
127 | if @item?
128 | @item.addClass('collapsed')
129 | @list.hide()
130 |
131 | $(' ')
132 | .prependTo(@item)
133 |
134 | container = $(' ').appendTo(@list)
135 | node = new Node(name:name, container:container, parent:@, expanded:expanded)
136 | @children.push node
137 | return node
138 |
139 | isActive: ->
140 | return @status == 'checked' or @status == 'semichecked'
141 |
142 | visitActive: (fun) ->
143 | if @isActive()
144 | fun(@)
145 | for child in @children
146 | child.visitActive(fun)
147 |
148 | visit: (fun) ->
149 | fun(@)
150 | for child in @children
151 | child.visit(fun)
152 |
--------------------------------------------------------------------------------
/less/main.less:
--------------------------------------------------------------------------------
1 | .clearfix(){
2 | &:before, &:after{
3 | clear: both;
4 | display: table;
5 | content: "";
6 | }
7 | }
8 |
9 | main{
10 | padding-top: @headerHeight+5px;
11 | transition: padding-left 0.25s;
12 | .sidebarPadding();
13 | padding-bottom: 100px;
14 | padding-right: 5px;
15 |
16 | ol.breadcrumbs{
17 | display: flex;
18 | padding: 5px 5px 10px 5px;
19 |
20 | li{
21 | font-size: 16px;
22 | font-weight: bold;
23 | padding-right: 10px;
24 |
25 | &:not(:last-child):after{
26 | padding-left: 8px;
27 | content: ">";
28 | font-weight: normal;
29 | }
30 | }
31 | }
32 |
33 | a{
34 | //color: #348CFF;
35 | color: #6dbfff;
36 | }
37 |
38 | span.tag{
39 | background-color: rgba(255,255,255,0.15);
40 | border: 1px solid rgba(0,0,0,0.05);
41 | box-shadow: 0px 2px 5px rgba(0,0,0,0.25);
42 | border-radius: 5px;
43 | padding: 2px 5px 2px 5px;
44 | margin-right: 5px;
45 | }
46 | pre{
47 | border: 1px solid rgba(255,255,255,0.15);
48 | border-radius: 5px;
49 | padding: 15px;
50 | font-size: 12px;
51 | }
52 |
53 | h1{
54 | font-family: 'Roboto';
55 | font-size: 24px;
56 | color: #aaa;
57 | padding: 0;
58 | margin: 0;
59 | margin-bottom: 20px;
60 | text-align: center;
61 | }
62 |
63 | label.caption{
64 | display: block;
65 | text-align: right;
66 | position: relative;
67 | top: 10px;
68 | color: rgba(255,255,255,0.25);
69 | }
70 |
71 | > div.flow{
72 | border: 1px solid #252d35;
73 | //background-color: #181c20;
74 | border-radius: 5px;
75 | padding: 5px;
76 | margin: 5px;
77 | margin-bottom: 10px;
78 | .clearfix();
79 |
80 | > div {
81 | //border: 1px solid #252d35;
82 | //background-color: #181c20;
83 | //background-color: @dark;
84 | border-radius: 5px;
85 |
86 | float: left;
87 | padding: 5px;
88 | margin: 5px;
89 | min-width: 150px;
90 | text-align: center;
91 | font-size: 12px;
92 |
93 | .label{
94 | display: block;
95 | margin-top: 10px;
96 | font-weight: bold;
97 | }
98 | }
99 | }
100 |
101 | div.row{
102 | .clearfix();
103 |
104 | > *{
105 | display: block;
106 | float: left;
107 | padding: 5px;
108 | width: 50%;
109 | width: 50%;
110 | }
111 |
112 | &.responsive{
113 | > *{
114 | @media(max-width: 1200px){
115 | width: 100%;
116 | }
117 | }
118 | }
119 | }
120 |
121 | div.full{
122 | margin: 5px;
123 | }
124 |
125 | div.box{
126 | border: 1px solid #252d35;
127 | background-color: #181c20;
128 | border-radius: 5px;
129 | padding: 10px;
130 | }
131 |
132 | > div.box{
133 | margin-bottom: 10px;
134 | }
135 |
136 | div.series{
137 | background-color: rgba(0,0,0,0.25);
138 | }
139 |
140 | div.gauge{
141 | display: inline-block;
142 | position: relative;
143 | text-align: center;
144 |
145 | div.percent{
146 | position: absolute;
147 | left: 50%;
148 | top: 30px;
149 | font-size: 16px;
150 | transform: translateX(-50%)
151 | }
152 | label{
153 | display: block;
154 | }
155 |
156 | &.large{
157 | div.percent{
158 | top: 60px;
159 | font-size: 32px;
160 | }
161 | }
162 |
163 | }
164 |
165 | .center{
166 | text-align: center;
167 | }
168 |
169 | div.credit-logos{
170 | display: flex;
171 | flex-flow: row wrap;
172 | align-items: center;
173 | > a{
174 | border: 1px solid @medium;
175 | margin-right: 10px;
176 | border-radius: 5px;
177 | > img{
178 | height: 50px;
179 | }
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
30 | WebGL Stats
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | Page does not work without JS.
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/views/traffic.coffee:
--------------------------------------------------------------------------------
1 | db = sys.import 'db'
2 | util = sys.import '/util'
3 | behavior = sys.import 'behavior'
4 | breadcrumbs = sys.import 'breadcrumbs'
5 | {Gauge, Series, Donut, StackedPercentage} = sys.import '/chart'
6 |
7 | exports.index = class Traffic
8 | constructor: (@filter, search) ->
9 | null
10 |
11 | breadcrumbs: ->
12 | breadcrumbs [
13 | ['Visitors', '/traffic']
14 | ]
15 |
16 | show: ->
17 | behavior.deactivate()
18 | behavior.collapse(@)
19 |
20 | @breadcrumbs()
21 |
22 | ## first row ##
23 | mainRow = $('
')
24 | .addClass('row')
25 | .addClass('responsive')
26 | .appendTo('main')
27 |
28 | col = $('
')
29 | .appendTo(mainRow)
30 | widget = $('
')
31 | .appendTo(col)
32 | $('Visits ')
33 | .appendTo(widget)
34 | @series().appendTo(widget)
35 |
36 | col = $('
')
37 | .appendTo(mainRow)
38 | widget = $('
')
39 | .appendTo(col)
40 | $('Platform (30d) ')
41 | .appendTo(widget)
42 | @donut('useragent.device').appendTo(widget)
43 |
44 |
45 | ## second row ##
46 | mainRow = $('
')
47 | .addClass('row')
48 | .addClass('responsive')
49 | .appendTo('main')
50 |
51 | col = $('
')
52 | .appendTo(mainRow)
53 |
54 | widget = $('
')
55 | .appendTo(col)
56 |
57 | $('Operating System (30d) ')
58 | .appendTo(widget)
59 |
60 | @donut('useragent.os').appendTo(widget)
61 |
62 | col = $('
')
63 | .appendTo(mainRow)
64 |
65 | widget = $('
')
66 | .appendTo(col)
67 |
68 | $('Browser (30d) ')
69 | .appendTo(widget)
70 |
71 | @donut('useragent.family').appendTo(widget)
72 |
73 | ## platform time series ##
74 | full = $('
')
75 | .appendTo('main')
76 |
77 | $('Platform ')
78 | .appendTo(full)
79 |
80 | @stackedPercentage('useragent.device').appendTo(full)
81 |
82 | ## os time series ##
83 | full = $('
')
84 | .appendTo('main')
85 |
86 | $('Operating System ')
87 | .appendTo(full)
88 |
89 | @stackedPercentage('useragent.os').appendTo(full)
90 |
91 | ## browser time series ##
92 | full = $('
')
93 | .appendTo('main')
94 |
95 | $('Browser ')
96 | .appendTo(full)
97 |
98 | @stackedPercentage('useragent.family').appendTo(full)
99 |
100 | donut: (bucketBy) ->
101 | chart = new Donut()
102 |
103 | @filter.onChange chart.elem, =>
104 | chart.elem.addClass('spinner')
105 | query =
106 | filterBy: {}
107 | bucketBy: bucketBy
108 | start: -30
109 |
110 | if @filter.platforms?
111 | query.filterBy.platform = @filter.platforms
112 |
113 | db.execute
114 | query: query
115 | success: (result) ->
116 | chart.elem.removeClass('spinner')
117 |
118 | values = for label, n in result.keys
119 | value = result.values[n]
120 | {
121 | label: (
122 | util.capitalize(label.replace(/_/g, ' ')) +
123 | " #{((value*100/result.total).toFixed(1))}% (#{util.formatNumber(value)})"
124 | )
125 | value:result.values[n]
126 | }
127 |
128 | chart.update(values)
129 |
130 | return $(chart.elem)
131 |
132 | series: (name) ->
133 | chart = new Series()
134 | @filter.onChange chart.elem, =>
135 | query =
136 | filterBy: {}
137 | series: @filter.series
138 |
139 | if @filter.platforms?
140 | query.filterBy.platform = @filter.platforms
141 |
142 | db.execute
143 | query: query
144 | success: (result) ->
145 | chart.update(result.values)
146 |
147 | return chart.elem
148 |
149 | stackedPercentage: (bucketBy) ->
150 | chart = new StackedPercentage()
151 |
152 | @filter.onChange chart.elem, =>
153 | query =
154 | filterBy: {}
155 | bucketBy: bucketBy
156 | series: @filter.series
157 |
158 | if @filter.platforms?
159 | query.filterBy.platform = @filter.platforms
160 |
161 | db.execute
162 | query: query
163 | success: (result) ->
164 | keys = result.keys
165 | xLabels = []
166 | data = []
167 |
168 | if keys[0] == null
169 | valueStart = 1
170 | keys.shift()
171 | else
172 | valueStart = 0
173 |
174 | for item in result.values
175 | xLabels.push(item.name)
176 | values = []
177 |
178 | for value in item.values[valueStart...]
179 | if item.total == 0
180 | values.push(0)
181 | else
182 | values.push(value/item.total)
183 |
184 | data.push(values)
185 |
186 | chart.update
187 | areaLabels: keys
188 | xLabels: xLabels
189 | data: data
190 | type: 'rel'
191 |
192 | return $(chart.elem)
193 |
--------------------------------------------------------------------------------
/res/sitemap.txt:
--------------------------------------------------------------------------------
1 | http://webglstats.com/
2 | http://webglstats.com/search
3 | http://webglstats.com/traffic
4 | http://webglstats.com/contributors
5 | http://webglstats.com/webgl
6 | http://webglstats.com/webgl/parameter/ALIASED_LINE_WIDTH_RANGE
7 | http://webglstats.com/webgl/parameter/ALIASED_POINT_SIZE_RANGE
8 | http://webglstats.com/webgl/parameter/DEPTH_BITS
9 | http://webglstats.com/webgl/parameter/MAX_COMBINED_TEXTURE_IMAGE_UNITS
10 | http://webglstats.com/webgl/parameter/MAX_CUBE_MAP_TEXTURE_SIZE
11 | http://webglstats.com/webgl/parameter/MAX_FRAGMENT_UNIFORM_VECTORS
12 | http://webglstats.com/webgl/parameter/MAX_RENDERBUFFER_SIZE
13 | http://webglstats.com/webgl/parameter/MAX_TEXTURE_IMAGE_UNITS
14 | http://webglstats.com/webgl/parameter/MAX_TEXTURE_SIZE
15 | http://webglstats.com/webgl/parameter/MAX_VARYING_VECTORS
16 | http://webglstats.com/webgl/parameter/MAX_VERTEX_ATTRIBS
17 | http://webglstats.com/webgl/parameter/MAX_VERTEX_TEXTURE_IMAGE_UNITS
18 | http://webglstats.com/webgl/parameter/MAX_VERTEX_UNIFORM_VECTORS
19 | http://webglstats.com/webgl/parameter/MAX_VIEWPORT_DIMS
20 | http://webglstats.com/webgl/parameter/SAMPLES
21 | http://webglstats.com/webgl/parameter/SAMPLE_BUFFERS
22 | http://webglstats.com/webgl/parameter/STENCIL_BITS
23 | http://webglstats.com/webgl/parameter/SUBPIXEL_BITS
24 | http://webglstats.com/webgl/extension/EXT_blend_minmax
25 | http://webglstats.com/webgl/extension/WEBGL_color_buffer_float
26 | http://webglstats.com/webgl/extension/EXT_color_buffer_half_float
27 | http://webglstats.com/webgl/extension/WEBGL_compressed_texture_atc
28 | http://webglstats.com/webgl/extension/WEBGL_compressed_texture_etc
29 | http://webglstats.com/webgl/extension/WEBGL_compressed_texture_etc1
30 | http://webglstats.com/webgl/extension/WEBGL_compressed_texture_pvrtc
31 | http://webglstats.com/webgl/extension/WEBGL_compressed_texture_s3tc
32 | http://webglstats.com/webgl/extension/WEBGL_compressed_texture_astc
33 | http://webglstats.com/webgl/extension/WEBGL_debug_renderer_info
34 | http://webglstats.com/webgl/extension/WEBGL_depth_texture
35 | http://webglstats.com/webgl/extension/EXT_disjoint_timer_query
36 | http://webglstats.com/webgl/extension/WEBGL_draw_buffers
37 | http://webglstats.com/webgl/extension/OES_element_index_uint
38 | http://webglstats.com/webgl/extension/EXT_frag_depth
39 | http://webglstats.com/webgl/extension/ANGLE_instanced_arrays
40 | http://webglstats.com/webgl/extension/WEBGL_lose_context
41 | http://webglstats.com/webgl/extension/EXT_sRGB
42 | http://webglstats.com/webgl/extension/EXT_shader_texture_lod
43 | http://webglstats.com/webgl/extension/OES_standard_derivatives
44 | http://webglstats.com/webgl/extension/EXT_texture_filter_anisotropic
45 | http://webglstats.com/webgl/extension/OES_texture_float
46 | http://webglstats.com/webgl/extension/OES_texture_float_linear
47 | http://webglstats.com/webgl/extension/OES_texture_half_float
48 | http://webglstats.com/webgl/extension/OES_texture_half_float_linear
49 | http://webglstats.com/webgl/extension/OES_vertex_array_object
50 | http://webglstats.com/webgl2
51 | http://webglstats.com/webgl2/parameter/ALIASED_LINE_WIDTH_RANGE
52 | http://webglstats.com/webgl2/parameter/ALIASED_POINT_SIZE_RANGE
53 | http://webglstats.com/webgl2/parameter/DEPTH_BITS
54 | http://webglstats.com/webgl2/parameter/MAX_3D_TEXTURE_SIZE
55 | http://webglstats.com/webgl2/parameter/MAX_ARRAY_TEXTURE_LAYERS
56 | http://webglstats.com/webgl2/parameter/MAX_COLOR_ATTACHMENTS
57 | http://webglstats.com/webgl2/parameter/MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS
58 | http://webglstats.com/webgl2/parameter/MAX_COMBINED_TEXTURE_IMAGE_UNITS
59 | http://webglstats.com/webgl2/parameter/MAX_COMBINED_UNIFORM_BLOCKS
60 | http://webglstats.com/webgl2/parameter/MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS
61 | http://webglstats.com/webgl2/parameter/MAX_CUBE_MAP_TEXTURE_SIZE
62 | http://webglstats.com/webgl2/parameter/MAX_DRAW_BUFFERS
63 | http://webglstats.com/webgl2/parameter/MAX_ELEMENTS_INDICES
64 | http://webglstats.com/webgl2/parameter/MAX_ELEMENTS_VERTICES
65 | http://webglstats.com/webgl2/parameter/MAX_ELEMENT_INDEX
66 | http://webglstats.com/webgl2/parameter/MAX_FRAGMENT_INPUT_COMPONENTS
67 | http://webglstats.com/webgl2/parameter/MAX_FRAGMENT_UNIFORM_BLOCKS
68 | http://webglstats.com/webgl2/parameter/MAX_FRAGMENT_UNIFORM_COMPONENTS
69 | http://webglstats.com/webgl2/parameter/MAX_FRAGMENT_UNIFORM_VECTORS
70 | http://webglstats.com/webgl2/parameter/MAX_PROGRAM_TEXEL_OFFSET
71 | http://webglstats.com/webgl2/parameter/MAX_RENDERBUFFER_SIZE
72 | http://webglstats.com/webgl2/parameter/MAX_SAMPLES
73 | http://webglstats.com/webgl2/parameter/MAX_SERVER_WAIT_TIMEOUT
74 | http://webglstats.com/webgl2/parameter/MAX_TEXTURE_IMAGE_UNITS
75 | http://webglstats.com/webgl2/parameter/MAX_TEXTURE_LOD_BIAS
76 | http://webglstats.com/webgl2/parameter/MAX_TEXTURE_SIZE
77 | http://webglstats.com/webgl2/parameter/MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS
78 | http://webglstats.com/webgl2/parameter/MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS
79 | http://webglstats.com/webgl2/parameter/MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS
80 | http://webglstats.com/webgl2/parameter/MAX_UNIFORM_BLOCK_SIZE
81 | http://webglstats.com/webgl2/parameter/MAX_UNIFORM_BUFFER_BINDINGS
82 | http://webglstats.com/webgl2/parameter/MAX_VARYING_COMPONENTS
83 | http://webglstats.com/webgl2/parameter/MAX_VARYING_VECTORS
84 | http://webglstats.com/webgl2/parameter/MAX_VERTEX_ATTRIBS
85 | http://webglstats.com/webgl2/parameter/MAX_VERTEX_OUTPUT_COMPONENTS
86 | http://webglstats.com/webgl2/parameter/MAX_VERTEX_TEXTURE_IMAGE_UNITS
87 | http://webglstats.com/webgl2/parameter/MAX_VERTEX_UNIFORM_BLOCKS
88 | http://webglstats.com/webgl2/parameter/MAX_VERTEX_UNIFORM_COMPONENTS
89 | http://webglstats.com/webgl2/parameter/MAX_VERTEX_UNIFORM_VECTORS
90 | http://webglstats.com/webgl2/parameter/MAX_VIEWPORT_DIMS
91 | http://webglstats.com/webgl2/parameter/MIN_PROGRAM_TEXEL_OFFSET
92 | http://webglstats.com/webgl2/parameter/SAMPLES
93 | http://webglstats.com/webgl2/parameter/SAMPLE_BUFFERS
94 | http://webglstats.com/webgl2/parameter/STENCIL_BITS
95 | http://webglstats.com/webgl2/parameter/SUBPIXEL_BITS
96 | http://webglstats.com/webgl2/extension/EXT_color_buffer_float
97 | http://webglstats.com/webgl2/extension/WEBGL_compressed_texture_atc
98 | http://webglstats.com/webgl2/extension/WEBGL_compressed_texture_etc
99 | http://webglstats.com/webgl2/extension/WEBGL_compressed_texture_etc1
100 | http://webglstats.com/webgl2/extension/WEBGL_compressed_texture_pvrtc
101 | http://webglstats.com/webgl2/extension/WEBGL_compressed_texture_s3tc
102 | http://webglstats.com/webgl2/extension/WEBGL_compressed_texture_astc
103 | http://webglstats.com/webgl2/extension/WEBGL_debug_renderer_info
104 | http://webglstats.com/webgl2/extension/EXT_disjoint_timer_query
105 | http://webglstats.com/webgl2/extension/EXT_disjoint_timer_query_webgl2
106 | http://webglstats.com/webgl2/extension/WEBGL_lose_context
107 | http://webglstats.com/webgl2/extension/EXT_texture_filter_anisotropic
108 | http://webglstats.com/webgl2/extension/OES_texture_float_linear
109 |
--------------------------------------------------------------------------------
/res/sitemap-ssl.txt:
--------------------------------------------------------------------------------
1 | https://webglstats.com/
2 | https://webglstats.com/search
3 | https://webglstats.com/traffic
4 | https://webglstats.com/contributors
5 | https://webglstats.com/webgl
6 | https://webglstats.com/webgl/parameter/ALIASED_LINE_WIDTH_RANGE
7 | https://webglstats.com/webgl/parameter/ALIASED_POINT_SIZE_RANGE
8 | https://webglstats.com/webgl/parameter/DEPTH_BITS
9 | https://webglstats.com/webgl/parameter/MAX_COMBINED_TEXTURE_IMAGE_UNITS
10 | https://webglstats.com/webgl/parameter/MAX_CUBE_MAP_TEXTURE_SIZE
11 | https://webglstats.com/webgl/parameter/MAX_FRAGMENT_UNIFORM_VECTORS
12 | https://webglstats.com/webgl/parameter/MAX_RENDERBUFFER_SIZE
13 | https://webglstats.com/webgl/parameter/MAX_TEXTURE_IMAGE_UNITS
14 | https://webglstats.com/webgl/parameter/MAX_TEXTURE_SIZE
15 | https://webglstats.com/webgl/parameter/MAX_VARYING_VECTORS
16 | https://webglstats.com/webgl/parameter/MAX_VERTEX_ATTRIBS
17 | https://webglstats.com/webgl/parameter/MAX_VERTEX_TEXTURE_IMAGE_UNITS
18 | https://webglstats.com/webgl/parameter/MAX_VERTEX_UNIFORM_VECTORS
19 | https://webglstats.com/webgl/parameter/MAX_VIEWPORT_DIMS
20 | https://webglstats.com/webgl/parameter/SAMPLES
21 | https://webglstats.com/webgl/parameter/SAMPLE_BUFFERS
22 | https://webglstats.com/webgl/parameter/STENCIL_BITS
23 | https://webglstats.com/webgl/parameter/SUBPIXEL_BITS
24 | https://webglstats.com/webgl/extension/EXT_blend_minmax
25 | https://webglstats.com/webgl/extension/WEBGL_color_buffer_float
26 | https://webglstats.com/webgl/extension/EXT_color_buffer_half_float
27 | https://webglstats.com/webgl/extension/WEBGL_compressed_texture_atc
28 | https://webglstats.com/webgl/extension/WEBGL_compressed_texture_etc
29 | https://webglstats.com/webgl/extension/WEBGL_compressed_texture_etc1
30 | https://webglstats.com/webgl/extension/WEBGL_compressed_texture_pvrtc
31 | https://webglstats.com/webgl/extension/WEBGL_compressed_texture_s3tc
32 | https://webglstats.com/webgl/extension/WEBGL_debug_renderer_info
33 | https://webglstats.com/webgl/extension/WEBGL_depth_texture
34 | https://webglstats.com/webgl/extension/EXT_disjoint_timer_query
35 | https://webglstats.com/webgl/extension/WEBGL_draw_buffers
36 | https://webglstats.com/webgl/extension/OES_element_index_uint
37 | https://webglstats.com/webgl/extension/EXT_frag_depth
38 | https://webglstats.com/webgl/extension/ANGLE_instanced_arrays
39 | https://webglstats.com/webgl/extension/WEBGL_lose_context
40 | https://webglstats.com/webgl/extension/EXT_sRGB
41 | https://webglstats.com/webgl/extension/EXT_shader_texture_lod
42 | https://webglstats.com/webgl/extension/OES_standard_derivatives
43 | https://webglstats.com/webgl/extension/EXT_texture_filter_anisotropic
44 | https://webglstats.com/webgl/extension/OES_texture_float
45 | https://webglstats.com/webgl/extension/OES_texture_float_linear
46 | https://webglstats.com/webgl/extension/OES_texture_half_float
47 | https://webglstats.com/webgl/extension/OES_texture_half_float_linear
48 | https://webglstats.com/webgl/extension/OES_vertex_array_object
49 | https://webglstats.com/webgl2
50 | https://webglstats.com/webgl2/parameter/ALIASED_LINE_WIDTH_RANGE
51 | https://webglstats.com/webgl2/parameter/ALIASED_POINT_SIZE_RANGE
52 | https://webglstats.com/webgl2/parameter/DEPTH_BITS
53 | https://webglstats.com/webgl2/parameter/MAX_3D_TEXTURE_SIZE
54 | https://webglstats.com/webgl2/parameter/MAX_ARRAY_TEXTURE_LAYERS
55 | https://webglstats.com/webgl2/parameter/MAX_COLOR_ATTACHMENTS
56 | https://webglstats.com/webgl2/parameter/MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS
57 | https://webglstats.com/webgl2/parameter/MAX_COMBINED_TEXTURE_IMAGE_UNITS
58 | https://webglstats.com/webgl2/parameter/MAX_COMBINED_UNIFORM_BLOCKS
59 | https://webglstats.com/webgl2/parameter/MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS
60 | https://webglstats.com/webgl2/parameter/MAX_CUBE_MAP_TEXTURE_SIZE
61 | https://webglstats.com/webgl2/parameter/MAX_DRAW_BUFFERS
62 | https://webglstats.com/webgl2/parameter/MAX_ELEMENTS_INDICES
63 | https://webglstats.com/webgl2/parameter/MAX_ELEMENTS_VERTICES
64 | https://webglstats.com/webgl2/parameter/MAX_ELEMENT_INDEX
65 | https://webglstats.com/webgl2/parameter/MAX_FRAGMENT_INPUT_COMPONENTS
66 | https://webglstats.com/webgl2/parameter/MAX_FRAGMENT_UNIFORM_BLOCKS
67 | https://webglstats.com/webgl2/parameter/MAX_FRAGMENT_UNIFORM_COMPONENTS
68 | https://webglstats.com/webgl2/parameter/MAX_FRAGMENT_UNIFORM_VECTORS
69 | https://webglstats.com/webgl2/parameter/MAX_PROGRAM_TEXEL_OFFSET
70 | https://webglstats.com/webgl2/parameter/MAX_RENDERBUFFER_SIZE
71 | https://webglstats.com/webgl2/parameter/MAX_SAMPLES
72 | https://webglstats.com/webgl2/parameter/MAX_SERVER_WAIT_TIMEOUT
73 | https://webglstats.com/webgl2/parameter/MAX_TEXTURE_IMAGE_UNITS
74 | https://webglstats.com/webgl2/parameter/MAX_TEXTURE_LOD_BIAS
75 | https://webglstats.com/webgl2/parameter/MAX_TEXTURE_SIZE
76 | https://webglstats.com/webgl2/parameter/MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS
77 | https://webglstats.com/webgl2/parameter/MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS
78 | https://webglstats.com/webgl2/parameter/MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS
79 | https://webglstats.com/webgl2/parameter/MAX_UNIFORM_BLOCK_SIZE
80 | https://webglstats.com/webgl2/parameter/MAX_UNIFORM_BUFFER_BINDINGS
81 | https://webglstats.com/webgl2/parameter/MAX_VARYING_COMPONENTS
82 | https://webglstats.com/webgl2/parameter/MAX_VARYING_VECTORS
83 | https://webglstats.com/webgl2/parameter/MAX_VERTEX_ATTRIBS
84 | https://webglstats.com/webgl2/parameter/MAX_VERTEX_OUTPUT_COMPONENTS
85 | https://webglstats.com/webgl2/parameter/MAX_VERTEX_TEXTURE_IMAGE_UNITS
86 | https://webglstats.com/webgl2/parameter/MAX_VERTEX_UNIFORM_BLOCKS
87 | https://webglstats.com/webgl2/parameter/MAX_VERTEX_UNIFORM_COMPONENTS
88 | https://webglstats.com/webgl2/parameter/MAX_VERTEX_UNIFORM_VECTORS
89 | https://webglstats.com/webgl2/parameter/MAX_VIEWPORT_DIMS
90 | https://webglstats.com/webgl2/parameter/MIN_PROGRAM_TEXEL_OFFSET
91 | https://webglstats.com/webgl2/parameter/SAMPLES
92 | https://webglstats.com/webgl2/parameter/SAMPLE_BUFFERS
93 | https://webglstats.com/webgl2/parameter/STENCIL_BITS
94 | https://webglstats.com/webgl2/parameter/SUBPIXEL_BITS
95 | https://webglstats.com/webgl2/extension/EXT_color_buffer_float
96 | https://webglstats.com/webgl2/extension/WEBGL_compressed_texture_atc
97 | https://webglstats.com/webgl2/extension/WEBGL_compressed_texture_etc
98 | https://webglstats.com/webgl2/extension/WEBGL_compressed_texture_etc1
99 | https://webglstats.com/webgl2/extension/WEBGL_compressed_texture_pvrtc
100 | https://webglstats.com/webgl2/extension/WEBGL_compressed_texture_s3tc
101 | https://webglstats.com/webgl2/extension/WEBGL_debug_renderer_info
102 | https://webglstats.com/webgl2/extension/EXT_disjoint_timer_query
103 | https://webglstats.com/webgl2/extension/EXT_disjoint_timer_query_webgl2
104 | https://webglstats.com/webgl2/extension/WEBGL_lose_context
105 | https://webglstats.com/webgl2/extension/EXT_texture_filter_anisotropic
106 | https://webglstats.com/webgl2/extension/OES_texture_float_linear
107 | https://webglstats.com/webgl/extension/WEBGL_compressed_texture_astc
108 | https://webglstats.com/webgl2/extension/WEBGL_compressed_texture_astc
109 |
--------------------------------------------------------------------------------
/src/views/main.coffee:
--------------------------------------------------------------------------------
1 | db = sys.import 'db'
2 | extensions = sys.import 'extensions'
3 | util = sys.import '/util'
4 | behavior = sys.import 'behavior'
5 | breadcrumbs = sys.import 'breadcrumbs'
6 | Parameters = sys.import 'parameters'
7 | {Donut, Gauge, Series} = sys.import '/chart'
8 |
9 | exports.index = class Main
10 | constructor: (@filter, search) ->
11 | behavior.activatable @
12 |
13 | showInfo: ->
14 | widget = $('
')
15 | .appendTo('main')
16 |
17 | $('WebGL Stats ')
18 | .appendTo(widget)
19 |
20 | $('''
21 | The statistics on this site help WebGL developers make decisions about hardware capabilities.
22 |
''').appendTo(widget)
23 |
24 | $('''
25 | Without contributing sites WebGL stats would be impossible. A special thanks to:
26 |
''').appendTo(widget)
27 |
28 | $('''
29 |
34 | ''').appendTo(widget)
35 |
36 | $('''
37 | If you want to contribute collecting the data just embed the code below into your page.
38 |
''').appendTo(widget)
39 |
40 |
41 | $('''<script src="//cdn.webglstats.com/stat.js" defer async></script> ''')
42 | .appendTo(widget)
43 |
44 | $('''
45 | You can check out the code for this site on github .
46 |
''').appendTo(widget)
47 |
48 | $('''
49 | WebGL Report allows you to see the parameters your browser has implemented.
50 |
''').appendTo(widget)
51 |
52 | breadcrumbs: (webglVersion) ->
53 | breadcrumbs [
54 | [util.versionLabel(webglVersion), "/#{util.versionPath(webglVersion)}"]
55 | ]
56 |
57 | show: (version, breadcrumbs=true) ->
58 | behavior.deactivate()
59 | behavior.collapse(@)
60 |
61 | if breadcrumbs
62 | @breadcrumbs(version)
63 | versionLabel = ''
64 | else
65 | versionLabel = util.versionLabel(version) + ' '
66 |
67 | mainRow = $('
')
68 | .addClass('row')
69 | .addClass('responsive')
70 | .appendTo('main')
71 | @supportGauges(version, versionLabel, mainRow)
72 | @caveatDonut(version, versionLabel, mainRow)
73 |
74 | @supportSeries(version, versionLabel)
75 |
76 | caveatDonut: (version, versionLabel, parent) ->
77 | col = $('
')
78 | .appendTo(parent)
79 | widget = $('
')
80 | .appendTo(col)
81 | $(' ')
82 | .text(versionLabel + 'Major Performance Caveat (30d)')
83 | .appendTo(widget)
84 | @donut(version, versionLabel).appendTo(widget)
85 |
86 | supportSeries: (version, versionLabel) ->
87 | widget = $('
')
88 | .appendTo('main')
89 | $(' ')
90 | .text(versionLabel + 'Support')
91 | .appendTo(widget)
92 | @series(version).appendTo(widget)
93 |
94 | supportGauges: (version, versionLabel, parent) ->
95 | col = $('
')
96 | .appendTo(parent)
97 | widget = $('
')
98 | .appendTo(col)
99 | $('Support (30d) ')
100 | .text(versionLabel + 'Support (30d)')
101 | .appendTo(widget)
102 | row = $('
')
103 | .appendTo(widget)
104 | col = $('
')
105 | .appendTo(row)
106 | @gauge(version, 'large', 'All')
107 | .appendTo(col)
108 | smallCharts = $('
')
109 | .appendTo(row)
110 | row = $('
')
111 | .appendTo(smallCharts)
112 | col = $('
').appendTo(row)
113 | @gauge(version, 'small', 'Desktop', 'desktop').appendTo(col)
114 | col = $('
').appendTo(row)
115 | @gauge(version, 'small', 'Smartphone', 'smartphone').appendTo(col)
116 | row = $('
')
117 | .appendTo(smallCharts)
118 | col = $('
').appendTo(row)
119 | @gauge(version, 'small', 'Tablet', 'tablet').appendTo(col)
120 | col = $('
').appendTo(row)
121 | @gauge(version, 'small', 'Console', 'game_console').appendTo(col)
122 |
123 | gauge: (webglVersion, size='small', label=null, device=null) =>
124 | chart = new Gauge(label:label, size:size)
125 |
126 | query =
127 | filterBy: {}
128 | bucketBy:'webgl'
129 | start: -30
130 |
131 | if device?
132 | query.filterBy['useragent.device'] = device
133 |
134 | @filter.onChange chart.elem, =>
135 | chart.elem.addClass('spinner')
136 | if @filter.platforms?
137 | query.filterBy.platform = @filter.platforms
138 | else
139 | delete query.filterBy.platform
140 |
141 | db.execute
142 | db: webglVersion
143 | query: query
144 | success: (result) ->
145 | percentage = result.values[1]/result.total
146 | chart.setLabel(label + " (#{util.formatNumber(result.values[1])})")
147 | chart.update(percentage*100)
148 | chart.elem.removeClass('spinner')
149 |
150 | return chart.elem
151 |
152 | series: (webglVersion) ->
153 | chart = new Series()
154 |
155 | @filter.onChange chart.elem, =>
156 | query =
157 | bucketBy:'webgl'
158 | series: @filter.series
159 |
160 | if @filter.platforms?
161 | query.filterBy =
162 | platform: @filter.platforms
163 |
164 | db.execute
165 | db: webglVersion
166 | query: query
167 | success: (result) ->
168 | chart.update(result.values)
169 |
170 | return chart.elem
171 |
172 | donut: (version, versionLabel) ->
173 | chart = new Donut()
174 |
175 | @filter.onChange chart.elem, =>
176 | chart.elem.addClass('spinner')
177 | query =
178 | filterBy: {webgl:true}
179 | bucketBy: 'webgl.majorPerformanceCaveat'
180 | start: -30
181 |
182 | if @filter.platforms?
183 | query.filterBy.platform = @filter.platforms
184 |
185 | db.execute
186 | db: version
187 | query: query
188 | success: (result) ->
189 | chart.elem.removeClass('spinner')
190 |
191 | values = for label, n in result.keys
192 | label = ({null:'Unknown', false:'False', true:'True'})[label]
193 | value = result.values[n]
194 | {
195 | label: (
196 | "#{label} #{((value*100/result.total).toFixed(1))}% (#{util.formatNumber(value)})"
197 | )
198 | value:result.values[n]
199 | }
200 |
201 | chart.update(values)
202 |
203 | return $(chart.elem)
204 |
205 | deactivate: ->
206 | null
207 |
--------------------------------------------------------------------------------
/src/views/filter.coffee:
--------------------------------------------------------------------------------
1 | db = sys.import 'db'
2 | util = sys.import '/util'
3 | behavior = sys.import 'behavior'
4 | hex = sys.import 'hex'
5 | Tree = sys.import 'tree'
6 |
7 | addNode = (parent, parts, count, key) ->
8 | parent.count += count
9 | name = parts.shift()
10 |
11 | if not parent.children?
12 | parent.children = {}
13 |
14 | child = parent.children[name]
15 | if not child?
16 | child = parent.children[name] = {count:0}
17 |
18 | if parts.length > 0
19 | addNode child, parts, count, key
20 | else
21 | child.count = count
22 | child.key = key
23 |
24 | buildTree = (items, counts) ->
25 | root = {count:0}
26 |
27 | for item, i in items
28 | count = counts[i]
29 | parts = item.split('|')
30 | addNode root, parts, count, item
31 |
32 | sortNode root
33 | return root
34 |
35 | sortNode = (node) ->
36 | if node.children?
37 | children = for name, child of node.children
38 | child.name = name
39 | child
40 |
41 | children.sort (a,b) ->
42 | if a.count < b.count then return 1
43 | else if a.count > b.count then return -1
44 | return 0
45 |
46 | for child in children
47 | sortNode child
48 |
49 | node.children = children
50 |
51 | class Radio
52 | constructor: (parent, @change) ->
53 | @change ?= ->
54 |
55 | @container = $('
')
56 | .appendTo(parent)
57 |
58 | @options = {}
59 | @value = null
60 |
61 | add: ({label, value, active=false}) ->
62 | value ?= label
63 |
64 | option = @options[value] = $('
')
65 | .text(label)
66 | .appendTo(@container)
67 | .click(=> @activate(value))
68 |
69 | if active
70 | @value = value
71 | option.addClass('active')
72 |
73 | return @
74 |
75 | activate: (activateValue) ->
76 | for value, option of @options
77 | option.removeClass('active')
78 |
79 | @options[activateValue].addClass('active')
80 | @value = activateValue
81 | @change(@value)
82 |
83 | exports.index = class Filter
84 | constructor: (parent, @app) ->
85 | for field in @app.dbmeta.webgl1.fields
86 | if field.name == 'platform'
87 | @platformBits = {}
88 | @platformList = field.values
89 | @platformBitCount = field.values.length
90 | @platformByteCount = Math.ceil(@platformBitCount/8)
91 | for name, i in field.values
92 | @platformBits[name] = i
93 | break
94 |
95 | #behavior.collapsable(@)
96 | @parent = $(parent)
97 | @link = @parent.find('a')
98 | @indicator = @link.find('span.indicator')
99 | @nodesByKey = {}
100 |
101 | @container = $('
')
102 | .appendTo(@parent)
103 |
104 | @content = $('
')
105 | .appendTo(@container)
106 |
107 | series = $('
')
108 | .appendTo(@content)
109 | $('Series ')
110 | .appendTo(series)
111 | @series = 'weekly'
112 | new Radio(series, @seriesChanged)
113 | .add(label: 'Day', value:'daily')
114 | .add(label:'Week', value:'weekly', active:true)
115 | .add(label:'Month', value:'monthly')
116 | .add(label:'Year', value:'yearly')
117 |
118 | @treeContainer = $('
')
119 | .appendTo(@content)
120 |
121 | @container.css('display', 'block')
122 | @container[0].style.height = '0px'
123 | @height = util.measureHeight(@container[0])
124 | @link.on 'click', @toggle
125 | @expanded = false
126 |
127 | @tree = new Tree container:@treeContainer, checkChange:@filterChanged, name:'All'
128 |
129 | db.execute
130 | query:
131 | bucketBy:'platform'
132 | start: -30
133 | success: (result) =>
134 | tree = buildTree result.keys, result.values
135 |
136 | for item in tree.children
137 | @addNode @tree, tree, item
138 |
139 | @tree.updateStatus()
140 |
141 | @height = util.measureHeight(@container[0])
142 |
143 | @platforms = @decodeQueryBits(@app.location.platforms)
144 | @updateIndicator()
145 | @listeners = []
146 |
147 | updateIndicator: ->
148 | if @platforms?
149 | @indicator.show()
150 | else
151 | @indicator.hide()
152 |
153 | set: (platforms) ->
154 | @platforms = @decodeQueryBits(platforms)
155 | @updateIndicator()
156 |
157 | if @platforms?
158 | for platform, node of @nodesByKey
159 | if platform in @platforms
160 | node.setStatus('checked')
161 | else
162 | node.setStatus('unchecked')
163 | else
164 | for platform, node of @nodesByKey
165 | node.setStatus('checked')
166 |
167 | @tree.updateStatus()
168 | @notifyListeners()
169 |
170 | onChange: (elem, listener) ->
171 | @listeners.push(elem:elem, change:listener)
172 | listener()
173 |
174 | notifyListeners: =>
175 | listeners = []
176 | for listener in @listeners
177 | if document.body.contains(listener.elem[0])
178 | listener.change()
179 | listeners.push(listener)
180 | @listeners = listeners
181 |
182 | seriesChanged: (value) =>
183 | @series = value
184 | @notifyListeners()
185 |
186 | filterChanged: =>
187 | if @tree.status == 'checked'
188 | @platforms = null
189 | @updateIndicator()
190 | @queryBits = null
191 | @app.location.setFilter(null)
192 | else
193 | values = []
194 | @tree.visitActive (node) ->
195 | if node.key?
196 | values.push(node.key)
197 | @platforms = values
198 | @updateIndicator()
199 | bits = @encodeQueryBits(@platforms)
200 | @app.location.setFilter(bits)
201 |
202 | @notifyListeners()
203 |
204 | encodeQueryBits: (platforms) ->
205 | bits = new Uint8Array(@platformByteCount)
206 | for name in platforms
207 | bitNum = @platformBits[name]
208 | byte = Math.floor(bitNum/8)
209 | bit = bitNum % 8
210 | bits[byte] |= 1 << bit
211 | bits = hex.encode(bits)
212 |
213 | return '0000' + bits #reserved 2 bytes at the front for future use
214 |
215 | decodeQueryBits: (bits) ->
216 | if not bits?
217 | return null
218 |
219 | bits = bits[4...] # reserved 2 bytes at the front for future use
220 |
221 | bits = hex.decode(bits)
222 | platforms = []
223 | for value, byte in bits
224 | for bit in [0...8]
225 | bitNum = byte*8 + bit
226 | if bitNum >= @platformList.length
227 | break
228 |
229 | if value & (1<
234 | name = dataChild.name + ' ' + Math.round(dataChild.count*100/dataParent.count).toFixed(0) + '%'
235 | childNode = parentNode.add(name, if depth < 0 then true else false)
236 | if dataChild.children?
237 | for item in dataChild.children
238 | @addNode childNode, dataChild, item, depth+1
239 | else
240 | childNode.key = dataChild.key
241 | @nodesByKey[dataChild.key] = childNode
242 | if @platforms?
243 | if childNode.key not in @platforms
244 | childNode.setStatus('unchecked')
245 |
246 | toggle: =>
247 | if @expanded
248 | @collapse()
249 | else
250 | @expand()
251 |
252 | expand: (instant=false) ->
253 | #behavior.collapse(@)
254 | @parent.addClass('expanded')
255 |
256 | @expanded = true
257 | if instant
258 | @container.addClass('notransition')
259 |
260 | @container[0].style.height = @height + 'px'
261 | util.after 0.4, =>
262 | @container[0].style.height = 'auto'
263 |
264 | if instant
265 | @container[0].getBoundingClientRect()
266 | @container.removeClass('notransition')
267 |
268 | collapse: (instant=false) ->
269 | if @expanded
270 | @expanded = false
271 | @height = util.measureHeight(@container[0])
272 | @container.addClass('notransition')
273 | @container[0].style.height = @height + 'px'
274 | @container.removeClass('notransition')
275 |
276 | util.nextFrame =>
277 | @parent.removeClass('expanded')
278 | if instant
279 | @container.addClass('notransition')
280 | @container[0].style.height = '0px'
281 | if instant
282 | @container[0].getBoundingClientRect()
283 | @container.removeClass('notransition')
284 |
285 | visitActive: (fun) ->
286 | @tree.visitActive(fun)
287 |
--------------------------------------------------------------------------------
/less/normalize.less:
--------------------------------------------------------------------------------
1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Change the default font family in all browsers (opinionated).
5 | * 2. Correct the line height in all browsers.
6 | * 3. Prevent adjustments of font size after orientation changes in
7 | * IE on Windows Phone and in iOS.
8 | */
9 |
10 | /* Document
11 | ========================================================================== */
12 |
13 | html {
14 | font-family: sans-serif; /* 1 */
15 | line-height: 1.15; /* 2 */
16 | -ms-text-size-adjust: 100%; /* 3 */
17 | -webkit-text-size-adjust: 100%; /* 3 */
18 | }
19 |
20 | /* Sections
21 | ========================================================================== */
22 |
23 | /**
24 | * Remove the margin in all browsers (opinionated).
25 | */
26 |
27 | body {
28 | margin: 0;
29 | }
30 |
31 | /**
32 | * Add the correct display in IE 9-.
33 | */
34 |
35 | article,
36 | aside,
37 | footer,
38 | header,
39 | nav,
40 | section {
41 | display: block;
42 | }
43 |
44 | /**
45 | * Correct the font size and margin on `h1` elements within `section` and
46 | * `article` contexts in Chrome, Firefox, and Safari.
47 | */
48 |
49 | h1 {
50 | font-size: 2em;
51 | margin: 0.67em 0;
52 | }
53 |
54 | /* Grouping content
55 | ========================================================================== */
56 |
57 | /**
58 | * Add the correct display in IE 9-.
59 | * 1. Add the correct display in IE.
60 | */
61 |
62 | figcaption,
63 | figure,
64 | main { /* 1 */
65 | display: block;
66 | }
67 |
68 | /**
69 | * Add the correct margin in IE 8.
70 | */
71 |
72 | figure {
73 | margin: 1em 40px;
74 | }
75 |
76 | /**
77 | * 1. Add the correct box sizing in Firefox.
78 | * 2. Show the overflow in Edge and IE.
79 | */
80 |
81 | hr {
82 | box-sizing: content-box; /* 1 */
83 | height: 0; /* 1 */
84 | overflow: visible; /* 2 */
85 | }
86 |
87 | /**
88 | * 1. Correct the inheritance and scaling of font size in all browsers.
89 | * 2. Correct the odd `em` font sizing in all browsers.
90 | */
91 |
92 | pre {
93 | font-family: monospace, monospace; /* 1 */
94 | font-size: 1em; /* 2 */
95 | }
96 |
97 | /* Text-level semantics
98 | ========================================================================== */
99 |
100 | /**
101 | * 1. Remove the gray background on active links in IE 10.
102 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
103 | */
104 |
105 | a {
106 | background-color: transparent; /* 1 */
107 | -webkit-text-decoration-skip: objects; /* 2 */
108 | }
109 |
110 | /**
111 | * Remove the outline on focused links when they are also active or hovered
112 | * in all browsers (opinionated).
113 | */
114 |
115 | a:active,
116 | a:hover {
117 | outline-width: 0;
118 | }
119 |
120 | /**
121 | * 1. Remove the bottom border in Firefox 39-.
122 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
123 | */
124 |
125 | abbr[title] {
126 | border-bottom: none; /* 1 */
127 | text-decoration: underline; /* 2 */
128 | text-decoration: underline dotted; /* 2 */
129 | }
130 |
131 | /**
132 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
133 | */
134 |
135 | b,
136 | strong {
137 | font-weight: inherit;
138 | }
139 |
140 | /**
141 | * Add the correct font weight in Chrome, Edge, and Safari.
142 | */
143 |
144 | b,
145 | strong {
146 | font-weight: bolder;
147 | }
148 |
149 | /**
150 | * 1. Correct the inheritance and scaling of font size in all browsers.
151 | * 2. Correct the odd `em` font sizing in all browsers.
152 | */
153 |
154 | code,
155 | kbd,
156 | samp {
157 | font-family: monospace, monospace; /* 1 */
158 | font-size: 1em; /* 2 */
159 | }
160 |
161 | /**
162 | * Add the correct font style in Android 4.3-.
163 | */
164 |
165 | dfn {
166 | font-style: italic;
167 | }
168 |
169 | /**
170 | * Add the correct background and color in IE 9-.
171 | */
172 |
173 | mark {
174 | background-color: #ff0;
175 | color: #000;
176 | }
177 |
178 | /**
179 | * Add the correct font size in all browsers.
180 | */
181 |
182 | small {
183 | font-size: 80%;
184 | }
185 |
186 | /**
187 | * Prevent `sub` and `sup` elements from affecting the line height in
188 | * all browsers.
189 | */
190 |
191 | sub,
192 | sup {
193 | font-size: 75%;
194 | line-height: 0;
195 | position: relative;
196 | vertical-align: baseline;
197 | }
198 |
199 | sub {
200 | bottom: -0.25em;
201 | }
202 |
203 | sup {
204 | top: -0.5em;
205 | }
206 |
207 | /* Embedded content
208 | ========================================================================== */
209 |
210 | /**
211 | * Add the correct display in IE 9-.
212 | */
213 |
214 | audio,
215 | video {
216 | display: inline-block;
217 | }
218 |
219 | /**
220 | * Add the correct display in iOS 4-7.
221 | */
222 |
223 | audio:not([controls]) {
224 | display: none;
225 | height: 0;
226 | }
227 |
228 | /**
229 | * Remove the border on images inside links in IE 10-.
230 | */
231 |
232 | img {
233 | border-style: none;
234 | }
235 |
236 | /**
237 | * Hide the overflow in IE.
238 | */
239 |
240 | svg:not(:root) {
241 | overflow: hidden;
242 | }
243 |
244 | /* Forms
245 | ========================================================================== */
246 |
247 | /**
248 | * 1. Change the font styles in all browsers (opinionated).
249 | * 2. Remove the margin in Firefox and Safari.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | font-family: sans-serif; /* 1 */
258 | font-size: 100%; /* 1 */
259 | line-height: 1.15; /* 1 */
260 | margin: 0; /* 2 */
261 | }
262 |
263 | /**
264 | * Show the overflow in IE.
265 | * 1. Show the overflow in Edge.
266 | */
267 |
268 | button,
269 | input { /* 1 */
270 | overflow: visible;
271 | }
272 |
273 | /**
274 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
275 | * 1. Remove the inheritance of text transform in Firefox.
276 | */
277 |
278 | button,
279 | select { /* 1 */
280 | text-transform: none;
281 | }
282 |
283 | /**
284 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
285 | * controls in Android 4.
286 | * 2. Correct the inability to style clickable types in iOS and Safari.
287 | */
288 |
289 | button,
290 | html [type="button"], /* 1 */
291 | [type="reset"],
292 | [type="submit"] {
293 | -webkit-appearance: button; /* 2 */
294 | }
295 |
296 | /**
297 | * Remove the inner border and padding in Firefox.
298 | */
299 |
300 | button::-moz-focus-inner,
301 | [type="button"]::-moz-focus-inner,
302 | [type="reset"]::-moz-focus-inner,
303 | [type="submit"]::-moz-focus-inner {
304 | border-style: none;
305 | padding: 0;
306 | }
307 |
308 | /**
309 | * Restore the focus styles unset by the previous rule.
310 | */
311 |
312 | button:-moz-focusring,
313 | [type="button"]:-moz-focusring,
314 | [type="reset"]:-moz-focusring,
315 | [type="submit"]:-moz-focusring {
316 | outline: 1px dotted ButtonText;
317 | }
318 |
319 | /**
320 | * Change the border, margin, and padding in all browsers (opinionated).
321 | */
322 |
323 | fieldset {
324 | border: 1px solid #c0c0c0;
325 | margin: 0 2px;
326 | padding: 0.35em 0.625em 0.75em;
327 | }
328 |
329 | /**
330 | * 1. Correct the text wrapping in Edge and IE.
331 | * 2. Correct the color inheritance from `fieldset` elements in IE.
332 | * 3. Remove the padding so developers are not caught out when they zero out
333 | * `fieldset` elements in all browsers.
334 | */
335 |
336 | legend {
337 | box-sizing: border-box; /* 1 */
338 | color: inherit; /* 2 */
339 | display: table; /* 1 */
340 | max-width: 100%; /* 1 */
341 | padding: 0; /* 3 */
342 | white-space: normal; /* 1 */
343 | }
344 |
345 | /**
346 | * 1. Add the correct display in IE 9-.
347 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
348 | */
349 |
350 | progress {
351 | display: inline-block; /* 1 */
352 | vertical-align: baseline; /* 2 */
353 | }
354 |
355 | /**
356 | * Remove the default vertical scrollbar in IE.
357 | */
358 |
359 | textarea {
360 | overflow: auto;
361 | }
362 |
363 | /**
364 | * 1. Add the correct box sizing in IE 10-.
365 | * 2. Remove the padding in IE 10-.
366 | */
367 |
368 | [type="checkbox"],
369 | [type="radio"] {
370 | box-sizing: border-box; /* 1 */
371 | padding: 0; /* 2 */
372 | }
373 |
374 | /**
375 | * Correct the cursor style of increment and decrement buttons in Chrome.
376 | */
377 |
378 | [type="number"]::-webkit-inner-spin-button,
379 | [type="number"]::-webkit-outer-spin-button {
380 | height: auto;
381 | }
382 |
383 | /**
384 | * 1. Correct the odd appearance in Chrome and Safari.
385 | * 2. Correct the outline style in Safari.
386 | */
387 |
388 | [type="search"] {
389 | -webkit-appearance: textfield; /* 1 */
390 | outline-offset: -2px; /* 2 */
391 | }
392 |
393 | /**
394 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
395 | */
396 |
397 | [type="search"]::-webkit-search-cancel-button,
398 | [type="search"]::-webkit-search-decoration {
399 | -webkit-appearance: none;
400 | }
401 |
402 | /**
403 | * 1. Correct the inability to style clickable types in iOS and Safari.
404 | * 2. Change font properties to `inherit` in Safari.
405 | */
406 |
407 | ::-webkit-file-upload-button {
408 | -webkit-appearance: button; /* 1 */
409 | font: inherit; /* 2 */
410 | }
411 |
412 | /* Interactive
413 | ========================================================================== */
414 |
415 | /*
416 | * Add the correct display in IE 9-.
417 | * 1. Add the correct display in Edge, IE, and Firefox.
418 | */
419 |
420 | details, /* 1 */
421 | menu {
422 | display: block;
423 | }
424 |
425 | /*
426 | * Add the correct display in all browsers.
427 | */
428 |
429 | summary {
430 | display: list-item;
431 | }
432 |
433 | /* Scripting
434 | ========================================================================== */
435 |
436 | /**
437 | * Add the correct display in IE 9-.
438 | */
439 |
440 | canvas {
441 | display: inline-block;
442 | }
443 |
444 | /**
445 | * Add the correct display in IE.
446 | */
447 |
448 | template {
449 | display: none;
450 | }
451 |
452 | /* Hidden
453 | ========================================================================== */
454 |
455 | /**
456 | * Add the correct display in IE 10-.
457 | */
458 |
459 | [hidden] {
460 | display: none;
461 | }
462 |
--------------------------------------------------------------------------------
/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
21 |
46 |
55 |
56 |
58 |
59 |
61 | image/svg+xml
62 |
64 |
65 |
66 |
67 |
68 |
73 |
81 |
84 |
88 |
92 |
96 |
100 |
104 |
108 |
112 |
116 |
120 |
124 |
125 |
128 |
134 |
140 |
146 |
152 |
158 |
159 |
160 |
161 |
--------------------------------------------------------------------------------
/src/views/extensions/index.coffee:
--------------------------------------------------------------------------------
1 | db = sys.import '../db'
2 | util = sys.import '/util'
3 | {Gauge, Series, StackedPercentage} = sys.import '/chart'
4 | NavlistExpand = sys.import '../navlist'
5 | info = sys.import 'info'
6 | notFound = sys.import '../not-found'
7 | breadcrumbs = sys.import '../breadcrumbs'
8 |
9 | extensionLabel = (name) ->
10 | parts = name.split('_')
11 | parts.shift()
12 | return parts.join('_')
13 |
14 | exports.index = class Extensions
15 | constructor: (@filter, search) ->
16 | @webgl1 = []
17 | @webgl2 = []
18 |
19 | for name, meta of info
20 | if meta.status in ['ratified', 'community']
21 | if 1 in meta.versions
22 | @webgl1.push
23 | name: name
24 | label: extensionLabel(name)
25 | if 2 in meta.versions
26 | @webgl2.push
27 | name: name
28 | label: extensionLabel(name)
29 |
30 | @webgl1.sort (a,b) ->
31 | if a.label < b.label then return -1
32 | else if a.label > b.label then return 1
33 | else return 0
34 |
35 | @webgl2.sort (a,b) ->
36 | if a.label < b.label then return -1
37 | else if a.label > b.label then return 1
38 | else return 0
39 |
40 | @nav1 = new NavlistExpand('#extension-webgl1', 'webgl/extension', @webgl1)
41 | @nav2 = new NavlistExpand('#extension-webgl2', 'webgl2/extension', @webgl2)
42 |
43 | @buildSearch(search)
44 |
45 | buildSearch: (search) ->
46 | for entry in @webgl1
47 | @searchAdd search, 'webgl', 'webgl1', entry
48 | for entry in @webgl2
49 | @searchAdd search, 'webgl2', 'webgl2', entry
50 |
51 | searchAdd: (search, path, version, entry) ->
52 | meta = info[entry.name]
53 | search.add
54 | id: "/#{path}/extension/#{entry.name}"
55 | titles: [
56 | entry.label,
57 | entry.name,
58 | entry.name.replace(/_/g, ' ')
59 | ]
60 | body: meta.description
61 | extra: if meta.params? then meta.params.join(' ') else null
62 | type: "#{util.versionLabel(version)} Extension"
63 | gauge: =>
64 | @gauge(version, entry.name)
65 |
66 | breadcrumbs: (webglVersion, name) ->
67 | breadcrumbs [
68 | [util.versionLabel(webglVersion), "/#{util.versionPath(webglVersion)}"]
69 | 'Extension'
70 | [name, "/#{util.versionPath(webglVersion)}/extension/#{name}"]
71 | ]
72 |
73 | show: (webglVersion, name, pageload) ->
74 | meta = info[name]
75 |
76 | if not meta?
77 | return notFound()
78 |
79 | if meta.status not in ['ratified', 'community']
80 | return notFound()
81 |
82 | if ({webgl1:1, webgl2:2})[webglVersion] not in meta.versions
83 | return notFound()
84 |
85 | switch webglVersion
86 | when 'webgl1' then @nav1.activate(name, pageload)
87 | when 'webgl2' then @nav2.activate(name, pageload)
88 |
89 | @breadcrumbs(webglVersion, name)
90 |
91 | row = $('
')
92 | .addClass('row')
93 | .addClass('responsive')
94 | .appendTo('main')
95 |
96 | col = $('
')
97 | .appendTo(row)
98 |
99 | widget = $('
')
100 | .appendTo(col)
101 |
102 | $(' ')
103 | .text(name)
104 | .appendTo(widget)
105 |
106 | $('
')
107 | .append(meta.description)
108 | .appendTo(widget)
109 |
110 | for version in meta.versions
111 | $(' ')
112 | .text("WebGL #{version}")
113 | .appendTo(widget)
114 |
115 | $(' ')
116 | .text(util.capitalize(meta.status))
117 | .appendTo(widget)
118 |
119 | $('Specification ')
120 | .attr('href', 'https://www.khronos.org/registry/webgl/extensions/' + name)
121 | .appendTo(widget)
122 |
123 | col = $('
')
124 | .appendTo(row)
125 |
126 | widget = $('
')
127 | .appendTo(col)
128 |
129 | @day30view(webglVersion, name, widget)
130 |
131 | widget = $('
')
132 | .appendTo('main')
133 |
134 | @series(webglVersion, name)
135 | .appendTo(widget)
136 |
137 | if meta.params?
138 | for param in meta.params
139 | widget = $('
')
140 | .appendTo('main')
141 |
142 | $(' ')
143 | .text(param.name ? param)
144 | .appendTo(widget)
145 | @stackedPercentage(webglVersion, name, param)
146 | .appendTo(widget)
147 |
148 | overview: (webglVersion, pageload) ->
149 | flow = $('
')
150 | .appendTo('main')
151 |
152 | $(' ')
153 | .text('Extensions')
154 | .appendTo(flow)
155 |
156 | if webglVersion == 'webgl1'
157 | collection = @webgl1
158 | else if webglVersion == 'webgl2'
159 | collection = @webgl2
160 |
161 | for entry in collection
162 | container = $('
')
163 | .appendTo(flow)
164 |
165 | @gauge(webglVersion, entry.name)
166 | .appendTo(container)
167 |
168 | $(' ')
169 | .attr('href', "/#{util.versionPath(webglVersion)}/extension/#{entry.name}")
170 | .text(entry.label)
171 | .appendTo(container)
172 |
173 | gauge: (webglVersion, name, size='small', label=null, device=null) ->
174 | chart = new Gauge(label:label, size:size)
175 |
176 | fieldName = "webgl.extensions.#{name}"
177 |
178 | @filter.onChange chart.elem, =>
179 | chart.elem.addClass('spinner')
180 | query =
181 | filterBy:
182 | webgl:true
183 | bucketBy:fieldName
184 | start: -30
185 |
186 | if device?
187 | query.filterBy['useragent.device'] = device
188 |
189 | if @filter.platforms?
190 | query.filterBy.platform = @filter.platforms
191 |
192 | db.execute
193 | db: webglVersion
194 | query: query
195 | success: (result) ->
196 | if result.total > 0
197 | percentage = result.values[1]/result.total
198 | else
199 | percentage = 0
200 | chart.setLabel(label + " (#{util.formatNumber(result.values[1])})")
201 | chart.update(percentage*100)
202 | chart.elem.removeClass('spinner')
203 |
204 | return chart.elem
205 |
206 | series: (webglVersion, name) ->
207 | fieldName = "webgl.extensions.#{name}"
208 |
209 | chart = new Series()
210 |
211 | @filter.onChange chart.elem, =>
212 | query =
213 | filterBy:
214 | webgl:true
215 | bucketBy:fieldName
216 | series: @filter.series
217 |
218 | if @filter.platforms?
219 | query.filterBy.platform = @filter.platforms
220 |
221 | db.execute
222 | db: webglVersion
223 | query: query
224 | success: (result) ->
225 | chart.update(result.values)
226 |
227 | return chart.elem
228 |
229 | stackedPercentage: (webglVersion, name, param) ->
230 | if typeof(param) == 'string'
231 | param =
232 | name:param
233 | type:'abs'
234 | nullable:'false'
235 |
236 | extname = "webgl.extensions.#{name}"
237 | fieldname = "#{extname}.#{param.name}"
238 | chart = new StackedPercentage()
239 |
240 | @filter.onChange chart.elem, =>
241 | query =
242 | filterBy:
243 | webgl:true
244 | "#{extname}":true
245 | bucketBy:fieldname
246 | series: @filter.series
247 |
248 | if @filter.platforms?
249 | query.filterBy.platform = @filter.platforms
250 |
251 | db.execute
252 | db: webglVersion
253 | query: query
254 | success: (result) ->
255 | keys = result.keys
256 | xLabels = []
257 | data = []
258 |
259 | if param.nullable
260 | if keys[0] == null
261 | keys[0] = 'Unknown'
262 | valueStart = 0
263 | else
264 | if keys[0] == null
265 | valueStart = 1
266 | keys.shift()
267 | else
268 | valueStart = 0
269 |
270 | for item in result.values
271 | xLabels.push(item.name)
272 | values = []
273 |
274 | for value in item.values[valueStart...]
275 | if item.total == 0
276 | values.push(0)
277 | else
278 | values.push(value/item.total)
279 |
280 | data.push(values)
281 |
282 | chart.update
283 | type: param.type
284 | areaLabels: keys
285 | xLabels: xLabels
286 | data: data
287 |
288 | return $(chart.elem)
289 |
290 | day30view: (webglVersion, name, parent) ->
291 | $('Support (30d) ')
292 | .appendTo(parent)
293 |
294 | row = $('
')
295 | .appendTo(parent)
296 |
297 | col = $('
')
298 | .appendTo(row)
299 |
300 | @gauge(webglVersion, name, 'large', 'All')
301 | .appendTo(col)
302 |
303 | smallCharts = $('
')
304 | .appendTo(row)
305 |
306 | row = $('
')
307 | .appendTo(smallCharts)
308 |
309 | col = $('
').appendTo(row)
310 | @gauge(webglVersion, name, 'small', 'Desktop', 'desktop').appendTo(col)
311 |
312 | col = $('
').appendTo(row)
313 | @gauge(webglVersion, name, 'small', 'Smartphone', 'smartphone').appendTo(col)
314 |
315 | row = $('
')
316 | .appendTo(smallCharts)
317 |
318 | col = $('
').appendTo(row)
319 | @gauge(webglVersion, name, 'small', 'Tablet', 'tablet').appendTo(col)
320 |
321 | col = $('
').appendTo(row)
322 | @gauge(webglVersion, name, 'small', 'Console', 'game_console').appendTo(col)
323 |
324 |
--------------------------------------------------------------------------------
/src/views/extensions/info.coffee:
--------------------------------------------------------------------------------
1 | exports.index =
2 | EXT_blend_minmax:
3 | description: '''
4 | This extension allows for a blending mode that uses the minimum or maximum of the incoming and present color.
5 | It is useful for medical imaging, volume rendering and general purpose computation on the gpu.
6 | '''
7 | status: 'ratified'
8 | versions: [1]
9 | WEBGL_color_buffer_float:
10 | description: '''
11 | This extension allows to render into a floating point texture.
12 |
13 | For historical reasons this is not reliably indicative of renderable floating point textures, and actual support has to be tested individually.
14 | '''
15 | status: 'community'
16 | versions: [1]
17 | EXT_color_buffer_half_float:
18 | description: '''
19 | This extension allows to render into a half precision floating point texture.
20 | '''
21 | status: 'community'
22 | versions: [1]
23 | WEBGL_compressed_texture_astc:
24 | description: '''
25 | Offers compressed texture format support for ASTC
26 | '''
27 | status: 'community'
28 | versions: [1,2]
29 | params: [
30 | {name:'supportedProfiles',nullable:true,type:'rel'}
31 | ]
32 | WEBGL_compressed_texture_atc:
33 | description: '''
34 | Offers compressed texture format support for ATC .
35 |
36 | '''
37 | status: 'community'
38 | versions: [1,2]
39 | WEBGL_compressed_texture_etc1:
40 | description: '''
41 | Offers compressed texture format support for ETC1 .
42 |
43 | Warning DO NOT USE. Often implemented in browsers by decompressing on the CPU and uploading full size to GPU with severe performance, vram and quality impacts. Fixed in Chrome 57 and Firefox ??.
44 | '''
45 | status: 'community'
46 | versions: [1,2]
47 | WEBGL_compressed_texture_pvrtc:
48 | description: '''
49 | Offers compressed texture format support for PVRTC .
50 | '''
51 | status: 'community'
52 | versions: [1,2]
53 | WEBGL_compressed_texture_s3tc:
54 | description: '''
55 | Offers compressed texture format support for S3TC .
56 | '''
57 | status: 'ratified'
58 | versions: [1,2]
59 | WEBGL_debug_renderer_info:
60 | description: '''
61 | Allows to query the GPU vendor and model.
62 | '''
63 | status: 'ratified'
64 | versions: [1,2]
65 | WEBGL_depth_texture:
66 | description: '''
67 | This extension offers the ability to create depth textures to attach to a framebuffer object.
68 | '''
69 | status: 'ratified'
70 | versions: [1]
71 | EXT_disjoint_timer_query:
72 | description: '''
73 | This extension offers support for querying the execution time of commands on the GPU.
74 | '''
75 | status: 'community'
76 | versions: [1,2]
77 | WEBGL_draw_buffers:
78 | description: '''
79 | This extension allows a framebuffer object to hold several
80 | textures to render to and a fragment shader to output to them selectively.
81 |
82 | It is also known as multi render target (MRT) .
83 | '''
84 | params: [
85 | 'MAX_COLOR_ATTACHMENTS_WEBGL'
86 | 'MAX_DRAW_BUFFERS_WEBGL'
87 | ]
88 | status: 'ratified'
89 | versions: [1]
90 | OES_element_index_uint:
91 | description: '''
92 | This extension allows for vertex buffer array indicies to be 32-bit unsigned integers.
93 | '''
94 | status: 'ratified'
95 | versions: [1]
96 | EXT_frag_depth:
97 | description: '''
98 | This extension allows a fragment shader to write the depth of a fragment by assigning to the builtin gl_FragDepth.
99 | '''
100 | status: 'ratified'
101 | versions: [1]
102 | ANGLE_instanced_arrays:
103 | description: '''
104 | This extension offers the ability to repeat some vertex attributes, which can be used to render many instances of an object.
105 |
106 | The technique is also known as Geometry Instancing .
107 | '''
108 | status: 'ratified'
109 | versions: [1]
110 | WEBGL_lose_context:
111 | description: '''
112 | This extension simulates a context loss and regain for testing purposes.
113 | '''
114 | status: 'ratified'
115 | versions: [1,2]
116 | EXT_sRGB:
117 | description: '''
118 | This extension offers a texture format with internal storage in sRGB.
119 |
120 | Rendering should usually be performed in linear space
121 | (see the importance of being linear ).
122 | Using this extension banding artifacts and incorrect blending outcomes can be avoided or mitgitated.
123 | '''
124 | status: 'community'
125 | versions: [1]
126 | EXT_shader_texture_lod:
127 | description: '''
128 | Allows a fragment shader to specify the LOD level using the texture[2D,2DProj,Cube]LodEXT functions. Alternatively also allows to specify S/T derivatives by using the texture[2D,2DProj,Cube]GradEXT functions.
129 | '''
130 | status: 'ratified'
131 | versions: [1]
132 | OES_standard_derivatives:
133 | description: '''
134 | This extension allows a fragment shader to obtain the derivatives of a value in regards to neighboring fragments.
135 | '''
136 | status: 'ratified'
137 | versions: [1]
138 | EXT_texture_filter_anisotropic:
139 | description: '''
140 | This extension allows anisotropic texture filtering .
141 | '''
142 | params: [
143 | 'MAX_TEXTURE_MAX_ANISOTROPY_EXT'
144 | ]
145 | status: 'ratified'
146 | versions: [1,2]
147 | OES_texture_float:
148 | description: '''
149 | Offers basic support for 32-bit floating point textures.
150 | '''
151 | status: 'ratified'
152 | versions: [1]
153 | OES_texture_float_linear:
154 | description: '''
155 | Offers the ability to linearly filter 32-bit floating point textures.
156 | '''
157 | status: 'ratified'
158 | versions: [1,2]
159 | OES_texture_half_float:
160 | description: '''
161 | Offers basic support for 16-bit floating point textures.
162 | '''
163 | status: 'ratified'
164 | versions: [1]
165 | OES_texture_half_float_linear:
166 | description: '''
167 | Offers the ability to linearly filter 16-bit floating point textures.
168 | '''
169 | status: 'ratified'
170 | versions: [1]
171 | OES_vertex_array_object:
172 | description: '''
173 | This extension provides a way to group vertex attribute pointer configurations into a vertex array object (VAO) for later use.
174 | '''
175 | status: 'ratified'
176 | versions: [1]
177 | WEBGL_compressed_texture_s3tc_srgb:
178 | description: '''
179 | '''
180 | status: 'draft'
181 | versions: [1,2]
182 | WEBGL_compressed_texture_etc:
183 | description: '''
184 | Offers compressed texture format support for ETC2 and EAC .
185 |
186 | Warning DO NOT USE. Often implemented in browsers by decompressing on the CPU and uploading full size to GPU with severe performance, vram and quality impacts. Fixed in Chrome 57 and Firefox ??.
187 | '''
188 | status: 'community'
189 | versions: [1,2]
190 | WEBGL_shared_resources:
191 | description: '''
192 | '''
193 | status: 'draft'
194 | versions: [1,2]
195 | WEBGL_security_sensitive_resources:
196 | description: '''
197 | '''
198 | status: 'draft'
199 | versions: [1,2]
200 | OES_EGL_image_external:
201 | description: '''
202 | '''
203 | status: 'proposal'
204 | versions: [1,2]
205 | WEBGL_debug:
206 | description: '''
207 | '''
208 | status: 'proposal'
209 | versions: [1,2]
210 | WEBGL_dynamic_texture:
211 | description: '''
212 | '''
213 | status: 'proposal'
214 | versions: [1,2]
215 | WEBGL_subarray_uploads:
216 | description: '''
217 | '''
218 | status: 'proposal'
219 | versions: [1,2]
220 | ###
221 | WEBGL_debug_shaders:
222 | description: '''
223 | '''
224 | status: 'ratified'
225 | versions: [1,2]
226 | ###
227 | EXT_color_buffer_float:
228 | description: '''
229 | This extension allows to render into a floating point texture.
230 | '''
231 | status: 'community'
232 | versions: [2]
233 | EXT_disjoint_timer_query_webgl2:
234 | description: '''
235 | This extension offers support for querying the execution time of commands on the GPU.
236 | '''
237 | status: 'community'
238 | versions: [2]
239 | WEBGL_get_buffer_sub_data_async:
240 | description: '''
241 | '''
242 | status: 'draft'
243 | versions: [2]
244 | EXT_float_blend:
245 | description: '''
246 | '''
247 | status: 'draft'
248 | versions: [2]
249 | EXT_clip_cull_distance:
250 | description: '''
251 | '''
252 | status: 'proposal'
253 | versions: [2]
254 | WEBGL_multiview:
255 | description: '''
256 | '''
257 | status: 'proposal'
258 | versions: [2]
259 | OES_fbo_render_mipmap:
260 | description: '''
261 | '''
262 | status: 'draft'
263 | versions: [1]
264 |
265 | webgl1only = '''
266 | OES_texture_float
267 | OES_texture_half_float
268 | OES_standard_derivatives
269 | OES_vertex_array_object
270 | WEBGL_depth_texture
271 | OES_element_index_uint
272 | EXT_frag_depth
273 | WEBGL_draw_buffers
274 | ANGLE_instanced_arrays
275 | OES_texture_half_float_linear
276 | EXT_blend_minmax
277 | EXT_shader_texture_lod
278 | EXT_color_buffer_half_float
279 | WEBGL_color_buffer_float
280 | EXT_sRGB
281 | OES_fbo_render_mipmap
282 | '''.trim().split('\n')
283 |
284 | webgl2only = '''
285 | EXT_color_buffer_float
286 | EXT_disjoint_timer_query_webgl2
287 | WEBGL_get_buffer_sub_data_async
288 | EXT_float_blend
289 | EXT_clip_cull_distance
290 | WEBGL_multiview
291 | '''.trim().split('\n')
292 |
293 | webgl12 = '''
294 | WEBGL_lose_context
295 | WEBGL_debug_renderer_info
296 | WEBGL_compressed_texture_s3tc
297 | WEBGL_compressed_texture_s3tc_srgb
298 | EXT_texture_filter_anisotropic
299 | OES_texture_float_linear
300 | WEBGL_compressed_texture_atc
301 | WEBGL_compressed_texture_pvrtc
302 | WEBGL_compressed_texture_etc1
303 | EXT_disjoint_timer_query
304 | WEBGL_compressed_texture_etc
305 | WEBGL_compressed_texture_astc
306 | WEBGL_shared_resources
307 | WEBGL_security_sensitive_resources
308 | OES_EGL_image_external
309 | WEBGL_debug
310 | WEBGL_dynamic_texture
311 | WEBGL_subarray_uploads
312 | WEBGL_debug_shaders
313 | '''.trim().split('\n')
314 |
315 | names = [
316 | 'blend_minmax'
317 | 'color_buffer_float'
318 | 'color_buffer_half_float'
319 | 'compressed_texture_astc'
320 | 'compressed_texture_atc'
321 | #'compressed_texture_es3' #was recently renamed
322 | 'compressed_texture_etc1'
323 | 'compressed_texture_pvrtc'
324 | 'compressed_texture_s3tc'
325 | 'debug_renderer_info'
326 | 'depth_texture'
327 | 'disjoint_timer_query'
328 | 'draw_buffers'
329 | 'element_index_uint'
330 | 'frag_depth'
331 | 'instanced_arrays'
332 | 'lose_context'
333 | 'sRGB'
334 | 'shader_texture_lod'
335 | 'standard_derivatives'
336 | 'texture_filter_anisotropic'
337 | 'texture_float'
338 | 'texture_float_linear'
339 | 'texture_half_float'
340 | 'texture_half_float_linear'
341 | 'vertex_array_object'
342 | ]
343 |
--------------------------------------------------------------------------------
/lib/lunr.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.7.2
3 | * Copyright (C) 2016 Oliver Nightingale
4 | * @license MIT
5 | */
6 | !function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.7.2",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.utils.asString=function(t){return void 0===t||null===t?"":t.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(e){if(!arguments.length||null==e||void 0==e)return[];if(Array.isArray(e))return e.map(function(e){return t.utils.asString(e).toLowerCase()});var n=t.tokenizer.seperator||t.tokenizer.separator;return e.toString().trim().toLowerCase().split(n)},t.tokenizer.seperator=!1,t.tokenizer.separator=/[\s\-]+/,t.tokenizer.load=function(t){var e=this.registeredFunctions[t];if(!e)throw new Error("Cannot load un-registered function: "+t);return e},t.tokenizer.label="default",t.tokenizer.registeredFunctions={"default":t.tokenizer},t.tokenizer.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing tokenizer: "+n),e.label=n,this.registeredFunctions[n]=e},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,r=0;n>r;r++){for(var o=t[r],s=0;i>s&&(o=this._stack[s](o,r,t),void 0!==o&&""!==o);s++);void 0!==o&&""!==o&&e.push(o)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(o===t)return r;t>o&&(e=r),o>t&&(n=r),i=n-e,r=e+Math.floor(i/2),o=this.elements[r]}return o===t?r:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,r=e+Math.floor(i/2),o=this.elements[r];i>1;)t>o&&(e=r),o>t&&(n=r),i=n-e,r=e+Math.floor(i/2),o=this.elements[r];return o>t?r:t>o?r+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,r=0,o=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>o-1||r>s-1)break;a[i]!==h[r]?a[i]h[r]&&r++:(n.add(a[i]),i++,r++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone();for(var r=0,o=n.toArray();rp;p++)c[p]===a&&d++;h+=d/f*l.boost}}this.tokenStore.add(a,{ref:o,tf:h})}n&&this.eventEmitter.emit("add",e,this)},t.Index.prototype.remove=function(t,e){var n=t[this._ref],e=void 0===e?!0:e;if(this.documentStore.has(n)){var i=this.documentStore.get(n);this.documentStore.remove(n),i.forEach(function(t){this.tokenStore.remove(t,n)},this),e&&this.eventEmitter.emit("remove",t,this)}},t.Index.prototype.update=function(t,e){var e=void 0===e?!0:e;this.remove(t,!1),this.add(t,!1),e&&this.eventEmitter.emit("update",t,this)},t.Index.prototype.idf=function(t){var e="@"+t;if(Object.prototype.hasOwnProperty.call(this._idfCache,e))return this._idfCache[e];var n=this.tokenStore.count(t),i=1;return n>0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(this.tokenizerFn(e)),i=new t.Vector,r=[],o=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*o,h=this,u=this.tokenStore.expand(e).reduce(function(n,r){var o=h.corpusTokens.indexOf(r),s=h.idf(r),u=1,l=new t.SortedSet;if(r!==e){var c=Math.max(3,r.length-e.length);u=1/Math.log(c)}o>-1&&i.insert(o,a*s*u);for(var f=h.tokenStore.get(r),d=Object.keys(f),p=d.length,v=0;p>v;v++)l.add(f[d[v]].ref);return n.union(l)},new t.SortedSet);r.push(u)},this);var a=r.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,r=new t.Vector,o=0;i>o;o++){var s=n.elements[o],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);r.insert(this.corpusTokens.indexOf(s),a*h)}return r},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,tokenizer:this.tokenizerFn.label,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",r=n+"[^aeiouy]*",o=i+"[aeiou]*",s="^("+r+")?"+o+r,a="^("+r+")?"+o+r+"("+o+")?$",h="^("+r+")?"+o+r+o+r,u="^("+r+")?"+i,l=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(u),p=/^(.+?)(ss|i)es$/,v=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,y=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),k=new RegExp("^"+r+i+"[^aeiouwxy]$"),x=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,F=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,_=/^(.+?)(s|t)(ion)$/,z=/^(.+?)e$/,O=/ll$/,P=new RegExp("^"+r+i+"[^aeiouwxy]$"),T=function(n){var i,r,o,s,a,h,u;if(n.length<3)return n;if(o=n.substr(0,1),"y"==o&&(n=o.toUpperCase()+n.substr(1)),s=p,a=v,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=g,a=m,s.test(n)){var T=s.exec(n);s=l,s.test(T[1])&&(s=y,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,u=k,a.test(n)?n+="e":h.test(n)?(s=y,n=n.replace(s,"")):u.test(n)&&(n+="e"))}if(s=x,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],r=T[2],s=l,s.test(i)&&(n=i+t[r])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],r=T[2],s=l,s.test(i)&&(n=i+e[r])}if(s=F,a=_,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=z,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=P,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=O,a=c,s.test(n)&&a.test(n)&&(s=y,n=n.replace(s,"")),"y"==o&&(n=o.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.generateStopWordFilter=function(t){var e=t.reduce(function(t,e){return t[e]=e,t},{});return function(t){return t&&e[t]!==t?t:void 0}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){return t.replace(/^\W+/,"").replace(/\W+$/,"")},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t.charAt(0),r=t.slice(1);return i in n||(n[i]={docs:{}}),0===r.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(r,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n
29 | return Math.round(a*(1-f) + b*f).toFixed(0)
30 |
31 | interpolateColors = (f, stops) ->
32 | c0 = Math.min(Math.floor(f*2), 1)
33 | c1 = c0+1
34 | f = (f%0.5)*2
35 | c0 = stops[c0]
36 | c1 = stops[c1]
37 |
38 | r = mix(c0[0], c1[0], f)
39 | g = mix(c0[1], c1[1], f)
40 | b = mix(c0[2], c1[2], f)
41 |
42 | return [r,g,b]
43 |
44 | class Table
45 | constructor: (parent) ->
46 | @table = $('')
47 | .appendTo(parent)
48 | thead = $('Value % ')
49 | .appendTo(@table)
50 | @tbody = $(' ')
51 | .appendTo(@table)
52 |
53 | fill: (values) ->
54 | @tbody.remove()
55 | @tbody = $(' ')
56 | .appendTo(@table)
57 |
58 | @rows = []
59 |
60 | for value, n in values
61 | [r,g,b] = colorStops[n%colorStops.length]
62 | color = "rgb(#{r},#{g},#{b})"
63 | row = $(' ')
64 | .appendTo(@tbody)
65 | connector = $(' ')
66 | .css('background-color', color)
67 | .appendTo(row)[0]
68 | $(' ')
69 | .text(value)
70 | .appendTo(row)
71 |
72 | percent = $(' ')
73 | .appendTo(row)[0]
74 |
75 | bar = $('
')
76 | .appendTo(row)
77 | .find('div')
78 |
79 | @rows.push(connector:connector, percent:percent, bar:bar)
80 |
81 | class Chart
82 | constructor: (parent) ->
83 | @canvas = $(' ')
84 | .appendTo(parent)[0]
85 |
86 | @canvas.width = 500
87 | @canvas.height = 450
88 | @ctx = @canvas.getContext('2d')
89 |
90 | @paddingLeft = 50
91 | @paddingRight = 0
92 | @paddingTop = 20
93 | @paddingBottom = 50
94 |
95 | requestAnimationFrame(@check)
96 |
97 | check: =>
98 | if document.body.contains(@canvas)
99 | if @canvas.width != @canvas.clientWidth or @canvas.height != @canvas.clientHeight
100 | @canvas.width = @canvas.clientWidth
101 | @canvas.height = @canvas.clientHeight
102 | @draw()
103 | requestAnimationFrame(@check)
104 |
105 | pruneData: (areaLabels, areas) ->
106 | resultLabels = []
107 | resultAreas = []
108 | for i in [0...areaLabels.length]
109 | max = 0
110 | for item in areas[i]
111 | max = Math.max(max, item.rel)
112 |
113 | if max > 1.0/100
114 | resultLabels.push areaLabels[i]
115 | resultAreas.push areas[i]
116 |
117 | #return [areaLabels, areas]
118 | return [resultLabels, resultAreas]
119 |
120 | update: ({areaLabels, @xLabels, data, @type}) ->
121 | @type ?= 'abs'
122 | width = @canvas.width
123 | height = @canvas.height
124 | ctx = @ctx
125 |
126 | ctx.clearRect(0,0,width,height)
127 |
128 | stacked = []
129 | for item in data
130 | values = []
131 | sum = 1
132 | for value in item
133 | values.push(abs:sum, rel:value)
134 | sum -= value
135 | stacked.push(values)
136 |
137 | areas = []
138 | for i in [0...areaLabels.length]
139 | series = []
140 | for item in stacked
141 | series.push(item[i])
142 | areas.push series
143 |
144 | [@areaLabels, @areas] = @pruneData(areaLabels, areas)
145 |
146 | @count = data.length
147 | @draw()
148 |
149 | xToPos: (x) ->
150 | f = x/(@count-1)
151 | width = @canvas.width - @paddingLeft - @paddingRight
152 | return @paddingLeft + f*width
153 |
154 | yToPos: (y) ->
155 | height = @canvas.height - @paddingTop - @paddingBottom
156 | return @paddingTop + height-y*height
157 |
158 | drawYAxis: ->
159 | ctx = @ctx
160 |
161 | height = 12
162 | ctx.fillStyle = 'rgba(255,255,255,0.5)'
163 | ctx.font = "#{height}px 'Source Sans Pro'"
164 | ctx.textBaseline = 'middle'
165 | ctx.textAlign = 'end'
166 |
167 | for i in [0...5]
168 | percent = (100-(i/4)*100).toFixed(0) + '%'
169 | y = @yToPos(1-(i/4))
170 | x = @paddingLeft - 10
171 | ctx.fillText(percent, x, y)
172 |
173 | drawXAxisMonths: ->
174 | labels = @xLabels
175 | currentMonth = null
176 | days = null
177 | months = []
178 |
179 | for label, x in labels
180 | month = label.split('-')[1]
181 | if month != currentMonth
182 | if days?
183 | months.push(days)
184 | days = []
185 | currentMonth = month
186 |
187 | days.push(day:label, x:x)
188 |
189 | if days?
190 | months.push(days)
191 |
192 |
193 | monthNames = [null,'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
194 | ctx = @ctx
195 | ctx.strokeStyle = 'rgba(0,0,0,0.3)'
196 |
197 | height = 12
198 | ctx.fillStyle = 'rgba(255,255,255,0.5)'
199 | ctx.font = "#{height}px 'Source Sans Pro'"
200 | ctx.textBaseline = 'alphabetic'
201 | ctx.textAlign = 'center'
202 |
203 | for month in months
204 | x = month[0].x
205 | num = parseInt(month[0].day.split('-')[1], 10)
206 | name = monthNames[num]
207 |
208 | ctx.beginPath()
209 | ctx.moveTo(@xToPos(x), @yToPos(0))
210 | ctx.lineTo(@xToPos(x), @yToPos(1))
211 | ctx.stroke()
212 |
213 | labelX = Math.floor((@xToPos(month[0].x) + @xToPos(month[month.length-1].x))/2)
214 | labelY = Math.floor(@yToPos(0)) + height + 5
215 | ctx.fillText(name, labelX, labelY)
216 |
217 | drawXAxisYears: ->
218 | labels = @xLabels
219 | currentYear = null
220 | days = null
221 | years = []
222 |
223 | for label, x in labels
224 | year = label.split('-')[0]
225 | if year != currentYear
226 | if days?
227 | years.push(days:days, name:currentYear)
228 | days = []
229 | currentYear = year
230 |
231 | days.push(day:label, x:x)
232 |
233 | if days?
234 | years.push(days:days, name:year)
235 |
236 | ctx = @ctx
237 | ctx.strokeStyle = 'rgba(0,0,0,0.3)'
238 |
239 | height = 12
240 | ctx.fillStyle = 'rgba(255,255,255,0.5)'
241 | ctx.font = "#{height}px 'Source Sans Pro'"
242 | ctx.textBaseline = 'alphabetic'
243 | ctx.textAlign = 'center'
244 |
245 | for year in years
246 | x = year.days[0].x
247 | name = year.name
248 |
249 | ctx.beginPath()
250 | ctx.moveTo(@xToPos(x), @yToPos(0))
251 | ctx.lineTo(@xToPos(x), @yToPos(1))
252 | ctx.stroke()
253 |
254 | labelX = Math.floor((@xToPos(year.days[0].x) + @xToPos(year.days[year.days.length-1].x))/2)
255 | labelY = Math.floor(@yToPos(0)) + height + 5
256 | ctx.fillText(name, labelX, labelY)
257 |
258 | draw: ->
259 | if not @areas?
260 | return
261 |
262 | areas = @areas
263 | ctx = @ctx
264 |
265 | for area, n in areas
266 | [r,g,b] = colorStops[n%colorStops.length]
267 |
268 | ctx.fillStyle = "rgb(#{r},#{g},#{b})"
269 |
270 | ctx.beginPath()
271 | ctx.moveTo(@xToPos(0),@yToPos(0))
272 | ctx.lineTo(@xToPos(0), @yToPos(area[0].abs))
273 | for x in [1...area.length]
274 | ctx.lineTo(@xToPos(x), @yToPos(area[x].abs))
275 | ctx.lineTo(@xToPos(@count-1), @yToPos(0))
276 | ctx.closePath()
277 | ctx.fill()
278 |
279 | ctx.strokeStyle = "rgba(0,0,0,0.5)"
280 | for area in areas
281 | ctx.beginPath()
282 | ctx.moveTo(@xToPos(0), @yToPos(area[0].abs))
283 | for x in [1...area.length]
284 | ctx.lineTo(@xToPos(x), @yToPos(area[x].abs))
285 | ctx.stroke()
286 |
287 | @drawYAxis()
288 | @drawXAxisYears()
289 |
290 | getSlice: (f) ->
291 | if @type == 'abs'
292 | i = Math.round(f*(@count-1))
293 | for area in @areas
294 | {abs: area[i].abs, display: area[i].abs}
295 | else if @type == 'rel'
296 | i = Math.round(f*(@count-1))
297 | for area in @areas
298 | {abs: area[i].abs, display: area[i].rel}
299 |
300 | class Overlay
301 | constructor: (parent, @chart, @table) ->
302 | @canvas = $(' ')
303 | .appendTo(parent)[0]
304 |
305 | @ctx = @canvas.getContext('2d')
306 |
307 | $(@chart.canvas)
308 | .hover(@mouseenter, @mouseleave)
309 | .mousemove(@mousemove)
310 |
311 | requestAnimationFrame(@check)
312 |
313 | check: =>
314 | if document.body.contains(@canvas)
315 | if @canvas.width != @canvas.clientWidth or @canvas.height != @canvas.clientHeight
316 | @canvas.width = @canvas.clientWidth
317 | @canvas.height = @canvas.clientHeight
318 | @draw()
319 | requestAnimationFrame(@check)
320 |
321 | mouseenter: =>
322 | @hover = true
323 |
324 | mouseleave: =>
325 | @hover = false
326 | @ctx.clearRect(0, 0, @canvas.width, @canvas.height)
327 |
328 | mousemove: ({originalEvent}) =>
329 | @draw(originalEvent)
330 |
331 | draw: (event) ->
332 | ctx = @ctx
333 | ctx.clearRect(0, 0, @canvas.width, @canvas.height)
334 |
335 | if not @hover
336 | return
337 |
338 | rect = @canvas.getBoundingClientRect()
339 | chartRect = @chart.canvas.getBoundingClientRect()
340 |
341 | chartLeft = chartRect.left - rect.left + @chart.paddingLeft
342 | chartTop = chartRect.top - rect.top + @chart.paddingTop
343 | chartRight = chartRect.right - rect.left - @chart.paddingRight
344 | chartBottom = chartRect.bottom - rect.top - @chart.paddingBottom
345 | width = chartRight - chartLeft
346 | height = chartBottom - chartTop
347 |
348 | x = event.clientX - rect.left
349 | y = event.clientY - rect.top
350 |
351 |
352 | f = (x-chartLeft)/width
353 |
354 | if f >= 0 and f <= 1
355 | ctx.strokeStyle = 'rgba(0,0,0,0.3)'
356 | ctx.beginPath()
357 | ctx.moveTo(x, chartTop)
358 | ctx.lineTo(x, chartBottom)
359 | ctx.stroke()
360 |
361 | slice = @chart.getSlice(f)
362 |
363 | for value, n in slice
364 | [r,g,b] = colorStops[n%colorStops.length]
365 | color = "rgb(#{r},#{g},#{b})"
366 | y = chartTop + (1-value.abs)*height
367 |
368 | #@table.rows[n].percent.textContent = (value*100).toFixed(1)
369 | #@table.rows[n].bar.css('width', value*100)
370 |
371 | #dots on the line
372 | ctx.fillStyle = color
373 | ctx.beginPath()
374 | ctx.arc(x,y,3,0,Math.PI*2)
375 | ctx.fill()
376 | ctx.strokeStyle = "rgba(0,0,0,0.4)"
377 | ctx.arc(x,y,3,0,Math.PI*2)
378 | ctx.stroke()
379 |
380 | # labels
381 |
382 | @drawLabels(slice, x, chartTop, height)
383 | @updateTable(f)
384 |
385 | updateTable: (f) ->
386 | slice = @chart.getSlice(f)
387 | for value, n in slice
388 | @table.rows[n].percent.textContent = (value.display*100).toFixed(1)
389 | @table.rows[n].bar.css('width', value.display*100)
390 |
391 |
392 | drawLabels: (slice, x, chartTop, height) ->
393 | fontSize = 14
394 | @ctx.font = "#{fontSize}px 'Source Sans Pro'"
395 | @ctx.textBaseline = 'middle'
396 |
397 | labels = []
398 | for value, i in slice
399 | y = chartTop + (1-value.abs)*height
400 | label = @chart.areaLabels[i] + ' ='
401 | percent = (value.display*100).toFixed(1) + '%'
402 | [r,g,b] = colorStops[i%colorStops.length]
403 | r = Math.round(r*0.75+255*0.25)
404 | g = Math.round(g*0.75+255*0.25)
405 | b = Math.round(b*0.75+255*0.25)
406 | color = "rgb(#{r},#{g},#{b})"
407 | labelWidth = @ctx.measureText(label).width
408 | percentWidth = @ctx.measureText(percent).width
409 |
410 | labels.push
411 | label: label
412 | labelWidth: labelWidth
413 | percent: percent
414 | percentWidth: percentWidth
415 | width: labelWidth + percentWidth + 5
416 | color: color
417 | y: y
418 |
419 | left = []
420 | right = []
421 | for label, i in labels
422 | if i % 2 == 0
423 | right.push(label)
424 | else
425 | left.push(label)
426 |
427 | bevel = Math.round(fontSize/2 + 2)
428 |
429 | @ctx.textAlign = 'left'
430 | l = Math.round(x+6)
431 | for item in right
432 | y = Math.round(item.y)
433 | t = y - bevel
434 | b = y + bevel
435 | r = l + item.width + bevel + 6
436 |
437 | @ctx.fillStyle = 'black'
438 | @ctx.beginPath()
439 | @ctx.moveTo(l,y)
440 | @ctx.lineTo(l+bevel, t)
441 | @ctx.lineTo(r, t)
442 | @ctx.lineTo(r,b)
443 | @ctx.lineTo(l+bevel, b)
444 | @ctx.closePath()
445 | @ctx.fill()
446 |
447 | @ctx.strokeStyle = 'white'
448 | @ctx.beginPath()
449 | @ctx.moveTo(l,y)
450 | @ctx.lineTo(l+bevel, t)
451 | @ctx.lineTo(r, t)
452 | @ctx.lineTo(r,b)
453 | @ctx.lineTo(l+bevel, b)
454 | @ctx.closePath()
455 | @ctx.stroke()
456 |
457 | @ctx.fillStyle = item.color
458 | @ctx.fillText(item.label, l+bevel+1, y)
459 |
460 | @ctx.fillStyle = 'white'
461 | @ctx.fillText(item.percent, l+bevel+1+item.labelWidth + 5, y)
462 |
463 | @ctx.textAlign = 'left'
464 | r = x - 6
465 | for item in left
466 | y = Math.round(item.y)
467 | t = y - bevel
468 | b = y + bevel
469 | l = r - item.width - bevel - 6
470 |
471 | @ctx.fillStyle = 'black'
472 | @ctx.beginPath()
473 | @ctx.moveTo(r,y)
474 | @ctx.lineTo(r-bevel, b)
475 | @ctx.lineTo(l, b)
476 | @ctx.lineTo(l,t)
477 | @ctx.lineTo(r-bevel, t)
478 | @ctx.closePath()
479 | @ctx.fill()
480 |
481 | @ctx.fillStyle = 'white'
482 | @ctx.beginPath()
483 | @ctx.moveTo(r,y)
484 | @ctx.lineTo(r-bevel, b)
485 | @ctx.lineTo(l, b)
486 | @ctx.lineTo(l,t)
487 | @ctx.lineTo(r-bevel, t)
488 | @ctx.closePath()
489 | @ctx.stroke()
490 |
491 | @ctx.fillStyle = item.color
492 | @ctx.fillText(item.label, l+5, y)
493 |
494 | @ctx.fillStyle = 'white'
495 | @ctx.fillText(item.percent, l+5+item.labelWidth + 5, y)
496 |
497 | exports.index = class StackedPercentage
498 | constructor: ->
499 | @elem = $('
')
500 | @chart = new Chart(@elem)
501 | @table = new Table(@elem)
502 | @overlay = new Overlay(@elem, @chart, @table)
503 |
504 | update: (params) ->
505 | @chart.update(params)
506 | @table.fill(@chart.areaLabels, params)
507 | @overlay.updateTable(1)
508 |
--------------------------------------------------------------------------------
/src/views/parameters.coffee:
--------------------------------------------------------------------------------
1 | db = sys.import 'db'
2 | util = sys.import '/util'
3 | NavlistExpand = sys.import 'navlist'
4 | {StackedPercentage, Bar} = sys.import '/chart'
5 | notFound = sys.import 'not-found'
6 | breadcrumbs = sys.import 'breadcrumbs'
7 |
8 | info =
9 | ALIASED_LINE_WIDTH_RANGE:
10 | description: '''
11 | The maximum thickness of line that can be rendered.
12 | '''
13 | versions: [1,2]
14 | ALIASED_POINT_SIZE_RANGE:
15 | description: '''
16 | The maximum size of point that can be rendered.
17 | '''
18 | versions: [1,2]
19 | DEPTH_BITS:
20 | description: '''
21 | The number of bits for the default depthbuffer. Bits may differ in case of rendering to a framebuffer object.
22 | '''
23 | versions: [1,2]
24 | MAX_COMBINED_TEXTURE_IMAGE_UNITS:
25 | description: '''
26 | The maximum number of texture units that can be used.
27 | If a unit is used by both vertex and fragment shader, this counts as two units against this limit.
28 | '''
29 | versions: [1,2]
30 | MAX_CUBE_MAP_TEXTURE_SIZE:
31 | description: '''
32 | The maximum size of any side of a cubemap.
33 | '''
34 | versions: [1,2]
35 | MAX_FRAGMENT_UNIFORM_VECTORS:
36 | description: '''
37 | The maximum number of 4-element vectors that can be passed as uniform to the vertex shader. All uniforms are 4-element aligned, a single uniform counts at least as one 4-element vector.
38 | '''
39 | versions: [1,2]
40 | MAX_RENDERBUFFER_SIZE:
41 | description: '''
42 | The largest renderbuffer that can be used. This limit indicates the maximum usable canvas size as well as the maximum usable framebuffer object attached renderbuffer or texture size.
43 | '''
44 | versions: [1,2]
45 | MAX_TEXTURE_IMAGE_UNITS:
46 | description: '''
47 | The maxium number of texture units that can be used in a fragment shader.
48 | '''
49 | versions: [1,2]
50 | MAX_TEXTURE_SIZE:
51 | description: '''
52 | The largest texture size (either width or height) that can be created. Note that VRAM may not allow a texture of any given size, it just expresses hardware/driver support for a given size.
53 | '''
54 | versions: [1,2]
55 | MAX_VARYING_VECTORS:
56 | description: '''
57 | The maximum number of 4-element vectors that can be used as varyings. Each varying counts as at least one 4-element vector.
58 | '''
59 | versions: [1,2]
60 | MAX_VERTEX_ATTRIBS:
61 | description: '''
62 | The maximum number of 4-element vectors that can be used as attributes to a vertex shader. Each attribute counts as at least one 4-element vector.
63 | '''
64 | versions: [1,2]
65 | MAX_VERTEX_TEXTURE_IMAGE_UNITS:
66 | description: '''
67 | The maximum number of texture units that can be used by a vertex shader. The value may be 0 which indicates no vertex shader texturing support.
68 | '''
69 | versions: [1,2]
70 | MAX_VERTEX_UNIFORM_VECTORS:
71 | description: '''
72 | The maximum number of 4-element vectors that can be passed as uniform to a vertex shader. All uniforms are 4-element aligned, a single uniform counts at least as one 4-element vector.
73 | '''
74 | versions: [1,2]
75 | MAX_VIEWPORT_DIMS:
76 | description: '''
77 | The maximum viewport dimension (either width or height), that the viewport can be set to.
78 | '''
79 | versions: [1,2]
80 | SAMPLES:
81 | description: '''
82 | Indicates the coverage mask of the default framebuffer. This value affects anti-aliasing and depth to coverage. For instance a value of 4 would indicate a 4x4 mask.
83 | '''
84 | versions: [1,2]
85 | SAMPLE_BUFFERS:
86 | description: '''
87 | Indicates if a sample buffer is associated with the default framebuffer, this indicates support for anti-aliasing and alpha to coverage support.
88 | '''
89 | versions: [1,2]
90 | STENCIL_BITS:
91 | description: '''
92 | The number of bits of the default framebuffer usable for stenciling.
93 | '''
94 | versions: [1,2]
95 | SUBPIXEL_BITS:
96 | description: '''
97 | The bit-precision used to position primitives in window coordinates.
98 | '''
99 | versions: [1,2]
100 | MAX_3D_TEXTURE_SIZE:
101 | description: '''
102 | The largest 3D texture size (width, height or depth) that can be created. Note that VRAM may not allow a texture of any given size, it just expresses hardware/driver support for a given size.
103 | '''
104 | versions: [2]
105 | MAX_ARRAY_TEXTURE_LAYERS:
106 | description: '''
107 | The maximum amount of texture layers an array texture can hold.
108 | '''
109 | versions: [2]
110 | MAX_COLOR_ATTACHMENTS:
111 | description: '''
112 | The maximum number of color attachments that a framebuffer object supports.
113 | '''
114 | versions: [2]
115 | MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS:
116 | description: '''
117 | The maximum amount 4-byte components allowable in fragment shader uniform blocks.
118 | '''
119 | versions: [2]
120 | MAX_COMBINED_UNIFORM_BLOCKS:
121 | description: '''
122 | The maximum amount of uniform blocks allowed per program.
123 | '''
124 | versions: [2]
125 | MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS:
126 | description: '''
127 | The maximum of of 4-byte components allowable in vertex shader uniform blocks.
128 | '''
129 | versions: [2]
130 | MAX_DRAW_BUFFERS:
131 | description: '''
132 | The maximum amount of simultaneous outputs that may be written in a fragment shader. This is used for multi render targets (MRT) .
133 | '''
134 | versions: [2]
135 | MAX_ELEMENT_INDEX:
136 | description: '''
137 | The maximum index value usable for an indexed vertex array.
138 | '''
139 | versions: [2]
140 | MAX_ELEMENTS_INDICES:
141 | description: '''
142 | The maximum amount of indices that can be used with an indexed vertex array.
143 | '''
144 | versions: [2]
145 | MAX_ELEMENTS_VERTICES:
146 | description: '''
147 | The maximum amount of vertex array vertices that's recommended.
148 | '''
149 | versions: [2]
150 | MAX_FRAGMENT_INPUT_COMPONENTS:
151 | description: '''
152 | The maximum amount of inputs for a fragment shader.
153 | '''
154 | versions: [2]
155 | MAX_FRAGMENT_UNIFORM_BLOCKS:
156 | description: '''
157 | The maximum amount of uniform blocks alowable for a fragment shader.
158 | '''
159 | versions: [2]
160 | MAX_FRAGMENT_UNIFORM_COMPONENTS:
161 | description: '''
162 | The maximum amount of of floats, integers or bools that can be in uniform storage for fragment shaders.
163 | '''
164 | versions: [2]
165 | MAX_PROGRAM_TEXEL_OFFSET:
166 | description: '''
167 | The maximum alowable texel offset in texture lookups.
168 | '''
169 | versions: [2]
170 | MAX_SAMPLES:
171 | description: '''
172 | Idicates the maximum supported size for multisampling. For instance 4 would indicate a maximum size of 4x4 MSAA .
173 | '''
174 | versions: [2]
175 | MAX_SERVER_WAIT_TIMEOUT:
176 | description: '''
177 | The maximum glWaitSync timeout interval.
178 | '''
179 | versions: [2]
180 | MAX_TEXTURE_LOD_BIAS:
181 | description: '''
182 | The maximum supported texture lookup LOD bias.
183 | '''
184 | versions: [2]
185 | MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS:
186 | description: '''
187 | The maximum number of components writable in interleaved feedback buffer mode.
188 | '''
189 | versions: [2]
190 | MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
191 | description: '''
192 | The maximum number of attributes or outputs which is supported for capture in separate transform feedback mode.
193 | '''
194 | versions: [2]
195 | MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS:
196 | description: '''
197 | The maximum number of components per attribute which is supported in separate transform feedback mode.
198 | '''
199 | versions: [2]
200 | MAX_UNIFORM_BLOCK_SIZE:
201 | description: '''
202 | The maximum size of a uniform block in 4-byte units.
203 | '''
204 | versions: [2]
205 | MAX_UNIFORM_BUFFER_BINDINGS:
206 | description: '''
207 | The maximum number of simultaneously usable uniform blocks.
208 | '''
209 | versions: [2]
210 | MAX_VARYING_COMPONENTS:
211 | description: '''
212 | The maximum number of 4-component vectors usable for varyings.
213 | '''
214 | versions: [2]
215 | MAX_VERTEX_OUTPUT_COMPONENTS:
216 | description: '''
217 | The maximum number of 4-component vectors that a vertex shader can write.
218 | '''
219 | versions: [2]
220 | MAX_VERTEX_UNIFORM_BLOCKS:
221 | description: '''
222 | The maximum number of uniform blocks per vertex shader.
223 | '''
224 | versions: [2]
225 | MAX_VERTEX_UNIFORM_COMPONENTS:
226 | description: '''
227 | The maximum number of floats, integers or booleans that can be in storage for a vertex shader.
228 | '''
229 | versions: [2]
230 | MIN_PROGRAM_TEXEL_OFFSET:
231 | description: '''
232 | The minimum texel offset for texture lookups.
233 | '''
234 | versions: [2]
235 |
236 | fieldNames =
237 | MAX_VIEWPORT_DIMS: 'MAX_VIEWPORT_DIMS.width'
238 | ALIASED_LINE_WIDTH_RANGE: 'ALIASED_LINE_WIDTH_RANGE.max'
239 | ALIASED_POINT_SIZE_RANGE: 'ALIASED_POINT_SIZE_RANGE.max'
240 |
241 | exports.index = class Parameters
242 | constructor: (@filter, search) ->
243 | @webgl1 = []
244 | @webgl2 = []
245 |
246 | for name, entry of info
247 | if 1 in entry.versions
248 | @webgl1.push
249 | name: name
250 | label: name
251 | if 2 in entry.versions
252 | @webgl2.push
253 | name: name
254 | label: name
255 |
256 | @webgl1.sort (a,b) ->
257 | if a.label < b.label then return -1
258 | else if a.label > b.label then return 1
259 | else return 0
260 |
261 | @webgl2.sort (a,b) ->
262 | if a.label < b.label then return -1
263 | else if a.label > b.label then return 1
264 | else return 0
265 |
266 | @nav1 = new NavlistExpand('#parameter-webgl1', 'webgl/parameter', @webgl1)
267 | @nav2 = new NavlistExpand('#parameter-webgl2', 'webgl2/parameter', @webgl2)
268 |
269 | @buildSearch(search)
270 |
271 | buildSearch: (search) ->
272 | for entry in @webgl1
273 | @searchAdd search, 'webgl', 'webgl1', entry.name
274 | for entry in @webgl2
275 | @searchAdd search, 'webgl2', 'webgl2', entry.name
276 |
277 | searchAdd: (search, path, version, name) ->
278 | search.add
279 | id: "/#{path}/parameter/#{name}"
280 | titles: [
281 | name
282 | name.replace(/_/g, ' ')
283 | ]
284 | body: info[name].description
285 | type: "#{util.versionLabel(version)} Parameter"
286 |
287 | breadcrumbs: (webglVersion, name) ->
288 | breadcrumbs [
289 | [util.versionLabel(webglVersion), "/#{util.versionPath(webglVersion)}"]
290 | 'Parameter'
291 | [name, "/#{util.versionPath(webglVersion)}/parameter/#{name}"]
292 | ]
293 |
294 | show: (webglVersion, name, pageload) ->
295 | meta = info[name]
296 |
297 | if not meta?
298 | return notFound()
299 |
300 | switch webglVersion
301 | when 'webgl1' then @nav1.activate(name, pageload)
302 | when 'webgl2' then @nav2.activate(name, pageload)
303 |
304 | @breadcrumbs webglVersion, name
305 |
306 | row = $('
')
307 | .appendTo('main')
308 |
309 | col = $('
')
310 | .appendTo(row)
311 |
312 | widget = $('
')
313 | .appendTo(col)
314 |
315 | $(' ')
316 | .text(name)
317 | .appendTo(widget)
318 |
319 | $('
')
320 | .append(meta.description)
321 | .appendTo(widget)
322 |
323 | for version in meta.versions
324 | $(' ')
325 | .text("WebGL #{version}")
326 | .appendTo(widget)
327 |
328 | col = $('
')
329 | .appendTo(row)
330 |
331 | widget = $('
')
332 | .appendTo(col)
333 |
334 | $('Support (30d) ')
335 | .appendTo(widget)
336 |
337 | @barchart(webglVersion, name).appendTo(widget)
338 |
339 | full = $('
')
340 | .appendTo('main')
341 |
342 | @series(webglVersion, name).appendTo(full)
343 |
344 | overview: (webglVersion) ->
345 | flow = $('
')
346 | .appendTo('main')
347 |
348 | $('Parameters ')
349 | .appendTo(flow)
350 |
351 | if webglVersion == 'webgl1'
352 | collection = @webgl1
353 | else if webglVersion == 'webgl2'
354 | collection = @webgl2
355 |
356 | for entry in collection
357 | container = $('
')
358 | .appendTo(flow)
359 |
360 | @chart(webglVersion, entry.name).appendTo(container)
361 |
362 | $(' ')
363 | .attr('href', "/webgl/parameter/#{entry.name}")
364 | .text(entry.label)
365 | .appendTo(container)
366 |
367 | series: (webglVersion, name) ->
368 | if fieldNames[name]?
369 | fieldName = "webgl.params.#{fieldNames[name]}"
370 | else
371 | fieldName = "webgl.params.#{name}"
372 |
373 | chart = new StackedPercentage()
374 |
375 | @filter.onChange chart.elem, =>
376 | query =
377 | filterBy:
378 | webgl:true
379 | bucketBy:fieldName
380 | series: @filter.series
381 |
382 | if @filter.platforms?
383 | query.filterBy.platform = @filter.platforms
384 |
385 | db.execute
386 | db: webglVersion
387 | query: query
388 | success: (result) ->
389 | keys = result.keys
390 | xLabels = []
391 | data = []
392 |
393 | if keys[0] == null
394 | valueStart = 1
395 | keys.shift()
396 | else
397 | valueStart = 0
398 |
399 | for item in result.values
400 | xLabels.push(item.name)
401 | values = []
402 |
403 | for value in item.values[valueStart...]
404 | if item.total == 0
405 | values.push(0)
406 | else
407 | values.push(value/item.total)
408 |
409 | data.push(values)
410 |
411 | chart.update
412 | areaLabels: keys
413 | xLabels: xLabels
414 | data: data
415 |
416 | return $(chart.elem)
417 |
418 | barchart: (webglVersion, name) ->
419 | if fieldNames[name]?
420 | fieldName = "webgl.params.#{fieldNames[name]}"
421 | else
422 | fieldName = "webgl.params.#{name}"
423 |
424 | chart = new Bar()
425 |
426 | @filter.onChange chart.elem, =>
427 | query =
428 | filterBy:
429 | webgl:true
430 | bucketBy:fieldName
431 | start: -30
432 |
433 | if @filter.platforms?
434 | query.filterBy.platform = @filter.platforms
435 |
436 | db.execute
437 | db: webglVersion
438 | query: query
439 | success: (result) ->
440 | values = result.values
441 | keys = result.keys
442 | if not keys[0]?
443 | values.shift()
444 | keys.shift()
445 | chart.update(keys, values)
446 |
447 | return chart.elem
448 |
449 | chart: (webglVersion, name) ->
450 | if fieldNames[name]?
451 | fieldName = "webgl.params.#{fieldNames[name]}"
452 | else
453 | fieldName = "webgl.params.#{name}"
454 |
455 | container = $('
')
456 |
457 | db.execute
458 | query:
459 | db: webglVersion
460 | filterBy:
461 | webgl:true
462 | bucketBy:fieldName
463 | start: -30
464 | success: (result) ->
465 | values = result.values
466 | keys = result.keys
467 | if not keys[0]?
468 | values.shift()
469 | keys.shift()
470 |
471 | container.sparkline values,
472 | type:'bar'
473 | chartRangeMin:0
474 | height:100
475 | barWidth: 8
476 | #width: 100
477 | tooltipFormatter: (sparkline, options, fields) ->
478 | offset = fields[0].offset
479 | value = fields[0].value
480 | label = result.keys[offset]
481 | return "#{label} - #{(value*100/result.total).toFixed(0)}% "
482 |
483 | return container
484 |
--------------------------------------------------------------------------------