5a2891c76b8d85c9dfbf7cafff8ffd037fe6e4fe
[dygraphs.git] / experimental / palette / palette.js
1 // Copyright (c) 2011 Google, Inc.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to deal
5 // in the Software without restriction, including without limitation the rights
6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 // copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 // THE SOFTWARE.
20
21 /**
22 * @fileoverview Dygraphs options palette.
23 *
24 * @author konigsberg@google.com (Robert Konigsberg)
25 */
26 "use strict";
27
28 function Palette() {
29 this.model = {};
30 this.onchange = function() {};
31 this.filterBar = null;
32 }
33
34 Palette.createChild = function(type, parentElement) {
35 var element = document.createElement(type);
36 parentElement.appendChild(element);
37 return element;
38 };
39
40 function Tooltip(parent) {
41 if (!parent) {
42 parent = document.getElementsByTagName("body")[0];
43 }
44 this.elem = Palette.createChild("div", parent);
45 this.title = Palette.createChild("div", this.elem);
46 this.elem.className = "tooltip";
47 this.title.className = "title";
48 this.type = Palette.createChild("div", this.elem);
49 this.type.className = "type";
50 this.body = Palette.createChild("div", this.elem);
51 this.body.className = "body";
52 this.hide();
53 }
54
55 Tooltip.prototype.show = function(source, event, title, type, body) {
56 this.title.innerHTML = title;
57 this.body.innerHTML = body;
58 this.type.innerText = type; // innerText for arrays.
59
60 var getTopLeft = function(element) {
61 var x = element.offsetLeft;
62 var y = element.offsetTop;
63 element = element.offsetParent;
64
65 while(element != null) {
66 x = parseInt(x) + parseInt(element.offsetLeft);
67 y = parseInt(y) + parseInt(element.offsetTop);
68 element = element.offsetParent;
69 }
70 return [y, x];
71 }
72
73 this.elem.style.height = source.style.height;
74 this.elem.style.width = "280";
75 var topLeft = getTopLeft(source);
76 this.elem.style.top = parseInt(topLeft[0] + source.offsetHeight) + 'px';
77 this.elem.style.left = parseInt(topLeft[1] + 10) + 'px';
78 this.elem.style.visibility = "visible";
79 }
80
81 Tooltip.prototype.hide = function() {
82 this.elem.style.visibility = "hidden";
83 }
84
85 Palette.prototype.create = function(document, parentElement) {
86 var palette = this;
87
88 var table = Palette.createChild("div", parentElement);
89 table.className = "palette";
90 table.width="300px";
91
92 this.tooltip = new Tooltip();
93
94 var row = Palette.createChild("div", table);
95 row.style.visibility = "visible";
96 row.className = "header";
97
98 Palette.createChild("span", row).innerText = "Filter:";
99 this.filterBar = Palette.createChild("input", Palette.createChild("span", row));
100 this.filterBar.type = "search";
101 this.filterBar.onkeyup = function() {
102 palette.filter(palette.filterBar.value)
103 };
104 this.filterBar.onclick = this.filterBar.onkeyup;
105 var go = document.createElement("button");
106 Palette.createChild("span", row).appendChild(go);
107 go.innerText = "Redraw"
108 go.onclick = function() {
109 palette.onchange();
110 };
111
112 for (var opt in opts) {
113 try {
114 if (opts.hasOwnProperty(opt)) {
115 var type = opts[opt].type;
116 var isFunction = type.indexOf("function(") == 0;
117 var row = Palette.createChild("div", table);
118 row.onmouseover = function(source, title, type, body, e) {
119 return function(e) {
120 palette.tooltip.show(source, e, title, type, body);
121 };
122 } (row, opt, type, Dygraph.OPTIONS_REFERENCE[opt].description);
123 row.onmouseout = function() { palette.tooltip.hide(); };
124
125 var div = Palette.createChild("span", row);
126 div.innerText = opt;
127 div.className = "name";
128
129 var value = Palette.createChild("span", row);
130 value.className = "option";
131
132 if (isFunction) {
133 var input = Palette.createChild("button", value);
134 input.onclick = function(opt, palette) {
135 return function(event) {
136 var entry = palette.model[opt];
137 var inputValue = entry.functionString;
138 if (inputValue == null || inputValue.length == 0) {
139 inputValue = opts[opt].type + "{ }";
140 }
141 var value = prompt("enter function", inputValue);
142 if (value != null) {
143 if (value.length == 0) {
144 value = null;
145 }
146 if (value != inputValue) {
147 entry.functionString = value;
148 entry.input.innerText = value ? "defined" : "not defined";
149 palette.onchange();
150 }
151 }
152 }
153 }(opt, this);
154 } else {
155 var input = Palette.createChild("input", value);
156 input.onkeypress = function(event) {
157 var keycode = event.which;
158 if (keycode == 13 || keycode == 8) {
159 palette.onchange();
160 }
161 }
162
163 input.type="text";
164 }
165 this.model[opt] = { input: input, row: row };
166 }
167 } catch(err) {
168 throw "For option " + opt + ":" + err;
169 }
170 }
171 this.filter("");
172 }
173
174 // TODO: replace semicolon parsing with comma parsing, and supporting quotes.
175 Palette.parseStringArray = function(value) {
176 if (value == null || value.length == 0) {
177 return null;
178 }
179 return value.split(";");
180 }
181
182 Palette.parseBooleanArray = function(value) {
183 if (value == null || value.length == 0) {
184 return null;
185 }
186 return value.split(',').map(function(x) { return x.trim() == "true"; });
187 }
188
189 Palette.parseFloatArray = function(value) {
190 if (value == null || value.length == 0) {
191 return null;
192 }
193 return value.split(',').map(function(x) { return parseFloat(x); });
194 }
195
196 Palette.parseIntArray = function(value) {
197 if (value == null || value.length == 0) {
198 return null;
199 }
200 return value.split(',').map(function(x) { return parseInt(x); });
201 }
202
203 Palette.prototype.read = function() {
204 var results = {};
205 for (var opt in this.model) {
206 if (this.model.hasOwnProperty(opt)) {
207 var type = opts[opt].type;
208 var isFunction = type.indexOf("function(") == 0;
209 var input = this.model[opt].input;
210 var value = isFunction ? this.model[opt].functionString : input.value;
211 if (value && value.length != 0) {
212 if (type == "boolean") {
213 results[opt] = value == "true";
214 } else if (type == "int") {
215 results[opt] = parseInt(value);
216 } else if (type == "float") {
217 results[opt] = parseFloat(value);
218 } else if (type == "array<string>") {
219 results[opt] = Palette.parseStringArray(value);
220 } else if (type == "array<float>") {
221 results[opt] = Palette.parseFloatArray(value);
222 } else if (type == "array<boolean>") {
223 results[opt] = Palette.parseBooleanArray(value);
224 } else if (type == "array<Date>") {
225 results[opt] = Palette.parseIntArray(value);
226 } else if (isFunction) {
227 var localVariable = null;
228 eval("localVariable = " + value);
229 results[opt] = localVariable;
230 } else {
231 results[opt] = value;
232 }
233 }
234 }
235 }
236 return results;
237 }
238
239 /**
240 * Write to input elements.
241 */
242 Palette.prototype.write = function(hash) {
243 var results = {};
244 for (var opt in this.model) {
245 // && hash.hasOwnProperty(opt)
246 if (this.model.hasOwnProperty(opt)) {
247 var input = this.model[opt].input;
248 var type = opts[opt].type;
249 var value = hash[opt];
250 if (type == "array<string>") {
251 if (value) {
252 input.value = value.join("; ");
253 }
254 } else if (type.indexOf("array") == 0) {
255 if (value) {
256 input.value = value.join(", ");
257 }
258 } else if (type.indexOf("function(") == 0) {
259 input.innerText = value ? "defined" : "not defined";
260 this.model[opt].functionString = value ? value.toString() : null;
261 } else {
262 if (value) {
263 input.value = value;
264 }
265 }
266 }
267 }
268 }
269
270 Palette.prototype.filter = function(pattern) {
271 pattern = pattern.toLowerCase();
272 var even = true;
273 for (var opt in this.model) {
274 if (this.model.hasOwnProperty(opt)) {
275 var row = this.model[opt].row;
276 var matches = opt.toLowerCase().indexOf(pattern) >= 0;
277 row.style.display = matches ? "block" : "none";
278 if (matches) {
279 row.className = even ? "even" : "odd";
280 even = !even;
281 }
282 }
283 }
284 }