veritatisquam
Version:
Carbon charting components, lerna monorepo
145 lines (124 loc) • 3.83 kB
text/typescript
// Internal Imports
import { Component } from '../component';
import * as Configuration from '../../configuration';
import {
Roles,
ScaleTypes,
Events,
ColorClassNameTypes,
CartesianOrientations,
} from '../../interfaces';
// D3 Imports
import { area } from 'd3-shape';
export class StackedArea extends Component {
type = 'area-stacked';
areaGenerator: any;
init() {
const eventsFragment = this.services.events;
// Highlight correct area on legend item hovers
eventsFragment.addEventListener(
Events.Legend.ITEM_HOVER,
this.handleLegendOnHover
);
// Un-highlight area on legend item mouseouts
eventsFragment.addEventListener(
Events.Legend.ITEM_MOUSEOUT,
this.handleLegendMouseOut
);
}
render(animate = true) {
const svg = this.getContainerSVG({ withinChartClip: true });
const self = this;
const options = this.getOptions();
const { groupMapsTo } = options.data;
const percentage = Object.keys(options.axes).some(
(axis) => options.axes[axis].percentage
);
const stackedData = this.model.getStackedData({
percentage,
groups: this.configs.groups,
});
// area doesnt have to use the main range and domain axes - they can be mapped to the secondary (in the case of a combo chart)
// however area _cannot_ have multiple datasets that are mapped to _different_ ranges and domains so we can use the first data item
const domainAxisPosition = this.services.cartesianScales.getDomainAxisPosition(
{ datum: stackedData[0][0] }
);
const rangeAxisPosition = this.services.cartesianScales.getRangeAxisPosition(
{ datum: stackedData[0][0] }
);
const mainYScale = this.services.cartesianScales.getScaleByPosition(
rangeAxisPosition
);
const areas = svg
.selectAll('path.area')
.data(stackedData, (d) => d[0][groupMapsTo]);
// D3 area generator function
this.areaGenerator = area()
.x((d: any, i) =>
this.services.cartesianScales.getValueThroughAxisPosition(
domainAxisPosition,
d.data.sharedStackKey,
i
)
)
.y0((d) => mainYScale(d[0]))
.y1((d) => mainYScale(d[1]))
.curve(this.services.curves.getD3Curve());
areas.exit().attr('opacity', 0).remove();
const enteringAreas = areas.enter().append('path').attr('opacity', 0);
enteringAreas
.merge(areas)
.data(stackedData, (d) => d[0][groupMapsTo])
.attr('class', 'area')
.attr('class', (d) =>
this.model.getColorClassName({
classNameTypes: [ColorClassNameTypes.FILL],
dataGroupName: d[0][groupMapsTo],
originalClassName: 'area',
})
)
.attr('fill', (d) => self.model.getFillColor(d[0][groupMapsTo]))
.attr('role', Roles.GRAPHICS_SYMBOL)
.attr('aria-roledescription', 'area')
.transition(
this.services.transitions.getTransition(
'area-update-enter',
animate
)
)
.attr('opacity', Configuration.area.opacity.selected)
.attr('d', this.areaGenerator);
}
handleLegendOnHover = (event: CustomEvent) => {
const { hoveredElement } = event.detail;
const options = this.getOptions();
const { groupMapsTo } = options.data;
this.parent
.selectAll('path.area')
.transition(
this.services.transitions.getTransition('legend-hover-area')
)
.attr('opacity', (d) => {
if (d[0][groupMapsTo] !== hoveredElement.datum().name) {
return Configuration.area.opacity.unselected;
}
return Configuration.area.opacity.selected;
});
};
handleLegendMouseOut = (event: CustomEvent) => {
this.parent
.selectAll('path.area')
.transition(
this.services.transitions.getTransition('legend-mouseout-area')
)
.attr('opacity', Configuration.area.opacity.selected);
};
destroy() {
// Remove event listeners
this.parent
.selectAll('path.area')
.on('mouseover', null)
.on('mousemove', null)
.on('mouseout', null);
}
}