in src/visual.ts [1145:1323]
private drawChart(options: IDualKpiOptions) {
let chartData: Array<IDualKpiDataPoint> = options.chartData;
let axisConfig: IAxisConfig = options.axisConfig;
const latestValue: number = chartData[chartData.length - 1].value,
isTopChart: boolean = options.position === DualKpiChartPositionType.top,
dataColor: string = isTopChart ? this.data.settings.dualKpiColors.dataColor : this.data.settings.dualKpiColorsBottom.dataColor,
chartOpacity: number = isTopChart ? this.data.settings.dualKpiColors.opacity : this.data.settings.dualKpiColorsBottom.opacity,
axisStrokeHighContrastColor: string = this.colorHelper.getHighContrastColor("foreground", this.data.settings.dualKpiColors.textColor),
isHighContrastMode: boolean = this.colorHelper.isHighContrast,
hoverLineStrokeColor: string = "#777";
let target = this.target;
let targetPadding = parseInt(jQuery(target).css("padding-left"), 10) || 0;
let margin = {
top: 7,
right: 0,
bottom: 0,
left: this.chartLeftMargin
};
if (this.size === DualKpiSize.medium || this.size === DualKpiSize.large) {
margin.left = 40;
}
let calcWidth = options.width - margin.right - margin.left,
calcHeight = options.height - margin.top - margin.bottom,
minValue = d3.min(chartData, (d) => d.value) || 0,
maxValue = d3.max(chartData, (d) => d.value) || 0;
let axisMinValue = axisConfig.min !== null && axisConfig.min !== undefined ? axisConfig.min : minValue;
let axisMaxValue = axisConfig.max !== null && axisConfig.max !== undefined ? axisConfig.max : maxValue;
let xScale = d3.scaleTime()
.domain(d3.extent(chartData, (d) => d.date))
.range([0, calcWidth]);
let yScale = d3.scaleLinear()
.domain([axisMinValue, axisMaxValue])
.clamp(true)
.range([calcHeight, 0]);
let yAxis = d3.axisLeft(yScale)
.tickValues([axisMinValue, axisMaxValue])
.tickFormat((d) => {
let axisTickLabel: string = String(this.axisNumberFormatter(d));
if (options.valueAsPercent) {
axisTickLabel = axisTickLabel + "%";
}
return axisTickLabel;
});
let seriesRenderer, fill, stroke, strokeWidth;
if (options.chartType === "area") {
seriesRenderer = d3.area()
.x((d: any) => xScale(d.date || new Date()))
.y0(calcHeight)
.y1((d: any) => yScale(d.value || 0));
fill = dataColor;
stroke = "none";
strokeWidth = 0;
} else {
seriesRenderer = d3.line()
.x((d: any) => xScale(d.date || new Date()))
.y((d: any) => yScale(d.value || 0));
fill = "none";
stroke = dataColor;
strokeWidth = 2;
}
let chartGroup: IChartGroup = options.element;
chartGroup.group
.attr("transform", "translate(" + margin.left + "," + (options.top + margin.top) + ")");
let chartArea: Selection = chartGroup.area;
chartArea
.datum(chartData)
.attr("style", "opacity: " + (chartOpacity / 100))
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("d", seriesRenderer as any);
let zeroAxis: Selection = chartGroup.zeroAxis;
let zeroPointOnAxis = axisMinValue <= 0 && axisMaxValue >= 0 ? true : false;
// DRAW line for x axis at zero position
// if formatting option for zero line set to true
// and if a value of zero is on the y-axis
if (options.showZeroLine && zeroPointOnAxis) {
let axisLine = d3.line()
.x((d: any) => xScale(d.date))
.y((d: any) => yScale(0));
zeroAxis
.datum(chartData)
.classed("hidden", false)
.attr("d", axisLine as any);
} else {
zeroAxis
.classed("hidden", true);
}
let axis: Selection = chartGroup.yAxis;
axis
.attr("class", "axis")
.classed(this.sizeCssClass, true)
.classed("axis-colored", !isHighContrastMode)
.call(yAxis);
if (isHighContrastMode) {
let axisTicks: Selection = axis.selectAll("g.tick");
axisTicks.selectAll("text").attr("fill", axisStrokeHighContrastColor);
axisTicks.select("line").attr("stroke", axisStrokeHighContrastColor);
axis.select("path").attr("stroke", axisStrokeHighContrastColor);
zeroAxis.style("stroke", axisStrokeHighContrastColor);
}
/* Add elements for hover behavior ******************************************************/
let hoverLine: Selection = chartGroup.hoverLine;
hoverLine
.classed(DualKpi.INVISIBLE, true)
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", 0)
.attr("y2", calcHeight)
.attr("stroke-width", 1)
.attr("stroke", this.colorHelper.isHighContrast ? axisStrokeHighContrastColor : hoverLineStrokeColor);
let chartBottom = margin.top + calcHeight;
let chartLeft = margin.left;
let hoverDataContainer: IHoverDataContainer = options.element.hoverDataContainer;
this.updateHoverDataContainer(hoverDataContainer, chartBottom, chartLeft, calcWidth, isTopChart);
this.dispatch.on("onDualKpiMouseMove." + options.position, ([leftPosition, topPosition]: number[]) => {
let areaScale: ElementScale = DualKpi.getScale(target);
let maxWidth: number = options.width - margin.left;
leftPosition = leftPosition / areaScale.x - margin.left - targetPadding;
let x = xScale.invert(leftPosition);
let i = this.dataBisector(chartData, x, 1);
let dataPoint = chartData[i];
if ((leftPosition > 0) &&
(topPosition > 0) &&
(leftPosition < maxWidth) &&
(topPosition < (options.height * 2 + 15)) &&
dataPoint) {
hoverLine.attr("transform", "translate(" + leftPosition + ", 0)");
hoverLine.classed(DualKpi.INVISIBLE, false);
let value: number = options.hoverDataPercentType === PercentType.lastDate ? chartData[chartData.length - 1].value
: options.hoverDataPercentType === PercentType.firstDate ? chartData[0].value
: chartData[i - 1] ? chartData[i - 1].value : 0;
this.showHoverData(hoverDataContainer, dataPoint, value, options.hoverDataPercentType, options.valueAsPercent, options.abbreviateHoverValue);
} else {
this.hideHoverData(hoverDataContainer, hoverLine);
}
});
this.dispatch.on("onDualKpiMouseOut." + options.position, () => {
this.hideHoverData(hoverDataContainer, hoverLine);
});
if (options.showTextOverlay) {
this.addOverlayText(options, latestValue, calcHeight, calcWidth, isTopChart);
} else {
options.element.chartOverlay.rect.classed(DualKpi.INVISIBLE, true);
options.element.chartOverlay.title.classed(DualKpi.INVISIBLE, true);
options.element.chartOverlay.text.classed(DualKpi.INVISIBLE, true);
}
}