function makeColorScaleFunc(specs, opts) {
    opts = opts || {};

    var domain = specs.domain;
    var range = specs.range;
    var N = range.length;
    var _range = new Array(N);

    for(var i = 0; i < N; i++) {
        var rgba = tinycolor(range[i]).toRgb();
        _range[i] = [rgba.r, rgba.g, rgba.b, rgba.a];
    }

    var _sclFunc = d3.scale.linear()
        .domain(domain)
        .range(_range)
        .clamp(true);

    var noNumericCheck = opts.noNumericCheck;
    var returnArray = opts.returnArray;
    var sclFunc;

    if(noNumericCheck && returnArray) {
        sclFunc = _sclFunc;
    } else if(noNumericCheck) {
        sclFunc = function(v) {
            return colorArray2rbga(_sclFunc(v));
        };
    } else if(returnArray) {
        sclFunc = function(v) {
            if(isNumeric(v)) return _sclFunc(v);
            else if(tinycolor(v).isValid()) return v;
            else return Color.defaultLine;
        };
    } else {
        sclFunc = function(v) {
            if(isNumeric(v)) return colorArray2rbga(_sclFunc(v));
            else if(tinycolor(v).isValid()) return v;
            else return Color.defaultLine;
        };
    }

    // colorbar draw looks into the d3 scale closure for domain and range
    sclFunc.domain = _sclFunc.domain;
    sclFunc.range = function() { return range; };

    return sclFunc;
}
General colorscale function generator.

@param {object} specs output of Colorscale.extractScale or precomputed domain, range.
 - domain {array}
 - range {array}

@param {object} opts
 - noNumericCheck {boolean} if true, scale func bypasses numeric checks
 - returnArray {boolean} if true, scale func return 4-item array instead of color strings

@return {function}