123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- # Request Animation Frame Polyfill
- # CoffeeScript version of http://paulirish.com/2011/requestanimationframe-for-smart-animating/
- do () ->
- vendors = ['ms', 'moz', 'webkit', 'o']
- for vendor in vendors
- if window.requestAnimationFrame
- break
- window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame']
- window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] or window[vendor + 'CancelRequestAnimationFrame']
- browserRequestAnimationFrame = null
- lastId = 0
- isCancelled = {}
- if not requestAnimationFrame
- window.requestAnimationFrame = (callback, element) ->
- currTime = new Date().getTime()
- timeToCall = Math.max(0, 16 - (currTime - lastTime))
- id = window.setTimeout(() ->
- callback(currTime + timeToCall)
- , timeToCall)
- lastTime = currTime + timeToCall
- return id
- # This implementation should only be used with the setTimeout()
- # version of window.requestAnimationFrame().
- window.cancelAnimationFrame = (id) ->
- clearTimeout(id)
- else if not window.cancelAnimationFrame
- browserRequestAnimationFrame = window.requestAnimationFrame
- window.requestAnimationFrame = (callback, element) ->
- myId = ++lastId
- browserRequestAnimationFrame(() ->
- if not isCancelled[myId]
- callback()
- , element)
- return myId
- window.cancelAnimationFrame = (id) ->
- isCancelled[id] = true
- String.prototype.hashCode = () ->
- hash = 0
- if this.length == 0
- return hash
- for i in [0...this.length]
- char = this.charCodeAt(i)
- hash = ((hash << 5) - hash) + char
- hash = hash & hash # Convert to 32bit integer
- return hash
- secondsToString = (sec) ->
- hr = Math.floor(sec / 3600)
- min = Math.floor((sec - (hr * 3600))/60)
- sec -= ((hr * 3600) + (min * 60))
- sec += ''
- min += ''
- while min.length < 2
- min = '0' + min
- while sec.length < 2
- sec = '0' + sec
- hr = if hr then hr + ':' else ''
- return hr + min + ':' + sec
- formatNumber = (num) ->
- return addCommas(num.toFixed(0))
- updateObjectValues = (obj1, obj2) ->
- for own key, val of obj2
- obj1[key] = val
- return obj1
- mergeObjects = (obj1, obj2) ->
- out = {}
- for own key, val of obj1
- out[key] = val
- for own key, val of obj2
- out[key] = val
- return out
- addCommas = (nStr) ->
- nStr += ''
- x = nStr.split('.')
- x1 = x[0]
- x2 = ''
- if x.length > 1
- x2 = '.' + x[1]
- rgx = /(\d+)(\d{3})/
- while rgx.test(x1)
- x1 = x1.replace(rgx, '$1' + ',' + '$2')
- return x1 + x2
- cutHex = (nStr) ->
- if nStr.charAt(0) == "#"
- return nStr.substring(1,7)
- return nStr
- class ValueUpdater
- animationSpeed: 32
- constructor: (addToAnimationQueue=true, @clear=true) ->
- if addToAnimationQueue
- AnimationUpdater.add(@)
- update: (force=false) ->
- if force or @displayedValue != @value
- if @ctx and @clear
- @ctx.clearRect(0, 0, @canvas.width, @canvas.height)
- diff = @value - @displayedValue
- if Math.abs(diff / @animationSpeed) <= 0.001
- @displayedValue = @value
- else
- @displayedValue = @displayedValue + diff / @animationSpeed
- @render()
- return true
- return false
- class BaseGauge extends ValueUpdater
- displayScale: 1
- setTextField: (textField) ->
- @textField = if textField instanceof TextRenderer then textField else new TextRenderer(textField)
- setMinValue: (@minValue, updateStartValue=true) ->
- if updateStartValue
- @displayedValue = @minValue
- for gauge in @gp or []
- gauge.displayedValue = @minValue
- setOptions: (options=null) ->
- @options = mergeObjects(@options, options)
- if @textField
- @textField.el.style.fontSize = options.fontSize + 'px'
- if @options.angle > .5
- @gauge.options.angle = .5
- @configDisplayScale()
- return @
- configDisplayScale: () ->
- prevDisplayScale = @displayScale
- if @options.highDpiSupport == false
- delete @displayScale
- else
- devicePixelRatio = window.devicePixelRatio or 1
- backingStorePixelRatio =
- @ctx.webkitBackingStorePixelRatio or
- @ctx.mozBackingStorePixelRatio or
- @ctx.msBackingStorePixelRatio or
- @ctx.oBackingStorePixelRatio or
- @ctx.backingStorePixelRatio or 1
- @displayScale = devicePixelRatio / backingStorePixelRatio
- if @displayScale != prevDisplayScale
- width = @canvas.G__width or @canvas.width
- height = @canvas.G__height or @canvas.height
- @canvas.width = width * @displayScale
- @canvas.height = height * @displayScale
- @canvas.style.width = "#{width}px"
- @canvas.style.height = "#{height}px"
- @canvas.G__width = width
- @canvas.G__height = height
- return @
- class TextRenderer
- constructor: (@el) ->
- # Default behaviour, override to customize rendering
- render: (gauge) ->
- @el.innerHTML = formatNumber(gauge.displayedValue)
- class AnimatedText extends ValueUpdater
- displayedValue: 0
- value: 0
- setVal: (value) ->
- @value = 1 * value
- constructor: (@elem, @text=false) ->
- @value = 1 * @elem.innerHTML
- if @text
- @value = 0
- render: () ->
- if @text
- textVal = secondsToString(@displayedValue.toFixed(0))
- else
- textVal = addCommas(formatNumber(@displayedValue))
- @elem.innerHTML = textVal
- AnimatedTextFactory =
- create: (objList) ->
- out = []
- for elem in objList
- out.push(new AnimatedText(elem))
- return out
- class GaugePointer extends ValueUpdater
- displayedValue: 0
- value: 0
- options:
- strokeWidth: 0.035
- length: 0.1
- color: "#000000"
- constructor: (@gauge) ->
- @ctx = @gauge.ctx
- @canvas = @gauge.canvas
- super(false, false)
- @setOptions()
- setOptions: (options=null) ->
- updateObjectValues(@options, options)
- @length = @canvas.height * @options.length
- @strokeWidth = @canvas.height * @options.strokeWidth
- @maxValue = @gauge.maxValue
- @minValue = @gauge.minValue
- @animationSpeed = @gauge.animationSpeed
- @options.angle = @gauge.options.angle
- render: () ->
- angle = @gauge.getAngle.call(@, @displayedValue)
- centerX = @canvas.width / 2
- centerY = @canvas.height * 0.9
- x = Math.round(centerX + @length * Math.cos(angle))
- y = Math.round(centerY + @length * Math.sin(angle))
- startX = Math.round(centerX + @strokeWidth * Math.cos(angle - Math.PI/2))
- startY = Math.round(centerY + @strokeWidth * Math.sin(angle - Math.PI/2))
- endX = Math.round(centerX + @strokeWidth * Math.cos(angle + Math.PI/2))
- endY = Math.round(centerY + @strokeWidth * Math.sin(angle + Math.PI/2))
- @ctx.fillStyle = @options.color
- @ctx.beginPath()
- @ctx.arc(centerX, centerY, @strokeWidth, 0, Math.PI*2, true)
- @ctx.fill()
- @ctx.beginPath()
- @ctx.moveTo(startX, startY)
- @ctx.lineTo(x, y)
- @ctx.lineTo(endX, endY)
- @ctx.fill()
- class Bar
- constructor: (@elem) ->
- updateValues: (arrValues) ->
- @value = arrValues[0]
- @maxValue = arrValues[1]
- @avgValue = arrValues[2]
- @render()
- render: () ->
- if @textField
- @textField.text(formatNumber(@value))
- if @maxValue == 0
- @maxValue = @avgValue * 2
- valPercent = (@value / @maxValue) * 100
- avgPercent = (@avgValue / @maxValue) * 100
- $(".bar-value", @elem).css({"width": valPercent + "%"})
- $(".typical-value", @elem).css({"width": avgPercent + "%"})
- class Gauge extends BaseGauge
- elem: null
- value: [20] # we support multiple pointers
- maxValue: 80
- minValue: 0
- displayedAngle: 0
- displayedValue: 0
- lineWidth: 40
- paddingBottom: 0.1
- percentColors: null,
- options:
- colorStart: "#6fadcf"
- colorStop: undefined
- gradientType: 0 # 0 : radial, 1 : linear
- strokeColor: "#e0e0e0"
- pointer:
- length: 0.8
- strokeWidth: 0.035
- angle: 0.15
- lineWidth: 0.44
- fontSize: 40
- limitMax: false
- constructor: (@canvas) ->
- super()
- @percentColors = null
- if typeof G_vmlCanvasManager != 'undefined'
- @canvas = window.G_vmlCanvasManager.initElement(@canvas)
- @ctx = @canvas.getContext('2d')
- @gp = [new GaugePointer(@)]
- @setOptions()
- @render()
- setOptions: (options=null) ->
- super(options)
- @configPercentColors()
- @lineWidth = @canvas.height * (1 - @paddingBottom) * @options.lineWidth # .2 - .7
- @radius = @canvas.height * (1 - @paddingBottom) - @lineWidth
- @ctx.clearRect(0, 0, @canvas.width, @canvas.height)
- @render()
- for gauge in @gp
- gauge.setOptions(@options.pointer)
- gauge.render()
- return @
- configPercentColors: () ->
- @percentColors = null;
- if (@options.percentColors != undefined)
- @percentColors = new Array()
- for i in [0..(@options.percentColors.length-1)]
- rval = parseInt((cutHex(@options.percentColors[i][1])).substring(0,2),16)
- gval = parseInt((cutHex(@options.percentColors[i][1])).substring(2,4),16)
- bval = parseInt((cutHex(@options.percentColors[i][1])).substring(4,6),16)
- @percentColors[i] = { pct: @options.percentColors[i][0], color: { r: rval, g: gval, b: bval } }
- set: (value) ->
- if not (value instanceof Array)
- value = [value]
- # check if we have enough GaugePointers initialized
- # lazy initialization
- if value.length > @gp.length
- for i in [0...(value.length - @gp.length)]
- @gp.push(new GaugePointer(@))
- # get max value and update pointer(s)
- i = 0
- max_hit = false
- for val in value
- if val > @maxValue
- @maxValue = @value * 1.1
- max_hit = true
- @gp[i].value = val
- @gp[i++].setOptions({maxValue: @maxValue, angle: @options.angle})
- @value = value[value.length - 1] # TODO: Span maybe??
- if max_hit
- unless @options.limitMax
- AnimationUpdater.run()
- else
- AnimationUpdater.run()
- getAngle: (value) ->
- return (1 + @options.angle) * Math.PI + ((value - @minValue) / (@maxValue - @minValue)) * (1 - @options.angle * 2) * Math.PI
- getColorForPercentage: (pct, grad) ->
- if pct == 0
- color = @percentColors[0].color;
- else
- color = @percentColors[@percentColors.length - 1].color;
- for i in [0..(@percentColors.length - 1)]
- if (pct <= @percentColors[i].pct)
- if grad == true
- # Gradually change between colors
- startColor = @percentColors[i - 1]
- endColor = @percentColors[i]
- rangePct = (pct - startColor.pct) / (endColor.pct - startColor.pct) # How far between both colors
- color = {
- r: Math.floor(startColor.color.r * (1 - rangePct) + endColor.color.r * rangePct),
- g: Math.floor(startColor.color.g * (1 - rangePct) + endColor.color.g * rangePct),
- b: Math.floor(startColor.color.b * (1 - rangePct) + endColor.color.b * rangePct)
- }
- else
- color = @percentColors[i].color
- break
- return 'rgb(' + [color.r, color.g, color.b].join(',') + ')'
-
- getColorForValue: (val, grad) ->
- pct = (val - @minValue) / (@maxValue - @minValue)
- return @getColorForPercentage(pct, grad);
- render: () ->
- # Draw using canvas
- w = @canvas.width / 2
- h = @canvas.height * (1 - @paddingBottom)
- displayedAngle = @getAngle(@displayedValue)
- if @textField
- @textField.render(@)
- @ctx.lineCap = "butt"
- if @options.customFillStyle != undefined
- fillStyle = @options.customFillStyle(@)
- else if @percentColors != null
- fillStyle = @getColorForValue(@displayedValue, true)
- else if @options.colorStop != undefined
- if @options.gradientType == 0
- fillStyle = this.ctx.createRadialGradient(w, h, 9, w, h, 70);
- else
- fillStyle = this.ctx.createLinearGradient(0, 0, w, 0);
- fillStyle.addColorStop(0, @options.colorStart)
- fillStyle.addColorStop(1, @options.colorStop)
- else
- fillStyle = @options.colorStart
- @ctx.strokeStyle = fillStyle
- @ctx.beginPath()
- @ctx.arc(w, h, @radius, (1 + @options.angle) * Math.PI, displayedAngle, false)
- @ctx.lineWidth = @lineWidth
- @ctx.stroke()
- @ctx.strokeStyle = @options.strokeColor
- @ctx.beginPath()
- @ctx.arc(w, h, @radius, displayedAngle, (2 - @options.angle) * Math.PI, false)
- @ctx.stroke()
- for gauge in @gp
- gauge.update(true)
- class BaseDonut extends BaseGauge
- lineWidth: 15
- displayedValue: 0
- value: 33
- maxValue: 80
- minValue: 0
- options:
- lineWidth: 0.10
- colorStart: "#6f6ea0"
- colorStop: "#c0c0db"
- strokeColor: "#eeeeee"
- shadowColor: "#d5d5d5"
- angle: 0.35
- constructor: (@canvas) ->
- super()
- if typeof G_vmlCanvasManager != 'undefined'
- @canvas = window.G_vmlCanvasManager.initElement(@canvas)
- @ctx = @canvas.getContext('2d')
- @setOptions()
- @render()
- getAngle: (value) ->
- return (1 - @options.angle) * Math.PI + ((value - @minValue) / (@maxValue - @minValue)) * ((2 + @options.angle) - (1 - @options.angle)) * Math.PI
- setOptions: (options=null) ->
- super(options)
- @lineWidth = @canvas.height * @options.lineWidth
- @radius = @canvas.height / 2 - @lineWidth/2
- return @
- set: (value) ->
- @value = value
- if @value > @maxValue
- @maxValue = @value * 1.1
- AnimationUpdater.run()
- render: () ->
- displayedAngle = @getAngle(@displayedValue)
- w = @canvas.width / 2
- h = @canvas.height / 2
- if @textField
- @textField.render(@)
- grdFill = @ctx.createRadialGradient(w, h, 39, w, h, 70)
- grdFill.addColorStop(0, @options.colorStart)
- grdFill.addColorStop(1, @options.colorStop)
- start = @radius - @lineWidth / 2
- stop = @radius + @lineWidth / 2
- @ctx.strokeStyle = @options.strokeColor
- @ctx.beginPath()
- @ctx.arc(w, h, @radius, (1 - @options.angle) * Math.PI, (2 + @options.angle) * Math.PI, false)
- @ctx.lineWidth = @lineWidth
- @ctx.lineCap = "round"
- @ctx.stroke()
- @ctx.strokeStyle = grdFill
- @ctx.beginPath()
- @ctx.arc(w, h, @radius, (1 - @options.angle) * Math.PI, displayedAngle, false)
- @ctx.stroke()
- class Donut extends BaseDonut
- strokeGradient: (w, h, start, stop) ->
- grd = @ctx.createRadialGradient(w, h, start, w, h, stop)
- grd.addColorStop(0, @options.shadowColor)
- grd.addColorStop(0.12, @options._orgStrokeColor)
- grd.addColorStop(0.88, @options._orgStrokeColor)
- grd.addColorStop(1, @options.shadowColor)
- return grd
- setOptions: (options=null) ->
- super(options)
- w = @canvas.width / 2
- h = @canvas.height / 2
- start = @radius - @lineWidth / 2
- stop = @radius + @lineWidth / 2
- @options._orgStrokeColor = @options.strokeColor
- @options.strokeColor = @strokeGradient(w, h, start, stop)
- return @
- window.AnimationUpdater =
- elements: []
- animId: null
- addAll: (list) ->
- for elem in list
- AnimationUpdater.elements.push(elem)
- add: (object) ->
- AnimationUpdater.elements.push(object)
- run: () ->
- animationFinished = true
- for elem in AnimationUpdater.elements
- if elem.update()
- animationFinished = false
- if not animationFinished
- AnimationUpdater.animId = requestAnimationFrame(AnimationUpdater.run)
- else
- cancelAnimationFrame(AnimationUpdater.animId)
- window.Gauge = Gauge
- window.Donut = Donut
- window.BaseDonut = BaseDonut
- window.TextRenderer = TextRenderer
|