diff --git a/pages/examples/testcases/swimLanes/horizontalMarkers.html b/pages/examples/testcases/swimLanes/horizontalMarkers.html
new file mode 100644
index 0000000..81ab708
--- /dev/null
+++ b/pages/examples/testcases/swimLanes/horizontalMarkers.html
@@ -0,0 +1,231 @@
+
+
+
+
+ Horizontal markers
+
+
+
+
+
+
+
+
+
+
+
+
Lane selections:
+ group 0:
+
+
+ group 1:
+
+
+ group 2:
+
+
+ group 3:
+
+
+ group 4:
+
+
+
+
+
\ No newline at end of file
diff --git a/src/UXClient/Components/LineChart/LineChart.scss b/src/UXClient/Components/LineChart/LineChart.scss
index c44e7bd..77fd9a6 100644
--- a/src/UXClient/Components/LineChart/LineChart.scss
+++ b/src/UXClient/Components/LineChart/LineChart.scss
@@ -269,6 +269,15 @@
}
}
}
+
+ .tsi-horizontalMarkerLine {
+ stroke-dasharray: 2,2;
+ }
+
+ .tsi-horizontalMarkerText {
+ font-style: italic;
+ text-anchor: end;
+ }
&.tsi-dark{
$grays: grays('dark');
diff --git a/src/UXClient/Components/LineChart/LineChart.ts b/src/UXClient/Components/LineChart/LineChart.ts
index b072e1e..5463201 100644
--- a/src/UXClient/Components/LineChart/LineChart.ts
+++ b/src/UXClient/Components/LineChart/LineChart.ts
@@ -15,6 +15,7 @@ import EventsPlot from '../EventsPlot';
import { AxisState } from '../../Models/AxisState';
import Marker from '../Marker';
import { swimlaneLabelConstants} from '../../Constants/Constants'
+import { HorizontalMarker } from '../../Utils/Interfaces';
class LineChart extends TemporalXAxisComponent {
private targetElement: any;
@@ -605,7 +606,7 @@ class LineChart extends TemporalXAxisComponent {
marker.render(millis, this.chartOptions, this.chartComponentData, {
chartMargins: this.chartMargins,
x: this.x,
- marginLeft: this.getMarkerMarginLeft(),
+ marginLeft: this.getMarkerMarginLeft() + (isSeriesLabels ? this.getAdditionalOffsetFromHorizontalMargin() : 0),
colorMap: this.colorMap,
yMap: this.yMap,
onChange: onChange,
@@ -1267,7 +1268,12 @@ class LineChart extends TemporalXAxisComponent {
this.aggregateExpressionOptions.forEach((aEO) => {
aEO.swimLane = 0;
});
- this.chartOptions.swimLaneOptions = {0: {yAxisType: this.chartOptions.yAxisState}};
+ // consolidate horizontal markers
+ const horizontalMarkers = [];
+ Object.values(this.chartOptions.swimLaneOptions).forEach((lane) => {
+ horizontalMarkers.push(...lane.horizontalMarkers);
+ });
+ this.chartOptions.swimLaneOptions = {0: {yAxisType: this.chartOptions.yAxisState, horizontalMarkers: horizontalMarkers}};
} else {
let minimumPresentSwimLane = this.aggregateExpressionOptions.reduce((currMin, aEO) => {
return Math.max(aEO.swimLane, currMin);
@@ -1287,6 +1293,102 @@ class LineChart extends TemporalXAxisComponent {
this.chartOptions.swimLaneOptions = this.originalSwimLaneOptions;
}
+ private getHorizontalMarkersWithYScales () {
+ let visibleCDOs = this.aggregateExpressionOptions.filter((cDO) => this.chartComponentData.displayState[cDO.aggKey]["visible"]);
+ const markerList = [];
+ const pushMarker = (cDO, marker, markerList) => {
+ if (this.chartOptions.yAxisState === YAxisStates.Overlap) {
+ return;
+ }
+ const domain = this.chartOptions.yAxisState === YAxisStates.Stacked ?
+ this.swimlaneYExtents[cDO.swimLane] :
+ this.swimlaneYExtents[0];
+ // filter out markers not in the y range of that lane and in lanes that have overlap axis
+ if (domain &&
+ this.chartOptions.swimLaneOptions?.[cDO.swimLane]?.yAxisType !== YAxisStates.Overlap &&
+ marker.value >= domain[0] &&
+ marker.value <= domain[1]) {
+ markerList.push({yScale: this.yMap[cDO.aggKey], ...marker});
+ }
+ }
+ visibleCDOs.forEach((cDO) => {
+ cDO.horizontalMarkers.forEach((horizontalMarkerParams: HorizontalMarker) => {
+ pushMarker(cDO, horizontalMarkerParams, markerList);
+ });
+ });
+
+ // find a visible CDO for a swimlane
+ const findFirstVisibleCDO = (swimlaneNumber) => {
+ return visibleCDOs.find((cDO) => {
+ return (cDO.swimLane === swimlaneNumber);
+ });
+ }
+
+ if (this.chartOptions.yAxisState !== YAxisStates.Overlap && this.chartOptions.swimLaneOptions) {
+ Object.keys(this.chartOptions.swimLaneOptions).forEach((swimlaneNumber) => {
+ const swimlaneOptions = this.chartOptions.swimLaneOptions[swimlaneNumber];
+ swimlaneOptions.horizontalMarkers?.forEach((horizontalMarkerParams: HorizontalMarker) => {
+ const firstVisibleCDO = findFirstVisibleCDO(Number(swimlaneNumber));
+ if (firstVisibleCDO) {
+ pushMarker(firstVisibleCDO, horizontalMarkerParams, markerList);
+ }
+ });
+ });
+ }
+ return markerList;
+ }
+
+ // having horizontal markers present should add additional right hand margin to allow space for series labels
+ private getAdditionalOffsetFromHorizontalMargin () {
+ return this.getHorizontalMarkersWithYScales().length ? 16 : 0;
+ }
+
+ private drawHorizontalMarkers () {
+ const markerList = this.getHorizontalMarkersWithYScales();
+ const self = this;
+
+ const markerContainers = this.svgSelection.select('.svgGroup').selectAll('.tsi-horizontalMarker')
+ .data(markerList);
+ markerContainers
+ .enter()
+ .append('g')
+ .merge(markerContainers)
+ .attr('class', 'tsi-horizontalMarker')
+ .attr("transform", (marker) => {
+ return "translate(" + 0 + "," + marker.yScale(marker.value) + ")";
+ })
+ .each(function (marker) {
+ const valueText = d3.select(this)
+ .selectAll('.tsi-horizontalMarkerText')
+ .data([marker.value]);
+ valueText
+ .enter()
+ .append('text')
+ .merge(valueText)
+ .attr('class', 'tsi-horizontalMarkerText')
+ .attr('x', self.chartWidth)
+ .attr('y', -4)
+ .text((value) => value);
+ valueText.exit().remove();
+
+ const valueLine = d3.select(this)
+ .selectAll('.tsi-horizontalMarkerLine')
+ .data([marker]);
+ valueLine
+ .enter()
+ .append('line')
+ .merge(valueLine)
+ .attr('class', 'tsi-horizontalMarkerLine')
+ .attr('stroke', marker => marker.color)
+ .attr('x1', 0)
+ .attr('y1', 0)
+ .attr('x2', self.chartWidth)
+ .attr('y2', 0);
+ valueLine.exit().remove();
+ });
+ markerContainers.exit().remove();
+ }
+
private createSwimlaneLabels(offsetsAndHeights, visibleCDOs){
// swimLaneLabels object contains data needed to render each lane label
@@ -1892,6 +1994,7 @@ class LineChart extends TemporalXAxisComponent {
}
this.renderAllMarkers();
+ this.drawHorizontalMarkers();
this.voronoiDiagram = this.voronoi(this.getFilteredAndSticky(this.chartComponentData.allValues));
}
diff --git a/src/UXClient/Models/ChartDataOptions.ts b/src/UXClient/Models/ChartDataOptions.ts
index 51041af..ecb272a 100644
--- a/src/UXClient/Models/ChartDataOptions.ts
+++ b/src/UXClient/Models/ChartDataOptions.ts
@@ -1,5 +1,6 @@
import { InterpolationFunctions, DataTypes, EventElementTypes } from "../Constants/Enums";
import Utils from "../Utils";
+import { HorizontalMarker } from '../Utils/Interfaces'
const DEFAULT_HEIGHT = 40;
// Represents an expression that is suitable for use as the expression options parameter in a chart component
@@ -35,6 +36,7 @@ class ChartDataOptions {
public image: string;
public isRawData: boolean;
public isVariableAliasShownOnTooltip: boolean;
+ public horizontalMarkers: Array;
constructor (optionsObject: Object){
this.searchSpan = Utils.getValueOrDefault(optionsObject, 'searchSpan');
@@ -67,6 +69,7 @@ class ChartDataOptions {
this.startAt = Utils.getValueOrDefault(optionsObject, 'startAt', null);
this.isRawData = Utils.getValueOrDefault(optionsObject, 'isRawData', false);
this.isVariableAliasShownOnTooltip = Utils.getValueOrDefault(optionsObject, 'isVariableAliasShownOnTooltip', true);
+ this.horizontalMarkers = Utils.getValueOrDefault(optionsObject, 'horizontalMarkers', []);
}
}
export {ChartDataOptions}
diff --git a/src/UXClient/Models/ChartOptions.ts b/src/UXClient/Models/ChartOptions.ts
index d7a320a..aab972a 100644
--- a/src/UXClient/Models/ChartOptions.ts
+++ b/src/UXClient/Models/ChartOptions.ts
@@ -3,13 +3,15 @@ import Utils from '../Utils';
import { Strings } from './Strings';
import { DefaultHierarchyNavigationOptions } from '../Constants/Constants';
import { InterpolationFunctions, YAxisStates } from '../Constants/Enums';
+import { HorizontalMarker } from '../Utils/Interfaces';
// Interfaces
interface swimLaneOption {
yAxisType: YAxisStates,
label?: string,
onClick?: (lane: number) => any,
- collapseEvents?: string
+ collapseEvents?: string,
+ horizontalMarkers?: Array
}
class ChartOptions {
diff --git a/src/UXClient/Utils/Interfaces.ts b/src/UXClient/Utils/Interfaces.ts
new file mode 100644
index 0000000..d67b9eb
--- /dev/null
+++ b/src/UXClient/Utils/Interfaces.ts
@@ -0,0 +1,4 @@
+export interface HorizontalMarker {
+ color: string,
+ value: number
+}
\ No newline at end of file