From 52e091071b841f9fc1314de661144583ec0be543 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Tue, 11 Jun 2024 11:08:27 +0200 Subject: [PATCH 01/23] =?UTF-8?q?=E2=9C=A8=20Now=20uses=20a=20svg=20render?= =?UTF-8?q?er=20(d3)=20instead=20of=20chartjs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + src/app/components/graphs/overview/chart.scss | 62 ++ .../graphs/overview/overview.component.html | 11 +- .../graphs/overview/overview.component.scss | 4 + .../graphs/overview/overview.component.ts | 843 ++++++++++-------- yarn.lock | 488 +++++++++- 6 files changed, 1035 insertions(+), 375 deletions(-) create mode 100644 src/app/components/graphs/overview/chart.scss diff --git a/package.json b/package.json index f888075..f071218 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@ngx-translate/core": "^12.1.2", "@ngx-translate/http-loader": "^4.0.0", "@swimlane/ngx-datatable": "^19.0.0", + "@types/d3": "^7.4.3", "ajv": "^6.12.0", "bootstrap": "^4.5.3", "chart.js": "^2.9.3", @@ -46,6 +47,7 @@ "chartjs-plugin-zoom": "^0.7.5", "class-transformer": "^0.3.1", "core-js": "^3.6.4", + "d3": "^7.9.0", "dexie": "^3.0.3", "dexie-export-import": "^1.0.0", "file-saver": "^2.0.2", diff --git a/src/app/components/graphs/overview/chart.scss b/src/app/components/graphs/overview/chart.scss new file mode 100644 index 0000000..8bbd625 --- /dev/null +++ b/src/app/components/graphs/overview/chart.scss @@ -0,0 +1,62 @@ +.hidden { + display: none; +} + +.repository { + clip-path: url(#clip); +} + +.tick > line { + stroke-width: 1px; + stroke: #E5E5E5; +} + +.axis > line { + stroke-width: 1px; + stroke: #E5E5E5; +} + +.commit { + stroke: #fff; + stroke-width: 1px; +} + +.commit-normal { + width: 6px; + height: 6px; + transform-box: fill-box; + transform-origin: center; + transform: translate(-3px,-3px) rotate(45deg); + +} + +.commit-normal:hover { + fill: white; + stroke-width: 5px; +} + +.commit_line { + stroke: #999; + stroke-width: 2px; +} + +.session { + stroke-width: 2px; + stroke: rgba(79, 195, 247,1.0); + fill: rgba(33, 150, 243, 0.15); +} + +.review { + stroke: blue; + fill: blue +} + +.correction { + stroke: red; + fill: red +} + +.other { + stroke: black; + fill: black +} \ No newline at end of file diff --git a/src/app/components/graphs/overview/overview.component.html b/src/app/components/graphs/overview/overview.component.html index d832473..eb150c4 100644 --- a/src/app/components/graphs/overview/overview.component.html +++ b/src/app/components/graphs/overview/overview.component.html @@ -65,8 +65,7 @@ - - + -
- -
-
-
- +
+
+
+ +
-
-
-
- - - +
+ + + +
+ +
-
-
- -
-
-
+
+
+
+ +
+
-

{{commit_date_format(hovered_commit.commitDate)}}

-

{{hovered_commit.message}}

-

-

{{hovered_commit.author}}

+

{{ commit_date_format(hovered_commit.commitDate) }}

+

{{ hovered_commit.message }}

+

+

{{ hovered_commit.author }}

-
-

{{hovered_group_commit[hovered_group_commit.length-1].message}}

-

-

{{ 'OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN' | translate }} : {{commit_date_format(hovered_group_commit[0].commitDate)}}

-

{{ 'OVERVIEW-GRAPH.COMMIT-TOOLTIP.END' | translate }} : {{commit_date_format(hovered_group_commit[hovered_group_commit.length-1].commitDate)}}

-

{{ 'OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER' | translate }} : {{hovered_group_commit.length}}

-
-
-

{{ 'OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN' | translate }} : {{commit_date_format(hovered_group_commit[0].commitDate)}}

-

{{ 'OVERVIEW-GRAPH.COMMIT-TOOLTIP.END' | translate }} : {{commit_date_format(hovered_group_commit[hovered_group_commit.length-1].commitDate)}}

-

{{ 'OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER' | translate }} : {{hovered_group_commit.length}}

-
+
+

{{ hovered_group_commit[hovered_group_commit.length - 1].message }}

