diff --git a/spec/arc-spec.js b/spec/arc-spec.js index 1c39d35..1f65b8a 100644 --- a/spec/arc-spec.js +++ b/spec/arc-spec.js @@ -198,7 +198,7 @@ describe('c3 chart arc', function () { .select('g.c3-shapes.c3-shapes-data.c3-arcs.c3-arcs-data') .select('path.c3-shape.c3-shape.c3-arc.c3-arc-data'); - expect(data.attr('d')).toMatch(/M-304,-3\..+A304,304 0 0,1 245\..+,-178\..+L237\..+,-172\..+A294,294 0 0,0 -294,-3\..+Z/); + expect(data.attr('d')).toMatch(/-258.4,-3\..+A258.4,258.4 0 0,1 209\..+,-151\..+L200\..+,-146\..+A248.39999999999998,248.39999999999998 0 0,0 -248.39999999999998,-3\..+Z/); }); }); @@ -229,7 +229,7 @@ describe('c3 chart arc', function () { .select('path.c3-shape.c3-shape.c3-arc.c3-arc-data'); // This test has bee updated to make tests pass. @TODO double-check this test is accurate. - expect(data.attr('d')).toMatch(/M-221.*?,-2\..+A221.*?,221.*? 0 1,1 -68.*?,210.*?L-65.*?,201.*?A211.*?,211.*? 0 1,0 -211.*?,-2.*?Z/); + expect(data.attr('d')).toMatch(/M-180.*?,-2\..+A180.*?,180.*? 0 1,1 -55.*?,171.*?L-52.*?,161.*?A170.*?,170.*? 0 1,0 -170.*?,-2.*?Z/); }); describe('with labels use custom text', function() { @@ -269,6 +269,99 @@ describe('c3 chart arc', function () { }); }); }); + + describe('with more than one data_column ', function () { + beforeAll(function () { + args = { + data: { + columns: [ + ['padded1', 100], + ['padded2', 90], + ['padded3', 50], + ['padded4', 20] + ] + }, + type: 'gauge', + color: { + pattern: ['#FF0000', '#F97600', '#F6C600', '#60B044'], + threshold: { + values: [30, 80, 95] + } + } + }; + }); + var arcColor = ['#60b044', '#f6c600', '#f97600', '#ff0000']; + + describe('should contain arcs ', function () { + it('each data_column should have one arc', function () { + chart.internal.main.selectAll('.c3-chart-arc .c3-arc').each(function (d, i) { + expect(d3.select(this).classed('c3-chart-data-' + args.data.columns[i][0])).toBeTruthy(); + }); + }); + + it('each arc should have the color from color_pattern if color_treshold is given ', function () { + chart.internal.main.selectAll('.c3-chart-arc .c3-arc').each(function (d, i) { + expect(d3.select(this).style('fill')).toBe(arcColor[i]); + }); + }); + }); + + describe('should contain backgrounds ', function () { + it('each data_column should have one background', function () { + chart.internal.main.selectAll('.c3-chart-arcs path.c3-chart-arcs-background').each(function (d, i) { + expect(d3.select(this).classed('c3-chart-arcs-background-'+ i)).toBeTruthy(); + }); + }); + + it('each background should have tbe same color', function () { + chart.internal.main.selectAll('.c3-chart-arcs path.c3-chart-arcs-background').each(function () { + expect(d3.select(this).style('fill')).toBe('#e0e0e0'); + }); + }); + }); + + describe('should contain labels', function () { + it('each data_column should have a label', function () { + chart.internal.main.selectAll('.c3-chart-arc .c3-gauge-value').each(function (d, i) { + expect(d3.select(this).text()).toBe(args.data.columns[i][1]); + }); + }); + + it('each label should have the same color', function () { + chart.internal.main.selectAll('.c3-chart-arc .c3-gauge-value').each(function () { + expect(d3.select(this).style('fill')).toBe('#000'); + }); + + }); + + it('if only one data_column is visible the label should have "" for transform', function () { + setTimeout(function () { + var textBeforeHide = chart.internal.main.select('.c3-chart-arc.c3-target.c3-target-padded4 text'); + expect(textBeforeHide.attr('transform')).not.toBe(''); + },1000); + chart.hide(['padded1', 'padded2', 'padded3']); + setTimeout(function () { + var textAfterHide = chart.internal.main.select('.c3-chart-arc.c3-target.c3-target-padded4 text'); + expect(textAfterHide.attr('transform')).toBe(''); + },1000); + + }); + }); + + describe('should contain labellines', function () { + it('each data_column should have a labelline', function () { + chart.internal.main.selectAll('.c3-chart-arc .c3-arc-label-line').each(function (d, i) { + expect(d3.select(this).classed('c3-data-' + args.data.columns[i][0])).toBeTruthy(); + }); + }); + + it('each labelline should have the color from color_pattern if color_treshold is given', function () { + chart.internal.main.selectAll('.c3-chart-arc .c3-arc-label-line').each(function (d, i) { + expect(d3.select(this).style('fill')).toBe(arcColor[i]); + }); + }); + }); + }); }); }); diff --git a/spec/legend-spec.js b/spec/legend-spec.js index 1f7b869..ff67cb1 100644 --- a/spec/legend-spec.js +++ b/spec/legend-spec.js @@ -274,4 +274,68 @@ describe('c3 chart legend', function () { }); }); + describe('legend item tile coloring with color_treshold', function () { + beforeAll(function () { + args = { + data: { + columns: [ + ['padded1', 100], + ['padded2', 90], + ['padded3', 50], + ['padded4', 20] + ] + }, + type: 'gauge', + color: { + pattern: ['#FF0000', '#F97600', '#F6C600', '#60B044'], + threshold: { + values: [30, 80, 95] + } + } + }; + }); + + // espacially for gauges with multiple arcs to have the same coloring between legend tiles, tooltip tiles and arc + it('selects the color from color_pattern if color_treshold is given', function () { + var tileColor = []; + d3.selectAll('.c3-legend-item-tile').each(function () { + tileColor.push(d3.select(this).style('stroke')); + }); + expect(tileColor[0]).toBe('rgb(96, 176, 68)'); + expect(tileColor[1]).toBe('rgb(246, 198, 0)'); + expect(tileColor[2]).toBe('rgb(249, 118, 0)'); + expect(tileColor[3]).toBe('rgb(255, 0, 0)'); + }); + }); + + describe('legend item tile coloring without color_treshold', function () { + beforeAll(function () { + args = { + data: { + columns: [ + ['padded1', 100], + ['padded2', 90], + ['padded3', 50], + ['padded4', 20] + ], + colors: { + 'padded1': '#60b044', + 'padded4': '#8b008b' + } + }, + type: 'gauge' + }; + }); + + it('selects the color from data_colors, data_color or default', function () { + var tileColor = []; + d3.selectAll('.c3-legend-item-tile').each(function () { + tileColor.push(d3.select(this).style('stroke')); + }); + expect(tileColor[0]).toBe('rgb(96, 176, 68)'); + expect(tileColor[1]).toBe('rgb(31, 119, 180)'); + expect(tileColor[2]).toBe('rgb(255, 127, 14)'); + expect(tileColor[3]).toBe('rgb(139, 0, 139)'); + }); + }); }); diff --git a/src/arc.js b/src/arc.js index c30df2b..b4f7248 100644 --- a/src/arc.js +++ b/src/arc.js @@ -12,11 +12,13 @@ c3_chart_internal_fn.initPie = function () { c3_chart_internal_fn.updateRadius = function () { var $$ = this, config = $$.config, - w = config.gauge_width || config.donut_width; - $$.radiusExpanded = Math.min($$.arcWidth, $$.arcHeight) / 2; + w = config.gauge_width || config.donut_width, + gaugeArcWidth = $$.filterTargetsToShow($$.data.targets).length * $$.config.gauge_arcs_minWidth; + $$.radiusExpanded = Math.min($$.arcWidth, $$.arcHeight) / 2 * ($$.hasType('gauge') ? 0.85 : 1); $$.radius = $$.radiusExpanded * 0.95; $$.innerRadiusRatio = w ? ($$.radius - w) / $$.radius : 0.6; $$.innerRadius = $$.hasType('donut') || $$.hasType('gauge') ? $$.radius * $$.innerRadiusRatio : 0; + $$.gaugeArcWidth = w ? w : (gaugeArcWidth <= $$.radius - $$.innerRadius ? $$.radius - $$.innerRadius : (gaugeArcWidth <= $$.radius ? gaugeArcWidth : $$.radius)); }; c3_chart_internal_fn.updateArc = function () { @@ -61,8 +63,13 @@ c3_chart_internal_fn.updateAngle = function (d) { }; c3_chart_internal_fn.getSvgArc = function () { - var $$ = this, - arc = $$.d3.svg.arc().outerRadius($$.radius).innerRadius($$.innerRadius), + var $$ = this, hasGaugeType = $$.hasType('gauge'), + singleArcWidth = $$.gaugeArcWidth / $$.filterTargetsToShow($$.data.targets).length, + arc = $$.d3.svg.arc().outerRadius(function(d) { + return hasGaugeType ? $$.radius - singleArcWidth * d.index : $$.radius; + }).innerRadius(function(d) { + return hasGaugeType ? $$.radius - singleArcWidth * (d.index + 1) : $$.innerRadius; + }), newArc = function (d, withoutUpdate) { var updated; if (withoutUpdate) { return arc(d); } // for interpolate @@ -75,8 +82,15 @@ c3_chart_internal_fn.getSvgArc = function () { }; c3_chart_internal_fn.getSvgArcExpanded = function (rate) { - var $$ = this, - arc = $$.d3.svg.arc().outerRadius($$.radiusExpanded * (rate ? rate : 1)).innerRadius($$.innerRadius); + rate = rate || 1; + var $$ = this, hasGaugeType = $$.hasType('gauge'), + singleArcWidth = $$.gaugeArcWidth / $$.filterTargetsToShow($$.data.targets).length, + expandWidth = Math.min($$.radiusExpanded * rate - $$.radius, singleArcWidth * 0.8 - (1 - rate) * 100), + arc = $$.d3.svg.arc().outerRadius(function(d){ + return hasGaugeType ? $$.radius - singleArcWidth * d.index + expandWidth : $$.radiusExpanded * rate; + }).innerRadius(function(d){ + return hasGaugeType ? $$.radius - singleArcWidth * (d.index + 1) : $$.innerRadius; + }); return function (d) { var updated = $$.updateAngle(d); return updated ? arc(updated) : "M 0 0"; @@ -90,8 +104,8 @@ c3_chart_internal_fn.getArc = function (d, withoutUpdate, force) { c3_chart_internal_fn.transformForArcLabel = function (d) { var $$ = this, config = $$.config, - updated = $$.updateAngle(d), c, x, y, h, ratio, translate = ""; - if (updated && !$$.hasType('gauge')) { + updated = $$.updateAngle(d), c, x, y, h, ratio, translate = "", hasGauge = $$.hasType('gauge'); + if (updated && !hasGauge) { c = this.svgArc.centroid(updated); x = isNaN(c[0]) ? 0 : c[0]; y = isNaN(c[1]) ? 0 : c[1]; @@ -105,6 +119,12 @@ c3_chart_internal_fn.transformForArcLabel = function (d) { } translate = "translate(" + (x * ratio) + ',' + (y * ratio) + ")"; } + else if (updated && hasGauge && $$.filterTargetsToShow($$.data.targets).length > 1) { + var y1 = Math.sin(updated.endAngle - Math.PI / 2); + x = Math.cos(updated.endAngle - Math.PI / 2) * ($$.radiusExpanded + 25); + y = y1 * ($$.radiusExpanded + 15 - Math.abs(y1 * 10)) + 3; + translate = "translate(" + x + ',' + y + ")"; + } return translate; }; @@ -287,7 +307,7 @@ c3_chart_internal_fn.initArc = function () { c3_chart_internal_fn.redrawArc = function (duration, durationForExit, withTransform) { var $$ = this, d3 = $$.d3, config = $$.config, main = $$.main, - mainArc; + mainArc, backgroundArc, mainArcLabelLine, hasGaugeType = $$.hasType('gauge'); mainArc = main.selectAll('.' + CLASS.arcs).selectAll('.' + CLASS.arc) .data($$.arcData.bind($$)); mainArc.enter().append('path') @@ -300,6 +320,37 @@ c3_chart_internal_fn.redrawArc = function (duration, durationForExit, withTransf } this._current = d; }); + if (hasGaugeType) { + mainArcLabelLine = main.selectAll('.' + CLASS.arcs).selectAll('.' + CLASS.arcLabelLine) + .data($$.arcData.bind($$)); + mainArcLabelLine.enter().append('rect') + .attr("class", function (d) { return CLASS.arcLabelLine + ' ' + CLASS.target + ' ' + CLASS.target + '-' + d.data.id; }); + if ($$.filterTargetsToShow($$.data.targets).length === 1) { + mainArcLabelLine.style("display", "none"); + } + else { + mainArcLabelLine + .style("fill", function (d) { return config.color_pattern.length > 0 ? $$.levelColor(d.data.values[0].value) : $$.color(d.data); }) + .style("display", config.gauge_labelLine_show ? "" : "none") + .each(function (d) { + var lineLength = 0, lineThickness = 2, x = 0, y = 0, transform = ""; + if ($$.hiddenTargetIds.indexOf(d.data.id) < 0) { + var updated = $$.updateAngle(d), + innerLineLength = $$.gaugeArcWidth / $$.filterTargetsToShow($$.data.targets).length * (updated.index + 1), + lineAngle = updated.endAngle - Math.PI / 2, + arcInnerRadius = $$.radius - innerLineLength, + linePositioningAngle = lineAngle - (arcInnerRadius === 0 ? 0 : (1 / arcInnerRadius)); + lineLength = $$.radiusExpanded - $$.radius + innerLineLength; + x = Math.cos(linePositioningAngle) * arcInnerRadius; + y = Math.sin(linePositioningAngle) * arcInnerRadius; + transform = "rotate(" + (lineAngle * 180 / Math.PI) + ", " + x + ", " + y + ")"; + } + d3.select(this) + .attr({ x: x, y: y, width: lineLength, height: lineThickness, transform: transform }) + .style("stroke-dasharray", "0, " + (lineLength + lineThickness) + ", 0"); + }); + } + } mainArc .attr("transform", function (d) { return !$$.isGaugeType(d.data) && withTransform ? "scale(0)" : ""; }) .on('mouseover', config.interaction_enabled ? function (d) { @@ -393,22 +444,31 @@ c3_chart_internal_fn.redrawArc = function (duration, durationForExit, withTransf .attr('class', function (d) { return $$.isGaugeType(d.data) ? CLASS.gaugeValue : ''; }) .text($$.textForArcLabel.bind($$)) .attr("transform", $$.transformForArcLabel.bind($$)) - .style('font-size', function (d) { return $$.isGaugeType(d.data) ? Math.round($$.radius / 5) + 'px' : ''; }) + .style('font-size', function (d) { return $$.isGaugeType(d.data) && $$.filterTargetsToShow($$.data.targets).length === 1 ? Math.round($$.radius / 5) + 'px' : ''; }) .transition().duration(duration) .style("opacity", function (d) { return $$.isTargetToShow(d.data.id) && $$.isArcType(d.data) ? 1 : 0; }); main.select('.' + CLASS.chartArcsTitle) - .style("opacity", $$.hasType('donut') || $$.hasType('gauge') ? 1 : 0); + .style("opacity", $$.hasType('donut') || hasGaugeType ? 1 : 0); + + if (hasGaugeType) { + var index = 0; + backgroundArc = $$.arcs.select('g.' + CLASS.chartArcsBackground).selectAll('path.' + CLASS.chartArcsBackground).data($$.data.targets); + backgroundArc.enter().append("path"); + backgroundArc + .attr("class", function (d, i) { return CLASS.chartArcsBackground + ' ' + CLASS.chartArcsBackground +'-'+ i; }) + .attr("d", function (d1) { + if ($$.hiddenTargetIds.indexOf(d1.id) >= 0) { return "M 0 0"; } - if ($$.hasType('gauge')) { - $$.arcs.select('.' + CLASS.chartArcsBackground) - .attr("d", function () { var d = { data: [{value: config.gauge_max}], startAngle: config.gauge_startingAngle, - endAngle: -1 * config.gauge_startingAngle * (config.gauge_fullCircle ? Math.PI : 1) + endAngle: -1 * config.gauge_startingAngle * (config.gauge_fullCircle ? Math.PI : 1), + index: index++ }; return $$.getArc(d, true, true); }); + backgroundArc.exit().remove(); + $$.arcs.select('.' + CLASS.chartArcsGaugeUnit) .attr("dy", ".75em") .text(config.gauge_label_show ? config.gauge_units : ''); @@ -425,7 +485,7 @@ c3_chart_internal_fn.redrawArc = function (duration, durationForExit, withTransf c3_chart_internal_fn.initGauge = function () { var arcs = this.arcs; if (this.hasType('gauge')) { - arcs.append('path') + arcs.append('g') .attr("class", CLASS.chartArcsBackground); arcs.append("text") .attr("class", CLASS.chartArcsGaugeUnit) diff --git a/src/class.js b/src/class.js index fa18b13..7cc0b61 100644 --- a/src/class.js +++ b/src/class.js @@ -39,6 +39,7 @@ export default { circle: 'c3-circle', circles: 'c3-circles', arc: 'c3-arc', + arcLabelLine: 'c3-arc-label-line', arcs: 'c3-arcs', area: 'c3-area', areas: 'c3-areas', diff --git a/src/config.js b/src/config.js index d95e2ec..18c22ad 100644 --- a/src/config.js +++ b/src/config.js @@ -184,6 +184,7 @@ c3_chart_internal_fn.getDefaultConfig = function () { // gauge gauge_fullCircle: false, gauge_label_show: true, + gauge_labelLine_show: true, gauge_label_format: undefined, gauge_min: 0, gauge_max: 100, @@ -191,6 +192,7 @@ c3_chart_internal_fn.getDefaultConfig = function () { gauge_label_extents: undefined, gauge_units: undefined, gauge_width: undefined, + gauge_arcs_minWidth: 5, gauge_expand: {}, gauge_expand_duration: 50, // donut diff --git a/src/core.js b/src/core.js index ab3de03..fc99f98 100644 --- a/src/core.js +++ b/src/core.js @@ -205,11 +205,6 @@ c3_chart_internal_fn.initWithData = function (data) { $$.addHiddenLegendIds(config.legend_hide === true ? $$.mapToIds($$.data.targets) : config.legend_hide); } - // when gauge, hide legend // TODO: fix - if ($$.hasType('gauge')) { - config.legend_show = false; - } - // Init sizes and scales $$.updateSizes(); $$.updateScales(); @@ -761,7 +756,7 @@ c3_chart_internal_fn.getTranslate = function (target) { y = config.axis_rotated ? 0 : $$.height2; } else if (target === 'arc') { x = $$.arcWidth / 2; - y = $$.arcHeight / 2; + y = $$.arcHeight / 2 - ($$.hasType('gauge') ? 6 : 0);// to prevent wrong display of min and max label } return "translate(" + x + "," + y + ")"; }; diff --git a/src/legend.js b/src/legend.js index 355a8e0..1f4ec73 100644 --- a/src/legend.js +++ b/src/legend.js @@ -311,7 +311,9 @@ c3_chart_internal_fn.updateLegend = function (targetIds, options, transitions) { tiles = $$.legend.selectAll('line.' + CLASS.legendItemTile) .data(targetIds); (withTransition ? tiles.transition() : tiles) - .style('stroke', $$.color) + .style('stroke', $$.levelColor ? function(id) { + return $$.levelColor($$.cache[id].values[0].value); + } : $$.color) .attr('x1', x1ForLegendTile) .attr('y1', yForLegendTile) .attr('x2', x2ForLegendTile) diff --git a/src/scss/arc.scss b/src/scss/arc.scss index f49c034..c9b6e28 100644 --- a/src/scss/arc.scss +++ b/src/scss/arc.scss @@ -5,7 +5,7 @@ .c3-chart-arcs .c3-chart-arcs-background { fill: #e0e0e0; - stroke: none; + stroke: #FFF; } .c3-chart-arcs .c3-chart-arcs-gauge-unit { fill: #000; diff --git a/src/scss/chart.scss b/src/scss/chart.scss index c815cae..c6fcc1e 100644 --- a/src/scss/chart.scss +++ b/src/scss/chart.scss @@ -22,8 +22,13 @@ .c3-chart-arc path { stroke: #fff; +} +.c3-chart-arc rect { + stroke: white; + stroke-width: 1; } + .c3-chart-arc text { fill: #fff; font-size: 13px; diff --git a/src/tooltip.js b/src/tooltip.js index 94c841a..3e8108c 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -140,7 +140,7 @@ c3_chart_internal_fn.tooltipPosition = function (dataToShow, tWidth, tHeight, el // Determin tooltip position if (forArc) { tooltipLeft = (($$.width - ($$.isLegendRight ? $$.getLegendWidth() : 0)) / 2) + mouse[0]; - tooltipTop = ($$.height / 2) + mouse[1] + 20; + tooltipTop = ($$.hasType('gauge') ? $$.height : $$.height / 2) + mouse[1] + 20; } else { svgLeft = $$.getSvgLeft(true); if (config.axis_rotated) {