Color Picker & Color Wheel
[raphael] / plugins / colorwheel.js
1 /*!
2  * Color Wheel 0.1.0 - Raphael plugin
3  *
4  * Copyright (c) 2010 John Weir (http://famedriver.com) & Dmitry Baranovskiy (http://raphaeljs.com)
5  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6  */
7 (function (Raphael) {
8     Raphael.colorpicker = function (x, y, size, initcolor, element) {
9         return new ColorPicker(x, y, size, initcolor, element);
10     };
11     var pi = Math.PI;
12     function angle(x, y) {
13         return (x < 0) * 180 + Math.atan(-y / -x) * 180 / pi;
14     }
15     var doc = document, win = window,
16         addEvent = (function () {
17         if (doc.addEventListener) {
18             return function (obj, type, fn, element) {
19                 var f = function (e) {
20                     return fn.call(element, e);
21                 };
22                 obj.addEventListener(type, f, false);
23                 return function () {
24                     obj.removeEventListener(type, f, false);
25                     return true;
26                 };
27             };
28         } else if (doc.attachEvent) {
29             return function (obj, type, fn, element) {
30                 var f = function (e) {
31                     return fn.call(element, e || win.event);
32                 };
33                 obj.attachEvent("on" + type, f);
34                 var detacher = function () {
35                     obj.detachEvent("on" + type, f);
36                     return true;
37                 };
38                 return detacher;
39             };
40         }
41     })(),
42         ColorPicker = function (x, y, size, initcolor, element) {
43             size = size || 200;
44             var w3 = 3 * size / 200,
45                 w1 = size / 200,
46                 fi = 1.6180339887,
47                 segments = pi * size / 4,
48                 size20 = size / 20,
49                 size2 = size / 2,
50                 padding = 2 * size / 200,
51                 height = size + size20 * 2 + padding * 3,
52                 t = this,
53                 H = 1, S = 1, B = 1, s = size - (size20 * 4),
54                 r = element ? Raphael(element, size, height) : Raphael(x, y, size, height),
55                 xy = s / 6 + size20 * 2 + padding,
56                 wh = s * 2 / 3 - padding * 2;
57             w1 < 1 && (w1 = 1);
58             w3 < 1 && (w3 = 1);
59
60
61             // ring drawing
62             var a = pi / 2 - pi * 2 / segments * 1.3,
63                 R = size2 - padding,
64                 R2 = size2 - padding - size20 * 2,
65                 path = ["M", size2, padding, "A", R, R, 0, 0, 1, R * Math.cos(a) + R + padding, R - R * Math.sin(a) + padding, "L", size2, size2, "z"].join();
66             for (var i = 0; i < segments; i++) {
67                 r.path(path).attr({
68                     stroke: "none",
69                     fill: "hsb(" + (segments - i) * (255 / segments) + ", 255, 200)",
70                     rotation: [90 + (360 / segments) * i, size2, size2]
71                 });
72             }
73             r.circle(size2, size2, R).attr({
74                 fill: "r#fff-#fff",
75                 "fill-opacity": 0,
76                 "stroke-width": 3,
77                 stroke: "#fff"
78             });
79
80             t.cursor = r.set();
81             t.cursor.push(r.circle(size2, size2, size20 / 2).attr({
82                 stroke: "#000",
83                 opacity: .5,
84                 "stroke-width": w3
85             }));
86             t.cursor.push(t.cursor[0].clone().attr({
87                 stroke: "#fff",
88                 opacity: 1,
89                 "stroke-width": w1
90             }));
91             t.disc = r.circle(size2, size2, R).attr({
92                 fill: "#000",
93                 "fill-opacity": 0,
94                 stroke: "none",
95                 cursor: "crosshair"
96             });
97             var style = t.disc.node.style;
98             style.unselectable = "on";
99             style.MozUserSelect =  "none";
100             style.WebkitUserSelect= "none";
101
102             // brightness drawing
103             var h = size20 * 2 + 2;
104             t.brect = r.rect(padding + h / fi / 2, size + padding * 2, size - padding * 2 - h / fi, h - padding * 2).attr({
105                 stroke: "#fff",
106                 fill: "180-#fff-#000"
107             });
108             t.cursorb = r.set();
109             t.cursorb.push(r.rect(size - padding - h / fi, size + padding, ~~(h / fi), h, 3 * size / 200).attr({
110                 stroke: "#000",
111                 opacity: .5,
112                 "stroke-width": w3
113             }));
114             t.cursorb.push(t.cursorb[0].clone().attr({
115                 stroke: "#fff",
116                 opacity: 1,
117                 "stroke-width": w1
118             }));
119             t.btop = t.brect.clone().attr({
120                 stroke: "#000",
121                 fill: "#000",
122                 opacity: 0
123             });
124             style = t.btop.node.style;
125             style.unselectable = "on";
126             style.MozUserSelect =  "none";
127             style.WebkitUserSelect= "none";
128         
129             t.minx = padding;
130             t.maxx = size - h / fi - padding;
131
132             t.H = t.S = t.B = 1;
133             t.padding = padding;
134             t.raphael = r;
135             t.size2 = size2;
136             t.size20 = size20;
137             t.x = x;
138             t.y = y;
139
140             // events
141             t.hson = addEvent(t.disc.node, "mousedown", function (e) {
142                 var scrollY = doc.documentElement.scrollTop || doc.body.scrollTop,
143                     scrollX = doc.documentElement.scrollLeft || doc.body.scrollLeft;
144                 this.hsOnTheMove = true;
145                 this.setHS(e.clientX + scrollX - this.x, e.clientY + scrollY - this.y);
146                 this.docmove = addEvent(doc, "mousemove", this.docOnMove, this);
147                 this.docup = addEvent(doc, "mouseup", this.docOnUp, this);
148             }, t);
149             t.bon = addEvent(t.btop.node, "mousedown", function (e) {
150                 var scrollX = doc.documentElement.scrollLeft || doc.body.scrollLeft;
151                 this.bOnTheMove = true;
152                 this.setB(e.clientX + scrollX - this.x);
153                 this.docmove = addEvent(doc, "mousemove", this.docOnMove, this);
154                 this.docup = addEvent(doc, "mouseup", this.docOnUp, this);
155             }, t);
156             t.winunload = addEvent(win, "unload", function () {
157                 this.hson();
158                 this.bon();
159                 this.docmove && this.docmove();
160                 this.docup && this.docup();
161                 this.winunload();
162             }, t);
163
164             t.color(initcolor || "#fff");
165             this.onchanged && this.onchanged(this.color());
166         };
167     ColorPicker.prototype.setB = function (x) {
168         x < this.minx && (x = this.minx);
169         x > this.maxx && (x = this.maxx);
170         this.cursorb.attr({x: x});
171         this.B = (x - this.minx) / (this.maxx - this.minx);
172         this.onchange && this.onchange(this.color());
173     };
174     ColorPicker.prototype.setHS = function (x, y) {
175         var X = x - this.size2,
176             Y = y - this.size2,
177             R = this.size2 - this.size20 / 2 - this.padding,
178             d = angle(X, Y),
179             rd = d * pi / 180;
180         isNaN(d) && (d = 0);
181         if (X * X + Y * Y > R * R) {
182             x = R * Math.cos(rd) + this.size2;
183             y = R * Math.sin(rd) + this.size2;
184         }
185         this.cursor.attr({cx: x, cy: y});
186         this.H = (1 - d / 360) % 1;
187         this.S = Math.min((X * X + Y * Y) / R / R, 1);
188         this.brect.attr({fill: "180-hsb(" + [this.H, this.S] + ",1)-#000"});
189         this.onchange && this.onchange(this.color());
190     };
191     ColorPicker.prototype.docOnMove = function (e) {
192         var scrollY = doc.documentElement.scrollTop || doc.body.scrollTop,
193             scrollX = doc.documentElement.scrollLeft || doc.body.scrollLeft;
194         if (this.hsOnTheMove) {
195             this.setHS(e.clientX + scrollX - this.x, e.clientY + scrollY - this.y);
196         }
197         if (this.bOnTheMove) {
198             this.setB(e.clientX + scrollX - this.x);
199         }
200         e.preventDefault && e.preventDefault();
201         e.returnValue = false;
202         return false;
203     };
204     ColorPicker.prototype.docOnUp = function (e) {
205         this.hsOnTheMove = this.bOnTheMove = false;
206         this.docmove();
207         delete this.docmove;
208         this.docup();
209         delete this.docup;
210         this.onchanged && this.onchanged(this.color());
211     };
212     ColorPicker.prototype.remove = function () {
213         this.raphael.remove();
214         this.color = function () {
215             return false;
216         };
217     };
218     ColorPicker.prototype.color = function (color) {
219         if (color) {
220             color = Raphael.getRGB(color);
221             var hex = color.hex;
222             color = Raphael.rgb2hsb(color.r, color.g, color.b);
223             d = color.h * 360;
224             this.H = color.h;
225             this.S = color.s;
226             this.B = color.b;
227
228             this.cursorb.attr({x: this.B * (this.maxx - this.minx) + this.minx});
229             this.brect.attr({fill: "180-hsb(" + [this.H, this.S] + ",1)-#000"});
230
231             var d = (1 - this.H) * 360,
232                 rd = d * pi / 180,
233                 R = (this.size2 - this.size20 / 2 - this.padding) * this.S,
234                 x = Math.cos(rd) * R + this.size2,
235                 y = Math.sin(rd) * R + this.size2;
236             this.cursor.attr({cx: x, cy: y});
237             return this;
238         } else {
239             return Raphael.hsb2rgb(this.H, this.S, this.B).hex;
240         }
241     };
242 })(window.Raphael);