├── 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 | $('ValueAbs.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 = $('