• colormaps.coffee

  • ¶

    Color Maps

  • ¶

    A colormap is an array of colors. A ColorMapProto Array prototype is provided to simplify access to the colormap’s colors.

    Maps are extremely useful:

    • Performance: Maps are created once, reducing the calls to primitives whenever a color is changed.
    • Space Effeciency: They vastly reduce the number of colors used.
    • Data: Their index provides a MatLab/NumPy/NetLogo “color as data” feature. Ex: “Heat” may be mapped to a gradient from green to red.
    ColorMaps  = {
  • ¶

    Color Array Utilities

    Several utilities for creating color arrays

  • ¶

    Gradients

  • ¶

    Ask the browser to use the canvas gradient feature to create nColors given the gradient color stops and locs.

    Stops are css strings or rgba arrays. Locs are floats from 0-1

    This is a powerful browser feature, can be used to create all the MatLab colormaps. See these gradient sites: Mozilla Gradient Doc, Colorzilla Gradient Editor, GitHub ColorMap Project

      gradientImageData: (nColors, stops, locs) ->
  • ¶

    Convert the color stops to css strings

        stops = (Color.convertColor c, "css" for c in stops)
  • ¶

    default locations for colors is equally spaced

        locs = u.aRamp 0, 1, stops.length if not locs?
  • ¶

    create a nColors x 1 canvas context

        ctx = u.createCtx nColors, 1
  • ¶

    create a new gradient and fill it with the color stops

        grad = ctx.createLinearGradient 0, 0, nColors, 0
        grad.addColorStop locs[i], stops[i] for i in [0...stops.length]
  • ¶

    draw the gradient returning the image data TypedArray

        ctx.fillStyle = grad
        ctx.fillRect 0, 0, nColors, 1
        u.ctxToImageData(ctx).data
  • ¶

    Array Conversion Utilities

  • ¶

    Convert Uint8Array into Array of 4 element Uint8 subarrays, 4 element JS Arrays, or colors. Useful for converting ImageData objects like gradients to color arrays.

      uint8ArrayToUint8s: (a) ->
        ( a.subarray(i,i+4) for i in [0...a.length] by 4 )
      uint8ArrayToRgbas: (a) ->
        ( [ a[i], a[i+1], a[i+2], a[i+3] ] for i in [0...a.length] by 4 )
      uint8ArrayToColors: (array, type) ->
        return new Uint32Array( array.buffer ) if type is "pixel"
        @arrayToColors(@uint8ArrayToUint8s(array), type)
  • ¶

    Convert array of colors or rgba arrays to array of colors of given type

      arrayToColors: (array, type) ->
        return array if Color.colorType(array[0]) is type
        array[i] = Color.convertColor(a, type) for a,i in array
        array
  • ¶

    Utility to permute 3 arrays.

    • If any arg is array, no change made.
    • If any arg is 1, replace with [max].
    • If any arg is n>1, replace with u.aIntRamp(0,max,n).

    Result is an array of arrays of len 3 permuting A1, A2, A3. Used by rgbColorMap and hslColorMap

      permuteColors: (A1, A2=A1, A3=A2, max=[255,255,255]) ->
        [A1, A2, A3] = for A, i in [A1, A2, A3] # multi-line comprehension
          if typeof A is "number"
            if A is 1 then [max[i]] else u.aIntRamp(0, max[i], A)
          else A
        @permuteArrays A1, A2, A3
  • ¶

    Permute simple arrays w/o conversions above.

      permuteArrays: (A1, A2=A1, A3=A2) ->
        array = []
        ((array.push [a1,a2,a3] for a1 in A1) for a2 in A2) for a3 in A3
        array
  • ¶

    ColorMaps

  • ¶

    Convert an array of colors to a colormap. Returns the original array, just for convenience.

      colorMap: (array, indexToo = false) ->
        array.__proto__ = @ColorMapProto
        array.init indexToo
  • ¶

    Use prototypal inheritance for converting array to colormap.

      ColorMapProto: {
        __proto__: Array.prototype
  • ¶

    Initialize array to be colormap. Create an index object for direct lookup of color in array if indexToo. If color type is “typed” add properties for map and index in map.

        init: (indexToo = false) ->
          @type = Color.colorType @[0]
          @index = {} if indexToo
          u.error "ColorMap type error" unless @type?
          if @type is "typed"
            for color,i in @ then color.ix = i; color.map = @
          @index[ @indexKey(color) ] = i for color,i in @ if @index
          @ # this is just the original array, returned for convenience.
  • ¶

    Given a color in the map, return the key it uses in the index object. The value will be the array index of the color.

        indexKey: (color) -> # make css strings lower case?
          if @type is "typed" then color.pixel else color
  • ¶

    Use the indexKey to test two map color’s equality.

        colorsEqual: (color1, color2) ->
          @indexKey(color1) is @indexKey(color2)
  • ¶

    Get a random index or color from this map given the input range, defaults to entire map.

        randomIndex: (start=0, stop=@length) -> u.randomInt2 start, stop
        randomColor: (start=0, stop=@length) -> @[ @randomIndex start, stop ]
  • ¶

    Use Array.sort, augmented by updating index if present and color.ix for typedColors

        sort: (compareFcn) ->
          Array.prototype.sort.call @, compareFcn
          @index[ @indexKey(color) ] = i for color,i in @ if @index
          color.ix = i for color,i in @ if @type is "typed"
          @
  • ¶

    Lookup color in map, returning index or undefined if not found

        lookup: (color) ->
          color = Color.convertColor color, @type # make sure color is our type
          return @index[ @indexKey(color) ] if @index
          for c,i in @ then return i if @colorsEqual(color, c)
          undefined
  • ¶

    Return the map color proportional to the number value between min, max. This is a linear interpolation based on the map indices.

        scaleColor: (number, min, max) ->
          if number < min then number = min
          if number > max then number = max
          scale = (@length-1)*((number-min)/(max-min))
          @[ Math.round scale ]
  • ¶

    Find the index/color closest to this r,g,b,a. Alpha only used for exact lookup optimization. Note: slow for large maps unless color cube or exact match.

        findClosestIndex: (r, g, b, a=255) -> # alpha not in rgbDistance function
  • ¶

    convert r to r,g,b,a if g not set

          [r, g, b, a] = Color.colorToArray r unless g?
  • ¶

    First directly find if rgb cube

          if @cube
            step = 255/(@cube-1)
            [rLoc, gLoc, bLoc] = (Math.round(c/step) for c in [r, g, b])
            return rLoc + gLoc*@cube + bLoc*@cube*@cube
  • ¶

    Then check if is exact match. Only use of alpha for opacity maps

          return ix if ix = @lookup [r,g,b,a]
  • ¶

    Finally use color distance to find closest color

          minDist = Infinity; ixMin = 0
          for color, i in @
            [r0, g0, b0] = Color.colorToArray color
            d = Color.rgbDistance r0, g0, b0, r, g, b
            if d < minDist then minDist = d; ixMin = i
          ixMin
        findClosestColor: (r, g, b, a=255) ->  @[ @findClosestIndex r, g, b, a ]
      }
  • ¶

    ColorMap Utilities

    Utilities for creating color arrays and associated maps. This is not exhaustive, you can follow these examples for your own use.

  • ¶

    Convert any array of rgb(a) or color values into colormap. Good for converting css names, pixels/image data

      basicColorMap: (array, type="typed", indexToo=false) ->
        array = @arrayToColors array, type
        @colorMap array, indexToo
  • ¶

    Create a gray map (gray: r=g=b) These are typically 256 entries but can be smaller by passing a size parameter.

      grayColorMap: (size=256, type="typed", indexToo=false) ->
        array = ( [i,i,i] for i in u.aIntRamp 0, 255, size )
        @basicColorMap array, type, indexToo
  • ¶

    Create a map with a random set of colors. randomColorMap: (nColors, type=”typed”, indexToo=false) -> array = (Color.randomRgb() for i in [0…nColors]) @basicColorMap array, type, indexToo

  • ¶

    Create a colormap by permuted rgb values.

    R, G, B can be either a number, (the number of steps beteen 0-255), or an array of values to use for the color.

    Ex: R = 3, corresponds to R = [0, 128, 255]

    The resulting map permutes the R, G, B values. Thus if R=G=B=4, the resulting map has 4*4*4=64 colors.

      rgbColorMap: (R, G=R, B=R, type="typed", indexToo=true) ->
        array = @permuteColors(R, G, B)
        array.cube = R if (typeof R is "number") and (R is G is B)
        @colorMap @arrayToColors(array, type), indexToo
      rgbColorCube: (cubeSide, type="typed", indexToo=false) ->
        @rgbColorMap cubeSide, cubeSide, cubeSide, type, indexToo
  • ¶

    Create an hsl map, inputs similar to above. Convert the HSL values to typedColors, default to bright hue ramp (L=50).

      hslColorMap: (H, S=1, L=1, type="typed", indexToo=false) ->
        hslArray = @permuteColors(H, S, L, [359,100,50])
        array = (Color.hslString a... for a in hslArray)
        @colorMap @arrayToColors(array, type), indexToo
  • ¶

    Use gradient to build an rgba array, then convert to colormap. This easily creates all the MatLab colormaps.

      gradientColorMap: (nColors, stops, locs, type="typed", indexToo=false) ->
        id = @gradientImageData(nColors, stops, locs)
        @colorMap @uint8ArrayToColors(id, type), indexToo
  • ¶

    The most popular MatLab gradient, “jet”:

      jetColors: [ [0,0,127], [0,0,255], [0,127,255], [0,255,255],
        [127,255,127], [255,255,0], [255,127,0], [255,0,0], [127,0,0] ]
  • ¶

    Ramp of a single color from black to color and to white if whiteToo rampStops: (color, whiteToo) -> if whiteToo then [“black”, color, “white”] else [“black”, color]

      rampColorMap: (color, width, whiteToo = false) ->
        stops = if whiteToo then ["black", color, "white"] else ["black", color]
        @gradientColorMap width, stops # @rampStops(color, whiteToo)
  • ¶

    NetLogo maps are sets of color ramps for the basic colors, but with slightly different r,g,b values designed for good color ballance. They typically go from black to near white shades. We provide an alternative to ramp from black to full color as well.

      netLogoColorMap: (width = 10, whiteToo = true) ->
        @namedColorMap @netLogoColors, width, whiteToo
      cssBasicColorMap: (width=18, whiteToo=false) ->
        @namedColorMap @basicCssColors, width, whiteToo
    
      namedColorMap: (names, width, whiteToo) ->
        map = []; ramps = {}
        if u.isArray names # convert array of named colors to object of name: name
          o = {}; o[n] = n for n in names; names = o
        for name, color of names
          ramps[name] = @rampColorMap color, width, whiteToo
          map = map.concat ramps[name]
        map = @basicColorMap map
        map[k] = v for k, v of ramps
  • ¶

    Hack: will remove; helps migration of turtle/patch.scaleColor

        map[Color.convertColor k, "css"] = v for k, v of ramps
        map
  • ¶

    Our basic css named color strings

      basicCssColors: ["gray", "red", "orange", "brown", "yellow", "green", "lime",
        "turquoise", "cyan", "skyblue", "blue", "violet", "magenta", "pink"]
  • ¶

    Netlogo’s basic named colors. NOTE: sky -> skyblue; css legal named color.

      netLogoColors:
        gray: [141, 141, 141],      red: [215, 50, 41]
        orange: [241, 106, 21],     brown: [157, 110, 72]
        yellow: [237, 237, 49],     green: [89, 176, 60]
        lime: [44, 209, 59],        turquoise: [29, 159, 120]
        cyan: [84, 196, 196],       skyblue: [45, 141, 190]
        blue: [52, 93, 169],        violet: [124, 80, 164]
        magenta: [167, 27, 106],    pink: [224, 127, 150]
  • ¶

    Create opacity map of the given base r,g,b color, with nOpacity opacity values, default to all 256

      opacityColorMap: (rgb, nOpacities = 256, type="typed", indexToo=false) ->
        [r, g, b] = rgb
        array = ( [r, g, b, a] for a in u.aIntRamp 0, 255, nOpacities )
        @colorMap @arrayToColors(array, type), indexToo
  • ¶

    Create shared maps and utilities

      createSharedMaps: ->
  • ¶

    A map of all 256 gray colors

        @Gray    = @grayColorMap()
  • ¶

    A popular rgb map of the best 256 colors

        @Rgb256  = @rgbColorMap(8,8,4)
  • ¶

    A large 4K color map with good matches for most colors

        @Rgb     = @rgbColorCube(16)
  • ¶

    The HTML “web safe colors”

        @Safe    = @rgbColorCube(6)
  • ¶

    The popular MatLab jet gradient

        @Jet     = @gradientColorMap 256, @jetColors
  • ¶

    The netlogo map. NetLogo.yellow etc are individual netlogo hue ramps

        @NetLogo = @netLogoColorMap 10
  • ¶

    Like above but largest < 256 map (252). Does not diminish to white. NetLogoRamps.yellow is thus a ramp from black to brightest NetLogo yellow hue while CssRamps.yellow is black to Css “yellow”

        @NetLogoRamps = @netLogoColorMap 18, false
        @CssRamps     = @cssBasicColorMap 18, false
  • ¶

    Return random gray/color from a shared map. Colormap colors are immutable, so if you don’t want a shared gray/color but one who’s color you will change via color.setColor(..), then use clone:

    color = ColorMaps.randomColor().clone()
    color = ColorMaps.Jet.scaleColor(.5).clone()
    
      randomGray: (min, max) -> @Gray.randomColor(min, max)
      randomColor: () -> @Rgb256.randomColor()
  • ¶

    Legacy/Temporary: replace util/patch/turtle scaleColor

      scaleColor: (color, number, min=0, max=1) ->
        if color.scaleColor? # colormap ramp
          return color.scaleColor number, min, max
        else if ramp = @CssRamps[Color.convertColor color, "css"]
          return ramp.scaleColor number, min, max
        else
          return @Rgb.findClosestColor Color.rgbLerp(color, number, min, max)
    }
    ColorMaps.createSharedMaps()