+

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN" | translate }} : + {{ commit_date_format(hovered_group_commit[0].commitDate) }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.END" | translate }} : + {{ + commit_date_format( + hovered_group_commit[hovered_group_commit.length - 1].commitDate + ) + }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER" | translate }} : + {{ hovered_group_commit.length }} +

+
+
+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN" | translate }} : + {{ commit_date_format(hovered_group_commit[0].commitDate) }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.END" | translate }} : + {{ + commit_date_format( + hovered_group_commit[hovered_group_commit.length - 1].commitDate + ) + }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER" | translate }} : + {{ hovered_group_commit.length }} +

+
-
- -
-
-
-

- - {{ 'OVERVIEW-GRAPH.LEGEND.COMMIT' | translate }} - {{ 'OVERVIEW-GRAPH.LEGEND.CLOSING' | translate }}
- - {{ 'OVERVIEW-GRAPH.LEGEND.INTERMEDIATE' | translate }} - - {{ 'OVERVIEW-GRAPH.LEGEND.BEFORE' | translate }} - - {{ 'OVERVIEW-GRAPH.LEGEND.BETWEEN' | translate }} - - {{ 'OVERVIEW-GRAPH.LEGEND.AFTER' | translate }}
- - {{ 'REVIEW' | translate }} - - {{ 'CORRECTION' | translate }} - - {{ 'OTHER' | translate }}
- - - - {{ 'SESSION' | translate }} -

-
+
+ +
+
+
+

+ + {{ "OVERVIEW-GRAPH.LEGEND.COMMIT" | translate }} + + {{ "OVERVIEW-GRAPH.LEGEND.CLOSING" | translate }}
+ + {{ "OVERVIEW-GRAPH.LEGEND.INTERMEDIATE" | translate }} + + {{ "OVERVIEW-GRAPH.LEGEND.BEFORE" | translate }} + + {{ "OVERVIEW-GRAPH.LEGEND.BETWEEN" | translate }} + + {{ "OVERVIEW-GRAPH.LEGEND.AFTER" | translate }}
+ + {{ "REVIEW" | translate }} + + {{ "CORRECTION" | translate }} + + {{ "OTHER" | translate }}
+ + + + + {{ "SESSION" | translate }} +

+
- + -{{ 'OVERVIEW-GRAPH.MARKERS.TOOLTIP' | translate }} -{{ 'OVERVIEW-GRAPH.RESET-ZOOM-TOOLTIP' | translate }} -{{ 'OVERVIEW-GRAPH.CHANGE-ZOOM-TOOLTIP' | translate }} -{{ 'OVERVIEW-GRAPH.CHANGE-TIME-SCALE-TOOLTIP' | translate }} -{{ 'OVERVIEW-GRAPH.RELOAD-TOOLTIP' | translate }} +{{ + "OVERVIEW-GRAPH.MARKERS.TOOLTIP" | translate +}} +{{ + "OVERVIEW-GRAPH.RESET-ZOOM-TOOLTIP" | translate +}} +{{ + "OVERVIEW-GRAPH.CHANGE-ZOOM-TOOLTIP" | translate +}} +{{ + "OVERVIEW-GRAPH.CHANGE-TIME-SCALE-TOOLTIP" | translate +}} +{{ + "OVERVIEW-GRAPH.RELOAD-TOOLTIP" | translate +}} diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 3c426bd..fb2c5d8 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -26,6 +26,7 @@ import * as d3 from "d3"; import { Repository } from "../../../models/Repository.model"; import { tick } from "@angular/core/testing"; import { rejects } from "assert"; +import { Utils } from "../../../services/utils"; @Component({ selector: "overview", @@ -132,6 +133,8 @@ export class OverviewComponent data_g: d3.Selection; commits_g: d3.Selection; + commit_date_format = Utils.COMMIT_DATE_FORMAT; + ngAfterViewInit(): void { this.height += this.dataService.repositories.length * 20; @@ -178,7 +181,8 @@ export class OverviewComponent if (event.keyCode === 32) { this.resetZoom(); } - }); + }) + .on("scroll"); this.clip = this.chart_svg .append("defs") @@ -799,7 +803,6 @@ export class OverviewComponent ): d3.Selection { let g = parent.append("g").datum([commit]); - g.classed("simple-commit", true); g.classed("simple-commit", true); let x = this.x_scale_copy(commit.commitDate); @@ -810,14 +813,10 @@ export class OverviewComponent if (commit.isCloture) { comp = comp.append("circle").attr("r", 3).attr("class", "commit-cloture"); - comp = comp.append("circle").attr("r", 3).attr("class", "commit-cloture"); } else { comp = comp.append("rect").attr("class", "commit-normal"); - comp = comp.append("rect").attr("class", "commit-normal"); } - comp.attr("fill", commit.color.color); - g.attr("date", (commit.commitDate as Date).getTime()); comp.attr("fill", commit.color.color); g.attr("date", (commit.commitDate as Date).getTime()); @@ -860,14 +859,11 @@ export class OverviewComponent g = this.getCommitSimpleComponent(parent, commit); if (before != null) { g.attr("before_date", before.attr("end_date") || before.attr("date")); - g.attr("before_date", before.attr("end_date") || before.attr("date")); } g.attr("after_date", time); } else { g = this.getCommitGroupComponent(parent, before, commit); g.attr("end_date", time); - g = this.getCommitGroupComponent(parent, before, commit); - g.attr("end_date", time); } g.classed("commit", true); From 3b47de22f48f89413b214caaebfa4c37c552cd1e Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Thu, 13 Jun 2024 15:13:15 +0200 Subject: [PATCH 07/23] Enable brushing --- .../graphs/overview/overview.component.html | 10 +---- .../graphs/overview/overview.component.scss | 2 +- .../graphs/overview/overview.component.ts | 45 ++++++++++++++++++- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.html b/src/app/components/graphs/overview/overview.component.html index 54c5174..0a29724 100644 --- a/src/app/components/graphs/overview/overview.component.html +++ b/src/app/components/graphs/overview/overview.component.html @@ -124,6 +124,7 @@ class="btn btn-outline-secondary" [ngbTooltip]="changeZoomTooltip" [disabled]="loading" + (click)="toggleDrag()" >
@@ -147,15 +148,6 @@
-
diff --git a/src/app/components/graphs/overview/overview.component.scss b/src/app/components/graphs/overview/overview.component.scss index d388282..7d83e49 100644 --- a/src/app/components/graphs/overview/overview.component.scss +++ b/src/app/components/graphs/overview/overview.component.scss @@ -1,6 +1,6 @@ .chart-container { width: 100%; - height: 600; + height: 600px; vertical-align: top; overflow: auto; } diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index fb2c5d8..1869064 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -86,6 +86,7 @@ export class OverviewComponent static GROUP_HEIGHT = 6; static CIRCLE_RADIUS = 6; + brush: d3.BrushBehavior; //////////////////////// constructor( @@ -148,6 +149,8 @@ export class OverviewComponent }` ); + const overview = this; + this.chart_svg = this.svg .append("g") .attr( @@ -181,8 +184,7 @@ export class OverviewComponent if (event.keyCode === 32) { this.resetZoom(); } - }) - .on("scroll"); + }); this.clip = this.chart_svg .append("defs") @@ -248,12 +250,23 @@ export class OverviewComponent this.zoom = d3 .zoom() .on("zoom", (event) => { + if (overview.drag) { + return; + } overview.x_scale_copy = event.transform.rescaleX(overview.x_scale); overview.x_g.call(this.x_axis.scale(overview.x_scale_copy)); overview.refreshElementState(); }) .scaleExtent([0.5, overview.maxZoom]); + this.brush = d3 + .brushX() + .extent([ + [0, 0], + [this.width, this.height], + ]) + .on("end", this.updateChart); + this.resetZoom(); } @@ -1002,6 +1015,27 @@ export class OverviewComponent toCommit.forEach((g) => g.classed("commit", true)); } + updateChart(event) { + console.log("HII ?"); + // What are the selected boundaries? + let extent = event.selection; + console.log(extent); + + // If no selection, back to initial coordinate. Otherwise, update X axis domain + if (!extent) { + this.x_scale_copy.domain([4, 8]); + } else { + this.x_scale_copy.domain([ + this.x_scale_copy.invert(extent[0]), + this.x_scale_copy.invert(extent[1]), + ]); + this.brush.clear(this.svg); + } + + // Update axis and area position + this.x_g.transition().duration(1000).call(d3.axisBottom(this.x_scale_copy)); + } + refreshElementState() { const overview = this; @@ -1070,10 +1104,17 @@ export class OverviewComponent .attr("x", (m: Milestone) => overview.x_scale_copy(m.date)); } + toggleDrag() { + this.drag = !this.drag; + } + resetZoom() { d3.select(".chart-container") .call(this.zoom) + .on("dblclick.zoom", null) .call(this.zoom.transform, d3.zoomIdentity.translate(0, 0).scale(1)); + + this.svg.append("g").attr("class", "brush").call(this.brush); } searchSubmit() { From 66efc61c5f7b04e3fb164689f8bf46903b78d2dc Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Thu, 13 Jun 2024 15:40:24 +0200 Subject: [PATCH 08/23] Add commit line for Pause-Resume --- .../graphs/overview/overview.component.ts | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 1869064..e4fb6d5 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -259,13 +259,13 @@ export class OverviewComponent }) .scaleExtent([0.5, overview.maxZoom]); - this.brush = d3 - .brushX() - .extent([ - [0, 0], - [this.width, this.height], - ]) - .on("end", this.updateChart); + // this.brush = d3 + // .brushX() + // .extent([ + // [0, 0], + // [this.width, this.height], + // ]) + // .on("end", overview.updateChart); this.resetZoom(); } @@ -929,6 +929,9 @@ export class OverviewComponent let minDateTime: number, maxDateTime: number; + let lines = []; + let current_line: Commit | undefined = undefined; + commits.forEach((commit) => { minDateTime = minDateTime == null @@ -938,16 +941,27 @@ export class OverviewComponent minDateTime == null ? commit.commitDate.getTime() : Math.max(commit.commitDate.getTime(), minDateTime); + if (commit.message === "Pause") current_line = commit; + else if (commit.message === "Resume" && current_line) { + lines.push([current_line.commitDate, commit.commitDate]); + current_line = undefined; + } before = overview.getCommitComponent(d3.select(this), commit, before); }); - overview.repositories_g[i] - .insert("line", ":first-child") - .attr("class", "commit_line") - .attr("min_date", minDateTime) - .attr("max_date", maxDateTime) - .attr("x1", overview.x_scale_copy(minDateTime)) - .attr("x2", overview.x_scale_copy(maxDateTime)); + if (lines.length === 0) { + lines.push([new Date(minDateTime), new Date(maxDateTime)]); + } + + lines.forEach(([d1, d2]) => { + overview.repositories_g[i] + .insert("line", ":first-child") + .attr("class", "commit_line") + .attr("min_date", d1.getTime()) + .attr("max_date", d2.getTime()) + .attr("x1", overview.x_scale_copy(d1)) + .attr("x2", overview.x_scale_copy(d2)); + }); }); } @@ -1015,11 +1029,11 @@ export class OverviewComponent toCommit.forEach((g) => g.classed("commit", true)); } - updateChart(event) { + onBrush(event) { console.log("HII ?"); // What are the selected boundaries? let extent = event.selection; - console.log(extent); + console.log(this.x_scale_copy); // If no selection, back to initial coordinate. Otherwise, update X axis domain if (!extent) { @@ -1114,7 +1128,7 @@ export class OverviewComponent .on("dblclick.zoom", null) .call(this.zoom.transform, d3.zoomIdentity.translate(0, 0).scale(1)); - this.svg.append("g").attr("class", "brush").call(this.brush); + // this.svg.append("g").attr("class", "brush").call(this.brush); } searchSubmit() { From 8303c6f228ad22c7df93f3140217d8f920755b6f Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Thu, 13 Jun 2024 15:42:29 +0200 Subject: [PATCH 09/23] Remove dragging as it's not implemented yet --- src/app/components/graphs/overview/overview.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.html b/src/app/components/graphs/overview/overview.component.html index 0a29724..77936b5 100644 --- a/src/app/components/graphs/overview/overview.component.html +++ b/src/app/components/graphs/overview/overview.component.html @@ -120,7 +120,7 @@ > - + -->
From 946ee7d1aa152dee435cbaabd3adc26e899cabd2 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Thu, 13 Jun 2024 16:07:36 +0200 Subject: [PATCH 10/23] Add missing commit date format --- src/app/services/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/services/utils.ts b/src/app/services/utils.ts index 7b950c4..3078102 100644 --- a/src/app/services/utils.ts +++ b/src/app/services/utils.ts @@ -11,6 +11,8 @@ export class Utils { static readonly DATE_FORMAT = "([0-9]{4}-[0-1]?[0-9]-[0-3]?[0-9] [0-2]?[0-9]:[0-5][0-9])|([0-9]{4}-[0-1]?[0-9]-[0-3]?[0-9]T[0-2]?[0-9]:[0-5][0-9](:[0-5][0-9])?(.[0-9]{3}Z?)?)"; + static readonly SLIDER_STEP = 86400000; + static readonly OVERVIEW_NAME_LENGTH_LIMIT = 20; static readonly COMMIT_DATE_FORMAT = (date: Date) => date.toLocaleDateString(undefined, { hour12: false, hour: "2-digit" }); From 96b212a7edcdb1e2cbbe1ac78b849870e0ea6c43 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Fri, 14 Jun 2024 16:40:06 +0200 Subject: [PATCH 11/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20date=20alignment=20b?= =?UTF-8?q?eing=20wrong?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/graphs/overview/chart.scss | 5 + .../graphs/overview/overview.component.scss | 3 +- .../graphs/overview/overview.component.ts | 169 ++++++++++++------ src/app/services/utils.ts | 17 +- 4 files changed, 132 insertions(+), 62 deletions(-) diff --git a/src/app/components/graphs/overview/chart.scss b/src/app/components/graphs/overview/chart.scss index 9cb99e0..e0f419c 100644 --- a/src/app/components/graphs/overview/chart.scss +++ b/src/app/components/graphs/overview/chart.scss @@ -6,6 +6,11 @@ clip-path: url(#clip); } +.tick_line { + stroke-width: 1px; + stroke: #e5e5e5; +} + .tick > line { stroke-width: 1px; stroke: #e5e5e5; diff --git a/src/app/components/graphs/overview/overview.component.scss b/src/app/components/graphs/overview/overview.component.scss index 7d83e49..21b33bb 100644 --- a/src/app/components/graphs/overview/overview.component.scss +++ b/src/app/components/graphs/overview/overview.component.scss @@ -1,6 +1,7 @@ .chart-container { width: 100%; - height: 600px; + min-height: 0px; + max-height: 600px; vertical-align: top; overflow: auto; } diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index e4fb6d5..70ea567 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -66,6 +66,12 @@ export class OverviewComponent addModal: boolean; savedMilestoneModal: Milestone; + // params + margin = { top: 10, right: 30, bottom: 80, left: 180 }; + width = 1800 - this.margin.left - this.margin.right; + height = 100 - this.margin.top - this.margin.bottom; + maxZoom: number; + // svg components svg: d3.Selection; scrollable: d3.Selection; @@ -76,10 +82,21 @@ export class OverviewComponent repositories_g: d3.Selection[]; axis_g: d3.Selection; other_g: d3.Selection; - sesssion_g: d3.Selection; + session_g: d3.Selection; review_g: d3.Selection; correction_g: d3.Selection; commits_line_g: d3.Selection; + data_g: d3.Selection; + commits_g: d3.Selection; + + x_scale: d3.ScaleTime; + x_scale_copy: d3.ScaleTime; // Used by zooming + x_axis: d3.Axis; + y_scale: d3.ScaleLinear; + y_axis: d3.Axis; + + clip: d3.Selection; + zoom: d3.ZoomBehavior; hovered_commit: Commit; hovered_group_commit: Commit[]; @@ -87,6 +104,7 @@ export class OverviewComponent static GROUP_HEIGHT = 6; static CIRCLE_RADIUS = 6; brush: d3.BrushBehavior; + current_zoom: any; //////////////////////// constructor( @@ -118,22 +136,6 @@ export class OverviewComponent this.unsubscribeAssignmentModified(this.assignmentsModified$); } - margin = { top: 10, right: 30, bottom: 80, left: 180 }; - width = 1800 - this.margin.left - this.margin.right; - height = 100 - this.margin.top - this.margin.bottom; - clip: d3.Selection; - zoom: d3.ZoomBehavior; - - x_scale: d3.ScaleTime; - x_scale_copy: d3.ScaleTime; // Used by zooming - x_axis: d3.Axis; - y_scale: d3.ScaleLinear; - y_axis: d3.Axis; - maxZoom: number; - - data_g: d3.Selection; - commits_g: d3.Selection; - commit_date_format = Utils.COMMIT_DATE_FORMAT; ngAfterViewInit(): void { @@ -158,13 +160,25 @@ export class OverviewComponent "translate(" + this.margin.left + "," + this.margin.top + ")" ); - d3.select(".chart-container") - .on("click", (event) => { - if (this.x_scale_copy == null) return; + this.data_g = this.chart_svg.append("g"); + + this.data_g + .append("rect") + .attr("id", "data") + .attr("width", this.width) + .attr("height", this.height) + .attr("opacity", "0") + .on("click", (event: MouseEvent) => { event.stopPropagation(); - const rawDate = this.x_scale_copy.invert(event.pageX); - this.openContextMenu(event.pageX, event.pageY, rawDate); - }) + var rect = (event.target as any).getBoundingClientRect(); + var x = + ((event.clientX - rect.left) / (rect.right - rect.left)) * + overview.width; //x position within the element. + let rawDate = overview.x_scale_copy.invert(x); + this.openContextMenu(x, event.pageY, rawDate); + }); + + d3.select(".chart-container") .on("mousemove", function (e) { var tooltip = document.getElementById("commit_hover") || @@ -245,6 +259,7 @@ export class OverviewComponent this.loadPoints(); this.setupZoom(); } + setupZoom() { const overview = this; this.zoom = d3 @@ -253,6 +268,7 @@ export class OverviewComponent if (overview.drag) { return; } + overview.current_zoom = event.transform; overview.x_scale_copy = event.transform.rescaleX(overview.x_scale); overview.x_g.call(this.x_axis.scale(overview.x_scale_copy)); overview.refreshElementState(); @@ -284,6 +300,10 @@ export class OverviewComponent review.questions?.includes(question) ).length); + if (this.session_g != null) this.session_g.remove(); + if (this.review_g != null) this.session_g.remove(); + if (this.correction_g != null) this.session_g.remove(); + if (this.other_g != null) this.session_g.remove(); if (this.dataService.sessions && this.showSessions) { this.loadSessions(); } @@ -459,6 +479,7 @@ export class OverviewComponent getRectForSession(g: d3.Selection, session: Session) { const overview = this; g.append("rect") + .datum(session) .attr("class", "session") .attr("clip-path", "url(#clip)") .attr("x", this.x_scale_copy(session.startDate)) @@ -487,12 +508,12 @@ export class OverviewComponent session.tpGroup === this.dataService.groupFilter ); - this.sesssion_g = this.chart_svg.append("g"); + this.session_g = this.chart_svg.append("g"); const overview = this; setTimeout(() => { - this.sesssion_g + this.session_g .selectAll(".session") .data(loaded_sessions) .enter() @@ -504,15 +525,16 @@ export class OverviewComponent getLineForMilestone( g: d3.Selection, - date: Date, + m: Milestone, class_: string ) { const overview = this; return g .append("rect") + .datum(m) .attr("clip-path", "url(#clip)") .attr("class", class_) - .attr("x", this.x_scale_copy(date)) + .attr("x", this.xScaledTimeZoned(m.date)) .attr("width", 1) .attr("y", 0) .attr("transform", "translate(" + [-1, 0] + ")") @@ -527,21 +549,17 @@ export class OverviewComponent loadReviews(milestone_filter: (review: Milestone) => number | boolean) { let loaded_reviews = this.dataService.reviews.filter(milestone_filter); - this.review_g = this.chart_svg.append("g"); + this.review_g = this.data_g.append("g"); const overview = this; setTimeout(() => { - this.sesssion_g + this.review_g .selectAll(".session") .data(loaded_reviews) .enter() .each(function (d: Milestone) { - overview.getLineForMilestone( - d3.select(this), - d.date, - "milestone session" - ); + overview.getLineForMilestone(d3.select(this), d, "milestone session"); }); }); @@ -556,7 +574,7 @@ export class OverviewComponent let loaded_corrections = this.dataService.corrections.filter(milestone_filter); - this.correction_g = this.chart_svg.append("g"); + this.correction_g = this.data_g.append("g"); const overview = this; @@ -568,7 +586,7 @@ export class OverviewComponent .each(function (d: Milestone) { overview.getLineForMilestone( d3.select(this), - d.date, + d, "milestone correction" ); }); @@ -583,7 +601,7 @@ export class OverviewComponent loadOthers(milestone_filter: (review: Milestone) => number | boolean) { let loaded_other = this.dataService.others.filter(milestone_filter); - this.other_g = this.chart_svg.append("g"); + this.other_g = this.data_g.append("g"); const overview = this; @@ -593,11 +611,7 @@ export class OverviewComponent .data(loaded_other) .enter() .each(function (d: Milestone) { - overview.getLineForMilestone( - d3.select(this), - d.date, - "milestone other" - ); + overview.getLineForMilestone(d3.select(this), d, "milestone other"); }); }); // label: { @@ -608,6 +622,7 @@ export class OverviewComponent } setupAxis(repositories: Repository[], minDate: Date, maxDate: Date) { + if (this.axis_g != null) this.axis_g.remove(); const overview = this; this.x_scale = d3 @@ -629,6 +644,7 @@ export class OverviewComponent if (ticks[ticks.length - 1] == null || ticks[0] == null) return ""; let spacing = (ticks[ticks.length - 1].getTime() - ticks[0].getTime()) / 1000; + return OverviewComponent.multiFormat(spacing, d); }); @@ -650,6 +666,7 @@ export class OverviewComponent .tickFormat((d) => repositories[d.valueOf() - 1]?.name || "") .tickSize(-this.width); + if (this.y_g != null) this.y_g.remove(); this.y_g = this.axis_g.append("g").call(this.y_axis); // Hide the first tick use to prevent data from being placed on top of the chart @@ -884,12 +901,25 @@ export class OverviewComponent return g; } - static formatDay = d3.utcFormat("%Y/%m/%d"); + static formatDay = d3.utcFormat("%d/%m/%Y"); static formatHour = d3.utcFormat("%H:%M"); static multiFormat(spacing: number, date: Date) { - if (spacing > 24 * 3600) return this.formatDay(date); - else return this.formatHour(date); + const options: Intl.NumberFormatOptions = { + useGrouping: false, + minimumIntegerDigits: 2, + }; + + if (spacing > 24 * 3600) + return `${date.getDate().toLocaleString(undefined, options)}/${date + .getMonth() + .toLocaleString(undefined, options)}/${date + .getFullYear() + .toLocaleString(undefined, options)}`; + else + return `${date.getHours().toLocaleString(undefined, options)}:${date + .getMinutes() + .toLocaleString(undefined, options)}`; } loadPoints() { @@ -900,7 +930,9 @@ export class OverviewComponent repository.tpGroup === this.dataService.groupFilter ); - this.repository_g = this.chart_svg.append("g"); + if (this.repository_g != null) this.repository_g.remove(); + + this.repository_g = this.data_g.append("g"); this.repositories_g = new Array(repositories.length); let [minDate, maxDate] = d3.extent( repositories.map((v) => v.commits).reduce((a, b) => a.concat(b), []), @@ -1030,10 +1062,8 @@ export class OverviewComponent } onBrush(event) { - console.log("HII ?"); // What are the selected boundaries? let extent = event.selection; - console.log(this.x_scale_copy); // If no selection, back to initial coordinate. Otherwise, update X axis domain if (!extent) { @@ -1050,6 +1080,21 @@ export class OverviewComponent this.x_g.transition().duration(1000).call(d3.axisBottom(this.x_scale_copy)); } + getOffset(d: Date) { + return ( + this.x_scale_copy(d) - + this.x_scale_copy(new Date(d.getTime() + d.getTimezoneOffset() * 60000)) + ); + } + + xScaledTimeZoned(d: Date) { + if (!d) { + return Number.MIN_VALUE; + } + + return this.x_scale_copy(d) + this.getOffset(d); + } + refreshElementState() { const overview = this; @@ -1058,8 +1103,8 @@ export class OverviewComponent let g: d3.Selection = d3.select(this); let commits = g.datum(); - let min_x = overview.x_scale_copy(commits[0].commitDate); - let max_x = overview.x_scale_copy( + let min_x = overview.xScaledTimeZoned(commits[0].commitDate); + let max_x = overview.xScaledTimeZoned( commits[commits.length - 1].commitDate ); @@ -1071,7 +1116,7 @@ export class OverviewComponent .attr( "transform", (commits: Commit[]) => - `translate(${overview.x_scale_copy(commits[0].commitDate)}, 0)` + `translate(${overview.xScaledTimeZoned(commits[0].commitDate)}, 0)` ); repo_g @@ -1091,31 +1136,34 @@ export class OverviewComponent overview.repositories_g.forEach((g, i) => { g.selectAll(".commit_line") .attr("x1", function () { - let real_x = overview.x_scale_copy( + let real_x = overview.xScaledTimeZoned( new Date(Number.parseInt(d3.select(this).attr("min_date"))) ); return Math.max(Math.min(real_x, overview.width), 0); }) .attr("x2", function () { - let real_x = overview.x_scale_copy( + let real_x = overview.xScaledTimeZoned( new Date(Number.parseInt(d3.select(this).attr("max_date"))) ); return Math.max(Math.min(real_x, overview.width), 0); }); }); - this.sesssion_g + this.session_g .selectAll(".session") - .attr("x", (s: Session) => overview.x_scale_copy(s.startDate)) + .attr("x", (s: Session) => { + return overview.xScaledTimeZoned(s.startDate); + }) .attr( "width", (s: Session) => - overview.x_scale_copy(s.endDate) - overview.x_scale_copy(s.startDate) + overview.xScaledTimeZoned(s.endDate) - + overview.xScaledTimeZoned(s.startDate) ); - this.chart_svg + this.data_g .selectAll(".milestone") - .attr("x", (m: Milestone) => overview.x_scale_copy(m.date)); + .attr("x", (m: Milestone) => overview.xScaledTimeZoned(m.date)); } toggleDrag() { @@ -1126,7 +1174,10 @@ export class OverviewComponent d3.select(".chart-container") .call(this.zoom) .on("dblclick.zoom", null) - .call(this.zoom.transform, d3.zoomIdentity.translate(0, 0).scale(1)); + .call( + this.zoom.transform, + this.current_zoom || d3.zoomIdentity.translate(0, 0).scale(1) + ); // this.svg.append("g").attr("class", "brush").call(this.brush); } diff --git a/src/app/services/utils.ts b/src/app/services/utils.ts index 3078102..f037be2 100644 --- a/src/app/services/utils.ts +++ b/src/app/services/utils.ts @@ -13,8 +13,21 @@ export class Utils { static readonly SLIDER_STEP = 86400000; static readonly OVERVIEW_NAME_LENGTH_LIMIT = 20; - static readonly COMMIT_DATE_FORMAT = (date: Date) => - date.toLocaleDateString(undefined, { hour12: false, hour: "2-digit" }); + static readonly COMMIT_DATE_FORMAT = (date: Date) => { + const options: Intl.NumberFormatOptions = { + useGrouping: false, + minimumIntegerDigits: 2, + }; + + let year = date.getFullYear().toLocaleString(undefined, options); + let month = date.getMonth().toLocaleString(undefined, options); + let day = date.getDate().toLocaleString(undefined, options); + let hour = date.getHours().toLocaleString(undefined, options); + let minute = date.getMinutes().toLocaleString(undefined, options); + let seconds = date.getSeconds().toLocaleString(undefined, options); + + return `${day}/${month}/${year} ${hour}:${minute}:${seconds}`; + }; static getTimeFromDate(date: Date) { return date From 944d812dbb430747946522666b7ce81de060e613 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Tue, 18 Jun 2024 13:41:35 +0200 Subject: [PATCH 12/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20display=20issue=20re?= =?UTF-8?q?lating=20to=20offset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/graphs/overview/chart.scss | 20 +++ .../graphs/overview/overview.component.ts | 153 +++++++++++------- src/app/services/utils.ts | 1 + 3 files changed, 116 insertions(+), 58 deletions(-) diff --git a/src/app/components/graphs/overview/chart.scss b/src/app/components/graphs/overview/chart.scss index e0f419c..92864f2 100644 --- a/src/app/components/graphs/overview/chart.scss +++ b/src/app/components/graphs/overview/chart.scss @@ -53,14 +53,34 @@ .review { stroke: blue; fill: blue; + background-color: blue; } .correction { stroke: red; fill: red; + background-color: red; } .other { stroke: black; fill: black; + background-color: black; } + +.milestone > .milestone_label { + border-radius: 10; +} + +.milestone > rect { + rx: 5px; + ry: 5px; +} + +.milestone > text { + padding: 5px; + color: white; + fill: white; + stroke: white; + user-select: none; +} \ No newline at end of file diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 70ea567..ffb34d5 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -67,7 +67,7 @@ export class OverviewComponent savedMilestoneModal: Milestone; // params - margin = { top: 10, right: 30, bottom: 80, left: 180 }; + margin = { top: 30, right: 30, bottom: 80, left: 180 }; width = 1800 - this.margin.left - this.margin.right; height = 100 - this.margin.top - this.margin.bottom; maxZoom: number; @@ -225,6 +225,8 @@ export class OverviewComponent this.loading = false; } + + this.refreshElementState(); }); } @@ -482,13 +484,13 @@ export class OverviewComponent .datum(session) .attr("class", "session") .attr("clip-path", "url(#clip)") - .attr("x", this.x_scale_copy(session.startDate)) + .attr("x", this.xScaledTimeZoned(session.startDate)) .attr("height", this.height) .attr("y", 0) .attr( "width", - this.x_scale_copy(session.endDate) - - this.x_scale_copy(session.startDate) + this.xScaledTimeZoned(session.endDate) - + this.xScaledTimeZoned(session.startDate) ) .on("click", (e) => overview.openEditSessionContextMenu( @@ -524,21 +526,50 @@ export class OverviewComponent } getLineForMilestone( - g: d3.Selection, + parent: d3.Selection, m: Milestone, - class_: string + class_: string, + index: number ) { const overview = this; - return g - .append("rect") - .datum(m) + let g = parent.append("g").attr("class", class_); + + // Line + g.append("rect") .attr("clip-path", "url(#clip)") - .attr("class", class_) - .attr("x", this.xScaledTimeZoned(m.date)) + .attr("x", 0) .attr("width", 1) .attr("y", 0) .attr("transform", "translate(" + [-1, 0] + ")") - .attr("height", this.height) + .attr("height", this.height); + + // Box + let box = g.append("rect").attr("y", 20); + + // Text + let text = g + .append("text") + .attr("y", -8) + .text(m.label || m.type.substring(0, m.type.length - 1) + " " + index) + .attr("text-anchor", "middle"); + + let bbox = text.node().getBBox(); + + bbox.width += 4; + bbox.height += 5; + bbox.x -= 2; + bbox.y -= 1; + + box.attr("width", bbox.width); + box.attr("height", bbox.height); + box.attr("x", -bbox.width / 2); + box.attr("y", bbox.y); + + let x = this.xScaledTimeZoned(m.date); + + return g + .attr("transform", `translate(${x}, 0)`) + .call((g) => g.classed("hidden", x < 0 || x > overview.width)) .on("click", (e, d: Milestone) => { e.stopPropagation(); const rawDate = this.x_scale.invert(e.pageX); @@ -555,19 +586,18 @@ export class OverviewComponent setTimeout(() => { this.review_g - .selectAll(".session") + .selectAll(".review") .data(loaded_reviews) .enter() - .each(function (d: Milestone) { - overview.getLineForMilestone(d3.select(this), d, "milestone session"); + .each(function (d: Milestone, i) { + overview.getLineForMilestone( + d3.select(this), + d, + "milestone review", + i + ); }); }); - - // label: { - // content: review.label || "Review " + (index + 1), - // enabled: true, - // position: "top", - // }, } loadCorrections(milestone_filter: (review: Milestone) => number | boolean) { @@ -583,19 +613,15 @@ export class OverviewComponent .selectAll(".correction") .data(loaded_corrections) .enter() - .each(function (d: Milestone) { + .each(function (d: Milestone, i) { overview.getLineForMilestone( d3.select(this), d, - "milestone correction" + "milestone correction", + i ); }); }); - // label: { - // content: correction.label || "Correction " + (index + 1), - // enabled: true, - // position: "top", - // }, } loadOthers(milestone_filter: (review: Milestone) => number | boolean) { @@ -610,15 +636,15 @@ export class OverviewComponent .selectAll(".other") .data(loaded_other) .enter() - .each(function (d: Milestone) { - overview.getLineForMilestone(d3.select(this), d, "milestone other"); + .each(function (d: Milestone, i) { + overview.getLineForMilestone( + d3.select(this), + d, + "milestone other", + i + ); }); }); - // label: { - // content: other.label || "Other " + (index + 1), - // enabled: true, - // position: "top", - // }, } setupAxis(repositories: Repository[], minDate: Date, maxDate: Date) { @@ -695,8 +721,8 @@ export class OverviewComponent } getCommitGroupPathD(first: Commit, last: Commit) { - let begin_x = this.x_scale_copy(first.commitDate); - let end_x = this.x_scale_copy(last.commitDate); + let begin_x = this.xScaledTimeZoned(first.commitDate); + let end_x = this.xScaledTimeZoned(last.commitDate); return `M 0 0 h ${Math.max(end_x - begin_x, 6)} a ${ OverviewComponent.CIRCLE_RADIUS @@ -715,8 +741,8 @@ export class OverviewComponent let g = parent.append("g").datum(sorted); - let begin_x = this.x_scale_copy(sorted[0].commitDate); - let end_x = this.x_scale_copy(sorted[sorted.length - 1].commitDate); + let begin_x = this.xScaledTimeZoned(sorted[0].commitDate); + let end_x = this.xScaledTimeZoned(sorted[sorted.length - 1].commitDate); g.attr("class", "commit-group commit") .append("path") @@ -751,7 +777,7 @@ export class OverviewComponent let g; if (group == null) { - let x = this.x_scale_copy(commit.commitDate); + let x = this.xScaledTimeZoned(commit.commitDate); g = parent.append("g").datum([commit]); @@ -807,8 +833,8 @@ export class OverviewComponent group.datum(all_commits); - let begin_x = this.x_scale_copy(all_commits[0].commitDate); - let end_x = this.x_scale_copy( + let begin_x = this.xScaledTimeZoned(all_commits[0].commitDate); + let end_x = this.xScaledTimeZoned( all_commits[all_commits.length - 1].commitDate ); @@ -835,7 +861,7 @@ export class OverviewComponent g.classed("simple-commit", true); - let x = this.x_scale_copy(commit.commitDate); + let x = this.xScaledTimeZoned(commit.commitDate); let comp: d3.Selection = g .append("a") @@ -850,7 +876,7 @@ export class OverviewComponent comp.attr("fill", commit.color.color); g.attr("date", (commit.commitDate as Date).getTime()); - g.attr("transfrom", `translate(${x}, 0)`) + g.attr("transform", `translate(${x}, 0)`) .on("mouseenter", () => (this.hovered_commit = commit)) .on("mouseleave", () => { if ( @@ -867,9 +893,9 @@ export class OverviewComponent shouldGroupCommit(commit_before: Commit, commit_after: Commit): boolean { return ( !commit_before.isCloture && - this.x_scale_copy(commit_after.commitDate) - - this.x_scale_copy(commit_before.commitDate) < - 3 + this.xScaledTimeZoned(commit_after.commitDate) - + this.xScaledTimeZoned(commit_before.commitDate) < + Utils.COMMIT_FUSE_RANGE ); } @@ -973,8 +999,8 @@ export class OverviewComponent minDateTime == null ? commit.commitDate.getTime() : Math.max(commit.commitDate.getTime(), minDateTime); - if (commit.message === "Pause") current_line = commit; - else if (commit.message === "Resume" && current_line) { + if (commit.message === "Resume") current_line = commit; + else if (commit.message === "Pause" && current_line) { lines.push([current_line.commitDate, commit.commitDate]); current_line = undefined; } @@ -991,8 +1017,8 @@ export class OverviewComponent .attr("class", "commit_line") .attr("min_date", d1.getTime()) .attr("max_date", d2.getTime()) - .attr("x1", overview.x_scale_copy(d1)) - .attr("x2", overview.x_scale_copy(d2)); + .attr("x1", overview.xScaledTimeZoned(d1)) + .attr("x2", overview.xScaledTimeZoned(d2)); }); }); } @@ -1005,9 +1031,10 @@ export class OverviewComponent let date = Number.parseInt(g.attr("after_date")); let range_in_pixel = - overview.x_scale_copy(range + date) - overview.x_scale_copy(date); + overview.xScaledTimeZoned(new Date(range + date)) - + overview.xScaledTimeZoned(new Date(date)); - if (range_in_pixel > 3) { + if (range_in_pixel >= Utils.COMMIT_FUSE_RANGE) { let before = undefined; let commits = g.datum() as Commit[]; g.remove(); @@ -1081,10 +1108,11 @@ export class OverviewComponent } getOffset(d: Date) { - return ( - this.x_scale_copy(d) - - this.x_scale_copy(new Date(d.getTime() + d.getTimezoneOffset() * 60000)) - ); + return 0; + // return ( + // this.x_scale_copy(d) - + // this.x_scale_copy(new Date(d.getTime() + d.getTimezoneOffset() * 60000)) + // ); } xScaledTimeZoned(d: Date) { @@ -1126,6 +1154,12 @@ export class OverviewComponent ); }); + this.data_g.selectAll(".milestone").each(function (m: Milestone) { + let g = d3.select(this); + let x = overview.xScaledTimeZoned(m.date); + g.classed("hidden", x < 0 || x > overview.width); + }); + overview.repositories_g.forEach((repo_g) => this.refreshRepoBySplittingGroup(repo_g) ); @@ -1163,7 +1197,10 @@ export class OverviewComponent this.data_g .selectAll(".milestone") - .attr("x", (m: Milestone) => overview.xScaledTimeZoned(m.date)); + .attr( + "transform", + (m: Milestone) => `translate(${overview.xScaledTimeZoned(m.date)}, 0)` + ); } toggleDrag() { diff --git a/src/app/services/utils.ts b/src/app/services/utils.ts index f037be2..951e7f0 100644 --- a/src/app/services/utils.ts +++ b/src/app/services/utils.ts @@ -13,6 +13,7 @@ export class Utils { static readonly SLIDER_STEP = 86400000; static readonly OVERVIEW_NAME_LENGTH_LIMIT = 20; + static readonly COMMIT_FUSE_RANGE = 5; static readonly COMMIT_DATE_FORMAT = (date: Date) => { const options: Intl.NumberFormatOptions = { useGrouping: false, From 3165a10fe7b3d474ffd9904cf8389f2dfb6f1b95 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Tue, 18 Jun 2024 14:32:31 +0200 Subject: [PATCH 13/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20commit=20link=20bein?= =?UTF-8?q?g=20opened=20on=20same=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/graphs/overview/overview.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index ffb34d5..ccf0b19 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -865,7 +865,8 @@ export class OverviewComponent let comp: d3.Selection = g .append("a") - .attr("href", (d) => d[0].url); + .attr("href", (d) => d[0].url) + .attr("target", "_blank"); if (commit.isCloture) { comp = comp.append("circle").attr("r", 3).attr("class", "commit-cloture"); From aadfdbebd07905e131ab58b92ca5c9ce3fee8c2e Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Tue, 18 Jun 2024 14:42:22 +0200 Subject: [PATCH 14/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20commit=20filter=20no?= =?UTF-8?q?t=20working?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/graphs/overview/overview.component.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index ccf0b19..9b496c2 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -982,9 +982,13 @@ export class OverviewComponent .attr("transform", `translate(0, ${overview.y_scale(i + 1)})`); let before = undefined; - let commits = repository.commits.sort( - (a, b) => a.commitDate.getTime() - b.commitDate.getTime() - ); + let commits = repository.commits + .filter( + (commit) => + !overview.searchFilter.length || + overview.searchFilter.includes(commit.question) + ) + .sort((a, b) => a.commitDate.getTime() - b.commitDate.getTime()); let minDateTime: number, maxDateTime: number; From 003d07f6dfb2e3d3fb44cc96ee2037bbc16ba74b Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Tue, 18 Jun 2024 15:35:15 +0200 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20zoom=20and=20size=20?= =?UTF-8?q?issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/graphs/overview/chart.scss | 16 ++++-- .../graphs/overview/overview.component.html | 2 +- .../graphs/overview/overview.component.scss | 1 + .../graphs/overview/overview.component.ts | 50 ++++++++++++------- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/app/components/graphs/overview/chart.scss b/src/app/components/graphs/overview/chart.scss index 92864f2..8fec5ad 100644 --- a/src/app/components/graphs/overview/chart.scss +++ b/src/app/components/graphs/overview/chart.scss @@ -2,6 +2,10 @@ display: none; } +text.repo_name { + font-size: 18px; +} + .repository { clip-path: url(#clip); } @@ -23,15 +27,19 @@ .commit { stroke: #fff; - stroke-width: 1px; + stroke-width: 2px; +} + +.commit-cloture { + r: 7px; } .commit-normal { - width: 6px; - height: 6px; + width: 12px; + height: 12px; transform-box: fill-box; transform-origin: center; - transform: translate(-3px, -3px) rotate(45deg); + transform: translate(-6px, -6px) rotate(45deg); } .commit-normal:hover { diff --git a/src/app/components/graphs/overview/overview.component.html b/src/app/components/graphs/overview/overview.component.html index 77936b5..8d8bb5d 100644 --- a/src/app/components/graphs/overview/overview.component.html +++ b/src/app/components/graphs/overview/overview.component.html @@ -109,7 +109,7 @@ -
-
+
+
+
+
+
+
+

{{ commit_date_format(hovered_commit.commitDate) }}

{{ hovered_commit.message }}

{{ hovered_commit.author }}

-
+

{{ hovered_group_commit[hovered_group_commit.length - 1].message }}

diff --git a/src/app/components/graphs/overview/overview.component.scss b/src/app/components/graphs/overview/overview.component.scss index 63fb77d..bdaf6e9 100644 --- a/src/app/components/graphs/overview/overview.component.scss +++ b/src/app/components/graphs/overview/overview.component.scss @@ -4,6 +4,19 @@ max-height: 600px; vertical-align: top; overflow: auto; + overscroll-behavior: none; +} + +.chart-container-absolute{ + position: absolute; + top:0; + left: 0; + width: 100%; + min-height: 0px; + max-height: 660px; + vertical-align: top; + pointer-events: none; + overflow: hidden; } .chart-container:focus { diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 93c5742..983189f 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -67,9 +67,10 @@ export class OverviewComponent savedMilestoneModal: Milestone; // params - margin = { top: 30, right: 30, bottom: 80, left: 250 }; + margin = { top: 0, right: 30, bottom: 80, left: 250 }; + margin_abs = { top: 30, right: 30, bottom: 80, left: 250 }; width = 1800 - this.margin.left - this.margin.right; - height = 100 - this.margin.top - this.margin.bottom; + height = 200 - this.margin.top - this.margin.bottom; maxZoom: number; // svg components @@ -81,6 +82,7 @@ export class OverviewComponent repository_g: d3.Selection; repositories_g: d3.Selection[]; axis_g: d3.Selection; + axis_abs_g: d3.Selection; other_g: d3.Selection; session_g: d3.Selection; review_g: d3.Selection; @@ -105,6 +107,9 @@ export class OverviewComponent static CIRCLE_RADIUS = 12; brush: d3.BrushBehavior; current_zoom: any; + chart_abs_g: d3.Selection; + svg_abs: d3.Selection; + real_height: number; //////////////////////// constructor( @@ -140,6 +145,7 @@ export class OverviewComponent ngAfterViewInit(): void { this.height += this.dataService.repositories.length * 35; + this.real_height = Math.min(570, this.height); this.svg = d3 .select(".chart-container") @@ -151,6 +157,16 @@ export class OverviewComponent }` ); + this.svg_abs = d3 + .select(".chart-container-absolute") + .append("svg") + .attr( + "viewBox", + `0 0 ${this.width + this.margin_abs.left + this.margin_abs.right} ${ + Math.min(600, this.height) + 2 * 30 /*padding*/ + } ` + ); + const overview = this; this.chart_svg = this.svg @@ -162,6 +178,13 @@ export class OverviewComponent this.data_g = this.chart_svg.append("g"); + this.chart_abs_g = this.svg_abs + .append("g") + .attr( + "transform", + "translate(" + this.margin_abs.left + "," + this.margin_abs.top + ")" + ); + this.data_g .append("rect") .attr("id", "data") @@ -192,6 +215,7 @@ export class OverviewComponent tooltip.style.top = y + 20 + "px"; tooltip.style.left = x + 20 + "px"; }) + .on("scroll", () => this.refreshElementState()) .attr("tabindex", "0") .attr("focusable", "true") .on("keypress", (event) => { @@ -270,26 +294,130 @@ export class OverviewComponent if (overview.drag) { return; } + overview.current_zoom = event.transform; - overview.x_scale_copy = event.transform.rescaleX(overview.x_scale); + overview.x_scale_copy = overview.current_zoom.rescaleX( + overview.x_scale + ); overview.x_g.call(this.x_axis.scale(overview.x_scale_copy)); overview.refreshElementState(); }) + .filter((event) => { + return event.shiftKey || !(event instanceof WheelEvent); + }) .scaleExtent([0.5, overview.maxZoom]); - this.data_g = this.data_g.call(this.zoom); //.on("dblclick.zoom", null); - // this.brush = d3 - // .brushX() - // .extent([ - // [0, 0], - // [this.width, this.height], - // ]) - // .on("end", overview.updateChart); + this.data_g = this.data_g.call(this.zoom).on("dblclick.zoom", null); this.resetZoom(true); } loadGraphDataAndRefresh() { + this.margin = { top: 0, right: 30, bottom: 80, left: 250 }; + this.margin_abs = { top: 30, right: 30, bottom: 80, left: 250 }; + this.width = 1800 - this.margin.left - this.margin.right; + this.height = 200 - this.margin.top - this.margin.bottom; + + const repositories: Repository[] = this.dataService.repositories.filter( + (repository) => + !this.dataService.groupFilter || + repository.tpGroup === this.dataService.groupFilter + ); + + this.height += repositories.length * 35; + this.real_height = Math.min(570, this.height); + + this.svg.remove(); + + this.svg = d3 + .select(".chart-container") + .append("svg") + .attr( + "viewBox", + `0 0 ${this.width + this.margin.left + this.margin.right} ${ + this.height + this.margin.top + this.margin.right + }` + ); + + this.svg_abs.remove(); + this.svg_abs = d3 + .select(".chart-container-absolute") + .append("svg") + .attr( + "viewBox", + `0 0 ${this.width + this.margin_abs.left + this.margin_abs.right} ${ + Math.min(600, this.height) + 2 * 30 /*padding*/ + } ` + ); + + const overview = this; + + this.chart_svg = this.svg + .append("g") + .attr( + "transform", + "translate(" + this.margin.left + "," + this.margin.top + ")" + ); + + this.data_g = this.chart_svg.append("g"); + + this.chart_abs_g = this.svg_abs + .append("g") + .attr( + "transform", + "translate(" + this.margin_abs.left + "," + this.margin_abs.top + ")" + ); + + this.data_g + .append("rect") + .attr("id", "data") + .attr("width", this.width) + .attr("height", this.height) + .attr("opacity", "0") + .on("click", (event: MouseEvent) => { + event.stopPropagation(); + var rect = (event.target as any).getBoundingClientRect(); + var x = + ((event.clientX - rect.left) / (rect.right - rect.left)) * + overview.width; //x position within the element. + let rawDate = overview.x_scale_copy.invert(x); + this.openContextMenu(x, event.pageY, rawDate); + }); + + d3.select(".chart-container") + .on("mousemove", function (e) { + var tooltip = + document.getElementById("commit_hover") || + document.getElementById("commit_group_hover"); + if (tooltip == null) { + return; + } + var x = e.clientX, + y = e.clientY; + + tooltip.style.top = y + 20 + "px"; + tooltip.style.left = x + 20 + "px"; + }) + .on("scroll", () => this.refreshElementState()) + .attr("tabindex", "0") + .attr("focusable", "true") + .on("keypress", (event) => { + if (event.keyCode === 32) { + this.resetZoom(false); + } + }); + + this.clip = this.chart_svg + .append("defs") + .append("svg:clipPath") + .attr("id", "clip") + .append("svg:rect") + .attr("width", this.width) + .attr("height", 2 * this.height) + .attr("fill", "black") + .attr("x", 0) + .attr("y", -this.height); + this.loadGraphData(); } @@ -511,7 +639,7 @@ export class OverviewComponent session.tpGroup === this.dataService.groupFilter ); - this.session_g = this.chart_svg.append("g"); + this.session_g = this.chart_abs_g.append("g"); const overview = this; @@ -537,12 +665,12 @@ export class OverviewComponent // Line g.append("rect") - .attr("clip-path", "url(#clip)") + // .attr("clip-path", "url(#clip)") .attr("x", 0) .attr("width", 1) .attr("y", 0) .attr("transform", "translate(" + [-1, 0] + ")") - .attr("height", this.height); + .attr("height", 100000); // Box let box = g.append("rect").attr("y", 20); @@ -581,7 +709,7 @@ export class OverviewComponent loadReviews(milestone_filter: (review: Milestone) => number | boolean) { let loaded_reviews = this.dataService.reviews.filter(milestone_filter); - this.review_g = this.data_g.append("g"); + this.review_g = this.chart_abs_g.append("g"); const overview = this; @@ -605,7 +733,7 @@ export class OverviewComponent let loaded_corrections = this.dataService.corrections.filter(milestone_filter); - this.correction_g = this.data_g.append("g"); + this.correction_g = this.chart_abs_g.append("g"); const overview = this; @@ -628,7 +756,7 @@ export class OverviewComponent loadOthers(milestone_filter: (review: Milestone) => number | boolean) { let loaded_other = this.dataService.others.filter(milestone_filter); - this.other_g = this.data_g.append("g"); + this.other_g = this.chart_abs_g.append("g"); const overview = this; @@ -663,7 +791,7 @@ export class OverviewComponent this.x_axis = d3 .axisBottom(this.x_scale_copy) .ticks(6) - .tickSize(-this.height); + .tickSize(-this.real_height); this.x_axis.tickFormat(function (d) { if (!(d instanceof Date)) return ""; @@ -676,10 +804,11 @@ export class OverviewComponent }); this.axis_g = this.chart_svg.insert("g", ":first-child"); + this.axis_abs_g = this.chart_abs_g.insert("g", ":first-child"); - this.x_g = this.axis_g + this.x_g = this.axis_abs_g .append("g") - .attr("transform", "translate(" + [0, this.height] + ")") + .attr("transform", "translate(" + [0, this.real_height] + ")") .call(this.x_axis); this.y_scale = d3 @@ -706,7 +835,7 @@ export class OverviewComponent .call((g) => g.classed("repo_name", true)); // Use custom domain - this.axis_g.selectAll(".domain").style("opacity", "0"); + this.axis_abs_g.selectAll(".domain").style("opacity", "0"); this.axis_g .append("g") @@ -853,13 +982,15 @@ export class OverviewComponent all_commits[all_commits.length - 1].commitDate ); - g.select("path").attr( - "d", - this.getCommitGroupPathD( - all_commits[0], - all_commits[all_commits.length - 1] + g.select("path") + .attr( + "d", + this.getCommitGroupPathD( + all_commits[0], + all_commits[all_commits.length - 1] + ) ) - ); + .attr("fill", all_commits[all_commits.length - 1].color.color); g.attr("group_range", Math.max(spacing, g.attr("group_range") || 0)); g.attr("transform", `translate(${begin_x}, 0)`); @@ -1144,19 +1275,26 @@ export class OverviewComponent } refreshElementState() { + const containerRect = (d3.select(".chart-container") as any) + .node() + .getBoundingClientRect(); + // const scrollY = (d3.select(".chart-container") as any).node().scrollTop; const overview = this; - overview.repositories_g.forEach((repo_g) => { + overview.repositories_g.forEach((repo_g, i: number) => { repo_g.selectAll(".commit").each(function () { let g: d3.Selection = d3.select(this); - let commits = g.datum(); - let min_x = overview.xScaledTimeZoned(commits[0].commitDate); - let max_x = overview.xScaledTimeZoned( - commits[commits.length - 1].commitDate - ); + let node = repo_g.node(); + let nodeRect = (node as any).getBoundingClientRect(); + + const nodeVisible = + nodeRect.right >= containerRect.left && + nodeRect.left <= containerRect.right && + nodeRect.bottom >= containerRect.top && + nodeRect.top <= containerRect.bottom; - g.classed("hidden", min_x < 0 || max_x >= overview.width); + g.classed("hidden", !nodeVisible); }); repo_g @@ -1174,7 +1312,7 @@ export class OverviewComponent ); }); - this.data_g.selectAll(".milestone").each(function (m: Milestone) { + this.chart_abs_g.selectAll(".milestone").each(function (m: Milestone) { let g = d3.select(this); let x = overview.xScaledTimeZoned(m.date); g.classed("hidden", x < 0 || x > overview.width); @@ -1215,7 +1353,7 @@ export class OverviewComponent overview.xScaledTimeZoned(s.startDate) ); - this.data_g + this.chart_abs_g .selectAll(".milestone") .attr( "transform", From 52554e9a2160ab083d9a5a27951c6fefa54a2a81 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Tue, 25 Jun 2024 14:34:48 +0200 Subject: [PATCH 17/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20tooltip=20being=20re?= =?UTF-8?q?ndered=20at=20wrong=20time=20and=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../graphs/overview/overview.component.html | 102 +++++++------- .../graphs/overview/overview.component.scss | 8 +- .../graphs/overview/overview.component.ts | 133 +++++++++++------- 3 files changed, 141 insertions(+), 102 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.html b/src/app/components/graphs/overview/overview.component.html index 1a7696a..ee24506 100644 --- a/src/app/components/graphs/overview/overview.component.html +++ b/src/app/components/graphs/overview/overview.component.html @@ -169,56 +169,62 @@
-
-

{{ commit_date_format(hovered_commit.commitDate) }}

-

{{ hovered_commit.message }}

-

-

{{ hovered_commit.author }}

-
-
-
-

{{ hovered_group_commit[hovered_group_commit.length - 1].message }}

+
+
+

{{ commit_date_format(hovered_commit.commitDate) }}

+

{{ hovered_commit.message }}

-

- {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN" | translate }} : - {{ commit_date_format(hovered_group_commit[0].commitDate) }} -

-

- {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.END" | translate }} : - {{ - commit_date_format( - hovered_group_commit[hovered_group_commit.length - 1].commitDate - ) - }} -

-

- {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER" | translate }} : - {{ hovered_group_commit.length }} -

+

{{ hovered_commit.author }}

-
-

- {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN" | translate }} : - {{ commit_date_format(hovered_group_commit[0].commitDate) }} -

-

- {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.END" | translate }} : - {{ - commit_date_format( - hovered_group_commit[hovered_group_commit.length - 1].commitDate - ) - }} -

-

- {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER" | translate }} : - {{ hovered_group_commit.length }} -

+
+
+

{{ hovered_group_commit[hovered_group_commit.length - 1].message }}

+

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN" | translate }} : + {{ commit_date_format(hovered_group_commit[0].commitDate) }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.END" | translate }} : + {{ + commit_date_format( + hovered_group_commit[hovered_group_commit.length - 1].commitDate + ) + }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER" | translate }} : + {{ hovered_group_commit.length }} +

+
+
+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.BEGIN" | translate }} : + {{ commit_date_format(hovered_group_commit[0].commitDate) }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.END" | translate }} : + {{ + commit_date_format( + hovered_group_commit[hovered_group_commit.length - 1].commitDate + ) + }} +

+

+ {{ "OVERVIEW-GRAPH.COMMIT-TOOLTIP.COMMIT_NUMBER" | translate }} : + {{ hovered_group_commit.length }} +

+
diff --git a/src/app/components/graphs/overview/overview.component.scss b/src/app/components/graphs/overview/overview.component.scss index bdaf6e9..34ff753 100644 --- a/src/app/components/graphs/overview/overview.component.scss +++ b/src/app/components/graphs/overview/overview.component.scss @@ -23,8 +23,12 @@ outline: none; } -#commit_hover { +#tooltip { position: absolute; +} + +#commit_hover { + width: fit-content; color: white; background-color: rgba(0, 0, 0, 0.7); font-size: 0; @@ -40,8 +44,8 @@ } #commit_group_hover { - position: absolute; transform: translateY(-90px); + width: fit-content; } #commit_group_hover > div { color: white; diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 983189f..9f040d0 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -102,6 +102,7 @@ export class OverviewComponent hovered_commit: Commit; hovered_group_commit: Commit[]; + hovered_g: d3.Selection; static GROUP_HEIGHT = 12; static CIRCLE_RADIUS = 12; @@ -203,19 +204,12 @@ export class OverviewComponent d3.select(".chart-container") .on("mousemove", function (e) { - var tooltip = - document.getElementById("commit_hover") || - document.getElementById("commit_group_hover"); - if (tooltip == null) { - return; - } - var x = e.clientX, - y = e.clientY; - - tooltip.style.top = y + 20 + "px"; - tooltip.style.left = x + 20 + "px"; + overview.refreshTooltip(e.clientX, e.clientY); + }) + .on("scroll", (e) => { + overview.refreshTooltip(e.clientX, e.clientY); + overview.refreshElementState(); }) - .on("scroll", () => this.refreshElementState()) .attr("tabindex", "0") .attr("focusable", "true") .on("keypress", (event) => { @@ -280,6 +274,27 @@ export class OverviewComponent } } + refreshTooltip(x?: number, y?: number) { + if (x == null || y == null) { + return; + } + var tooltip = document.getElementById("tooltip"); + if (tooltip == null) { + return; + } + + tooltip.style.top = y + 20 + "px"; + tooltip.style.left = x + 20 + "px"; + + if (this.hovered_g) { + if (this.hovered_g.select(":hover").empty()) { + this.hovered_commit = undefined; + this.hovered_group_commit = undefined; + this.hovered_g = null; + } + } + } + loadGraphData() { this.loadAnnotations(); this.loadPoints(); @@ -295,6 +310,13 @@ export class OverviewComponent return; } + if (event.sourceEvent != null) { + overview.refreshTooltip( + event.sourceEvent.clientX, + event.sourceEvent.clientY + ); + } + overview.current_zoom = event.transform; overview.x_scale_copy = overview.current_zoom.rescaleX( overview.x_scale @@ -386,17 +408,7 @@ export class OverviewComponent d3.select(".chart-container") .on("mousemove", function (e) { - var tooltip = - document.getElementById("commit_hover") || - document.getElementById("commit_group_hover"); - if (tooltip == null) { - return; - } - var x = e.clientX, - y = e.clientY; - - tooltip.style.top = y + 20 + "px"; - tooltip.style.left = x + 20 + "px"; + overview.refreshTooltip(e.clientX, e.clientY); }) .on("scroll", () => this.refreshElementState()) .attr("tabindex", "0") @@ -905,9 +917,16 @@ export class OverviewComponent g.attr("group_range", range); g.attr("transform", `translate(${begin_x}, 0)`) - .on("mouseenter", (e, d) => (this.hovered_group_commit = d)) + .on("mouseenter", (e, d) => { + this.hovered_commit = undefined; + this.hovered_group_commit = d; + this.hovered_g = g; + }) .on("mouseleave", () => { - this.hovered_group_commit = undefined; + if (this.hovered_g === g) { + this.hovered_g = undefined; + this.hovered_group_commit = undefined; + } }); return g; @@ -1024,7 +1043,11 @@ export class OverviewComponent g.attr("date", (commit.commitDate as Date).getTime()); g.attr("transform", `translate(${x}, 0)`) - .on("mouseenter", () => (this.hovered_commit = commit)) + .on("mouseenter", () => { + this.hovered_commit = commit; + this.hovered_group_commit = undefined; + this.hovered_g = undefined; + }) .on("mouseleave", () => { if ( this.hovered_commit && @@ -1186,6 +1209,10 @@ export class OverviewComponent overview.xScaledTimeZoned(new Date(date)); if (range_in_pixel >= Utils.COMMIT_FUSE_RANGE) { + if (overview.hovered_g === g) { + overview.hovered_g = undefined; + overview.hovered_group_commit = undefined; + } let before = undefined; let commits = g.datum() as Commit[]; g.remove(); @@ -1278,39 +1305,41 @@ export class OverviewComponent const containerRect = (d3.select(".chart-container") as any) .node() .getBoundingClientRect(); - // const scrollY = (d3.select(".chart-container") as any).node().scrollTop; const overview = this; - overview.repositories_g.forEach((repo_g, i: number) => { - repo_g.selectAll(".commit").each(function () { - let g: d3.Selection = d3.select(this); + if (overview.repository_g) + overview.repositories_g.forEach((repo_g, i: number) => { + repo_g.selectAll(".commit").each(function () { + let g: d3.Selection = d3.select(this); - let node = repo_g.node(); - let nodeRect = (node as any).getBoundingClientRect(); + let node = repo_g.node(); + let nodeRect = (node as any).getBoundingClientRect(); - const nodeVisible = - nodeRect.right >= containerRect.left && - nodeRect.left <= containerRect.right && - nodeRect.bottom >= containerRect.top && - nodeRect.top <= containerRect.bottom; + const nodeVisible = + nodeRect.right >= containerRect.left && + nodeRect.left <= containerRect.right && + nodeRect.bottom >= containerRect.top && + nodeRect.top <= containerRect.bottom; - g.classed("hidden", !nodeVisible); - }); + g.classed("hidden", !nodeVisible); + }); - repo_g - .selectAll(".commit:not(.hidden)") - .attr( - "transform", - (commits: Commit[]) => - `translate(${overview.xScaledTimeZoned(commits[0].commitDate)}, 0)` - ); + repo_g + .selectAll(".commit:not(.hidden)") + .attr( + "transform", + (commits: Commit[]) => + `translate(${overview.xScaledTimeZoned( + commits[0].commitDate + )}, 0)` + ); - repo_g - .selectAll("path:not(.hidden)") - .attr("d", (commits: Commit[]) => - this.getCommitGroupPathD(commits[0], commits[commits.length - 1]) - ); - }); + repo_g + .selectAll("path:not(.hidden)") + .attr("d", (commits: Commit[]) => + this.getCommitGroupPathD(commits[0], commits[commits.length - 1]) + ); + }); this.chart_abs_g.selectAll(".milestone").each(function (m: Milestone) { let g = d3.select(this); From f41d9be3985fad794cfb6608419c27308cc48210 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Wed, 3 Jul 2024 09:42:03 +0200 Subject: [PATCH 18/23] Make overview responsive --- src/app/components/graphs/overview/chart.scss | 2 +- .../graphs/overview/overview.component.html | 6 +- .../graphs/overview/overview.component.scss | 31 ++- .../graphs/overview/overview.component.ts | 229 +++++++----------- 4 files changed, 122 insertions(+), 146 deletions(-) diff --git a/src/app/components/graphs/overview/chart.scss b/src/app/components/graphs/overview/chart.scss index 1f3a73b..5025dd6 100644 --- a/src/app/components/graphs/overview/chart.scss +++ b/src/app/components/graphs/overview/chart.scss @@ -77,7 +77,7 @@ text.repo_name { } .milestone { - pointer-events: all; + pointer-events: auto; } .milestone > .milestone_label { diff --git a/src/app/components/graphs/overview/overview.component.html b/src/app/components/graphs/overview/overview.component.html index ee24506..3c9d301 100644 --- a/src/app/components/graphs/overview/overview.component.html +++ b/src/app/components/graphs/overview/overview.component.html @@ -163,11 +163,9 @@
-
+
-
-
-
+
svg { + position: relative; +} + .chart-container:focus { outline: none; } diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 9f040d0..313e764 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -67,10 +67,10 @@ export class OverviewComponent savedMilestoneModal: Milestone; // params - margin = { top: 0, right: 30, bottom: 80, left: 250 }; - margin_abs = { top: 30, right: 30, bottom: 80, left: 250 }; - width = 1800 - this.margin.left - this.margin.right; - height = 200 - this.margin.top - this.margin.bottom; + inner_margin; + margin_abs; + width; + height; maxZoom: number; // svg components @@ -111,6 +111,11 @@ export class OverviewComponent chart_abs_g: d3.Selection; svg_abs: d3.Selection; real_height: number; + chart_width: number; + repo_spacing: number; + inner_width: number; + inner_height: number; + scrollable_height: number; //////////////////////// constructor( @@ -142,92 +147,47 @@ export class OverviewComponent this.unsubscribeAssignmentModified(this.assignmentsModified$); } + getDisplayedRepositories(): Repository[] { + return this.dataService.repositories.filter( + (repository) => + !this.dataService.groupFilter || + repository.tpGroup === this.dataService.groupFilter + ); + } + commit_date_format = Utils.COMMIT_DATE_FORMAT; - ngAfterViewInit(): void { - this.height += this.dataService.repositories.length * 35; - this.real_height = Math.min(570, this.height); + updateVariableFromCss(): void { + let chart_div = document.getElementById("chart"); - this.svg = d3 - .select(".chart-container") - .append("svg") - .attr( - "viewBox", - `0 0 ${this.width + this.margin.left + this.margin.right} ${ - this.height + this.margin.top + this.margin.right - }` - ); + var style = getComputedStyle(chart_div); - this.svg_abs = d3 - .select(".chart-container-absolute") - .append("svg") - .attr( - "viewBox", - `0 0 ${this.width + this.margin_abs.left + this.margin_abs.right} ${ - Math.min(600, this.height) + 2 * 30 /*padding*/ - } ` - ); + var css_var_number = (name: string, dash = true) => + Number.parseInt(style.getPropertyValue((dash ? "--" : "") + name)); - const overview = this; + let rect = chart_div.getBoundingClientRect(); + this.width = rect.width; + this.height = rect.height; - this.chart_svg = this.svg - .append("g") - .attr( - "transform", - "translate(" + this.margin.left + "," + this.margin.top + ")" - ); - - this.data_g = this.chart_svg.append("g"); + this.chart_width = Math.min( + (css_var_number("chart-width-left-spacing-ratio") * this.width) / 100, + this.width - css_var_number("chart-width-max-left-spacing") + ); - this.chart_abs_g = this.svg_abs - .append("g") - .attr( - "transform", - "translate(" + this.margin_abs.left + "," + this.margin_abs.top + ")" - ); + this.inner_margin = { + top: css_var_number("top-inner"), + bottom: css_var_number("bottom-inner"), + }; - this.data_g - .append("rect") - .attr("id", "data") - .attr("width", this.width) - .attr("height", this.height) - .attr("opacity", "0") - .on("click", (event: MouseEvent) => { - event.stopPropagation(); - var rect = (event.target as any).getBoundingClientRect(); - var x = - ((event.clientX - rect.left) / (rect.right - rect.left)) * - overview.width; //x position within the element. - let rawDate = overview.x_scale_copy.invert(x); - this.openContextMenu(x, event.pageY, rawDate); - }); + this.inner_width = this.width; + this.inner_height = + this.height - this.inner_margin.top - this.inner_margin.bottom; - d3.select(".chart-container") - .on("mousemove", function (e) { - overview.refreshTooltip(e.clientX, e.clientY); - }) - .on("scroll", (e) => { - overview.refreshTooltip(e.clientX, e.clientY); - overview.refreshElementState(); - }) - .attr("tabindex", "0") - .attr("focusable", "true") - .on("keypress", (event) => { - if (event.keyCode === 32) { - this.resetZoom(false); - } - }); + this.repo_spacing = css_var_number("repo-space"); + } - this.clip = this.chart_svg - .append("defs") - .append("svg:clipPath") - .attr("id", "clip") - .append("svg:rect") - .attr("width", this.width) - .attr("height", 2 * this.height) - .attr("fill", "black") - .attr("x", 0) - .attr("y", -this.height); + ngAfterViewInit(): void { + this.refresh(); setTimeout(() => { if (this.dataService.repoToLoad) { @@ -334,74 +294,53 @@ export class OverviewComponent this.resetZoom(true); } - loadGraphDataAndRefresh() { - this.margin = { top: 0, right: 30, bottom: 80, left: 250 }; - this.margin_abs = { top: 30, right: 30, bottom: 80, left: 250 }; - this.width = 1800 - this.margin.left - this.margin.right; - this.height = 200 - this.margin.top - this.margin.bottom; - - const repositories: Repository[] = this.dataService.repositories.filter( - (repository) => - !this.dataService.groupFilter || - repository.tpGroup === this.dataService.groupFilter + refresh() { + this.updateVariableFromCss(); + this.scrollable_height = Math.max( + this.height - this.inner_margin.top - this.inner_margin.bottom, + this.getDisplayedRepositories().length * this.repo_spacing ); - this.height += repositories.length * 35; - this.real_height = Math.min(570, this.height); - - this.svg.remove(); + d3.select(window).on("resize", () => this.loadGraphDataAndRefresh()); this.svg = d3 .select(".chart-container") .append("svg") - .attr( - "viewBox", - `0 0 ${this.width + this.margin.left + this.margin.right} ${ - this.height + this.margin.top + this.margin.right - }` - ); + .attr("preserveAspectRatio", "none") + .attr("viewBox", `0 0 ${this.width} ${this.scrollable_height}`); - this.svg_abs.remove(); this.svg_abs = d3 .select(".chart-container-absolute") .append("svg") - .attr( - "viewBox", - `0 0 ${this.width + this.margin_abs.left + this.margin_abs.right} ${ - Math.min(600, this.height) + 2 * 30 /*padding*/ - } ` - ); + .attr("preserveAspectRatio", "none") + .attr("viewBox", `0 0 ${this.width} ${this.height}`); const overview = this; + const translation = [this.width - this.chart_width, 0]; + this.chart_svg = this.svg .append("g") - .attr( - "transform", - "translate(" + this.margin.left + "," + this.margin.top + ")" - ); + .attr("transform", "translate(" + translation + ")"); this.data_g = this.chart_svg.append("g"); this.chart_abs_g = this.svg_abs .append("g") - .attr( - "transform", - "translate(" + this.margin_abs.left + "," + this.margin_abs.top + ")" - ); + .attr("transform", "translate(" + translation + ")"); this.data_g .append("rect") .attr("id", "data") - .attr("width", this.width) - .attr("height", this.height) + .attr("width", this.inner_width) + .attr("height", this.inner_height) .attr("opacity", "0") .on("click", (event: MouseEvent) => { event.stopPropagation(); var rect = (event.target as any).getBoundingClientRect(); var x = ((event.clientX - rect.left) / (rect.right - rect.left)) * - overview.width; //x position within the element. + overview.inner_width; //x position within the element. let rawDate = overview.x_scale_copy.invert(x); this.openContextMenu(x, event.pageY, rawDate); }); @@ -425,10 +364,17 @@ export class OverviewComponent .attr("id", "clip") .append("svg:rect") .attr("width", this.width) - .attr("height", 2 * this.height) + .attr("height", 2 * this.scrollable_height) .attr("fill", "black") .attr("x", 0) - .attr("y", -this.height); + .attr("y", -this.scrollable_height); + } + + loadGraphDataAndRefresh() { + this.svg.remove(); + this.svg_abs.remove(); + + this.refresh(); this.loadGraphData(); } @@ -626,8 +572,8 @@ export class OverviewComponent .attr("class", "session") .attr("clip-path", "url(#clip)") .attr("x", this.xScaledTimeZoned(session.startDate)) - .attr("height", this.height) - .attr("y", 0) + .attr("height", 100) + .attr("y", this.inner_margin.bottom + this.inner_height) .attr( "width", this.xScaledTimeZoned(session.endDate) - @@ -679,13 +625,13 @@ export class OverviewComponent g.append("rect") // .attr("clip-path", "url(#clip)") .attr("x", 0) - .attr("width", 1) .attr("y", 0) - .attr("transform", "translate(" + [-1, 0] + ")") - .attr("height", 100000); + .attr("width", 1) + .attr("height", this.inner_height) + .attr("transform", "translate(" + [-1, 0] + ")"); // Box - let box = g.append("rect").attr("y", 20); + let box = g.append("rect").attr("y", 0); // Text let text = g @@ -709,7 +655,7 @@ export class OverviewComponent let x = this.xScaledTimeZoned(m.date); return g - .attr("transform", `translate(${x}, 0)`) + .attr("transform", `translate(${x}, ${this.inner_margin.top})`) .call((g) => g.classed("hidden", x < 0 || x > overview.width)) .on("click", (e, d: Milestone) => { e.stopPropagation(); @@ -795,7 +741,7 @@ export class OverviewComponent this.x_scale = d3 .scaleTime() .domain([minDate, maxDate]) - .range([0, this.width]) + .range([0, this.inner_width]) .nice(); this.x_scale_copy = this.x_scale.copy(); @@ -803,7 +749,7 @@ export class OverviewComponent this.x_axis = d3 .axisBottom(this.x_scale_copy) .ticks(6) - .tickSize(-this.real_height); + .tickSize(-this.inner_height); this.x_axis.tickFormat(function (d) { if (!(d instanceof Date)) return ""; @@ -820,13 +766,16 @@ export class OverviewComponent this.x_g = this.axis_abs_g .append("g") - .attr("transform", "translate(" + [0, this.real_height] + ")") + .attr( + "transform", + "translate(" + [0, this.inner_height + this.inner_margin.bottom] + ")" + ) .call(this.x_axis); this.y_scale = d3 .scaleLinear() .domain([0, repositories.length + 1]) - .range([0, this.height]); + .range([0, this.scrollable_height]); this.y_axis = d3 .axisLeft(this.y_scale) @@ -848,6 +797,7 @@ export class OverviewComponent // Use custom domain this.axis_abs_g.selectAll(".domain").style("opacity", "0"); + this.axis_g.selectAll(".domain").style("opacity", "0"); this.axis_g .append("g") @@ -856,7 +806,7 @@ export class OverviewComponent .attr("x1", 0) .attr("x2", 0) .attr("y1", 0) - .attr("y2", this.height); + .attr("y2", this.scrollable_height); this.axis_g .append("g") @@ -864,8 +814,8 @@ export class OverviewComponent .append("line") .attr("x1", 0) .attr("x2", this.width) - .attr("y1", this.height) - .attr("y2", this.height); + .attr("y1", this.scrollable_height) + .attr("y2", this.scrollable_height); } getCommitGroupPathD(first: Commit, last: Commit) { @@ -1120,11 +1070,7 @@ export class OverviewComponent loadPoints() { const overview = this; - const repositories: Repository[] = this.dataService.repositories.filter( - (repository) => - !this.dataService.groupFilter || - repository.tpGroup === this.dataService.groupFilter - ); + const repositories: Repository[] = this.getDisplayedRepositories(); if (this.repository_g != null) this.repository_g.remove(); @@ -1386,7 +1332,10 @@ export class OverviewComponent .selectAll(".milestone") .attr( "transform", - (m: Milestone) => `translate(${overview.xScaledTimeZoned(m.date)}, 0)` + (m: Milestone) => + `translate(${overview.xScaledTimeZoned(m.date)}, ${ + this.inner_margin.top + })` ); } From 720c75ab927b619052b04bd279b82c177594787d Mon Sep 17 00:00:00 2001 From: Mika Pons Date: Sat, 6 Jul 2024 00:29:37 +0200 Subject: [PATCH 19/23] =?UTF-8?q?=F0=9F=9A=B8=20Adjust=20overview=20graph?= =?UTF-8?q?=20height?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../graphs/overview/overview.component.scss | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.scss b/src/app/components/graphs/overview/overview.component.scss index 11756af..bcdb1cf 100644 --- a/src/app/components/graphs/overview/overview.component.scss +++ b/src/app/components/graphs/overview/overview.component.scss @@ -1,8 +1,6 @@ #chart { - --top-inner: 100px; - --bottom-inner: 100px; - --left-inner: 0px; - --right-inner: 0px; + --top-inner: 30px; + --bottom-inner: 20px; --chart-height: 100%; @@ -14,9 +12,9 @@ width: 100%; --chart-width-left-spacing-ratio: 90%; --chart-width-max-left-spacing: 50px; - // to change chart-with + // to change chart-with - height: 600px; + height: 620px; } .chart-container { @@ -31,9 +29,9 @@ overscroll-behavior: none; } -.chart-container-absolute{ +.chart-container-absolute { position: absolute; - top:0; + top: 0; left: 0; width: 100%; height: 100%; @@ -44,7 +42,7 @@ overflow: hidden; } -.chart-container-absolute > svg { +.chart-container-absolute>svg { position: relative; } @@ -67,7 +65,7 @@ user-select: none; } -#commit_hover > p { +#commit_hover>p { font-size: 10px; margin: 2px; } @@ -76,7 +74,8 @@ transform: translateY(-90px); width: fit-content; } -#commit_group_hover > div { + +#commit_group_hover>div { color: white; background-color: rgba(0, 0, 0, 0.7); font-size: 0; @@ -85,7 +84,7 @@ } -#commit_group_hover > div > p { +#commit_group_hover>div>p { font-size: 10px; margin: 2px; -} +} \ No newline at end of file From ed098699bbe2cd69fa2ecf81f78f05c79dc5def6 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Mon, 8 Jul 2024 09:14:04 +0200 Subject: [PATCH 20/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20scroll=20problem=20a?= =?UTF-8?q?nd=20contextual=20menus=20position?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/graphs/overview/overview.component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/components/graphs/overview/overview.component.ts b/src/app/components/graphs/overview/overview.component.ts index 313e764..095450e 100644 --- a/src/app/components/graphs/overview/overview.component.ts +++ b/src/app/components/graphs/overview/overview.component.ts @@ -333,7 +333,7 @@ export class OverviewComponent .append("rect") .attr("id", "data") .attr("width", this.inner_width) - .attr("height", this.inner_height) + .attr("height", this.scrollable_height) .attr("opacity", "0") .on("click", (event: MouseEvent) => { event.stopPropagation(); @@ -342,7 +342,7 @@ export class OverviewComponent ((event.clientX - rect.left) / (rect.right - rect.left)) * overview.inner_width; //x position within the element. let rawDate = overview.x_scale_copy.invert(x); - this.openContextMenu(x, event.pageY, rawDate); + this.openContextMenu(event.pageX, event.pageY, rawDate); }); d3.select(".chart-container") @@ -1057,9 +1057,9 @@ export class OverviewComponent }; if (spacing > 24 * 3600) - return `${date.getDate().toLocaleString(undefined, options)}/${date - .getMonth() - .toLocaleString(undefined, options)}/${date + return `${date.getDate().toLocaleString(undefined, options)}/${( + date.getMonth() + 1 + ).toLocaleString(undefined, options)}/${date .getFullYear() .toLocaleString(undefined, options)}`; else From bf76495785b9c4521a621aa7e886f231ae92f637 Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Mon, 8 Jul 2024 10:30:24 +0200 Subject: [PATCH 21/23] Add chart to menu --- src/app/app-routing.module.ts | 6 +++ src/app/app.module.ts | 2 + .../graphs/personal/personal.component.html | 0 .../graphs/personal/personal.component.scss | 0 .../graphs/personal/personal.component.ts | 54 +++++++++++++++++++ .../app-nav-layout.component.html | 2 + src/assets/i18n/en.json | 3 +- src/assets/i18n/fr.json | 5 +- 8 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/app/components/graphs/personal/personal.component.html create mode 100644 src/app/components/graphs/personal/personal.component.scss create mode 100644 src/app/components/graphs/personal/personal.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index eb7f1fd..3b30739 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -11,6 +11,7 @@ import { HomeNavLayoutComponent } from "@components/nav-layouts/home-nav-layout/ import { AuthGuard } from "@guards/auth.guard"; import { DataLoadingGuard } from "@guards/data-loading.guard"; import { DataProvidedGuard } from "@guards/data-provided.guard"; +import { PersonalComponent } from "./components/graphs/personal/personal.component"; const HOME_ROUTES: Routes = [ { path: "home", component: HomeComponent }, @@ -34,6 +35,11 @@ const APP_ROUTES: Routes = [ canActivate: [AuthGuard, DataProvidedGuard], component: QuestionsCompletionComponent, }, + { + path: "personal", + canActivate: [AuthGuard, DataProvidedGuard], + component: PersonalComponent, + }, { path: "", redirectTo: "/home", pathMatch: "full" }, ]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 45a0686..425236f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -49,6 +49,7 @@ import { HelpNavItemComponent } from "./components/nav-items/help-nav-item/help- import { AppNavLayoutComponent } from "./components/nav-layouts/app-nav-layout/app-nav-layout.component"; import { HomeNavLayoutComponent } from "./components/nav-layouts/home-nav-layout/home-nav-layout.component"; import { OverviewGraphContextualMenuComponent } from "./components/overview-graph-contextual-menu/overview-graph-contextual-menu.component"; +import { PersonalComponent } from "./components/graphs/personal/personal.component"; /** * Firebase configuration file @@ -112,6 +113,7 @@ export function appInitializerFactory( OverviewComponent, StudentsCommitsComponent, QuestionsCompletionComponent, + PersonalComponent, MetadataComponent, EditRepositoriesComponent, ConfigurationComponent, diff --git a/src/app/components/graphs/personal/personal.component.html b/src/app/components/graphs/personal/personal.component.html new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/graphs/personal/personal.component.scss b/src/app/components/graphs/personal/personal.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/graphs/personal/personal.component.ts b/src/app/components/graphs/personal/personal.component.ts new file mode 100644 index 0000000..d5a96ed --- /dev/null +++ b/src/app/components/graphs/personal/personal.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from "@angular/core"; +import { BaseGraphComponent } from "../base-graph.component"; +import { DataService } from "../../../services/data.service"; +import { CommitsService } from "../../../services/commits.service"; +import { TranslateService } from "@ngx-translate/core"; +import { LoaderService } from "../../../services/loader.service"; +import { AssignmentsService } from "../../../services/assignments.service"; + +@Component({ + selector: "personal", + templateUrl: "./personal.component.html", + styleUrls: ["./personal.component.scss"], +}) +export class PersonalComponent extends BaseGraphComponent implements OnInit { + /** + * StudentsCommitsComponent constructor + * @param dataService Service used to store and get data + * @param commitsService Service used to update dict variable + * @param translateService Service used to translate the application + */ + constructor( + public dataService: DataService, + private commitsService: CommitsService, + public translateService: TranslateService, + protected loaderService: LoaderService, + protected assignmentsService: AssignmentsService + ) { + super(loaderService, assignmentsService, dataService); + } + + /** + * Updates dict variable with students data and loads graph labels which displays data on the graph + */ + loadGraphDataAndRefresh() { + let translations = this.translateService.instant([ + "STUDENT", + "COMMITS-COUNT", + "COMMITS-PERCENTAGE", + ]); + } + + loadGraph(startDate?: string, endDate?: string) { + this.loading = true; + this.loaderService.loadRepositories(startDate, endDate).subscribe(() => { + this.loadGraphMetadata( + this.dataService.repositories, + this.dataService.reviews, + this.dataService.corrections, + this.dataService.questions + ); + this.loading = false; + }); + } +} diff --git a/src/app/components/nav-layouts/app-nav-layout/app-nav-layout.component.html b/src/app/components/nav-layouts/app-nav-layout/app-nav-layout.component.html index 2cb73c9..547dae5 100644 --- a/src/app/components/nav-layouts/app-nav-layout/app-nav-layout.component.html +++ b/src/app/components/nav-layouts/app-nav-layout/app-nav-layout.component.html @@ -20,6 +20,8 @@ 'NAVBAR.STUDENTS-COMMITS' | translate }} {{ 'NAVBAR.QUESTIONS-COMPLETION' | translate }} + {{ + 'NAVBAR.PERSONAL' | translate }}