| nv.models.legend = function() { |
| "use strict"; |
| //============================================================ |
| // Public Variables with Default Settings |
| //------------------------------------------------------------ |
| |
| var margin = {top: 5, right: 0, bottom: 5, left: 0} |
| , width = 400 |
| , height = 20 |
| , getKey = function(d) { return d.key } |
| , color = nv.utils.defaultColor() |
| , align = true |
| , rightAlign = true |
| , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. |
| , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time) |
| , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') |
| ; |
| |
| //============================================================ |
| |
| |
| function chart(selection) { |
| selection.each(function(data) { |
| var availableWidth = width - margin.left - margin.right, |
| container = d3.select(this); |
| |
| |
| //------------------------------------------------------------ |
| // Setup containers and skeleton of chart |
| |
| var wrap = container.selectAll('g.nv-legend').data([data]); |
| var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); |
| var g = wrap.select('g'); |
| |
| wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); |
| |
| //------------------------------------------------------------ |
| |
| |
| var series = g.selectAll('.nv-series') |
| .data(function(d) { return d }); |
| var seriesEnter = series.enter().append('g').attr('class', 'nv-series') |
| .on('mouseover', function(d,i) { |
| dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects |
| }) |
| .on('mouseout', function(d,i) { |
| dispatch.legendMouseout(d,i); |
| }) |
| .on('click', function(d,i) { |
| dispatch.legendClick(d,i); |
| if (updateState) { |
| if (radioButtonMode) { |
| //Radio button mode: set every series to disabled, |
| // and enable the clicked series. |
| data.forEach(function(series) { series.disabled = true}); |
| d.disabled = false; |
| } |
| else { |
| d.disabled = !d.disabled; |
| if (data.every(function(series) { return series.disabled})) { |
| //the default behavior of NVD3 legends is, if every single series |
| // is disabled, turn all series' back on. |
| data.forEach(function(series) { series.disabled = false}); |
| } |
| } |
| dispatch.stateChange({ |
| disabled: data.map(function(d) { return !!d.disabled }) |
| }); |
| } |
| }) |
| .on('dblclick', function(d,i) { |
| dispatch.legendDblclick(d,i); |
| if (updateState) { |
| //the default behavior of NVD3 legends, when double clicking one, |
| // is to set all other series' to false, and make the double clicked series enabled. |
| data.forEach(function(series) { |
| series.disabled = true; |
| }); |
| d.disabled = false; |
| dispatch.stateChange({ |
| disabled: data.map(function(d) { return !!d.disabled }) |
| }); |
| } |
| }); |
| seriesEnter.append('circle') |
| .style('stroke-width', 2) |
| .attr('class','nv-legend-symbol') |
| .attr('r', 5); |
| seriesEnter.append('text') |
| .attr('text-anchor', 'start') |
| .attr('class','nv-legend-text') |
| .attr('dy', '.32em') |
| .attr('dx', '8'); |
| series.classed('disabled', function(d) { return d.disabled }); |
| series.exit().remove(); |
| series.select('circle') |
| .style('fill', function(d,i) { return d.color || color(d,i)}) |
| .style('stroke', function(d,i) { return d.color || color(d, i) }); |
| series.select('text').text(getKey); |
| |
| |
| //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) |
| |
| // NEW ALIGNING CODE, TODO: clean up |
| if (align) { |
| |
| var seriesWidths = []; |
| series.each(function(d,i) { |
| var legendText = d3.select(this).select('text'); |
| var nodeTextLength; |
| try { |
| nodeTextLength = legendText.node().getComputedTextLength(); |
| } |
| catch(e) { |
| nodeTextLength = nv.utils.calcApproxTextWidth(legendText); |
| } |
| |
| seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding |
| }); |
| |
| var seriesPerRow = 0; |
| var legendWidth = 0; |
| var columnWidths = []; |
| |
| while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { |
| columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; |
| legendWidth += seriesWidths[seriesPerRow++]; |
| } |
| if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row |
| |
| |
| while ( legendWidth > availableWidth && seriesPerRow > 1 ) { |
| columnWidths = []; |
| seriesPerRow--; |
| |
| for (var k = 0; k < seriesWidths.length; k++) { |
| if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) |
| columnWidths[k % seriesPerRow] = seriesWidths[k]; |
| } |
| |
| legendWidth = columnWidths.reduce(function(prev, cur, index, array) { |
| return prev + cur; |
| }); |
| } |
| |
| var xPositions = []; |
| for (var i = 0, curX = 0; i < seriesPerRow; i++) { |
| xPositions[i] = curX; |
| curX += columnWidths[i]; |
| } |
| |
| series |
| .attr('transform', function(d, i) { |
| return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')'; |
| }); |
| |
| //position legend as far right as possible within the total width |
| if (rightAlign) { |
| g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); |
| } |
| else { |
| g.attr('transform', 'translate(0' + ',' + margin.top + ')'); |
| } |
| |
| height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20); |
| |
| } else { |
| |
| var ypos = 5, |
| newxpos = 5, |
| maxwidth = 0, |
| xpos; |
| series |
| .attr('transform', function(d, i) { |
| var length = d3.select(this).select('text').node().getComputedTextLength() + 28; |
| xpos = newxpos; |
| |
| if (width < margin.left + margin.right + xpos + length) { |
| newxpos = xpos = 5; |
| ypos += 20; |
| } |
| |
| newxpos += length; |
| if (newxpos > maxwidth) maxwidth = newxpos; |
| |
| return 'translate(' + xpos + ',' + ypos + ')'; |
| }); |
| |
| //position legend as far right as possible within the total width |
| g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); |
| |
| height = margin.top + margin.bottom + ypos + 15; |
| |
| } |
| |
| }); |
| |
| return chart; |
| } |
| |
| |
| //============================================================ |
| // Expose Public Variables |
| //------------------------------------------------------------ |
| |
| chart.dispatch = dispatch; |
| chart.options = nv.utils.optionsFunc.bind(chart); |
| |
| chart.margin = function(_) { |
| if (!arguments.length) return margin; |
| margin.top = typeof _.top != 'undefined' ? _.top : margin.top; |
| margin.right = typeof _.right != 'undefined' ? _.right : margin.right; |
| margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; |
| margin.left = typeof _.left != 'undefined' ? _.left : margin.left; |
| return chart; |
| }; |
| |
| chart.width = function(_) { |
| if (!arguments.length) return width; |
| width = _; |
| return chart; |
| }; |
| |
| chart.height = function(_) { |
| if (!arguments.length) return height; |
| height = _; |
| return chart; |
| }; |
| |
| chart.key = function(_) { |
| if (!arguments.length) return getKey; |
| getKey = _; |
| return chart; |
| }; |
| |
| chart.color = function(_) { |
| if (!arguments.length) return color; |
| color = nv.utils.getColor(_); |
| return chart; |
| }; |
| |
| chart.align = function(_) { |
| if (!arguments.length) return align; |
| align = _; |
| return chart; |
| }; |
| |
| chart.rightAlign = function(_) { |
| if (!arguments.length) return rightAlign; |
| rightAlign = _; |
| return chart; |
| }; |
| |
| chart.updateState = function(_) { |
| if (!arguments.length) return updateState; |
| updateState = _; |
| return chart; |
| }; |
| |
| chart.radioButtonMode = function(_) { |
| if (!arguments.length) return radioButtonMode; |
| radioButtonMode = _; |
| return chart; |
| }; |
| |
| //============================================================ |
| |
| |
| return chart; |
| } |