in src/UXClient/Components/LinePlot/LinePlot.ts [50:345]
public render (chartOptions, visibleAggI, agg, aggVisible: boolean, aggregateGroup, chartComponentData, yAxisState: AxisState,
chartHeight, visibleAggCount, colorMap, previousAggregateData, x, areaPath, strokeOpacity, y, yMap, defs, chartDataOptions,
previousIncludeDots, yTopAndHeight, svgSelection, categoricalMouseover, categoricalMouseout, yAxisOnClick) {
this.previousIncludeDots = previousIncludeDots;
this.defs = defs;
this.chartOptions = chartOptions;
this.chartHeight = chartHeight;
this.visibleAggCount = visibleAggCount;
this.chartComponentData = chartComponentData;
this.x = x;
this.y = y;
let aggKey = agg.aggKey;
this.aggregateGroup = aggregateGroup;
const yAxisHasOnClick = yAxisOnClick && typeof yAxisOnClick === "function";
visibleAggI = yAxisState.positionInGroup;
this.yTop = yTopAndHeight[0];
this.height = yTopAndHeight[1];
let aggY;
let aggLine;
let aggEnvelope;
let aggGapLine;
this.yAxisState = yAxisState;
let yExtent = this.yAxisState.yExtent;
aggY = d3.scaleLinear();
aggY.range([this.height, this.chartOptions.aggTopMargin]);
if (this.chartComponentData.aggHasVisibleSplitBys(aggKey)) {
var yRange = (yExtent[1] - yExtent[0]) > 0 ? yExtent[1] - yExtent[0] : 1;
var yOffsetPercentage = 10 / (this.chartHeight / ((this.yAxisState.axisType === YAxisStates.Overlap) ? 1 : this.visibleAggCount));
let yDomainMin = this.chartOptions.isArea ?
(Math.max(yExtent[0] - (yRange * yOffsetPercentage), 0)) :
(yExtent[0] - (yRange * yOffsetPercentage));
aggY.domain([yDomainMin, yExtent[1] + (yRange * (10 / this.chartHeight))]);
} else {
aggY.domain([0,1]);
yExtent = [0, 1];
}
aggLine = d3.line()
.curve(this.chartComponentData.displayState[aggKey].interpolationFunction ? d3[this.chartComponentData.displayState[aggKey].interpolationFunction] : this.chartOptions.interpolationFunction)
.defined((d: any) => {
return (d.measures !== null) &&
(d.measures[this.chartComponentData.getVisibleMeasure(d.aggregateKey, d.splitBy)] !== null);
})
.x((d: any) => this.getXPosition(d, this.x))
.y((d: any) => {
return d.measures ? aggY(d.measures[this.chartComponentData.getVisibleMeasure(d.aggregateKey, d.splitBy)]) : null;
});
aggEnvelope = d3.area()
.curve(this.chartComponentData.displayState[aggKey].interpolationFunction ? d3[this.chartComponentData.displayState[aggKey].interpolationFunction] : this.chartOptions.interpolationFunction)
.defined((d: any) => (d.measures !== null) && (d.measures['min'] !== null) && (d.measures['max'] !== null))
.x((d: any) => this.getXPosition(d, this.x))
.y0((d: any) => d.measures ? aggY(d.measures['max']) : 0)
.y1((d: any) => d.measures ? aggY(d.measures['min']) : 0);
aggGapLine = aggLine;
let localY = aggY.copy();
localY.range([this.yTop + this.height, this.yTop + this.chartOptions.aggTopMargin]);
yMap[aggKey] = localY;
var yAxis: any = this.aggregateGroup.selectAll(".yAxis")
.data([aggKey]);
var visibleYAxis = (aggVisible && (this.yAxisState.axisType !== YAxisStates.Shared || visibleAggI === 0));
yAxis = yAxis.enter()
.append("g")
.attr("class", `yAxis ${yAxisHasOnClick ? `tsi-clickableYAxis tsi-swimLaneAxis-${this.chartComponentData.displayState[aggKey].aggregateExpression.swimLane}` : ''}`)
.merge(yAxis)
.style("visibility", ((visibleYAxis && !this.chartOptions.yAxisHidden) ? "visible" : "hidden"));
if (this.yAxisState.axisType === YAxisStates.Overlap) {
yAxis.call(d3.axisLeft(aggY).tickFormat(Utils.formatYAxisNumber).tickValues(yExtent))
.selectAll("text")
.attr("y", (d, j) => {return (j == 0) ? (-visibleAggI * 16) : (visibleAggI * 16) })
.style("fill", this.chartComponentData.displayState[aggKey].color);
}
else {
yAxis.call(d3.axisLeft(aggY).tickFormat(Utils.formatYAxisNumber)
.ticks(Math.max(2, Math.ceil(this.height/(this.yAxisState.axisType === YAxisStates.Stacked ? this.visibleAggCount : 1)/90))))
.selectAll("text").classed("standardYAxisText", true)
}
// If yAxisOnClick present, attach to yAxis
if(yAxisHasOnClick){
yAxis.on("click", () => {
yAxisOnClick();
})
let label = document.getElementsByClassName(`tsi-swimLaneLabel-${agg.swimLane}`)[0];
if(label){
yAxis.on("mouseover", () => {
label.classList.add("tsi-axisHover");
yAxis.selectAll("text").classed("tsi-boldYAxisText", true)
})
yAxis.on("mouseout", () => {
label.classList.remove("tsi-axisHover");
yAxis.selectAll("text").classed("tsi-boldYAxisText", false)
})
}
}
yAxis.exit().remove();
var guideLinesData = {
x: this.x,
y: aggY,
visible: visibleYAxis
};
let splitByColors = Utils.createSplitByColors(this.chartComponentData.displayState, aggKey, this.chartOptions.keepSplitByColor);
let includeDots = this.chartOptions.includeDots || this.chartComponentData.displayState[aggKey].includeDots;
let self = this;
let splitByGroups = this.aggregateGroup.selectAll(".tsi-splitByGroup")
.data(Object.keys(this.chartComponentData.timeArrays[aggKey]));
splitByGroups.enter()
.append("g")
.attr("class", "tsi-splitByGroup " + agg.aggKey)
.merge(splitByGroups)
.each(function (splitBy, j) {
colorMap[aggKey + "_" + splitBy] = splitByColors[j];
// creation of segments between each gap in the data
var segments = [];
var lineData = self.chartComponentData.timeArrays[aggKey][splitBy];
var visibleMeasure = self.chartComponentData.getVisibleMeasure(aggKey, splitBy);
for (var i = 0; i < lineData.length - 1; i++) {
if (lineData[i].measures !== null && lineData[i].measures[visibleMeasure] !== null) {
var scannerI: number = i + 1;
while(scannerI < lineData.length && ((lineData[scannerI].measures == null) ||
lineData[scannerI].measures[visibleMeasure] == null)) {
scannerI++;
}
if (scannerI < lineData.length && scannerI != i + 1) {
segments.push([lineData[i], lineData[scannerI]]);
}
i = scannerI - 1;
}
}
var durationFunction = (d) => {
let previousUndefined = previousAggregateData.get(this) === undefined;
return (self.chartOptions.noAnimate || previousUndefined) ? 0 : self.TRANSDURATION
}
var gapPath = d3.select(this).selectAll(".tsi-gapLine")
.data(segments.map((d) => {
d.inTransition = true;
return d;
}));
gapPath.enter()
.append("path")
.attr("class", "tsi-valueElement tsi-gapLine")
.merge(gapPath as d3.Selection<SVGPathElement,any,any,unknown>)
.style("visibility", (d: any) => {
return (self.chartComponentData.isSplitByVisible(aggKey, splitBy)) ? "visible" : "hidden";
})
.transition()
.duration(durationFunction)
.ease(d3.easeExp)
.attr("stroke-dasharray","5,5")
.attr("stroke", splitByColors[j])
.attrTween('d', function (d) {
var previous = d3.select(this).attr('d');
var current = aggLine(d);
return interpolatePath(previous, current);
})
.on('end', (d: any) => {
d.inTransition = false;
});
var path = d3.select(this).selectAll(".tsi-valueLine")
.data([self.chartComponentData.timeArrays[aggKey][splitBy]].map(d => {
d.inTransition = true;
return d;
}));
path.enter()
.append("path")
.attr("class", "tsi-valueElement tsi-valueLine")
.merge(path as d3.Selection<SVGPathElement,any,any,unknown>)
.style("visibility", (d: any) => {
return (self.chartComponentData.isSplitByVisible(aggKey, splitBy)) ? "visible" : "hidden";
})
.transition()
.duration(durationFunction)
.ease(d3.easeExp)
.attr("stroke", splitByColors[j])
.attr("stroke-opacity", self.strokeOpacity)
.attrTween('d', function (d) {
var previous = d3.select(this).attr('d');
var current = aggLine(d);
return interpolatePath(previous, current);
})
.on('end', (d: any) => {
d.inTransition = false;
});
if (self.chartOptions.includeDots || self.chartComponentData.displayState[aggKey].includeDots) {
let dots = d3.select(this).selectAll(".tsi-valueDot")
.data(self.chartComponentData.timeArrays[aggKey][splitBy].filter((d) => {
return d && d.measures && d.measures[self.chartComponentData.getVisibleMeasure(d.aggregateKey, d.splitBy)] !== null;
}), (d: any, i) => {
return d.dateTime.toString();
});
dots.enter()
.append('circle')
.attr('class', 'tsi-valueElement tsi-valueDot')
.attr('r', 3)
.merge(dots as d3.Selection<SVGCircleElement,unknown,any,unknown>)
.style("visibility", (d: any) => {
return (self.chartComponentData.isSplitByVisible(aggKey, splitBy) && d.measures) ? "visible" : "hidden";
})
.transition()
.duration(function (d, i) {
return (self.previousIncludeDots.get(this) === true) ? durationFunction(d) : 0;
})
.ease(d3.easeExp)
.attr("fill", splitByColors[j])
.attr('cx', (d: any) => self.getXPosition(d, self.x))
.attr('cy', (d: any) => {
return d.measures ? aggY(d.measures[self.chartComponentData.getVisibleMeasure(d.aggregateKey, d.splitBy)]) : null;
})
.each(function () {
self.previousIncludeDots.set(this, includeDots);
})
dots.exit().remove();
} else {
d3.select(this).selectAll(".tsi-valueDot").remove();
}
let envelopeData = {};
if ((self.chartComponentData.displayState[aggKey].includeEnvelope || self.chartOptions.includeEnvelope) && self.chartComponentData.isPossibleEnvelope(aggKey, splitBy)) {
envelopeData = self.chartComponentData.timeArrays[aggKey][splitBy].map((d: any) => ({...d, isEnvelope: true}));
}
let envelope = d3.select(this).selectAll(".tsi-valueEnvelope")
.data([envelopeData]);
envelope.enter()
.append("path")
.attr("class", "tsi-valueElement tsi-valueEnvelope")
.merge(envelope as d3.Selection<SVGPathElement,{},any,unknown>)
.style("visibility", (d: any) => {
return (self.chartComponentData.isSplitByVisible(aggKey, splitBy)) ? "visible" : "hidden";
})
.transition()
.duration(durationFunction)
.ease(d3.easeExp)
.style("fill", splitByColors[j])
.attr("fill-opacity", .2)
.attr("d", aggEnvelope);
if (self.chartOptions.isArea) {
self.createAreaPath(aggY);
var area = d3.select(this).selectAll(".tsi-valueArea")
.data([self.chartComponentData.timeArrays[aggKey][splitBy]]);
// logic for shiny gradient fill via url()
let svgId = Utils.guid();
let lg = self.defs.selectAll('linearGradient')
.data([self.chartComponentData.timeArrays[aggKey][splitBy]]);
var gradient = lg.enter()
.append('linearGradient');
gradient.merge(lg)
.attr('id', svgId).attr('x1', '0%').attr('x2', '0%').attr('y1', '0%').attr('y2', '100%');
gradient.append('stop').attr('offset', '0%').attr('style', () =>{return 'stop-color:' + splitByColors[j] + ';stop-opacity:.2'});
gradient.append('stop').attr('offset', '100%').attr('style', () =>{return 'stop-color:' + splitByColors[j] + ';stop-opacity:.03'});
lg.exit().remove();
area.enter()
.append("path")
.attr("class", "tsi-valueArea")
.merge(area as d3.Selection<SVGPathElement,any,any,unknown>)
.style("fill", 'url(#' + (svgId) + ')')
.style("visibility", (d: any) => {
return (self.chartComponentData.isSplitByVisible(aggKey, splitBy)) ? "visible" : "hidden";
})
.transition()
.duration(durationFunction)
.ease(d3.easeExp)
.attr("d", self.areaPath);
area.exit().remove();
}
gapPath.exit().remove();
path.exit().remove();
previousAggregateData.set(this, splitBy);
});
splitByGroups.exit().remove();
}