(nv.models).multiBarHorizontal = function () { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ var margin = { top: 0, right: 0, bottom: 0, left: 0 } , width = 960 , height = 500 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one , container = null , x = d3.scale.ordinal() , y = d3.scale.linear() , getX = function (d, _i) { return d.x } , getY = function (d, _i) { return d.y } , getYerr = function (d, _i) { return d.yErr } , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove , color = nv.utils.defaultColor() , barColor = null // adding the ability to set the color for each rather than the whole group , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled , stacked = false , showValues = false , valueStyle = (_d, _i) => 'fill: #333; stroke: rgba(0,0,0,0)' , valuePadding = 60 , groupSpacing = 0.1 , fillOpacity = 0.75 , valueFormat = d3.format(',.2f') , xDomain , yDomain , xRange , yRange , duration = 250 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') ; //============================================================ // Private Variables //------------------------------------------------------------ var x0, y0; //used to store previous scales var renderWatch: any = nv.utils.renderWatch(dispatch, duration); function chart(selection) { renderWatch.reset(); selection.each(function (data: any[]) { var availableWidth = width - margin.left - margin.right, availableHeight = height - margin.top - margin.bottom; container = d3.select(this); nv.utils.initSVG(container); if (stacked) data = d3.layout.stack() .offset('zero') .values(function (d: any) { return d.values }) .y(getY) (data); //add series index and key to each data point for reference data.forEach(function (series, i) { series.values.forEach(function (point) { point.series = i; point.key = series.key; }); }); // HACK for negative value stacking if (stacked) data[0].values.map(function (_d, i) { var posBase = 0, negBase = 0; data.map(function (d) { var f = d.values[i] f.size = Math.abs(f.y); if (f.y < 0) { f.y1 = negBase - f.size; negBase = negBase - f.size; } else { f.y1 = posBase; posBase = posBase + f.size; } }); }); // Setup Scales // remap and flatten the data for use in calculating the scales' domains var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate data.map(function (d) { return d.values.map(function (d, i) { return { x: getX(d, i), y: getY(d, i), y0: d.y0, y1: d.y1 } }) }); x.domain(xDomain || d3.merge(seriesData).map(function (d: any) { return d.x })) .rangeBands(xRange || [0, availableHeight], groupSpacing); y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function (d: any) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1) : d.y }).concat(forceY))) if (showValues && !stacked) y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0)]); else y.range(yRange || [0, availableWidth]); x0 = x0 || x; y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0), y(0)]); // Setup containers and skeleton of chart var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); var gEnter = wrapEnter.append('g'); gEnter.append('g').attr('class', 'nv-groups'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); var groups = wrap.select('.nv-groups').selectAll('.nv-group') .data(function (d) { return d }, function (_d, i) { return i }); groups.enter().append('g') .style('stroke-opacity', 1e-6) .style('fill-opacity', 1e-6); groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups') .style('stroke-opacity', 1e-6) .style('fill-opacity', 1e-6) .remove(); groups .attr('class', function (_d, i) { return 'nv-group nv-series-' + i }) .classed('hover', function (d) { return d.hover }) .style('fill', function (d, i) { return color(d, i) }) .style('stroke', function (d, i) { return color(d, i) }); groups.watchTransition(renderWatch, 'multibarhorizontal: groups') .style('stroke-opacity', 1) .style('fill-opacity', fillOpacity); var bars = groups.selectAll('g.nv-bar') .data(function (d) { return d.values }); bars.exit().remove(); var barsEnter = bars.enter().append('g') .attr('transform', function (d: any, i, j) { return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length) + x(getX(d, i))) + ')' }); barsEnter.append('rect') .attr('width', 0) .attr('height', x.rangeBand() / (stacked ? 1 : data.length)) bars .on('mouseover', function (d, i) { //TODO: figure out why j works above, but not here d3.select(this).classed('hover', true); dispatch.elementMouseover({ data: d, index: i, color: d3.select(this).style("fill") }); }) .on('mouseout', function (d, i) { d3.select(this).classed('hover', false); dispatch.elementMouseout({ data: d, index: i, color: d3.select(this).style("fill") }); }) .on('mouseout', function (d, i) { dispatch.elementMouseout({ data: d, index: i, color: d3.select(this).style("fill") }); }) .on('mousemove', function (d, i) { dispatch.elementMousemove({ data: d, index: i, color: d3.select(this).style("fill") }); }) .on('click', function (d, i) { var element = this; dispatch.elementClick({ data: d, index: i, color: d3.select(this).style("fill"), event: d3.event, element: element }); (d3.event).stopPropagation(); }) .on('dblclick', function (d, i) { dispatch.elementDblClick({ data: d, index: i, color: d3.select(this).style("fill") }); (d3.event).stopPropagation(); }); if (getYerr(data[0], 0)) { barsEnter.append('polyline'); bars.select('polyline') .attr('fill', 'none') .attr('points', function (d, i) { var xerr = getYerr(d, i) , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)]; xerr = xerr.map(function (e) { return y(e + ((getY(d, i) < 0) ? 0 : getY(d, i))) - y(0); }); var a = [[xerr[0], -mid], [xerr[0], mid], [xerr[0], 0], [xerr[1], 0], [xerr[1], -mid], [xerr[1], mid]]; return a.map(function (path) { return path.join(',') }).join(' '); }) .attr('transform', function (_d, _i) { var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2); return 'translate(0, ' + mid + ')'; }); } barsEnter.append('text'); if (showValues) { bars.select('text') .attr('text-anchor', function (d, i) { if (stacked) { return 'middle'; } return getY(d, i) < 0 ? 'end' : 'start' }) .attr('y', () => { if (stacked) { return x.rangeBand() / 2; } return x.rangeBand() / (data.length * 2) }) .attr('dy', '.32em') .attr('style', (d, i) => valueStyle(d, i)) .text(function (d, i) { var t = valueFormat(getY(d, i)) , yerr = getYerr(d, i); if (yerr === undefined) return t; if (!yerr.length) return t + '±' + valueFormat(Math.abs(yerr)); return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0])); }); bars.watchTransition(renderWatch, 'multibarhorizontal: bars') .select('text') .attr('x', (d: any, i) => { if (stacked) { return (Math.abs(y(getY(d, i) + d.y0) - y(d.y0)) || 0) / 2 } return getY(d, i) < 0 ? -4 : y(getY(d, i)) - y(0) + 4 }); } else { bars.selectAll('text').text(''); } bars .attr('class', function (d, i) { return getY(d, i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) if (barColor) { if (!disabled) disabled = data.map(function () { return true }); bars .style('fill', function (d, i, j) { return d3.rgb(barColor(d, i)).darker(disabled.map(function (_d, i) { return i }).filter(function (_d, i) { return !disabled[i] })[j]).toString(); }) .style('stroke', function (d, i, j) { return d3.rgb(barColor(d, i)).darker(disabled.map(function (_d, i) { return i }).filter(function (_d, i) { return !disabled[i] })[j]).toString(); }); } if (stacked) (bars).watchTransition(renderWatch, 'multibarhorizontal: bars') .attr('transform', function (d, i) { return 'translate(' + y(d.y1) + ',' + x(getX(d, i)) + ')' }) .select('rect') .attr('width', function (d, i) { return Math.abs(y(getY(d, i) + d.y0) - y(d.y0)) || 0 }) .attr('height', x.rangeBand()); else (bars).watchTransition(renderWatch, 'multibarhorizontal: bars') .attr('transform', function (d, i) { //TODO: stacked must be all positive or all negative, not both? const tx = (getY(d, i) < 0 ? y(getY(d, i)) : y(0)); const ty = (d.series * x.rangeBand() / data.length + x(getX(d, i))); return `translate(${tx}, ${ty})`; }) .select('rect') .attr('height', x.rangeBand() / data.length) .attr('width', function (d, i) { return Math.max(Math.abs(y(getY(d, i)) - y(0)), 1) || 0 }); //store old scales for use in transitions on update x0 = x.copy(); y0 = y.copy(); }); renderWatch.renderEnd('multibarHorizontal immediate'); return chart; } //============================================================ // Expose Public Variables //------------------------------------------------------------ (chart).dispatch = dispatch; (chart).options = nv.utils.optionsFunc.bind(chart); (chart)._options = Object.create({}, { // simple options, just get/set the necessary values width: { get: function () { return width; }, set: function (_) { width = _; } }, height: { get: function () { return height; }, set: function (_) { height = _; } }, x: { get: function () { return getX; }, set: function (_) { getX = _; } }, y: { get: function () { return getY; }, set: function (_) { getY = _; } }, yErr: { get: function () { return getYerr; }, set: function (_) { getYerr = _; } }, xScale: { get: function () { return x; }, set: function (_) { x = _; } }, yScale: { get: function () { return y; }, set: function (_) { y = _; } }, xDomain: { get: function () { return xDomain; }, set: function (_) { xDomain = _; } }, yDomain: { get: function () { return yDomain; }, set: function (_) { yDomain = _; } }, xRange: { get: function () { return xRange; }, set: function (_) { xRange = _; } }, yRange: { get: function () { return yRange; }, set: function (_) { yRange = _; } }, forceY: { get: function () { return forceY; }, set: function (_) { forceY = _; } }, stacked: { get: function () { return stacked; }, set: function (_) { stacked = _; } }, showValues: { get: function () { return showValues; }, set: function (_) { showValues = _; } }, valueStyle: {get: function(){return valueStyle;}, set: function(_){valueStyle=_;}}, disabled: { get: function () { return disabled; }, set: function (_) { disabled = _; } }, id: { get: function () { return id; }, set: function (_) { id = _; } }, valueFormat: { get: function () { return valueFormat; }, set: function (_) { valueFormat = _; } }, valuePadding: { get: function () { return valuePadding; }, set: function (_) { valuePadding = _; } }, groupSpacing: { get: function () { return groupSpacing; }, set: function (_) { groupSpacing = _; } }, fillOpacity: { get: function () { return fillOpacity; }, set: function (_) { fillOpacity = _; } }, // options that require extra logic in the setter margin: { get: function () { return margin; }, set: function (_) { margin.top = _.top !== undefined ? _.top : margin.top; margin.right = _.right !== undefined ? _.right : margin.right; margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; margin.left = _.left !== undefined ? _.left : margin.left; } }, duration: { get: function () { return duration; }, set: function (_) { duration = _; renderWatch.reset(duration); } }, color: { get: function () { return color; }, set: function (_) { color = nv.utils.getColor(_); } }, barColor: { get: function () { return barColor; }, set: function (_) { barColor = _ ? nv.utils.getColor(_) : null; } } }); nv.utils.initOptions(chart); return chart; };