@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
369 lines (310 loc) • 11 kB
JavaScript
import { drawing as draw, geometry as geom } from '@progress/kendo-drawing';
import { ChartElement, ShapeBuilder, TextBox, Box, createPatternFill } from '../../core';
import PointEventsMixin from '../mixins/point-events-mixin';
import { OUTSIDE_END, INSIDE_END, PIE, FADEIN, TOOLTIP_OFFSET, CHART_POINT_ROLE, CHART_POINT_CLASSNAME, CHART_POINT_ROLE_DESCRIPTION } from '../constants';
import hasGradientOverlay from '../utils/has-gradient-overlay';
import { TOP, BOTTOM, LEFT, RIGHT, DEFAULT_FONT, CIRCLE, WHITE, CENTER, DEFAULT_PRECISION } from '../../common/constants';
import { autoTextColor, setDefaultOptions, getSpacing, getTemplate, deepExtend, round, rad } from '../../common';
import AccessibilityAttributesMixin from '../mixins/accessibility-attributes-mixin';
class PieSegment extends ChartElement {
constructor(value, sector, options) {
super(options);
this.value = value;
this.sector = sector;
}
render() {
if (this._rendered || this.visible === false) {
return;
}
this._rendered = true;
this.createLabel();
}
createLabel() {
const labels = this.options.labels;
const chartService = this.owner.chartService;
let labelText = this.getLabelText(labels);
if (labels.visible && (labelText || labelText === 0)) {
if (labels.position === CENTER || labels.position === INSIDE_END) {
if (!labels.color) {
labels.color = autoTextColor(this.options.color);
}
if (!labels.background) {
labels.background = this.options.color;
}
} else {
const themeLabels = chartService.theme.seriesDefaults.labels;
labels.color = labels.color || themeLabels.color;
labels.background = labels.background || themeLabels.background;
}
this.label = new TextBox(labelText, deepExtend({}, labels, {
align: CENTER,
vAlign: "",
animation: {
type: FADEIN,
delay: this.animationDelay
}
}), this.pointData());
this.append(this.label);
}
}
getLabelText(options) {
let labelTemplate = getTemplate(options);
if (labelTemplate) {
return labelTemplate(this.pointData());
}
return this.owner.chartService.format.auto(options.format, this.value);
}
reflow(targetBox) {
this.render();
this.box = targetBox;
this.reflowLabel();
}
reflowLabel() {
const { options: { labels: labelsOptions }, label } = this;
const sector = this.sector.clone();
const labelsDistance = labelsOptions.distance;
const angle = sector.middle();
if (label) {
const labelHeight = label.box.height();
const labelWidth = label.box.width();
let lp;
if (labelsOptions.position === CENTER) {
sector.radius = Math.abs((sector.radius - labelHeight) / 2) + labelHeight;
lp = sector.point(angle);
label.reflow(new Box(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else if (labelsOptions.position === INSIDE_END) {
sector.radius = sector.radius - labelHeight / 2;
lp = sector.point(angle);
label.reflow(new Box(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else {
let x1;
lp = sector.clone().expand(labelsDistance).point(angle);
if (lp.x >= sector.center.x) {
x1 = lp.x + labelWidth;
label.orientation = RIGHT;
} else {
x1 = lp.x - labelWidth;
label.orientation = LEFT;
}
label.reflow(new Box(x1, lp.y - labelHeight, lp.x, lp.y));
}
}
}
createVisual() {
const { sector, options } = this;
super.createVisual();
this.addAccessibilityAttributesToVisual();
if (this.value) {
if (options.visual) {
const startAngle = (sector.startAngle + 180) % 360;
const visual = options.visual({
category: this.category,
dataItem: this.dataItem,
value: this.value,
series: this.series,
percentage: this.percentage,
center: new geom.Point(sector.center.x, sector.center.y),
radius: sector.radius,
innerRadius: sector.innerRadius,
startAngle: startAngle,
endAngle: startAngle + sector.angle,
options: options,
sender: this.getSender(),
createVisual: () => {
const group = new draw.Group();
this.createSegmentVisual(group);
return group;
}
});
if (visual) {
this.visual.append(visual);
}
} else {
this.createSegmentVisual(this.visual);
}
}
}
createSegmentVisual(group) {
const { sector, options } = this;
const borderOptions = options.border || {};
const border = borderOptions.width > 0 ? {
stroke: {
color: borderOptions.color,
width: borderOptions.width,
opacity: borderOptions.opacity,
dashType: borderOptions.dashType
}
} : {};
const color = options.color;
const fill = createPatternFill(options.pattern, {
color: color,
opacity: options.opacity
});
const visual = this.createSegment(sector, deepExtend({
fill: fill,
stroke: {
opacity: options.opacity
},
zIndex: options.zIndex
}, border));
group.append(visual);
if (hasGradientOverlay(options)) {
group.append(this.createGradientOverlay(visual, {
baseColor: color,
fallbackFill: fill
}, deepExtend({
center: [ sector.center.x, sector.center.y ],
innerRadius: sector.innerRadius,
radius: sector.radius,
userSpace: true
}, options.overlay)));
}
}
createSegment(sector, options) {
if (options.singleSegment) {
return new draw.Circle(new geom.Circle(new geom.Point(sector.center.x, sector.center.y), sector.radius), options);
}
return ShapeBuilder.current.createRing(sector, options);
}
createAnimation() {
const { options, sector: { center } } = this;
deepExtend(options, {
animation: {
center: [ center.x, center.y ],
delay: this.animationDelay
}
});
super.createAnimation();
}
createHighlight(options) {
const highlight = this.options.highlight || {};
const border = highlight.border || {};
return this.createSegment(this.sector, deepExtend({}, options, {
fill: {
color: highlight.color,
opacity: highlight.opacity
},
stroke: {
opacity: border.opacity,
width: border.width,
color: border.color,
dashType: border.dashType
}
}));
}
highlightVisual() {
return this.visual.children[0];
}
highlightVisualArgs() {
const sector = this.sector;
return {
options: this.options,
radius: sector.radius,
innerRadius: sector.innerRadius,
center: new geom.Point(sector.center.x, sector.center.y),
startAngle: sector.startAngle,
endAngle: sector.angle + sector.startAngle,
visual: this.visual
};
}
createFocusHighlight(style) {
const borderWidth = this.options.focusHighlight.border.width;
const result = this.createSegment(this.sector, deepExtend({}, style, {
stroke: {
width: borderWidth * 2
}
}));
const clipPath = new draw.MultiPath();
clipPath.paths.push(draw.Path.fromRect(result.bbox()));
clipPath.paths.push(this.createSegment(this.sector, {}));
result.clip(clipPath);
return result;
}
tooltipAnchor() {
const sector = this.sector.clone().expand(TOOLTIP_OFFSET);
const midAndle = sector.middle();
const midPoint = sector.point(midAndle);
return {
point: midPoint,
align: tooltipAlignment(midAndle + 180)
};
}
formatValue(format) {
return this.owner.formatPointValue(this, format);
}
pointData() {
return {
dataItem: this.dataItem,
category: this.category,
value: this.value,
series: this.series,
percentage: this.percentage
};
}
getIndex() {
return this.index;
}
}
const RAD_30 = round(rad(30), DEFAULT_PRECISION);
const RAD_60 = round(rad(60), DEFAULT_PRECISION);
function tooltipAlignment(angle) {
const radians = rad(angle);
const sine = round(Math.sin(radians), DEFAULT_PRECISION);
const cosine = round(Math.cos(radians), DEFAULT_PRECISION);
let horizontal;
if (Math.abs(sine) > RAD_60) {
horizontal = CENTER;
} else if (cosine < 0) {
horizontal = RIGHT;
} else {
horizontal = LEFT;
}
let vertical;
if (Math.abs(sine) < RAD_30) {
vertical = CENTER;
} else if (sine < 0) {
vertical = BOTTOM;
} else {
vertical = TOP;
}
return {
horizontal: horizontal,
vertical: vertical
};
}
setDefaultOptions(PieSegment, {
color: WHITE,
overlay: {
gradient: "roundedBevel"
},
border: {
width: 0.5
},
labels: {
visible: false,
distance: 35,
font: DEFAULT_FONT,
margin: getSpacing(0.5),
align: CIRCLE,
zIndex: 1,
position: OUTSIDE_END
},
animation: {
type: PIE
},
highlight: {
visible: true,
border: {
width: 1
}
},
visible: true,
accessibility: {
role: CHART_POINT_ROLE,
className: CHART_POINT_CLASSNAME,
ariaRoleDescription: CHART_POINT_ROLE_DESCRIPTION
}
});
deepExtend(PieSegment.prototype, PointEventsMixin);
deepExtend(PieSegment.prototype, AccessibilityAttributesMixin);
export default PieSegment;