Skip to content

Commit 7198e46

Browse files
authored
Merge pull request #291 from iteratec/feature/legendLabelsInTimeSeriesChart
Feature: Legend labels in time series chart
2 parents 8a23f68 + 632efc4 commit 7198e46

File tree

10 files changed

+338
-80
lines changed

10 files changed

+338
-80
lines changed

frontend/src/app/modules/time-series/components/time-series-line-chart/time-series-line-chart.component.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ osm-time-series-line-chart {
6060
}
6161
}
6262

63+
.summary-label-key {
64+
font-weight: bold;
65+
}
66+
67+
.summary-label {
68+
font-weight: normal;
69+
}
70+
6371
.legend-entry {
6472
cursor: pointer;
6573
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import {EventResultSeriesDTO} from './event-result-series.model';
2+
import {SummaryLabel} from "./summary-label.model";
23

34
export interface EventResultDataDTO {
45
series: EventResultSeriesDTO[];
6+
summaryLabels: SummaryLabel[];
57
}
68

79
export class EventResultData implements EventResultDataDTO {
810
series: EventResultSeriesDTO[];
11+
summaryLabels: SummaryLabel[];
912

1013
constructor() {
1114
this.series = [];
15+
this.summaryLabels = [];
1216
}
1317
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface SummaryLabel {
2+
key: string;
3+
label: string;
4+
}

frontend/src/app/modules/time-series/services/line-chart.service.ts

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {TimeSeriesPoint} from 'src/app/modules/time-series/models/time-series-po
4747
import {parseDate} from 'src/app/utils/date.util';
4848
import {getColorScheme} from 'src/app/enums/color-scheme.enum';
4949
import {ChartCommons} from "../../../enums/chart-commons.enum";
50+
import {SummaryLabel} from "../models/summary-label.model";
5051

5152
/**
5253
* Generate line charts with ease and fun 😎
@@ -67,10 +68,9 @@ export class LineChartService {
6768
private _height: number = 550 - this._margin.top - this._margin.bottom;
6869
private _labelGroupHeight: number;
6970
private _legendGroupTop: number = this._margin.top + this._height + 50;
70-
private _legendGroupLeft: number = this._margin.left;
71-
private _legendGroupHeight;
72-
private _legendGroupColumnWidth;
73-
private _legendGroupColumns;
71+
private _legendGroupHeight: number;
72+
private _legendGroupColumnWidth: number;
73+
private _legendGroupColumns: number;
7474
private legendDataMap: Object = {};
7575
private brush: BrushBehavior<{}>;
7676
private focusedLegendEntry: string;
@@ -118,6 +118,7 @@ this.calculateLegendDimensions();
118118

119119
this.addBrush(chart, xScale, yScale, data);
120120
this.addLegendsToChart(chart, incomingData);
121+
this.setSummaryLabel(chart, incomingData.summaryLabels);
121122
this.addDataLinesToChart(chart, xScale, yScale, data);
122123

123124
this.bringMouseMarkerToTheFront(xScale, yScale);
@@ -141,12 +142,14 @@ this.calculateLegendDimensions();
141142

142143
let labelDataMap = {};
143144
incomingData.series.forEach((data: EventResultSeriesDTO) => {
144-
let label = data.identifier;
145+
if (incomingData.summaryLabels.length > 0 && incomingData.summaryLabels[0].key != "measurand") {
146+
data.identifier = this.translateMeasurand(data);
147+
}
145148
let key = this.generateKey(data);
146149
labelDataMap[key] = {
147-
text: label,
150+
text: data.identifier,
148151
key: key,
149-
show: true,
152+
show: true
150153
}
151154
});
152155
this.legendDataMap = labelDataMap;
@@ -156,27 +159,40 @@ this.calculateLegendDimensions();
156159
* Prepares the incoming data for drawing with D3.js
157160
*/
158161
private prepareData(incomingData: EventResultDataDTO): TimeSeries[] {
159-
160162
return incomingData.series.map((data: EventResultSeriesDTO) => {
161163
let lineChartData: TimeSeries = new TimeSeries();
164+
if (incomingData.summaryLabels.length > 0 && incomingData.summaryLabels[0].key != "measurand") {
165+
data.identifier = this.translateMeasurand(data);
166+
}
162167
lineChartData.key = this.generateKey(data);
163168

164169
lineChartData.values = data.data.map((point: EventResultPointDTO) => {
165170
let lineChartDataPoint: TimeSeriesPoint = new TimeSeriesPoint();
166171
lineChartDataPoint.date = parseDate(point.date);
167172
lineChartDataPoint.value = point.value;
168-
lineChartDataPoint.tooltipText = data.jobGroup + ' | ' + data.measuredEvent + ' : '; // TODO Set exact label text when IT-2793 is implemented
173+
lineChartDataPoint.tooltipText = data.identifier + ' : ';
169174
return lineChartDataPoint;
170175
});
171176

172177
return lineChartData;
173178
});
174179
}
175180

181+
private translateMeasurand(data: EventResultSeriesDTO): string {
182+
let splitLabelList: string[] = data.identifier.split(' | ');
183+
let splitLabel: string = this.translationService.instant('frontend.de.iteratec.isr.measurand.' + splitLabelList[0]);
184+
if (!splitLabel.startsWith('frontend.de.iteratec.isr.measurand.')) {
185+
splitLabelList[0] = splitLabel;
186+
}
187+
return splitLabelList.join(' | ');
188+
}
189+
176190
private generateKey(data: EventResultSeriesDTO): string {
177-
return data.jobGroup
178-
+ data.measuredEvent
179-
+ data.data.length;
191+
let key: string = data.identifier.replace(/[^_a-zA-Z0-9-]/g, "");
192+
if (new RegExp('[0-9]').test(key.charAt(0))) {
193+
key = key.replace(/[0-9]/, '_');
194+
}
195+
return key;
180196
}
181197

182198
/**
@@ -189,14 +205,69 @@ this.calculateLegendDimensions();
189205
.attr('width', this._width + this._margin.left + this._margin.right)
190206
.attr('height', 0);
191207

208+
svg.append('g')
209+
.attr('id', 'header-group')
210+
.attr('transform', `translate(${this._margin.left}, ${this._margin.top - 16})`);
211+
212+
let chart = svg.append('g') // g = grouping element; group all other stuff into the chart
213+
.attr('id', 'time-series-chart-drawing-area')
214+
.attr('transform', `translate(${this._margin.left}, ${this._margin.top})`); // translates the origin to the top left corner (default behavior of D3)
215+
192216
svg.append('g')
193217
.attr('id', 'time-series-chart-legend')
194218
.attr('class', 'legend-group')
195-
.attr('transform', `translate(${this._legendGroupLeft}, ${this._legendGroupTop})`);
219+
.attr('transform', `translate(${this._margin.left}, ${this._legendGroupTop})`);
196220

197-
return svg.append('g') // g = grouping element; group all other stuff into the chart
198-
.attr('id', 'time-series-chart-drawing-area')
199-
.attr('transform', 'translate(' + this._margin.left + ', ' + this._margin.top + ')'); // translates the origin to the top left corner (default behavior of D3)
221+
return chart;
222+
}
223+
224+
private setSummaryLabel(chart: D3Selection<D3BaseType, {}, D3ContainerElement, {}>, summaryLabels: SummaryLabel[]): void {
225+
d3Select('g#header-group').selectAll('.summary-label-text').remove();
226+
if (summaryLabels.length > 0) {
227+
d3Select('#header-group')
228+
.append('g')
229+
.attr('class', 'summary-label-text')
230+
.append('text')
231+
.attr('id', 'summary-label-part0')
232+
.attr('x', this._width / 2)
233+
.attr('text-anchor', 'middle')
234+
.attr('fill', '#555555');
235+
236+
summaryLabels.forEach((summaryLabel: SummaryLabel, index: number) => {
237+
this.translationService
238+
.get('frontend.de.iteratec.osm.timeSeries.chart.label.' + summaryLabel.key)
239+
.pipe(take(1))
240+
.subscribe((key: string) => {
241+
if (summaryLabel.key == 'measurand') {
242+
this.translationService
243+
.get('frontend.de.iteratec.isr.measurand.' + summaryLabel.label)
244+
.pipe(take(1))
245+
.subscribe((label: string) => {
246+
if (label.startsWith('frontend.de.iteratec.isr.measurand.')) {
247+
label = summaryLabel.label
248+
}
249+
label = index < summaryLabels.length - 1 ? `${label} | ` : label;
250+
this.addSummaryLabel(key, label, index);
251+
});
252+
} else {
253+
const label: string = index < summaryLabels.length - 1 ? `${summaryLabel.label} | ` : summaryLabel.label;
254+
this.addSummaryLabel(key, label, index);
255+
}
256+
});
257+
});
258+
chart.selectAll('.summary-label-text').remove();
259+
}
260+
}
261+
262+
private addSummaryLabel(key: string, label: string, index: number): void {
263+
d3Select(`#summary-label-part${index}`)
264+
.append('tspan')
265+
.attr('id', `summary-label-part${index + 1}`)
266+
.attr('class', 'summary-label-key')
267+
.text(`${key}: `)
268+
.append('tspan')
269+
.attr('class', 'summary-label')
270+
.text(label);
200271
}
201272

202273
public startResize(svgElement: ElementRef): void {
@@ -538,7 +609,7 @@ this.calculateLegendDimensions();
538609
})
539610
// fade in
540611
.transition().duration(500).style('opacity', (timeSeries: TimeSeries) => {
541-
return (this.legendDataMap[timeSeries.key].show) ? '1' : '0.2';
612+
return (this.legendDataMap[timeSeries.key].show) ? '1' : '0.1';
542613
});
543614

544615
return resultingSelection;

grails-app/i18n/messages.properties

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,9 @@ frontend.default.button.save=Save
10691069
frontend.default.button.cancel=Cancel
10701070
frontend.de.iteratec.osm.measurement.setup.title=Measurement Setup
10711071
frontend.de.iteratec.osm.applicationDashboard.kpi.title=Total Customer Satisfaction
1072+
frontend.de.iteratec.isr.measurand.PAGE_CONSTRUCTION_STARTED=Is it happening?
1073+
frontend.de.iteratec.isr.measurand.PAGE_SHOWS_USEFUL_CONTENT=Is it useful?
1074+
frontend.de.iteratec.isr.measurand.PAGE_IS_USABLE=Is it useable?
10721075
frontend.de.iteratec.isr.measurand.DOC_COMPLETE_TIME=Document Complete
10731076
frontend.de.iteratec.isr.measurand.DOM_TIME=DOM Time
10741077
frontend.de.iteratec.isr.measurand.FIRST_BYTE=First Byte
@@ -1274,3 +1277,8 @@ frontend.de.iteratec.osm.barchart.filter.noFilterAsc=Ascending
12741277
frontend.de.iteratec.osm.barchart.filter.label=Filter
12751278
frontend.de.iteratec.osm.barchart.filter.customerJourneyHeader=Customer Journey
12761279
frontend.de.iteratec.osm.timeSeries.loadTimes=Load times
1280+
frontend.de.iteratec.osm.timeSeries.chart.label.measurand=Measurand
1281+
frontend.de.iteratec.osm.timeSeries.chart.label.application=Application
1282+
frontend.de.iteratec.osm.timeSeries.chart.label.measuredEvent=Measured step
1283+
frontend.de.iteratec.osm.timeSeries.chart.label.location=Location
1284+
frontend.de.iteratec.osm.timeSeries.chart.label.connectivity=Connectivity

grails-app/i18n/messages_de.properties

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,9 @@ frontend.default.button.save=Speichern
10511051
frontend.default.button.cancel=Abbrechen
10521052
frontend.de.iteratec.osm.measurement.setup.title=Setup Messungen
10531053
frontend.de.iteratec.osm.applicationDashboard.kpi.title=Gesamte Kundenzufriedenheit
1054+
frontend.de.iteratec.isr.measurand.PAGE_CONSTRUCTION_STARTED=Is it happening?
1055+
frontend.de.iteratec.isr.measurand.PAGE_SHOWS_USEFUL_CONTENT=Is it useful?
1056+
frontend.de.iteratec.isr.measurand.PAGE_IS_USABLE=Is it useable?
10541057
frontend.de.iteratec.isr.measurand.DOC_COMPLETE_TIME=Document Complete
10551058
frontend.de.iteratec.isr.measurand.DOM_TIME=DOM Time
10561059
frontend.de.iteratec.isr.measurand.FIRST_BYTE=First Byte
@@ -1248,3 +1251,8 @@ frontend.de.iteratec.osm.barchart.filter.noFilterAsc=Aufsteigend
12481251
frontend.de.iteratec.osm.barchart.filter.label=Filtern
12491252
frontend.de.iteratec.osm.barchart.filter.customerJourneyHeader=Customer Journey
12501253
frontend.de.iteratec.osm.timeSeries.loadTimes=Ladezeiten
1254+
frontend.de.iteratec.osm.timeSeries.chart.label.measurand=Messgröße
1255+
frontend.de.iteratec.osm.timeSeries.chart.label.application=Anwendung
1256+
frontend.de.iteratec.osm.timeSeries.chart.label.measuredEvent=Messschritt
1257+
frontend.de.iteratec.osm.timeSeries.chart.label.location=Location
1258+
frontend.de.iteratec.osm.timeSeries.chart.label.connectivity=Anbindung

0 commit comments

Comments
 (0)