Skip to content

Commit 08b099c

Browse files
committed
Merge branch 'filter-timeline' into 'main'
Filter timeline See merge request ExplorViz/code/frontend!247
2 parents 9ad5399 + d8dcd93 commit 08b099c

File tree

3 files changed

+144
-68
lines changed

3 files changed

+144
-68
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{{#if this.showDummyTimeline}}
2+
<div class='timeline-no-timestamps-outer'>
3+
<div class='timeline-no-timestamps-inner'>
4+
No timestamps available!
5+
</div>
6+
</div>
7+
<div
8+
class='plotlyDiv timeline-blur-effect'
9+
{{did-insert this.setupPlotlyTimelineChart}}
10+
>
11+
</div>
12+
13+
{{else}}
14+
<div style='display:flex' class='flex-row'>
15+
<div style='width: 75px; margin-left: 5px '>
16+
<div style='font-weight: bold;'>Filter</div>
17+
<div>Max:</div>
18+
<Input
19+
id='maxRequestFilter'
20+
class='form-control input-lg'
21+
placeholder=''
22+
{{on 'input' this.setMaxRequestFilter}}
23+
/>
24+
<div>Min:</div>
25+
<Input
26+
id='minRequestFilter'
27+
class='form-control input-lg'
28+
placeholder='0'
29+
{{on 'input' this.setMinRequestFilter}}
30+
/>
31+
</div>
32+
<div
33+
style='width: 100%;'
34+
class='plotlyDiv'
35+
{{on 'mouseenter' this.handleMouseEnter}}
36+
{{on 'mouseleave' this.handleMouseLeave}}
37+
{{did-update this.updatePlotlyTimelineChart @timelineDataObject}}
38+
{{did-insert this.setupPlotlyTimelineChart}}
39+
>
40+
</div>
41+
</div>
42+
{{/if}}

app/components/visualization/page-setup/bottom-bar/runtime/plotly-timeline.gts renamed to app/components/visualization/page-setup/bottom-bar/runtime/plotly-timeline.ts

+99-68
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import debugLogger from 'ember-debug-logger';
44
import { Timestamp } from 'explorviz-frontend/utils/landscape-schemes/timestamp';
55
import Plotly from 'plotly.js-dist';
66
// #region Template Imports
7-
import { on } from '@ember/modifier';
8-
import didInsert from '@ember/render-modifiers/modifiers/did-insert';
9-
import didUpdate from '@ember/render-modifiers/modifiers/did-update';
107
import { TimelineDataObject } from 'explorviz-frontend/utils/timeline/timeline-data-object-handler';
118
// #endregion
129

@@ -41,6 +38,9 @@ export default class PlotlyTimeline extends Component<IArgs> {
4138

4239
plotlyTimestampsWithoutNullValues: any;
4340

41+
minRequestFilter = 10;
42+
maxRequestFilter = Number.MAX_SAFE_INTEGER;
43+
4444
// BEGIN template-argument getters for default values
4545
get defaultMarkerColor() {
4646
return '#1f77b4';
@@ -356,7 +356,7 @@ export default class PlotlyTimeline extends Component<IArgs> {
356356
}
357357

358358
this.debug('updateMarkerStates');
359-
this.resetHighlingInStateObjects();
359+
this.resetHighlightInStateObjects();
360360

361361
for (const [
362362
commitId,
@@ -376,10 +376,12 @@ export default class PlotlyTimeline extends Component<IArgs> {
376376

377377
selectedTimestampsForCommit.forEach((timestamp) => {
378378
const timestampId = timestamp.epochMilli;
379-
markerStates[timestampId].color =
380-
timelineDataForCommit.highlightedMarkerColor;
381-
markerStates[timestampId].size = this.highlightedMarkerSize;
382-
markerStates[timestampId].emberModel = timestamp;
379+
if (markerStates[timestampId]) {
380+
markerStates[timestampId].color =
381+
timelineDataForCommit.highlightedMarkerColor;
382+
markerStates[timestampId].size = this.highlightedMarkerSize;
383+
markerStates[timestampId].emberModel = timestamp;
384+
}
383385
});
384386
this.markerStateMap.set(commitId, markerStates);
385387
}
@@ -502,6 +504,30 @@ export default class PlotlyTimeline extends Component<IArgs> {
502504
};
503505
}
504506

507+
autoScale() {
508+
Plotly.relayout(this.timelineDiv, {
509+
'xaxis.autorange': true,
510+
'yaxis.autorange': true,
511+
});
512+
}
513+
514+
@action
515+
setMinRequestFilter(event: any) {
516+
const minRequestInput = event.target.value;
517+
this.minRequestFilter = Number.parseInt(minRequestInput) || 0;
518+
this.updatePlotlyTimelineChart();
519+
this.autoScale();
520+
}
521+
522+
@action
523+
setMaxRequestFilter(event: any) {
524+
const maxRequestInput = event.target.value;
525+
this.maxRequestFilter =
526+
Number.parseInt(maxRequestInput) || Number.MAX_SAFE_INTEGER;
527+
this.updatePlotlyTimelineChart();
528+
this.autoScale();
529+
}
530+
505531
hoverText(x: (string | null)[], y: (number | null)[], commit: string) {
506532
return x.map(
507533
(xi, i) =>
@@ -518,10 +544,10 @@ export default class PlotlyTimeline extends Component<IArgs> {
518544
hoverdistance: 3,
519545
hovermode: 'closest',
520546
margin: {
521-
b: 40,
522-
pad: 5,
523-
t: 40,
524-
r: 40,
547+
b: 50,
548+
pad: 0,
549+
t: 0,
550+
r: 20,
525551
},
526552
yaxis: {
527553
fixedrange: true,
@@ -539,7 +565,7 @@ export default class PlotlyTimeline extends Component<IArgs> {
539565
}
540566

541567
getUpdatedPlotlyDataObject(
542-
timestampsOfOneCommit: Timestamp[],
568+
unfilteredTimestampsOfOneCommit: Timestamp[],
543569
markerStatesOfOneCommit: IMarkerStates,
544570
commitId: string
545571
) {
@@ -551,25 +577,39 @@ export default class PlotlyTimeline extends Component<IArgs> {
551577
.replace('.000Z', '');
552578
}
553579

554-
function getGapRectangleObj() {
580+
function getDashedLine() {
555581
return {
556582
layer: 'below',
557-
type: 'rect',
583+
type: 'line',
558584
xref: 'x',
559-
yref: 'paper',
585+
yref: 'y',
560586
x0: '0',
561587
y0: 0.1,
562588
x1: '0',
563589
y1: 1,
564590
fillcolor: '#d3d3d3',
565591
opacity: 0.4,
566592
line: {
567-
width: 3,
593+
width: 1,
568594
dash: 'dot',
569595
},
570596
};
571597
}
572598

599+
function styleNewDayIndicator(gapIndicator: any) {
600+
gapIndicator.type = 'rect';
601+
gapIndicator.yref = 'paper';
602+
gapIndicator.y0 = 0.05;
603+
gapIndicator.y1 = 1;
604+
gapIndicator.fillcolor = '#ff0000';
605+
}
606+
607+
const timestampsOfOneCommit = unfilteredTimestampsOfOneCommit.filter(
608+
(timestamp) =>
609+
timestamp.spanCount > this.minRequestFilter &&
610+
timestamp.spanCount < this.maxRequestFilter
611+
);
612+
573613
const colors: string[] = [];
574614
const sizes: number[] = [];
575615

@@ -580,59 +620,75 @@ export default class PlotlyTimeline extends Component<IArgs> {
580620

581621
const shapes = [];
582622

583-
let tempGapRectObj = null;
623+
let tempGapIndicator = null;
584624

585625
let nextExpectedTimestamp = 0;
586626
let i = 0;
587627

628+
const TIMESTAMP_INTERVAL = 10000;
629+
588630
while (i < timestampsOfOneCommit.length) {
589631
const timestamp = timestampsOfOneCommit[i];
590632
const timestampId = timestamp.epochMilli;
591633

592-
// only add real timestamps and shapes in the data arrays
593-
let addCurentTimestampToDataObject = false;
634+
// Only add real timestamps and shapes in the data arrays
635+
let addCurrentTimestampToDataObject = false;
594636

595637
if (nextExpectedTimestamp === 0) {
596-
// first timestamp in series
638+
// First timestamp, must exist do to while loop condition
597639
x.push(getTimestampTickLabel(timestampId));
598640
y.push(timestamp.spanCount);
599-
nextExpectedTimestamp = timestampId;
641+
nextExpectedTimestamp = timestampId + TIMESTAMP_INTERVAL;
600642
i++;
601-
addCurentTimestampToDataObject = true;
602-
} else if (nextExpectedTimestamp === timestampId) {
603-
// subsequent timestamps
643+
addCurrentTimestampToDataObject = true;
644+
} else if (nextExpectedTimestamp >= timestampId) {
645+
// Next timestamp is within expected time frame
604646
x.push(getTimestampTickLabel(timestampId));
605647
y.push(timestamp.spanCount);
606648
i++;
607-
if (tempGapRectObj) {
608-
tempGapRectObj.x1 = getTimestampTickLabel(timestampId);
609-
shapes.push(tempGapRectObj);
610-
tempGapRectObj = null;
649+
// Add missing coordinates to gap indicator
650+
if (tempGapIndicator) {
651+
tempGapIndicator.x1 = getTimestampTickLabel(timestampId);
652+
tempGapIndicator.y1 = timestamp.spanCount;
653+
const END_OF_ISO_DATE = 10;
654+
if (
655+
tempGapIndicator.x0.substring(0, END_OF_ISO_DATE) !=
656+
tempGapIndicator.x1.substring(0, END_OF_ISO_DATE)
657+
) {
658+
styleNewDayIndicator(tempGapIndicator);
659+
}
660+
661+
shapes.push(tempGapIndicator);
662+
tempGapIndicator = null;
611663
}
612-
addCurentTimestampToDataObject = true;
664+
addCurrentTimestampToDataObject = true;
665+
nextExpectedTimestamp = timestampId + TIMESTAMP_INTERVAL;
613666
} else if (timestamp.epochMilli === null) {
614-
// edge case if API will return null values in the future
667+
// Edge case if API will return null values in the future
615668
x.push(null);
616669
y.push(null);
617670
i++;
618671
} else {
619-
// gap fills for timestamps that did not occur
620-
if (!tempGapRectObj) {
621-
addCurentTimestampToDataObject = true;
672+
// Gap fills for missing timestamps (outside of expected timestamp interval)
673+
if (!tempGapIndicator) {
674+
addCurrentTimestampToDataObject = true;
622675
x.push(null);
623676
y.push(null);
624-
tempGapRectObj = getGapRectangleObj();
625-
tempGapRectObj.x0 = getTimestampTickLabel(
626-
nextExpectedTimestamp - 10000
627-
);
677+
tempGapIndicator = getDashedLine();
678+
const lastNonNullTimestamp =
679+
nextExpectedTimestamp - TIMESTAMP_INTERVAL;
680+
tempGapIndicator.x0 = getTimestampTickLabel(lastNonNullTimestamp);
681+
// Get last non-null value
682+
tempGapIndicator.y0 = y.filter((y) => y != null).at(-1)!;
628683
}
684+
nextExpectedTimestamp =
685+
timestampsOfOneCommit[i].epochMilli ||
686+
timestampId + TIMESTAMP_INTERVAL;
629687
}
630688

631-
nextExpectedTimestamp += 10000;
632-
633689
const markerState = markerStatesOfOneCommit[timestampId];
634690

635-
if (addCurentTimestampToDataObject) {
691+
if (addCurrentTimestampToDataObject) {
636692
if (markerState) {
637693
colors.push(markerState.color);
638694
sizes.push(markerState.size);
@@ -651,7 +707,7 @@ export default class PlotlyTimeline extends Component<IArgs> {
651707
}
652708
timestampIds.push(timestampId);
653709
}
654-
addCurentTimestampToDataObject = false;
710+
addCurrentTimestampToDataObject = false;
655711
}
656712

657713
this.markerStateMap.set(commitId, markerStatesOfOneCommit);
@@ -690,7 +746,7 @@ export default class PlotlyTimeline extends Component<IArgs> {
690746
};
691747
}
692748

693-
resetHighlingInStateObjects() {
749+
resetHighlightInStateObjects() {
694750
this.selectedCommitTimestampsMap = new Map();
695751
this.markerStateMap = new Map();
696752
}
@@ -718,29 +774,4 @@ export default class PlotlyTimeline extends Component<IArgs> {
718774
}
719775

720776
// END Helper functions
721-
722-
<template>
723-
{{#if this.showDummyTimeline}}
724-
<div class='timeline-no-timestamps-outer'>
725-
<div class='timeline-no-timestamps-inner'>
726-
No timestamps available!
727-
</div>
728-
</div>
729-
<div
730-
class='plotlyDiv timeline-blur-effect'
731-
{{didInsert this.setupPlotlyTimelineChart}}
732-
>
733-
</div>
734-
735-
{{else}}
736-
<div
737-
class='plotlyDiv'
738-
{{on 'mouseenter' this.handleMouseEnter}}
739-
{{on 'mouseleave' this.handleMouseLeave}}
740-
{{didUpdate this.updatePlotlyTimelineChart @timelineDataObject}}
741-
{{didInsert this.setupPlotlyTimelineChart}}
742-
>
743-
</div>
744-
{{/if}}
745-
</template>
746777
}

app/utils/application-rendering/camera-controls.ts

+3
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export default class CameraControls {
129129

130130
const position = center.clone().sub(direction);
131131

132+
// Avoid camera being too high
133+
position.y = Math.min(position.y, 20);
134+
132135
// Center to turn camera around should always be on ground level
133136
center.y = 0;
134137

0 commit comments

Comments
 (0)