blob: 42e371cf3f30aa28c905d674eaa864ef0ca39597 [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
Simon Huntc2367d52014-11-29 19:30:23 -080030 // TODO: sensible defaults
31 function createDragBehavior(force, selectCb, atDragEnd,
32 dragEnabled, clickEnabled) {
Simon Hunt1a9eff92014-11-07 11:06:34 -080033 var draggedThreshold = d3.scale.linear()
34 .domain([0, 0.1])
35 .range([5, 20])
36 .clamp(true),
Simon Hunt6e18fe32014-11-29 13:35:41 -080037 drag,
38 fSel = isF(selectCb),
39 fEnd = isF(atDragEnd),
Simon Huntc2367d52014-11-29 19:30:23 -080040 fDEn = isF(dragEnabled),
41 fCEn = isF(clickEnabled),
Simon Hunt6e18fe32014-11-29 13:35:41 -080042 bad = [];
Simon Hunt1a9eff92014-11-07 11:06:34 -080043
Simon Hunt6e18fe32014-11-29 13:35:41 -080044 function naf(what) {
45 return 'd3util.createDragBehavior(): '+ what + ' is not a function';
Simon Hunt1a9eff92014-11-07 11:06:34 -080046 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080047
48 if (!fSel) {
49 bad.push(naf('selectCb'));
Simon Hunt1a9eff92014-11-07 11:06:34 -080050 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080051 if (!fEnd) {
52 bad.push(naf('atDragEnd'));
Simon Hunt01095ff2014-11-13 16:37:29 -080053 }
Simon Huntc2367d52014-11-29 19:30:23 -080054 if (!fDEn) {
55 bad.push(naf('dragEnabled'));
56 }
57 if (!fCEn) {
58 bad.push(naf('clickEnabled'));
Simon Hunt6e18fe32014-11-29 13:35:41 -080059 }
60
61 if (bad.length) {
62 alert(bad.join('\n'));
63 return null;
64 }
65
Simon Hunt1a9eff92014-11-07 11:06:34 -080066
67 function dragged(d) {
68 var threshold = draggedThreshold(force.alpha()),
69 dx = d.oldX - d.px,
70 dy = d.oldY - d.py;
71 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
72 d.dragged = true;
73 }
74 return d.dragged;
75 }
76
77 drag = d3.behavior.drag()
78 .origin(function(d) { return d; })
79 .on('dragstart', function(d) {
Simon Huntc2367d52014-11-29 19:30:23 -080080 if (clickEnabled() || dragEnabled()) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080081 d3.event.sourceEvent.stopPropagation();
82
83 d.oldX = d.x;
84 d.oldY = d.y;
85 d.dragged = false;
86 d.fixed |= 2;
87 d.dragStarted = true;
88 }
Simon Hunt1a9eff92014-11-07 11:06:34 -080089 })
90 .on('drag', function(d) {
Simon Huntc2367d52014-11-29 19:30:23 -080091 if (dragEnabled()) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080092 d.px = d3.event.x;
93 d.py = d3.event.y;
94 if (dragged(d)) {
95 if (!force.alpha()) {
96 force.alpha(.025);
97 }
Simon Hunt1a9eff92014-11-07 11:06:34 -080098 }
99 }
100 })
101 .on('dragend', function(d) {
Paul Greysonfcba0e82014-11-13 10:21:16 -0800102 if (d.dragStarted) {
103 d.dragStarted = false;
104 if (!dragged(d)) {
Simon Huntc2367d52014-11-29 19:30:23 -0800105 // consider this the same as a 'click'
106 // (selection of a node)
107 if (clickEnabled()) {
108 selectCb(d, this); // TODO: set 'this' context instead of param
109 }
Paul Greysonfcba0e82014-11-13 10:21:16 -0800110 }
111 d.fixed &= ~6;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800112
Paul Greysonfcba0e82014-11-13 10:21:16 -0800113 // hook at the end of a drag gesture
Simon Huntc2367d52014-11-29 19:30:23 -0800114 if (dragEnabled()) {
115 atDragEnd(d, this); // TODO: set 'this' context instead of param
116 }
Paul Greysonfcba0e82014-11-13 10:21:16 -0800117 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800118 });
119
120 return drag;
121 }
122
Simon Hunt9f1bced2014-12-02 14:36:39 -0800123 function loadGlow(defs) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800124 // TODO: parameterize color
125
Simon Hunt9f1bced2014-12-02 14:36:39 -0800126 var glow = defs.append('filter')
Simon Hunt1a9eff92014-11-07 11:06:34 -0800127 .attr('x', '-50%')
128 .attr('y', '-50%')
129 .attr('width', '200%')
130 .attr('height', '200%')
131 .attr('id', 'blue-glow');
132
133 glow.append('feColorMatrix')
134 .attr('type', 'matrix')
Simon Hunt9f1bced2014-12-02 14:36:39 -0800135 .attr('values',
136 '0 0 0 0 0 ' +
137 '0 0 0 0 0 ' +
138 '0 0 0 0 .7 ' +
139 '0 0 0 1 0 ');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800140
141 glow.append('feGaussianBlur')
142 .attr('stdDeviation', 3)
143 .attr('result', 'coloredBlur');
144
145 glow.append('feMerge').selectAll('feMergeNode')
146 .data(['coloredBlur', 'SourceGraphic'])
147 .enter().append('feMergeNode')
148 .attr('in', String);
149 }
150
Simon Hunt8f40cce2014-11-23 15:57:30 -0800151 // --- Ordinal scales for 7 values.
152 // TODO: tune colors for light and dark themes
Simon Hunt925dfc02014-11-29 12:11:51 -0800153 // Note: These colors look good on the white background. Still, need to tune for dark.
Simon Hunt8f40cce2014-11-23 15:57:30 -0800154
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800155 // blue brown brick red sea green purple dark teal lime
156 var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
157 lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800158
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800159 darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
160 darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
Simon Hunt8f40cce2014-11-23 15:57:30 -0800161
162 function cat7() {
163 var colors = {
164 light: {
165 norm: d3.scale.ordinal().range(lightNorm),
166 mute: d3.scale.ordinal().range(lightMute)
167 },
168 dark: {
169 norm: d3.scale.ordinal().range(darkNorm),
170 mute: d3.scale.ordinal().range(darkMute)
171 }
172 },
173 tcid = 'd3utilTestCard';
174
175 function get(id, muted, theme) {
176 // NOTE: since we are lazily assigning domain ids, we need to
177 // get the color from all 4 scales, to keep the domains
178 // in sync.
179 var ln = colors.light.norm(id),
180 lm = colors.light.mute(id),
181 dn = colors.dark.norm(id),
182 dm = colors.dark.mute(id);
183 if (theme === 'dark') {
184 return muted ? dm : dn;
185 } else {
186 return muted ? lm : ln;
187 }
188 }
189
190 function testCard(svg) {
191 var g = svg.select('g#' + tcid),
192 dom = d3.range(7),
193 k, muted, theme, what;
194
195 if (!g.empty()) {
196 g.remove();
197
198 } else {
199 g = svg.append('g')
200 .attr('id', tcid)
201 .attr('transform', 'scale(4)translate(20,20)');
202
203 for (k=0; k<4; k++) {
204 muted = k%2;
205 what = muted ? ' muted' : ' normal';
206 theme = k < 2 ? 'light' : 'dark';
207 dom.forEach(function (id, i) {
208 var x = i * 20,
209 y = k * 20,
210 f = get(id, muted, theme);
211 g.append('circle').attr({
212 cx: x,
213 cy: y,
214 r: 5,
215 fill: f
216 });
217 });
218 g.append('rect').attr({
219 x: 140,
220 y: k * 20 - 5,
221 width: 32,
222 height: 10,
223 rx: 2,
224 fill: '#888'
225 });
226 g.append('text').text(theme + what)
227 .attr({
228 x: 142,
229 y: k * 20 + 2,
230 fill: 'white'
231 })
232 .style('font-size', '4pt');
233 }
234 }
235 }
236
237 return {
238 testCard: testCard,
239 get: get
240 };
241 }
242
Simon Hunt1a9eff92014-11-07 11:06:34 -0800243 // === register the functions as a library
244 onos.ui.addLib('d3util', {
245 createDragBehavior: createDragBehavior,
Simon Hunt9f1bced2014-12-02 14:36:39 -0800246 loadGlow: loadGlow,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800247 cat7: cat7
Simon Hunt1a9eff92014-11-07 11:06:34 -0800248 });
249
250}(ONOS));