blob: 0161459df17bb2f4a1aa32ec9cc37cc2c4c28abf [file] [log] [blame]
Simon Hunt1a9eff92014-11-07 11:06:34 -08001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 Utility functions for D3 visualizations.
19
20 @author Simon Hunt
21 */
22
23(function (onos) {
24 'use strict';
25
Simon Hunt6e18fe32014-11-29 13:35:41 -080026 function isF(f) {
27 return $.isFunction(f) ? f : null;
28 }
29
30 function createDragBehavior(force, selectCb, atDragEnd, enabled) {
Simon Hunt1a9eff92014-11-07 11:06:34 -080031 var draggedThreshold = d3.scale.linear()
32 .domain([0, 0.1])
33 .range([5, 20])
34 .clamp(true),
Simon Hunt6e18fe32014-11-29 13:35:41 -080035 drag,
36 fSel = isF(selectCb),
37 fEnd = isF(atDragEnd),
38 fEnb = isF(enabled),
39 bad = [];
Simon Hunt1a9eff92014-11-07 11:06:34 -080040
Simon Hunt6e18fe32014-11-29 13:35:41 -080041 function naf(what) {
42 return 'd3util.createDragBehavior(): '+ what + ' is not a function';
Simon Hunt1a9eff92014-11-07 11:06:34 -080043 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080044
45 if (!fSel) {
46 bad.push(naf('selectCb'));
Simon Hunt1a9eff92014-11-07 11:06:34 -080047 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080048 if (!fEnd) {
49 bad.push(naf('atDragEnd'));
Simon Hunt01095ff2014-11-13 16:37:29 -080050 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080051 if (!fEnb) {
52 bad.push(naf('enabled'));
53 }
54
55 if (bad.length) {
56 alert(bad.join('\n'));
57 return null;
58 }
59
Simon Hunt1a9eff92014-11-07 11:06:34 -080060
61 function dragged(d) {
62 var threshold = draggedThreshold(force.alpha()),
63 dx = d.oldX - d.px,
64 dy = d.oldY - d.py;
65 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
66 d.dragged = true;
67 }
68 return d.dragged;
69 }
70
71 drag = d3.behavior.drag()
72 .origin(function(d) { return d; })
73 .on('dragstart', function(d) {
Simon Hunt6e18fe32014-11-29 13:35:41 -080074 if (enabled()) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080075 d3.event.sourceEvent.stopPropagation();
76
77 d.oldX = d.x;
78 d.oldY = d.y;
79 d.dragged = false;
80 d.fixed |= 2;
81 d.dragStarted = true;
82 }
Simon Hunt1a9eff92014-11-07 11:06:34 -080083 })
84 .on('drag', function(d) {
Simon Hunt6e18fe32014-11-29 13:35:41 -080085 if (enabled()) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080086 d.px = d3.event.x;
87 d.py = d3.event.y;
88 if (dragged(d)) {
89 if (!force.alpha()) {
90 force.alpha(.025);
91 }
Simon Hunt1a9eff92014-11-07 11:06:34 -080092 }
93 }
94 })
95 .on('dragend', function(d) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080096 if (d.dragStarted) {
97 d.dragStarted = false;
98 if (!dragged(d)) {
99 // consider this the same as a 'click' (selection of node)
100 selectCb(d, this); // TODO: set 'this' context instead of param
101 }
102 d.fixed &= ~6;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800103
Paul Greysonfcba0e82014-11-13 10:21:16 -0800104 // hook at the end of a drag gesture
105 atDragEnd(d, this); // TODO: set 'this' context instead of param
106 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800107 });
108
109 return drag;
110 }
111
112 function appendGlow(svg) {
113 // TODO: parameterize color
114
115 var glow = svg.append('filter')
116 .attr('x', '-50%')
117 .attr('y', '-50%')
118 .attr('width', '200%')
119 .attr('height', '200%')
120 .attr('id', 'blue-glow');
121
122 glow.append('feColorMatrix')
123 .attr('type', 'matrix')
124 .attr('values', '0 0 0 0 0 ' +
125 '0 0 0 0 0 ' +
126 '0 0 0 0 .7 ' +
127 '0 0 0 1 0 ');
128
129 glow.append('feGaussianBlur')
130 .attr('stdDeviation', 3)
131 .attr('result', 'coloredBlur');
132
133 glow.append('feMerge').selectAll('feMergeNode')
134 .data(['coloredBlur', 'SourceGraphic'])
135 .enter().append('feMergeNode')
136 .attr('in', String);
137 }
138
Simon Hunt8f40cce2014-11-23 15:57:30 -0800139 // --- Ordinal scales for 7 values.
140 // TODO: tune colors for light and dark themes
Simon Hunt925dfc02014-11-29 12:11:51 -0800141 // Note: These colors look good on the white background. Still, need to tune for dark.
Simon Hunt8f40cce2014-11-23 15:57:30 -0800142
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800143 // blue brown brick red sea green purple dark teal lime
144 var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
145 lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800146
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800147 darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
148 darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
Simon Hunt8f40cce2014-11-23 15:57:30 -0800149
150 function cat7() {
151 var colors = {
152 light: {
153 norm: d3.scale.ordinal().range(lightNorm),
154 mute: d3.scale.ordinal().range(lightMute)
155 },
156 dark: {
157 norm: d3.scale.ordinal().range(darkNorm),
158 mute: d3.scale.ordinal().range(darkMute)
159 }
160 },
161 tcid = 'd3utilTestCard';
162
163 function get(id, muted, theme) {
164 // NOTE: since we are lazily assigning domain ids, we need to
165 // get the color from all 4 scales, to keep the domains
166 // in sync.
167 var ln = colors.light.norm(id),
168 lm = colors.light.mute(id),
169 dn = colors.dark.norm(id),
170 dm = colors.dark.mute(id);
171 if (theme === 'dark') {
172 return muted ? dm : dn;
173 } else {
174 return muted ? lm : ln;
175 }
176 }
177
178 function testCard(svg) {
179 var g = svg.select('g#' + tcid),
180 dom = d3.range(7),
181 k, muted, theme, what;
182
183 if (!g.empty()) {
184 g.remove();
185
186 } else {
187 g = svg.append('g')
188 .attr('id', tcid)
189 .attr('transform', 'scale(4)translate(20,20)');
190
191 for (k=0; k<4; k++) {
192 muted = k%2;
193 what = muted ? ' muted' : ' normal';
194 theme = k < 2 ? 'light' : 'dark';
195 dom.forEach(function (id, i) {
196 var x = i * 20,
197 y = k * 20,
198 f = get(id, muted, theme);
199 g.append('circle').attr({
200 cx: x,
201 cy: y,
202 r: 5,
203 fill: f
204 });
205 });
206 g.append('rect').attr({
207 x: 140,
208 y: k * 20 - 5,
209 width: 32,
210 height: 10,
211 rx: 2,
212 fill: '#888'
213 });
214 g.append('text').text(theme + what)
215 .attr({
216 x: 142,
217 y: k * 20 + 2,
218 fill: 'white'
219 })
220 .style('font-size', '4pt');
221 }
222 }
223 }
224
225 return {
226 testCard: testCard,
227 get: get
228 };
229 }
230
Simon Hunt1a9eff92014-11-07 11:06:34 -0800231 // === register the functions as a library
232 onos.ui.addLib('d3util', {
233 createDragBehavior: createDragBehavior,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800234 appendGlow: appendGlow,
235 cat7: cat7
Simon Hunt1a9eff92014-11-07 11:06:34 -0800236 });
237
238}(ONOS));