@carbon/charts
Version:
Carbon charting components
397 lines • 17.4 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// Internal Imports
import { Component } from '../component';
import { DOMUtils } from '../../services';
import { Tools } from '../../tools';
import { CalloutDirections, Roles, Events, Alignments, ColorClassNameTypes, } from '../../interfaces';
import * as Configuration from '../../configuration';
// D3 Imports
import { select } from 'd3-selection';
import { arc, pie } from 'd3-shape';
import { interpolate } from 'd3-interpolate';
// Pie slice tween function
function arcTween(a, arcFunc) {
var _this = this;
var i = interpolate(this._current, a);
return function (t) {
_this._current = i(t);
return arcFunc(_this._current);
};
}
var Pie = /** @class */ (function (_super) {
__extends(Pie, _super);
function Pie() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.type = 'pie';
// Highlight elements that match the hovered legend item
_this.handleLegendOnHover = function (event) {
var hoveredElement = event.detail.hoveredElement;
var groupMapsTo = _this.getOptions().data.groupMapsTo;
_this.parent
.selectAll('path.slice')
.transition(_this.services.transitions.getTransition('legend-hover-bar'))
.attr('opacity', function (d) {
return d.data[groupMapsTo] !== hoveredElement.datum()['name'] ? 0.3 : 1;
});
};
// Un-highlight all elements
_this.handleLegendMouseOut = function (event) {
_this.parent
.selectAll('path.slice')
.transition(_this.services.transitions.getTransition('legend-mouseout-bar'))
.attr('opacity', 1);
};
return _this;
}
Pie.prototype.init = function () {
var eventsFragment = this.services.events;
// Highlight correct circle on legend item hovers
eventsFragment.addEventListener(Events.Legend.ITEM_HOVER, this.handleLegendOnHover);
// Un-highlight circles on legend item mouseouts
eventsFragment.addEventListener(Events.Legend.ITEM_MOUSEOUT, this.handleLegendMouseOut);
};
Pie.prototype.getInnerRadius = function () {
return Configuration.pie.innerRadius;
};
Pie.prototype.render = function (animate) {
var _this = this;
if (animate === void 0) { animate = true; }
var self = this;
var svg = this.getContainerSVG();
// remove any slices that are valued at 0 because they dont need to be rendered and will create extra padding
var displayData = this.model
.getDisplayData()
.filter(function (data) { return data.value > 0; });
var options = this.getOptions();
var groupMapsTo = options.data.groupMapsTo;
// Compute the outer radius needed
var radius = this.computeRadius();
this.arc = arc().innerRadius(this.getInnerRadius()).outerRadius(radius);
// Set the hover arc radius
this.hoverArc = arc()
.innerRadius(this.getInnerRadius())
.outerRadius(radius + Configuration.pie.hoverArc.outerRadiusOffset);
// Setup the pie layout
var pieLayout = pie()
.value(function (d) { return d.value; })
.sort(null)
.padAngle(Configuration.pie.padAngle);
// Sort pie layout data based off of the indecies the layout creates
var pieLayoutData = pieLayout(displayData).sort(function (a, b) { return a.index - b.index; });
// Update data on all slices
var slicesGroup = DOMUtils.appendOrSelect(svg, 'g.slices')
.attr('role', Roles.GROUP)
.attr('data-name', 'slices');
var paths = slicesGroup
.selectAll('path.slice')
.data(pieLayoutData, function (d) { return d.data[groupMapsTo]; });
// Remove slices that need to be exited
paths.exit().attr('opacity', 0).remove();
// Add new slices that are being introduced
var enteringPaths = paths
.enter()
.append('path')
.classed('slice', true)
.attr('opacity', 0);
// Update styles & position on existing and entering slices
enteringPaths
.merge(paths)
.attr('class', function (d) {
return _this.model.getColorClassName({
classNameTypes: [ColorClassNameTypes.FILL],
dataGroupName: d.data[groupMapsTo],
originalClassName: 'slice',
});
})
.style('fill', function (d) { return self.model.getFillColor(d.data[groupMapsTo]); })
.attr('d', this.arc)
.transition(this.services.transitions.getTransition('pie-slice-enter-update', animate))
.attr('opacity', 1)
// a11y
.attr('role', Roles.GRAPHICS_SYMBOL)
.attr('aria-roledescription', 'slice')
.attr('aria-label', function (d) {
return d.value + ", " + (Tools.convertValueToPercentage(d.data.value, displayData) + '%');
})
// Tween
.attrTween('d', function (a) {
return arcTween.bind(this)(a, self.arc);
});
// Draw the slice labels
var renderLabels = options.pie.labels.enabled;
var labelData = renderLabels
? pieLayoutData.filter(function (x) { return x.value > 0; })
: [];
var labelsGroup = DOMUtils.appendOrSelect(svg, 'g.labels')
.attr('role', Roles.GROUP)
.attr('data-name', 'labels');
var labels = labelsGroup
.selectAll('text.pie-label')
.data(labelData, function (d) { return d.data[groupMapsTo]; });
// Remove labels that are existing
labels.exit().attr('opacity', 0).remove();
// Add labels that are being introduced
var enteringLabels = labels
.enter()
.append('text')
.classed('pie-label', true);
// Update styles & position on existing & entering labels
var calloutData = [];
enteringLabels
.merge(labels)
.style('text-anchor', 'middle')
.text(function (d) {
if (options.pie.labels.formatter) {
return options.pie.labels.formatter(d);
}
return (Tools.convertValueToPercentage(d.data.value, displayData) +
'%');
})
// Calculate dimensions in order to transform
.datum(function (d) {
var marginedRadius = radius + 7;
var theta = (d.endAngle - d.startAngle) / 2 + d.startAngle;
var deg = (theta / Math.PI) * 180;
var textLength = this.getComputedTextLength();
d.textOffsetX = textLength / 2;
d.textOffsetY = deg > 90 && deg < 270 ? 10 : 0;
d.xPosition =
(d.textOffsetX + marginedRadius) * Math.sin(theta);
d.yPosition =
(d.textOffsetY + marginedRadius) * -Math.cos(theta);
return d;
})
.attr('transform', function (d, i) {
var totalSlices = labelData.length;
var sliceAngleDeg = (d.endAngle - d.startAngle) * (180 / Math.PI);
// check if last 2 slices (or just last) are < the threshold
if (i >= totalSlices - 2) {
if (sliceAngleDeg < Configuration.pie.callout.minSliceDegree) {
var labelTranslateX = void 0, labelTranslateY = void 0;
if (d.index === totalSlices - 1) {
labelTranslateX =
d.xPosition +
Configuration.pie.callout.offsetX +
Configuration.pie.callout.textMargin +
d.textOffsetX;
labelTranslateY =
d.yPosition - Configuration.pie.callout.offsetY;
// Set direction of callout
d.direction = CalloutDirections.RIGHT;
calloutData.push(d);
}
else {
labelTranslateX =
d.xPosition -
Configuration.pie.callout.offsetX -
d.textOffsetX -
Configuration.pie.callout.textMargin;
labelTranslateY =
d.yPosition - Configuration.pie.callout.offsetY;
// Set direction of callout
d.direction = CalloutDirections.LEFT;
calloutData.push(d);
}
return "translate(" + labelTranslateX + ", " + labelTranslateY + ")";
}
}
return "translate(" + d.xPosition + ", " + d.yPosition + ")";
});
// Render pie label callouts
this.renderCallouts(calloutData);
var optionName = Tools.getProperty(options, 'donut')
? 'donut'
: 'pie';
var alignment = Tools.getProperty(options, optionName, 'alignment');
var width = DOMUtils.getSVGElementSize(this.getParent(), {
useAttr: true,
}).width;
// don't add padding for labels & callouts if they are disabled
var xOffset = renderLabels ? Configuration.pie.xOffset : 0;
var yOffset = renderLabels ? Configuration.pie.yOffset : 0;
// Position Pie
var pieTranslateX = radius + xOffset;
if (alignment === Alignments.CENTER) {
pieTranslateX = width / 2;
}
else if (alignment === Alignments.RIGHT) {
pieTranslateX = width - radius - Configuration.pie.xOffset;
}
var pieTranslateY = radius + yOffset;
if (calloutData.length > 0) {
pieTranslateY += Configuration.pie.yOffsetCallout;
}
svg.attr('transform', "translate(" + pieTranslateX + ", " + pieTranslateY + ")");
// Add event listeners
this.addEventListeners();
};
Pie.prototype.renderCallouts = function (calloutData) {
var svg = DOMUtils.appendOrSelect(this.getContainerSVG(), 'g.callouts')
.attr('role', Roles.GROUP)
.attr('data-name', 'callouts');
// Update data on callouts
var callouts = svg.selectAll('g.callout').data(calloutData);
callouts.exit().remove();
var enteringCallouts = callouts
.enter()
.append('g')
.classed('callout', true)
// a11y
.attr('role', Roles.GRAPHICS_SYMBOL + " " + Roles.GROUP)
.attr('aria-roledescription', 'label callout');
// Update data values for each callout
// For the horizontal and vertical lines to use
enteringCallouts.merge(callouts).datum(function (d) {
var xPosition = d.xPosition, yPosition = d.yPosition, direction = d.direction;
if (direction === CalloutDirections.RIGHT) {
d.startPos = {
x: xPosition,
y: yPosition + d.textOffsetY,
};
// end position for the callout line
d.endPos = {
x: xPosition + Configuration.pie.callout.offsetX,
y: yPosition -
Configuration.pie.callout.offsetY +
d.textOffsetY,
};
// the intersection point of the vertical and horizontal line
d.intersectPointX =
d.endPos.x - Configuration.pie.callout.horizontalLineLength;
}
else {
// start position for the callout line
d.startPos = {
x: xPosition,
y: yPosition + d.textOffsetY,
};
// end position for the callout line should be bottom aligned to the title
d.endPos = {
x: xPosition - Configuration.pie.callout.offsetX,
y: yPosition -
Configuration.pie.callout.offsetY +
d.textOffsetY,
};
// the intersection point of the vertical and horizontal line
d.intersectPointX =
d.endPos.x + Configuration.pie.callout.horizontalLineLength;
}
// Store the necessary data in the DOM element
return d;
});
// draw vertical line
var enteringVerticalLines = enteringCallouts
.append('line')
.classed('vertical-line', true);
enteringVerticalLines
.merge(svg.selectAll('line.vertical-line'))
.datum(function (d) {
return select(this.parentNode).datum();
})
.style('stroke-width', '1px')
.attr('x1', function (d) { return d.startPos.x; })
.attr('y1', function (d) { return d.startPos.y; })
.attr('x2', function (d) { return d.intersectPointX; })
.attr('y2', function (d) { return d.endPos.y; });
// draw horizontal line
var enteringHorizontalLines = enteringCallouts
.append('line')
.classed('horizontal-line', true);
enteringHorizontalLines
.merge(svg.selectAll('line.horizontal-line'))
.datum(function (d) {
return select(this.parentNode).datum();
})
.style('stroke-width', '1px')
.attr('x1', function (d) { return d.intersectPointX; })
.attr('y1', function (d) { return d.endPos.y; })
.attr('x2', function (d) { return d.endPos.x; })
.attr('y2', function (d) { return d.endPos.y; });
};
Pie.prototype.addEventListeners = function () {
var self = this;
this.parent
.selectAll('path.slice')
.on('mouseover', function (datum) {
var hoveredElement = select(this);
hoveredElement
.classed('hovered', true)
.transition(self.services.transitions.getTransition('pie_slice_mouseover'))
.attr('d', self.hoverArc);
// Dispatch mouse event
self.services.events.dispatchEvent(Events.Pie.SLICE_MOUSEOVER, {
element: select(this),
datum: datum,
});
var groupMapsTo = self.getOptions().data.groupMapsTo;
// Show tooltip
self.services.events.dispatchEvent(Events.Tooltip.SHOW, {
hoveredElement: hoveredElement,
items: [
{
label: datum.data[groupMapsTo],
value: datum.data.value,
},
],
});
})
.on('mousemove', function (datum) {
var hoveredElement = select(this);
// Dispatch mouse event
self.services.events.dispatchEvent(Events.Pie.SLICE_MOUSEMOVE, {
element: hoveredElement,
datum: datum,
});
// Show tooltip
self.services.events.dispatchEvent(Events.Tooltip.MOVE);
})
.on('click', function (datum) {
// Dispatch mouse event
self.services.events.dispatchEvent(Events.Pie.SLICE_CLICK, {
element: select(this),
datum: datum,
});
})
.on('mouseout', function (datum) {
var hoveredElement = select(this);
hoveredElement
.classed('hovered', false)
.transition(self.services.transitions.getTransition('pie_slice_mouseover'))
.attr('d', self.arc);
// Dispatch mouse event
self.services.events.dispatchEvent(Events.Pie.SLICE_MOUSEOUT, {
element: hoveredElement,
datum: datum,
});
// Hide tooltip
self.services.events.dispatchEvent(Events.Tooltip.HIDE, {
hoveredElement: hoveredElement,
});
});
};
// Helper functions
Pie.prototype.computeRadius = function () {
var _a = DOMUtils.getSVGElementSize(this.parent, {
useAttrs: true,
}), width = _a.width, height = _a.height;
var options = this.getOptions();
var radius = Math.min(width, height) / 2;
var renderLabels = options.pie.labels.enabled;
return renderLabels ? radius + Configuration.pie.radiusOffset : radius;
};
return Pie;
}(Component));
export { Pie };
//# sourceMappingURL=../../../src/components/graphs/pie.js.map