Permalink
Fetching contributors…
Cannot retrieve contributors at this time
273 lines (230 sloc) 7.34 KB
# minimal multi-purpose interface
# @requires utils color analyze
chroma.scale = (colors, positions) ->
# constructor
_mode = 'rgb'
_nacol = chroma '#ccc'
_spread = 0
_fixed = false
_domain = [0, 1]
_pos = []
_padding = [0,0]
_classes = false
_colors = []
_out = false
_min = 0
_max = 1
_correctLightness = false
_colorCache = {}
_useCache = true
_gamma = 1
# private methods
setColors = (colors) ->
if not colors?
colors = ['#fff', '#000']
if colors? and type(colors) == 'string' and chroma.brewer?
colors = chroma.brewer[colors] || chroma.brewer[colors.toLowerCase()] || colors
if type(colors) == 'array'
# make a copy of the colors
colors = colors.slice(0)
# convert to chroma classes
for c in [0..colors.length-1]
col = colors[c]
colors[c] = chroma(col) if type(col) == "string"
# auto-fill color position
_pos.length = 0
for c in [0..colors.length-1]
_pos.push c/(colors.length-1)
resetCache()
_colors = colors
getClass = (value) ->
if _classes?
n = _classes.length-1
i = 0
while i < n and value >= _classes[i]
i++
return i-1
return 0
tmap = (t) -> t
classifyValue = (value) ->
val = value
if _classes.length > 2
n = _classes.length-1
i = getClass(value)
minc = _classes[0] + (_classes[1]-_classes[0]) * (0 + _spread * 0.5) # center of 1st class
maxc = _classes[n-1] + (_classes[n]-_classes[n-1]) * (1 - _spread * 0.5) # center of last class
val = _min + ((_classes[i] + (_classes[i+1] - _classes[i]) * 0.5 - minc) / (maxc-minc)) * (_max - _min)
val
getColor = (val, bypassMap=false) ->
if isNaN(val) then return _nacol
if not bypassMap
if _classes and _classes.length > 2
# find the class
c = getClass val
t = c / (_classes.length-2)
else if _max != _min
# just interpolate between min/max
t = (val - _min) / (_max - _min)
else
t = 1
else
t = val
if not bypassMap
t = tmap t # lightness correction
t = pow t, _gamma if _gamma != 1
t = _padding[0] + (t * (1 - _padding[0] - _padding[1]))
t = Math.min(1, Math.max(0, t))
k = Math.floor(t * 10000)
if _useCache && _colorCache[k]
col = _colorCache[k]
else
if type(_colors) == 'array'
for i in [0.._pos.length-1]
p = _pos[i]
if t <= p
col = _colors[i]
break
if t >= p and i == _pos.length-1
col = _colors[i]
break
if t > p and t < _pos[i+1]
t = (t-p)/(_pos[i+1]-p)
col = chroma.interpolate _colors[i], _colors[i+1], t, _mode
break
else if type(_colors) == 'function'
col = _colors t
_colorCache[k] = col if _useCache
col
resetCache = () ->
_colorCache = {}
setColors colors
# public interface
f = (v) ->
c = chroma getColor v
if _out and c[_out] then c[_out]() else c
f.classes = (classes) ->
if classes?
if type(classes) == 'array'
_classes = classes
_domain = [classes[0], classes[classes.length-1]]
else
d = chroma.analyze _domain
if classes == 0
_classes = [d.min, d.max]
else
_classes = chroma.limits d, 'e', classes
return f
_classes
f.domain = (domain) ->
if not arguments.length
return _domain
_min = domain[0]
_max = domain[domain.length-1]
_pos = []
k = _colors.length
if domain.length == k and _min != _max
# update positions
for d in domain
_pos.push (d-_min) / (_max-_min)
else
for c in [0..k-1]
_pos.push c/(k-1)
_domain = [_min, _max]
f
f.mode = (_m) ->
if not arguments.length
return _mode
_mode = _m
resetCache()
f
f.range = (colors, _pos) ->
setColors colors, _pos
f
f.out = (_o) ->
_out = _o
f
f.spread = (val) ->
if not arguments.length
return _spread
_spread = val
f
f.correctLightness = (v=true) ->
_correctLightness = v
resetCache()
if _correctLightness
tmap = (t) ->
L0 = getColor(0, true).lab()[0]
L1 = getColor(1, true).lab()[0]
pol = L0 > L1
L_actual = getColor(t, true).lab()[0]
L_ideal = L0 + (L1 - L0) * t
L_diff = L_actual - L_ideal
t0 = 0
t1 = 1
max_iter = 20
while Math.abs(L_diff) > 1e-2 and max_iter-- > 0
do () ->
L_diff *= -1 if pol
if L_diff < 0
t0 = t
t += (t1 - t) * 0.5
else
t1 = t
t += (t0 - t) * 0.5
L_actual = getColor(t, true).lab()[0]
L_diff = L_actual - L_ideal
t
else
tmap = (t) -> t
f
f.padding = (p) ->
if p?
if type(p) == 'number'
p = [p,p]
_padding = p
f
else
_padding
f.colors = (numColors, out) ->
# If no arguments are given, return the original colors that were provided
out = 'hex' if arguments.length < 2
result = []
if arguments.length == 0
result = _colors.slice 0
else if numColors == 1
result = [f(0.5)]
else if numColors > 1
dm = _domain[0]
dd = _domain[1] - dm
result = [0...numColors].map (i) -> f( dm + i/(numColors-1) * dd )
else # returns all colors based on the defined classes
colors = []
samples = []
if _classes and _classes.length > 2
for i in [1..._classes.length]
samples.push (_classes[i-1]+_classes[i])*0.5
else
samples = _domain
result = samples.map (v) -> f(v)
if chroma[out]
result = result.map (c) -> c[out]()
result
f.cache = (c) ->
if c?
_useCache = c
f
else
_useCache
f.gamma = (g) ->
if g?
_gamma = g
f
else
_gamma
f
# some pre-defined color scales:
chroma.scales ?= {}
chroma.scales.cool = ->
chroma.scale [chroma.hsl(180,1,.9), chroma.hsl(250,.7,.4)]
chroma.scales.hot = ->
chroma.scale(['#000','#f00','#ff0','#fff'], [0,.25,.75,1]).mode('rgb')