blob: 00ca8873da0da2710cdcbc7f4e024d94075743c1 [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.
Simon Hunt1a9eff92014-11-07 11:06:34 -080019 */
20
21(function (onos) {
22 'use strict';
23
Simon Hunt6e18fe32014-11-29 13:35:41 -080024 function isF(f) {
25 return $.isFunction(f) ? f : null;
26 }
27
Simon Huntc2367d52014-11-29 19:30:23 -080028 // TODO: sensible defaults
29 function createDragBehavior(force, selectCb, atDragEnd,
30 dragEnabled, clickEnabled) {
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),
Simon Huntc2367d52014-11-29 19:30:23 -080038 fDEn = isF(dragEnabled),
39 fCEn = isF(clickEnabled),
Simon Hunt6e18fe32014-11-29 13:35:41 -080040 bad = [];
Simon Hunt1a9eff92014-11-07 11:06:34 -080041
Simon Hunt6e18fe32014-11-29 13:35:41 -080042 function naf(what) {
43 return 'd3util.createDragBehavior(): '+ what + ' is not a function';
Simon Hunt1a9eff92014-11-07 11:06:34 -080044 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080045
46 if (!fSel) {
47 bad.push(naf('selectCb'));
Simon Hunt1a9eff92014-11-07 11:06:34 -080048 }
Simon Hunt6e18fe32014-11-29 13:35:41 -080049 if (!fEnd) {
50 bad.push(naf('atDragEnd'));
Simon Hunt01095ff2014-11-13 16:37:29 -080051 }
Simon Huntc2367d52014-11-29 19:30:23 -080052 if (!fDEn) {
53 bad.push(naf('dragEnabled'));
54 }
55 if (!fCEn) {
56 bad.push(naf('clickEnabled'));
Simon Hunt6e18fe32014-11-29 13:35:41 -080057 }
58
59 if (bad.length) {
60 alert(bad.join('\n'));
61 return null;
62 }
63
Simon Hunt1a9eff92014-11-07 11:06:34 -080064
65 function dragged(d) {
66 var threshold = draggedThreshold(force.alpha()),
67 dx = d.oldX - d.px,
68 dy = d.oldY - d.py;
69 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
70 d.dragged = true;
71 }
72 return d.dragged;
73 }
74
75 drag = d3.behavior.drag()
76 .origin(function(d) { return d; })
77 .on('dragstart', function(d) {
Simon Huntc2367d52014-11-29 19:30:23 -080078 if (clickEnabled() || dragEnabled()) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080079 d3.event.sourceEvent.stopPropagation();
80
81 d.oldX = d.x;
82 d.oldY = d.y;
83 d.dragged = false;
84 d.fixed |= 2;
85 d.dragStarted = true;
86 }
Simon Hunt1a9eff92014-11-07 11:06:34 -080087 })
88 .on('drag', function(d) {
Simon Huntc2367d52014-11-29 19:30:23 -080089 if (dragEnabled()) {
Paul Greysonfcba0e82014-11-13 10:21:16 -080090 d.px = d3.event.x;
91 d.py = d3.event.y;
92 if (dragged(d)) {
93 if (!force.alpha()) {
94 force.alpha(.025);
95 }
Simon Hunt1a9eff92014-11-07 11:06:34 -080096 }
97 }
98 })
99 .on('dragend', function(d) {
Paul Greysonfcba0e82014-11-13 10:21:16 -0800100 if (d.dragStarted) {
101 d.dragStarted = false;
102 if (!dragged(d)) {
Simon Huntc2367d52014-11-29 19:30:23 -0800103 // consider this the same as a 'click'
104 // (selection of a node)
105 if (clickEnabled()) {
106 selectCb(d, this); // TODO: set 'this' context instead of param
107 }
Paul Greysonfcba0e82014-11-13 10:21:16 -0800108 }
109 d.fixed &= ~6;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800110
Paul Greysonfcba0e82014-11-13 10:21:16 -0800111 // hook at the end of a drag gesture
Simon Huntc2367d52014-11-29 19:30:23 -0800112 if (dragEnabled()) {
113 atDragEnd(d, this); // TODO: set 'this' context instead of param
114 }
Paul Greysonfcba0e82014-11-13 10:21:16 -0800115 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800116 });
117
118 return drag;
119 }
120
Simon Hunt9f1bced2014-12-02 14:36:39 -0800121 function loadGlow(defs) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800122 // TODO: parameterize color
123
Simon Hunt9f1bced2014-12-02 14:36:39 -0800124 var glow = defs.append('filter')
Simon Hunt1a9eff92014-11-07 11:06:34 -0800125 .attr('x', '-50%')
126 .attr('y', '-50%')
127 .attr('width', '200%')
128 .attr('height', '200%')
129 .attr('id', 'blue-glow');
130
131 glow.append('feColorMatrix')
132 .attr('type', 'matrix')
Simon Hunt9f1bced2014-12-02 14:36:39 -0800133 .attr('values',
134 '0 0 0 0 0 ' +
135 '0 0 0 0 0 ' +
136 '0 0 0 0 .7 ' +
137 '0 0 0 1 0 ');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800138
139 glow.append('feGaussianBlur')
140 .attr('stdDeviation', 3)
141 .attr('result', 'coloredBlur');
142
143 glow.append('feMerge').selectAll('feMergeNode')
144 .data(['coloredBlur', 'SourceGraphic'])
145 .enter().append('feMergeNode')
146 .attr('in', String);
147 }
148
Simon Hunt8f40cce2014-11-23 15:57:30 -0800149 // --- Ordinal scales for 7 values.
150 // TODO: tune colors for light and dark themes
Simon Hunt925dfc02014-11-29 12:11:51 -0800151 // Note: These colors look good on the white background. Still, need to tune for dark.
Simon Hunt8f40cce2014-11-23 15:57:30 -0800152
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800153 // blue brown brick red sea green purple dark teal lime
154 var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
155 lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800156
Thomas Vachuska1e68bdd2014-11-29 13:53:10 -0800157 darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
158 darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
Simon Hunt8f40cce2014-11-23 15:57:30 -0800159
160 function cat7() {
161 var colors = {
162 light: {
163 norm: d3.scale.ordinal().range(lightNorm),
164 mute: d3.scale.ordinal().range(lightMute)
165 },
166 dark: {
167 norm: d3.scale.ordinal().range(darkNorm),
168 mute: d3.scale.ordinal().range(darkMute)
169 }
170 },
171 tcid = 'd3utilTestCard';
172
173 function get(id, muted, theme) {
174 // NOTE: since we are lazily assigning domain ids, we need to
175 // get the color from all 4 scales, to keep the domains
176 // in sync.
177 var ln = colors.light.norm(id),
178 lm = colors.light.mute(id),
179 dn = colors.dark.norm(id),
180 dm = colors.dark.mute(id);
181 if (theme === 'dark') {
182 return muted ? dm : dn;
183 } else {
184 return muted ? lm : ln;
185 }
186 }
187
188 function testCard(svg) {
189 var g = svg.select('g#' + tcid),
190 dom = d3.range(7),
191 k, muted, theme, what;
192
193 if (!g.empty()) {
194 g.remove();
195
196 } else {
197 g = svg.append('g')
198 .attr('id', tcid)
199 .attr('transform', 'scale(4)translate(20,20)');
200
201 for (k=0; k<4; k++) {
202 muted = k%2;
203 what = muted ? ' muted' : ' normal';
204 theme = k < 2 ? 'light' : 'dark';
205 dom.forEach(function (id, i) {
206 var x = i * 20,
207 y = k * 20,
208 f = get(id, muted, theme);
209 g.append('circle').attr({
210 cx: x,
211 cy: y,
212 r: 5,
213 fill: f
214 });
215 });
216 g.append('rect').attr({
217 x: 140,
218 y: k * 20 - 5,
219 width: 32,
220 height: 10,
221 rx: 2,
222 fill: '#888'
223 });
224 g.append('text').text(theme + what)
225 .attr({
226 x: 142,
227 y: k * 20 + 2,
228 fill: 'white'
229 })
230 .style('font-size', '4pt');
231 }
232 }
233 }
234
235 return {
236 testCard: testCard,
237 get: get
238 };
239 }
240
Simon Hunt1a9eff92014-11-07 11:06:34 -0800241 // === register the functions as a library
242 onos.ui.addLib('d3util', {
243 createDragBehavior: createDragBehavior,
Simon Hunt9f1bced2014-12-02 14:36:39 -0800244 loadGlow: loadGlow,
Simon Hunt8f40cce2014-11-23 15:57:30 -0800245 cat7: cat7
Simon Hunt1a9eff92014-11-07 11:06:34 -0800246 });
247
248}(ONOS));