Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / static / fusion / raptor / d3 / js / models / legend.js
1 nv.models.legend = function() {
2   "use strict";
3   //============================================================
4   // Public Variables with Default Settings
5   //------------------------------------------------------------
6
7   var margin = {top: 5, right: 0, bottom: 5, left: 0}
8     , width = 400
9     , height = 20
10     , getKey = function(d) { return d.key }
11     , color = nv.utils.defaultColor()
12     , align = true
13     , rightAlign = true
14     , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
15     , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
16     , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
17     ;
18
19   //============================================================
20
21
22   function chart(selection) {
23     selection.each(function(data) {
24       var availableWidth = width - margin.left - margin.right,
25           container = d3.select(this);
26
27
28       //------------------------------------------------------------
29       // Setup containers and skeleton of chart
30
31       var wrap = container.selectAll('g.nv-legend').data([data]);
32       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
33       var g = wrap.select('g');
34
35       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
36
37       //------------------------------------------------------------
38
39
40       var series = g.selectAll('.nv-series')
41           .data(function(d) { return d });
42       var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
43           .on('mouseover', function(d,i) {
44             dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
45           })
46           .on('mouseout', function(d,i) {
47             dispatch.legendMouseout(d,i);
48           })
49           .on('click', function(d,i) {
50             dispatch.legendClick(d,i);
51             if (updateState) {
52                if (radioButtonMode) {
53                    //Radio button mode: set every series to disabled,
54                    //  and enable the clicked series.
55                    data.forEach(function(series) { series.disabled = true});
56                    d.disabled = false;
57                }
58                else {
59                    d.disabled = !d.disabled;
60                    if (data.every(function(series) { return series.disabled})) {
61                        //the default behavior of NVD3 legends is, if every single series
62                        // is disabled, turn all series' back on.
63                        data.forEach(function(series) { series.disabled = false});
64                    }
65                }
66                dispatch.stateChange({
67                   disabled: data.map(function(d) { return !!d.disabled })
68                });
69             }
70           })
71           .on('dblclick', function(d,i) {
72             dispatch.legendDblclick(d,i);
73             if (updateState) {
74                 //the default behavior of NVD3 legends, when double clicking one,
75                 // is to set all other series' to false, and make the double clicked series enabled.
76                 data.forEach(function(series) {
77                    series.disabled = true;
78                 });
79                 d.disabled = false; 
80                 dispatch.stateChange({
81                     disabled: data.map(function(d) { return !!d.disabled })
82                 });
83             }
84           });
85       seriesEnter.append('circle')
86           .style('stroke-width', 2)
87           .attr('class','nv-legend-symbol')
88           .attr('r', 5);
89       seriesEnter.append('text')
90           .attr('text-anchor', 'start')
91           .attr('class','nv-legend-text')
92           .attr('dy', '.32em')
93           .attr('dx', '8');
94       series.classed('disabled', function(d) { return d.disabled });
95       series.exit().remove();
96       series.select('circle')
97           .style('fill', function(d,i) { return d.color || color(d,i)})
98           .style('stroke', function(d,i) { return d.color || color(d, i) });
99       series.select('text').text(getKey);
100
101
102       //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
103
104       // NEW ALIGNING CODE, TODO: clean up
105       if (align) {
106
107         var seriesWidths = [];
108         series.each(function(d,i) {
109               var legendText = d3.select(this).select('text');
110               var nodeTextLength;
111               try {
112                 nodeTextLength = legendText.node().getComputedTextLength();
113               }
114               catch(e) {
115                 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
116               }
117              
118               seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
119             });
120
121         var seriesPerRow = 0;
122         var legendWidth = 0;
123         var columnWidths = [];
124
125         while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
126           columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
127           legendWidth += seriesWidths[seriesPerRow++];
128         }
129         if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
130
131
132         while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
133           columnWidths = [];
134           seriesPerRow--;
135
136           for (var k = 0; k < seriesWidths.length; k++) {
137             if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
138               columnWidths[k % seriesPerRow] = seriesWidths[k];
139           }
140
141           legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
142                           return prev + cur;
143                         });
144         }
145
146         var xPositions = [];
147         for (var i = 0, curX = 0; i < seriesPerRow; i++) {
148             xPositions[i] = curX;
149             curX += columnWidths[i];
150         }
151
152         series
153             .attr('transform', function(d, i) {
154               return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
155             });
156
157         //position legend as far right as possible within the total width
158         if (rightAlign) {
159            g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
160         }
161         else {
162            g.attr('transform', 'translate(0' + ',' + margin.top + ')');
163         }
164
165         height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
166
167       } else {
168
169         var ypos = 5,
170             newxpos = 5,
171             maxwidth = 0,
172             xpos;
173         series
174             .attr('transform', function(d, i) {
175               var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
176               xpos = newxpos;
177
178               if (width < margin.left + margin.right + xpos + length) {
179                 newxpos = xpos = 5;
180                 ypos += 20;
181               }
182
183               newxpos += length;
184               if (newxpos > maxwidth) maxwidth = newxpos;
185
186               return 'translate(' + xpos + ',' + ypos + ')';
187             });
188
189         //position legend as far right as possible within the total width
190         g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
191
192         height = margin.top + margin.bottom + ypos + 15;
193
194       }
195
196     });
197
198     return chart;
199   }
200
201
202   //============================================================
203   // Expose Public Variables
204   //------------------------------------------------------------
205
206   chart.dispatch = dispatch;
207   chart.options = nv.utils.optionsFunc.bind(chart);
208   
209   chart.margin = function(_) {
210     if (!arguments.length) return margin;
211     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
212     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
213     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
214     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
215     return chart;
216   };
217
218   chart.width = function(_) {
219     if (!arguments.length) return width;
220     width = _;
221     return chart;
222   };
223
224   chart.height = function(_) {
225     if (!arguments.length) return height;
226     height = _;
227     return chart;
228   };
229
230   chart.key = function(_) {
231     if (!arguments.length) return getKey;
232     getKey = _;
233     return chart;
234   };
235
236   chart.color = function(_) {
237     if (!arguments.length) return color;
238     color = nv.utils.getColor(_);
239     return chart;
240   };
241
242   chart.align = function(_) {
243     if (!arguments.length) return align;
244     align = _;
245     return chart;
246   };
247
248   chart.rightAlign = function(_) {
249     if (!arguments.length) return rightAlign;
250     rightAlign = _;
251     return chart;
252   };
253
254   chart.updateState = function(_) {
255     if (!arguments.length) return updateState;
256     updateState = _;
257     return chart;
258   };
259
260   chart.radioButtonMode = function(_) {
261     if (!arguments.length) return radioButtonMode;
262     radioButtonMode = _;
263     return chart;
264   };
265
266   //============================================================
267
268
269   return chart;
270 }