jui.redefine("util.color", [ "util.base", "util.math" ], function(_, math) { function generateHash(name) { // Return a vector (0.0->1.0) that is a hash of the input string. // The hash is computed to favor early characters over later ones, so // that strings with similar starts have similar vectors. Only the first // 6 characters are considered. var hash = 0, weight = 1, max_hash = 0, mod = 10, max_char = 6; if (name) { for (var i = 0; i < name.length; i++) { if (i > max_char) { break; } hash += weight * (name.charCodeAt(i) % mod); max_hash += weight * (mod - 1); weight *= 0.70; } if (max_hash > 0) { hash = hash / max_hash; } } return hash; } /** * @class util.color * * color utility * * @singleton */ var self = { regex : /(linear|radial)\((.*)\)(.*)/i, /** * @method format * * convert color to format string * * // hex * color.format({ r : 255, g : 255, b : 255 }, 'hex') // #FFFFFF * * // rgb * color.format({ r : 255, g : 255, b : 255 }, 'rgb') // rgba(255, 255, 255, 0.5); * * // rgba * color.format({ r : 255, g : 255, b : 255, a : 0.5 }, 'rgb') // rgba(255, 255, 255, 0.5); * * @param {Object} obj obj has r, g, b and a attributes * @param {"hex"/"rgb"} type format string type * @returns {*} */ format : function(obj, type) { if (type == 'hex') { var r = obj.r.toString(16); if (obj.r < 16) r = "0" + r; var g = obj.g.toString(16); if (obj.g < 16) g = "0" + g; var b = obj.b.toString(16); if (obj.b < 16) b = "0" + b; return "#" + [r,g,b].join("").toUpperCase(); } else if (type == 'rgb') { if (typeof obj.a == 'undefined') { return "rgb(" + [obj.r, obj.g, obj.b].join(",") + ")"; } else { return "rgba(" + [obj.r, obj.g, obj.b, obj.a].join(",") + ")"; } } return obj; }, /** * @method scale * * get color scale * * var c = color.scale().domain('#FF0000', '#00FF00'); * * // get middle color * c(0.5) == #808000 * * // get middle color list * c.ticks(20); // return array , [startColor, ......, endColor ] * * @returns {func} scale function */ scale : function() { var startColor, endColor; function func(t, type) { var obj = { r : parseInt(startColor.r + (endColor.r - startColor.r) * t, 10) , g : parseInt(startColor.g + (endColor.g - startColor.g) * t, 10), b : parseInt(startColor.b + (endColor.b - startColor.b) * t, 10) }; return self.format(obj, type); } func.domain = function(start, end) { startColor = self.rgb(start); endColor = self.rgb(end); return func; } func.ticks = function (n) { var unit = (1/n); var start = 0; var colors = []; while(start <= 1) { var c = func(start, 'hex'); colors.push(c); start = math.plus(start, unit); } return colors; } return func; }, /** * @method map * * create color map * * var colorList = color.map(['#352a87', '#0f5cdd', '#00b5a6', '#ffc337', '#fdff00'], count) * * @param {Array} color_list * @param {Number} count a divide number * @returns {Array} converted color list */ map : function (color_list, count) { var colors = []; count = count || 5; var scale = self.scale(); for(var i = 0, len = color_list.length-1; i < len; i++) { if (i == 0) { colors = scale.domain(color_list[i], color_list[i + 1]).ticks(count); } else { var colors2 = scale.domain(color_list[i], color_list[i + 1]).ticks(count); colors2.shift(); colors = colors.concat(colors2); } } return colors; }, /** * @method rgb * * parse string to rgb color * * color.rgb("#FF0000") === { r : 255, g : 0, b : 0 } * * color.rgb("rgb(255, 0, 0)") == { r : 255, g : 0, b : } * * @param {String} str color string * @returns {Object} rgb object */ rgb : function (str) { if (typeof str == 'string') { if (str.indexOf("rgb(") > -1) { var arr = str.replace("rgb(", "").replace(")","").split(","); for(var i = 0, len = arr.length; i < len; i++) { arr[i] = parseInt(_.trim(arr[i]), 10); } return { r : arr[0], g : arr[1], b : arr[2], a : 1 }; } else if (str.indexOf("rgba(") > -1) { var arr = str.replace("rgba(", "").replace(")","").split(","); for(var i = 0, len = arr.length; i < len; i++) { if (len - 1 == i) { arr[i] = parseFloat(_.trim(arr[i])); } else { arr[i] = parseInt(_.trim(arr[i]), 10); } } return { r : arr[0], g : arr[1], b : arr[2], a : arr[3]}; } else if (str.indexOf("#") == 0) { str = str.replace("#", ""); var arr = []; if (str.length == 3) { for(var i = 0, len = str.length; i < len; i++) { var char = str.substr(i, 1); arr.push(parseInt(char+char, 16)); } } else { for(var i = 0, len = str.length; i < len; i+=2) { arr.push(parseInt(str.substr(i, 2), 16)); } } return { r : arr[0], g : arr[1], b : arr[2], a : 1 }; } } return str; }, /** * @method HSVtoRGB * * convert hsv to rgb * * color.HSVtoRGB(0,0,1) === #FFFFF === { r : 255, g : 0, b : 0 } * * @param {Number} H hue color number (min : 0, max : 360) * @param {Number} S Saturation number (min : 0, max : 1) * @param {Number} V Value number (min : 0, max : 1 ) * @returns {Object} */ HSVtoRGB : function (H, S, V) { if (H == 360) { H = 0; } var C = S * V; var X = C * (1 - Math.abs((H/60) % 2 -1) ); var m = V - C; var temp = []; if (0 <= H && H < 60) { temp = [C, X, 0]; } else if (60 <= H && H < 120) { temp = [X, C, 0]; } else if (120 <= H && H < 180) { temp = [0, C, X]; } else if (180 <= H && H < 240) { temp = [0, X, C]; } else if (240 <= H && H < 300) { temp = [X, 0, C]; } else if (300 <= H && H < 360) { temp = [C, 0, X]; } return { r : Math.ceil((temp[0] + m) * 255), g : Math.ceil((temp[1] + m) * 255), b : Math.ceil((temp[2] + m) * 255) }; }, /** * @method RGBtoHSV * * convert rgb to hsv * * color.RGBtoHSV(0, 0, 255) === { h : 240, s : 1, v : 1 } === '#FFFF00' * * @param {Number} R red color value * @param {Number} G green color value * @param {Number} B blue color value * @return {Object} hsv color code */ RGBtoHSV : function (R, G, B) { var R1 = R / 255; var G1 = G / 255; var B1 = B / 255; var MaxC = Math.max(R1, G1, B1); var MinC = Math.min(R1, G1, B1); var DeltaC = MaxC - MinC; var H = 0; if (DeltaC == 0) { H = 0; } else if (MaxC == R1) { H = 60 * (( (G1 - B1) / DeltaC) % 6); } else if (MaxC == G1) { H = 60 * (( (B1 - R1) / DeltaC) + 2); } else if (MaxC == B1) { H = 60 * (( (R1 - G1) / DeltaC) + 4); } if (H < 0) { H = 360 + H; } var S = 0; if (MaxC == 0) S = 0; else S = DeltaC / MaxC; var V = MaxC; return { h : H, s : S, v : V }; }, trim : function (str) { return (str || "").replace(/^\s+|\s+$/g, ''); }, /** * @method lighten * * rgb 컬러 밝은 농도로 변환 * * @param {String} color RGB color code * @param {Number} rate 밝은 농도 * @return {String} */ lighten : function(color, rate) { color = color.replace(/[^0-9a-f]/gi, ''); rate = rate || 0; var rgb = [], c, i; for (i = 0; i < 6; i += 2) { c = parseInt(color.substr(i,2), 16); c = Math.round(Math.min(Math.max(0, c + (c * rate)), 255)).toString(16); rgb.push(("00"+c).substr(c.length)); } return "#" + rgb.join(""); }, /** * @method darken * * rgb 컬러 어두운 농도로 변환 * * @param {String} color RGB color code * @param {Number} rate 어두운 농도 * @return {String} */ darken : function(color, rate) { return this.lighten(color, -rate) }, /** * @method parse * * gradient color string parsing * * @param {String} color * @returns {*} */ parse : function(color) { return this.parseGradient(color); }, /** * @method parseGrident * * gradient parser * * linear(left) #fff,#000 * linear(right) #fff,50 yellow,black * radial(50%,50%,50%,50,50) * * @param {String} color */ parseGradient : function(color) { var matches = color.match(this.regex); if (!matches) return color; var type = this.trim(matches[1]); var attr = this.parseAttr(type, this.trim(matches[2])); var stops = this.parseStop(this.trim(matches[3])); var obj = { type : type + "Gradient", attr : attr, children : stops }; return obj; }, parseStop : function(stop) { var stop_list = stop.split(","); var stops = []; for(var i = 0, len = stop_list.length; i < len; i++) { var stop = stop_list[i]; var arr = stop.split(" "); if (arr.length == 0) continue; if (arr.length == 1) { stops.push({ type : "stop", attr : {"stop-color" : arr[0] } }) } else if (arr.length == 2) { stops.push({ type : "stop", attr : {"offset" : arr[0], "stop-color" : arr[1] } }) } else if (arr.length == 3) { stops.push({ type : "stop", attr : {"offset" : arr[0], "stop-color" : arr[1], "stop-opacity" : arr[2] } }) } } var start = -1; var end = -1; for(var i = 0, len = stops.length; i < len; i++) { var stop = stops[i]; if (i == 0) { if (!stop.offset) stop.offset = 0; } else if (i == len - 1) { if (!stop.offset) stop.offset = 1; } if (start == -1 && typeof stop.offset == 'undefined') { start = i; } else if (end == -1 && typeof stop.offset == 'undefined') { end = i; var count = end - start; var endOffset = stops[end].offset.indexOf("%") > -1 ? parseFloat(stops[end].offset)/100 : stops[end].offset; var startOffset = stops[start].offset.indexOf("%") > -1 ? parseFloat(stops[start].offset)/100 : stops[start].offset; var dist = endOffset - startOffset var value = dist/ count; var offset = startOffset + value; for(var index = start + 1; index < end; index++) { stops[index].offset = offset; offset += value; } start = end; end = -1; } } return stops; }, parseAttr : function(type, str) { if (type == 'linear') { switch(str) { case "": case "left": return { x1 : 0, y1 : 0, x2 : 1, y2 : 0, direction : str || "left" }; case "right": return { x1 : 1, y1 : 0, x2 : 0, y2 : 0, direction : str }; case "top": return { x1 : 0, y1 : 0, x2 : 0, y2 : 1, direction : str }; case "bottom": return { x1 : 0, y1 : 1, x2 : 0, y2 : 0, direction : str }; case "top left": return { x1 : 0, y1 : 0, x2 : 1, y2 : 1, direction : str }; case "top right": return { x1 : 1, y1 : 0, x2 : 0, y2 : 1, direction : str }; case "bottom left": return { x1 : 0, y1 : 1, x2 : 1, y2 : 0, direction : str }; case "bottom right": return { x1 : 1, y1 : 1, x2 : 0, y2 : 0, direction : str }; default : var arr = str.split(","); for(var i = 0, len = arr.length; i < len; i++) { if (arr[i].indexOf("%") == -1) arr[i] = parseFloat(arr[i]); } return { x1 : arr[0], y1 : arr[1],x2 : arr[2], y2 : arr[3] }; } } else { var arr = str.split(","); for(var i = 0, len = arr.length; i < len; i++) { if (arr[i].indexOf("%") == -1) arr[i] = parseFloat(arr[i]); } return { cx : arr[0], cy : arr[1],r : arr[2], fx : arr[3], fy : arr[4] }; } }, colorHash : function(name, callback) { // Return an rgb() color string that is a hash of the provided name, // and with a warm palette. var vector = 0; if (name) { name = name.replace(/.*`/, ""); // drop module name if present name = name.replace(/\(.*/, ""); // drop extra info vector = generateHash(name); } if(typeof(callback) == "function") { return callback(vector); } return { r: 200 + Math.round(55 * vector), g: 0 + Math.round(230 * (1 - vector)), b: 0 + Math.round(55 * (1 - vector)) }; } }; self.map.parula = function (count) { return self.map(['#352a87', '#0f5cdd', '#00b5a6', '#ffc337', '#fdff00'], count); } self.map.jet = function (count) { return self.map(['#00008f', '#0020ff', '#00ffff', '#51ff77', '#fdff00', '#ff0000', '#800000'], count); } self.map.hsv = function (count) { return self.map(['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#ff00ff', '#ff0000'], count); } self.map.hot = function (count) { return self.map(['#0b0000', '#ff0000', '#ffff00', '#ffffff'], count); } self.map.pink = function (count) { return self.map(['#1e0000', '#bd7b7b', '#e7e5b2', '#ffffff'], count); } self.map.bone = function (count) { return self.map(['#000000', '#4a4a68', '#a6c6c6', '#ffffff'], count); } self.map.copper = function (count) { return self.map(['#000000', '#3d2618', '#9d623e', '#ffa167', '#ffc77f'], count); } return self; }, null, true);