@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
335 lines (272 loc) • 9.24 kB
JavaScript
import { drawing as draw, Color } from '@progress/kendo-drawing';
import BarLabel from './bar-label';
import { CHART_POINT_ROLE_DESCRIPTION, CHART_POINT_CLASSNAME, CHART_POINT_ROLE, BORDER_BRIGHTNESS, TOOLTIP_OFFSET } from '../constants';
import hasGradientOverlay from '../utils/has-gradient-overlay';
import { ChartElement, createPatternFill, Point, Box } from '../../core';
import PointEventsMixin from '../mixins/point-events-mixin';
import NoteMixin from '../mixins/note-mixin';
import AccessibilityAttributesMixin from '../mixins/accessibility-attributes-mixin';
import { WHITE, LEFT, RIGHT, BOTTOM, TOP } from '../../common/constants';
import { alignPathToPixel, deepExtend, defined, getTemplate, valueOrDefault } from '../../common';
import unclipBox from '../utils/unclip-box';
const BAR_ALIGN_MIN_WIDTH = 6;
class Bar extends ChartElement {
constructor(value, options) {
super();
this.options = options;
this.color = options.color || WHITE;
this.aboveAxis = valueOrDefault(this.options.aboveAxis, true);
this.value = value;
}
render() {
if (this._rendered) {
return;
}
this._rendered = true;
this.createLabel();
this.createNote();
if (this.errorBar) {
this.append(this.errorBar);
}
}
createLabel() {
const options = this.options;
const labels = options.labels;
if (labels.visible) {
this.label = this.createLabelElement(labels);
this.append(this.label);
}
}
createLabelElement(options) {
return new BarLabel(this.getLabelText(options),
deepExtend({
vertical: this.options.vertical
},
options
), this.pointData());
}
getLabelText(options) {
let labelTemplate = getTemplate(options);
if (labelTemplate) {
return labelTemplate(this.pointData());
}
return this.formatValue(options.format);
}
formatValue(format) {
return this.owner.formatPointValue(this, format);
}
reflow(targetBox) {
this.render();
const label = this.label;
this.box = targetBox;
if (label) {
label.options.aboveAxis = this.aboveAxis;
label.reflow(targetBox);
}
if (this.note) {
this.note.reflow(targetBox);
}
if (this.errorBars) {
for (let i = 0; i < this.errorBars.length; i++) {
this.errorBars[i].reflow(targetBox);
}
}
}
createVisual() {
const { box, options } = this;
const customVisual = options.visual;
if (this.visible !== false) {
super.createVisual();
this.addAccessibilityAttributesToVisual();
if (customVisual) {
const visual = this.rectVisual = customVisual({
category: this.category,
dataItem: this.dataItem,
value: this.value,
sender: this.getSender(),
series: this.series,
percentage: this.percentage,
stackValue: this.stackValue,
runningTotal: this.runningTotal,
total: this.total,
rect: box.toRect(),
createVisual: () => {
const group = new draw.Group();
this.createRect(group);
return group;
},
options: options
});
if (visual) {
this.visual.append(visual);
}
} else if (box.width() > 0 && box.height() > 0) {
this.createRect(this.visual);
}
}
}
createRect(visual) {
const options = this.options;
const border = options.border;
const strokeOpacity = defined(border.opacity) ? border.opacity : options.opacity;
const rect = this.box.toRect();
rect.size.width = Math.round(rect.size.width);
const path = this.rectVisual = draw.Path.fromRect(rect, {
fill: createPatternFill(options.pattern, {
color: this.color,
opacity: options.opacity
}),
stroke: {
color: this.getBorderColor(),
width: border.width,
opacity: strokeOpacity,
dashType: border.dashType
}
});
const width = this.box.width();
const height = this.box.height();
const size = options.vertical ? width : height;
if (size > BAR_ALIGN_MIN_WIDTH) {
alignPathToPixel(path);
// Fixes lineJoin issue in firefox when the joined lines are parallel
if (width < 1 || height < 1) {
path.options.stroke.lineJoin = "round";
}
}
visual.append(path);
if (hasGradientOverlay(options)) {
const overlay = this.createGradientOverlay(path, { baseColor: this.color }, deepExtend({
end: !options.vertical ? [ 0, 1 ] : undefined
}, options.overlay));
visual.append(overlay);
}
}
createHighlight(style) {
const highlight = draw.Path.fromRect(this.box.toRect(), style);
return alignPathToPixel(highlight);
}
highlightVisual() {
return this.rectVisual;
}
highlightVisualArgs() {
return {
options: this.options,
rect: this.box.toRect(),
visual: this.rectVisual
};
}
createFocusHighlight(style) {
const borderWidth = this.options.focusHighlight.border.width;
const highlight = draw.Path.fromRect(this.box.pad(borderWidth / 2).toRect(), style);
return alignPathToPixel(highlight);
}
getBorderColor() {
const color = this.color;
const border = this.options.border;
const brightness = border._brightness || BORDER_BRIGHTNESS;
let borderColor = border.color;
if (!defined(borderColor)) {
borderColor = new Color(color).brightness(brightness).toHex();
}
return borderColor;
}
tooltipAnchor() {
const { options, box, aboveAxis } = this;
const clipBox = this.owner.pane.clipBox() || box;
let horizontalAlign = LEFT;
let verticalAlign = TOP;
let x, y;
if (options.vertical) {
x = Math.min(box.x2, clipBox.x2) + TOOLTIP_OFFSET;
if (aboveAxis) {
y = Math.max(box.y1, clipBox.y1);
} else {
y = Math.min(box.y2, clipBox.y2);
verticalAlign = BOTTOM;
}
} else {
const x1 = Math.max(box.x1, clipBox.x1);
const x2 = Math.min(box.x2, clipBox.x2);
if (options.isStacked) {
verticalAlign = BOTTOM;
if (aboveAxis) {
horizontalAlign = RIGHT;
x = x2;
} else {
x = x1;
}
y = Math.max(box.y1, clipBox.y1) - TOOLTIP_OFFSET;
} else {
if (aboveAxis) {
x = x2 + TOOLTIP_OFFSET;
} else {
x = x1 - TOOLTIP_OFFSET;
horizontalAlign = RIGHT;
}
y = Math.max(box.y1, clipBox.y1);
}
}
return {
point: new Point(x, y),
align: {
horizontal: horizontalAlign,
vertical: verticalAlign
}
};
}
overlapsBox(box) {
return this.box.overlaps(box);
}
unclipBox() {
const label = this.label && this.label.textBox;
return unclipBox(this.box.clone(), [label, this.note]);
}
labelBox() {
const label = this.label && this.label.textBox;
return label ? label.box : new Box();
}
noteBox() {
return this.note ? this.note.box : new Box();
}
pointData() {
return {
dataItem: this.dataItem,
category: this.category,
value: this.value,
percentage: this.percentage,
stackValue: this.stackValue,
runningTotal: this.runningTotal,
total: this.total,
series: this.series
};
}
getIndex() {
return this.categoryIx;
}
}
deepExtend(Bar.prototype, PointEventsMixin);
deepExtend(Bar.prototype, NoteMixin);
deepExtend(Bar.prototype, AccessibilityAttributesMixin);
Bar.prototype.defaults = {
border: {
width: 1
},
vertical: true,
overlay: {
gradient: "glass"
},
labels: {
visible: false,
format: "{0}"
},
opacity: 1,
notes: {
label: {}
},
accessibility: {
role: CHART_POINT_ROLE,
className: CHART_POINT_CLASSNAME,
ariaRoleDescription: CHART_POINT_ROLE_DESCRIPTION
}
};
export default Bar;