mirror of https://github.com/ColorlibHQ/gentelella
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
525 lines
14 KiB
525 lines
14 KiB
# 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
|