src/UXClient/Components/PieChart/PieChart.ts (213 lines of code) (raw):
import * as d3 from 'd3';
import './PieChart.scss';
import Utils from "../../Utils";
import { TooltipMeasureFormat } from "./../../Constants/Enums";
import Legend from './../Legend';
import ContextMenu from './../ContextMenu';
import { PieChartData } from '../../Models/PieChartData';
import Slider from '../Slider';
import Tooltip from '../Tooltip';
import { ChartVisualizationComponent } from '../../Interfaces/ChartVisualizationComponent';
class PieChart extends ChartVisualizationComponent {
private contextMenu: ContextMenu;
chartComponentData = new PieChartData();
constructor(renderTarget: Element){
super(renderTarget);
this.chartMargins = {
top: 20,
bottom: 28,
left: 0,
right: 0
}
}
PieChart() { }
public render(data: any, options: any, aggregateExpressionOptions: any) {
super.render(data, options, aggregateExpressionOptions);
this.chartComponentData.mergeDataToDisplayStateAndTimeArrays(this.data, this.chartOptions.timestamp, this.aggregateExpressionOptions);
var timestamp = (options && options.timestamp != undefined) ? options.timestamp : this.chartComponentData.allTimestampsArray[0];
var targetElement = d3.select(this.renderTarget)
.classed("tsi-pieChart", true);
if (this.svgSelection == null) {
this.svgSelection = targetElement.append("svg")
.attr("class", "tsi-pieChartSVG tsi-chartSVG")
.attr('title', this.getString('Pie chart'));
var g = this.svgSelection.append("g");
var tooltip = new Tooltip(d3.select(this.renderTarget));
d3.select(this.renderTarget).append('div').classed('tsi-sliderWrapper', true);
this.draw = (isFromResize = false) => {
// Determine the number of timestamps present, add margin for slider
if(this.chartComponentData.allTimestampsArray.length > 1)
this.chartMargins.bottom = 68;
if(this.chartOptions.legend == "compact") {
this.chartMargins.top = 68;
} else {
this.chartMargins.top = 20;
}
this.width = this.getWidth();
var height = +targetElement.node().getBoundingClientRect().height;
if (!isFromResize) {
this.chartWidth = this.getChartWidth();
}
var chartHeight = height;
var usableHeight = height - this.chartMargins.bottom - this.chartMargins.top
var outerRadius = (Math.min(usableHeight, this.chartWidth) - 10) / 2;
var innerRadius = this.chartOptions.arcWidthRatio &&
(this.chartOptions.arcWidthRatio < 1 && this.chartOptions.arcWidthRatio > 0) ?
outerRadius - (outerRadius * this.chartOptions.arcWidthRatio) :
0;
this.svgSelection
.attr("width", this.chartWidth)
.attr("height", chartHeight)
this.svgSelection.select("g").attr("transform", "translate(" + (this.chartWidth / 2) + "," + (chartHeight / 2) + ")");
var timestamp = (this.chartOptions.timestamp != undefined) ? this.chartOptions.timestamp : this.chartComponentData.allTimestampsArray[0];
this.chartComponentData.updateFlatValueArray(timestamp);
super.themify(targetElement, this.chartOptions.theme);
if (!this.chartOptions.hideChartControlPanel && this.chartControlsPanel === null) {
this.chartControlsPanel = Utils.createControlPanel(this.renderTarget, this.CONTROLSWIDTH, this.chartMargins.top, this.chartOptions);
} else if (this.chartOptions.hideChartControlPanel && this.chartControlsPanel !== null){
this.removeControlPanel();
}
if (this.ellipsisItemsExist() && !this.chartOptions.hideChartControlPanel) {
this.drawEllipsisMenu();
this.chartControlsPanel.style("top", Math.max((this.chartMargins.top - 24), 0) + 'px');
} else {
this.removeControlPanel();
}
var labelMouseover = (aggKey: string, splitBy: string = null) => {
//filter out the selected timeseries/splitby
var selectedFilter = (d: any, j: number ) => {
return !(d.data.aggKey == aggKey && (splitBy == null || d.data.splitBy == splitBy))
}
this.svgSelection.selectAll(".tsi-pie-path")
.filter(selectedFilter)
.attr("stroke-opacity", .3)
.attr("fill-opacity", .3);
}
var labelMouseout = (aggregateKey: string, splitBy: string) => {
this.svgSelection.selectAll(".tsi-pie-path")
.attr("stroke-opacity", 1)
.attr("fill-opacity", 1);
}
function drawTooltip (d: any, mousePosition) {
var xPos = mousePosition[0];
var yPos = mousePosition[1];
tooltip.render(self.chartOptions.theme);
let color = Utils.colorSplitBy(self.chartComponentData.displayState, d.data.splitByI, d.data.aggKey, self.chartOptions.keepSplitByColor);
tooltip.draw(d, self.chartComponentData, xPos, yPos, {...self.chartMargins, top: 0, bottom: 0}, (text) => {
self.tooltipFormat(self.convertToTimeValueFormat(d.data), text, TooltipMeasureFormat.SingleValue);
}, null, 20, 20, color);
}
this.legendObject.draw(this.chartOptions.legend, this.chartComponentData, labelMouseover,
this.svgSelection, this.chartOptions, labelMouseout);
var pie = d3.pie()
.sort(null)
.value(function(d: any) {
return Math.abs(d.val);
});
var path: any = d3.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
var arc = g.selectAll(".tsi-pie-arc")
.data(pie(this.chartComponentData.flatValueArray));
var arcEntered = arc
.enter().append("g")
.merge(arc)
.attr("class", "tsi-pie-arc");
var self = this;
var drawArc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return drawArc(i(t));
};
}
var self = this;
function pathMouseout (d: any) {
if (self.contextMenu && self.contextMenu.contextMenuVisible)
return;
tooltip.hide();
labelMouseout(d.data.aggKey, d.data.splitBy);
(<any>self.legendObject.legendElement.selectAll('.tsi-splitByLabel')).classed("inFocus", false);
}
function pathMouseInteraction (d: any) {
if (this.contextMenu && this.contextMenu.contextMenuVisible)
return;
pathMouseout(d);
labelMouseover(d.data.aggKey, d.data.splitBy);
(<any>self.legendObject.legendElement.selectAll('.tsi-splitByLabel').filter(function (filteredSplitBy: string) {
return (d3.select(this.parentNode).datum() == d.data.aggKey) && (filteredSplitBy == d.data.splitBy);
})).classed("inFocus", true);
drawTooltip(d, d3.mouse(self.svgSelection.node()));
}
var mouseOutArcOnContextMenuClick = () => {
arcEntered.selectAll("path").each(pathMouseout);
}
arcEntered.each(function () {
var pathElem = d3.select(this).selectAll<SVGPathElement, unknown>(".tsi-pie-path").data(d => [d]);
var pathEntered = pathElem.enter()
.append("path")
.attr("class", "tsi-pie-path")
.attr("d", drawArc)
.on("mouseover", pathMouseInteraction)
.on("mousemove" , pathMouseInteraction)
.on("mouseout", pathMouseout)
.on("contextmenu", (d: any, i) => {
if (self.chartComponentData.displayState[d.data.aggKey].contextMenuActions &&
self.chartComponentData.displayState[d.data.aggKey].contextMenuActions.length) {
var mousePosition = d3.mouse(<any>targetElement.node());
d3.event.preventDefault();
self.contextMenu.draw(self.chartComponentData, self.renderTarget, self.chartOptions,
mousePosition, d.data.aggKey, d.data.splitBy, mouseOutArcOnContextMenuClick,
new Date(self.chartComponentData.timestamp));
}
})
.each(function(d) { (<any>this)._current = d; })
.merge(pathElem as d3.Selection<SVGPathElement, unknown, any, unknown>)
.transition()
.duration(self.TRANSDURATION)
.ease(d3.easeExp)
.attrTween("d", arcTween)
.attr("fill", (d: any) => {
return Utils.colorSplitBy(self.chartComponentData.displayState, d.data.splitByI, d.data.aggKey, self.chartOptions.keepSplitByColor);
})
.attr("class", "tsi-pie-path");
});
arc.exit().remove();
/******************** Temporal Slider ************************/
if(this.chartComponentData.allTimestampsArray.length > 1){
d3.select(this.renderTarget).select('.tsi-sliderWrapper').classed('tsi-hidden', false);
slider.render(this.chartComponentData.allTimestampsArray.map(ts => {
var action = () => {
this.chartOptions.timestamp = ts;
this.render(this.chartComponentData.data, this.chartOptions, this.aggregateExpressionOptions);
}
return {label: Utils.timeFormat(this.chartComponentData.usesSeconds, this.chartComponentData.usesMillis,
this.chartOptions.offset, this.chartOptions.is24HourTime, null, null, this.chartOptions.dateLocale)(new Date(ts)), action: action};
}), this.chartOptions, this.chartWidth, Utils.timeFormat(this.chartComponentData.usesSeconds, this.chartComponentData.usesMillis,
this.chartOptions.offset, this.chartOptions.is24HourTime, null, null, this.chartOptions.dateLocale)(new Date(this.chartComponentData.timestamp)));
}
else{
slider.remove();
d3.select(this.renderTarget).select('.tsi-sliderWrapper').classed('tsi-hidden', true);
}
}
this.legendObject = new Legend(this.draw, this.renderTarget, this.CONTROLSWIDTH);
this.contextMenu = new ContextMenu(this.draw, this.renderTarget);
// temporal slider
var slider = new Slider(<any>d3.select(this.renderTarget).select('.tsi-sliderWrapper').node());
window.addEventListener("resize", () => {
if (!this.chartOptions.suppressResizeListener)
this.draw();
});
}
this.draw();
this.gatedShowGrid();
d3.select("html").on("click." + Utils.guid(), () => {
if (this.ellipsisContainer && d3.event.target != this.ellipsisContainer.select(".tsi-ellipsisButton").node()) {
this.ellipsisMenu.setMenuVisibility(false);
}
});
this.legendPostRenderProcess(this.chartOptions.legend, this.svgSelection, true);
}
}
export default PieChart