diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 2ec9ff596..2d8dbdd32 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -12,6 +12,7 @@ import { TeamsComponent } from './component/teams/teams.component'; const routes: Routes = [ { path: '', component: MatrixComponent }, { path: 'circular-heatmap', component: CircularHeatmapComponent }, + { path: 'matrix', component: MatrixComponent }, { path: 'activity-description', component: ActivityDescriptionComponent }, { path: 'mapping', component: MappingComponent }, { path: 'usage', redirectTo: 'usage/' }, diff --git a/src/app/app.component.css b/src/app/app.component.css index 56e066580..64dc7b499 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -2,21 +2,34 @@ .main-container { width: 100%; height: 100%; - /*border: 10px solid yellow;*/ } .sidenav-content { display: flex; padding: 10px; - align-items: left; - justify-content: left; - /*background-color: red;*/ + justify-content: space-between; } -.example-sidenav { +.sidenav-menu { padding: 20px; } +.menu-close { + position: absolute; + right: 0; + top: 2px; + background: transparent; + border: 0; + color: #ddd; +} + .github-fork-ribbon:before { background-color: #333; } + +@media only screen and (max-width: 750px) { + .github-fork-ribbon { + display: none; + } + +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 7b5e0dc55..1cc32689b 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,11 +1,18 @@ - + +
- { + this.menuIsOpen = false; + }, 600); + } + } + + toggleMenu(): void { + this.menuIsOpen = !this.menuIsOpen; + localStorage.setItem('state.menuIsOpen', this.menuIsOpen.toString()); + } } diff --git a/src/app/component/circular-heatmap/circular-heatmap.component.css b/src/app/component/circular-heatmap/circular-heatmap.component.css index 3f73baa0e..2056fa017 100644 --- a/src/app/component/circular-heatmap/circular-heatmap.component.css +++ b/src/app/component/circular-heatmap/circular-heatmap.component.css @@ -17,22 +17,34 @@ } .right-panel { - float: right; - width: 25%; margin: 5%; padding: 20px; + height: 80vh; + display: grid; + grid-template-rows: auto 1fr auto; } -.downloadButtonClass { - position: fixed; - padding: 10px; - bottom: 5%; - right: 5%; + +.heatmapClass { + display: grid; + grid-template-rows: auto 1fr auto; + grid-template-columns: 6fr 4fr; + height: 100%; + width: 100%; } -.resetButtonClass { - position: fixed; - padding: 10px; - bottom: 5%; - right: 18%; + +.heatmapChart { + grid-row: 1/4; + display: grid; + justify-items: center; + align-content: space-between; +} +#chart { + width: 100%; + max-width: min(100vh - 60px, 100vw - 60px); +} + +.downloadButtonClass { + margin: 10px 0; } .overlay-details { z-index: 2; @@ -69,15 +81,86 @@ justify-content: top; margin-left: auto; } +.team-filter { + padding: 0.4rem; + grid-column: 2/3; + display: flex; + flex-direction: column; +} + +.team-filter.hidden { + height: 0; + visibility: collapse; +} + +.filter-toggle .hidden { + display: none; +} .team-list { list-style-type: none; margin: 0; padding: 0 1em; } -/* .team-filter { +.mat-chip-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: 1rem; +} +.filter-container { + position: relative; +} +button.filter-toggle { + position: absolute; + top: 2px; + right: 2px; + border: 0; + background-color: transparent; + z-index: 1; padding: 0; - width: 60%; -} */ -.team-filter > .mat-form-field { - display: block; + min-width: 0; + line-height: 24px; } + +.footer-buttons { + grid-column: 2/3; + place-self: flex-end; + margin: 0 1rem; + padding-top: 1rem; + display: flex; + align-items: flex-end; + flex-direction: column; +} + +@media only screen and (max-width: 750px) { + .heatmapClass { + grid-template-rows: auto auto 1fr auto; + grid-template-columns: 1fr; + } + + .team-filter, .heatmapChart, .footer-buttons { + grid-column: 1; + } + + .team-filter { + grid-row: 1; + padding: 0.4rem; + } + + .mat-chip-list { + padding: 0.4rem; + } + + .heatmapChart { + grid-row: 2; + } + + #chart { + max-width: max(60vh, 60vw); + } + + .overlay-details { + width: 100%; + } + +} \ No newline at end of file diff --git a/src/app/component/circular-heatmap/circular-heatmap.component.html b/src/app/component/circular-heatmap/circular-heatmap.component.html index 43ba21b35..ebf44f91a 100644 --- a/src/app/component/circular-heatmap/circular-heatmap.component.html +++ b/src/app/component/circular-heatmap/circular-heatmap.component.html @@ -192,9 +192,18 @@

Nothing to show

-
-
-
+
+
+
+ +
Team Group Filter @@ -225,68 +234,69 @@

Nothing to show

- - - {{ cardHeader }} - {{ cardSubheader }} - - - - - - + + + +
    +
  • + - {{ activity['activityName'] }} - - - - -
      -
    • - - {{ teamname }} - -
    • -
    - - No Teams Selected, Please select a team from the filters - above. - -
    - - - + {{ teamname }} +
    +
  • +
+ + No Teams Selected, Please select a team from the filters + above. + +
+
+
+
+ - - -
diff --git a/src/app/component/circular-heatmap/circular-heatmap.component.ts b/src/app/component/circular-heatmap/circular-heatmap.component.ts index fa5e33da0..4d737b11f 100644 --- a/src/app/component/circular-heatmap/circular-heatmap.component.ts +++ b/src/app/component/circular-heatmap/circular-heatmap.component.ts @@ -61,6 +61,7 @@ export class CircularHeatmapComponent implements OnInit { segment_labels: string[] = []; activityDetails: any; showOverlay: boolean; + showFilters: boolean; markdown: md = md(); constructor( @@ -69,6 +70,7 @@ export class CircularHeatmapComponent implements OnInit { public modal: ModalMessageComponent ) { this.showOverlay = false; + this.showFilters = true; } ngOnInit(): void { @@ -417,9 +419,9 @@ export class CircularHeatmapComponent implements OnInit { .data([dataset]) .enter() .append('svg') - .attr('width', '60%') // 70% forces the heatmap down - .attr('height', 'auto') - .attr('viewBox', '0 0 1150 1150') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 1200 1200') .append('g') .attr( 'transform', @@ -443,75 +445,36 @@ export class CircularHeatmapComponent implements OnInit { svg .selectAll('path') .on('click', function (d) { - console.log(d); - try { - curr = d.explicitOriginalTarget.__data__; - } catch { - curr = d.srcElement.__data__; - } - var index = 0; - var cnt = 0; - for (var i = 0; i < _self.ALL_CARD_DATA.length; i++) { - if ( - _self.ALL_CARD_DATA[i]['SubDimension'] === curr.SubDimension && - _self.ALL_CARD_DATA[i]['Level'] === curr.Level - ) { - index = i; - break; - } + _self.setSectorCursor(svg, '#hover', ''); + + var clickedId = d.currentTarget.id; + var index = parseInt(clickedId.replace('index-', '')); + curr = _self.ALL_CARD_DATA[index]; + console.log('index', curr['Activity']); + + if (curr['Done%'] == -1) { + _self.showActivityCard = false; + _self.setSectorCursor(svg, '#selected', ''); + } else { + _self.showActivityCard = true; + _self.currentDimension = curr.Dimension; + _self.cardSubheader = curr.Level; + _self.activityData = curr.Activity; + _self.cardHeader = curr.SubDimension; + _self.setSectorCursor(svg, '#selected', clickedId); } - console.log('index', _self.ALL_CARD_DATA[index]['Activity']); - _self.currentDimension = curr.Dimension; - _self.cardSubheader = curr.Level; - _self.activityData = curr.Activity; - _self.cardHeader = curr.SubDimension; - _self.showActivityCard = true; }) .on('mouseover', function (d) { - //console.log(d.toElement.__data__.Name) - try { - curr = d.explicitOriginalTarget.__data__; - } catch { - curr = d.toElement.__data__; - } - // increase the segment height of the one being hovered as well as all others of the same date - // while decreasing the height of all others accordingly - if (curr['Done%'] != -1) { - d3.selectAll( - '#segment-' + - curr.SubDimension.replace(/ /g, '-') + - '-' + - curr.Level.replaceAll(' ', '-') - ).attr('fill', 'yellow'); + curr = d.currentTarget.__data__; + + if (curr && curr['Done%'] != -1) { + var clickedId = d.srcElement.id; + _self.setSectorCursor(svg, '#hover', clickedId); } }) .on('mouseout', function (d) { - //console.log(d.explicitOriginalTarget.__data__.Day) - - if (curr['Done%'] != -1) { - d3.selectAll( - '#segment-' + - curr.SubDimension.replace(/ /g, '-') + - '-' + - curr.Level.replaceAll(' ', '-') - ).attr('fill', function (p) { - var color = d3 - .scaleLinear() - .domain([0, 1]) - .range(['white', 'green']); - // how to access a function within reusable charts - //console.log(color(d.Done)); - return color(curr['Done%']); - }); - } else { - d3.selectAll( - '#segment-' + - curr.SubDimension.replace(/ /g, '-') + - '-' + - curr.Level.replaceAll(' ', '-') - ).attr('fill', '#DCDCDC'); - } + _self.setSectorCursor(svg, '#hover', ''); }); this.reColorHeatmap(); } @@ -573,13 +536,8 @@ export class CircularHeatmapComponent implements OnInit { .attr('class', function (d: any) { return 'segment-' + d.SubDimension.replace(/ /g, '-'); }) - .attr('id', function (d: any) { - return ( - 'segment-' + - d.SubDimension.replace(/ /g, '-') + - '-' + - d.Level.replaceAll(' ', '-') - ); + .attr('id', function (d: any, i: number) { + return 'index-' + i; }) .attr( 'd', @@ -590,9 +548,7 @@ export class CircularHeatmapComponent implements OnInit { .startAngle(sa) .endAngle(ea) ) - .attr('stroke', function (d) { - return '#252525'; - }) + .attr('stroke', '#252525') .attr('fill', function (d) { return color(accessor(d)); }); @@ -640,6 +596,31 @@ export class CircularHeatmapComponent implements OnInit { .text(function (d: any) { return d; }); + var cursors = svg + .append('g') + .classed('cursors', true) + .attr( + 'transform', + 'translate(' + + (margin.left + offset) + + ',' + + (margin.top + offset) + + ')' + ); + cursors + .append('path') + .attr('id', 'hover') + .attr('pointer-events', 'none') + .attr('stroke', 'green') + .attr('stroke-width', '7') + .attr('fill', 'transparent'); + cursors + .append('path') + .attr('id', 'selected') + .attr('pointer-events', 'none') + .attr('stroke', '#232323') + .attr('stroke-width', '4') + .attr('fill', 'transparent'); }); } @@ -721,15 +702,21 @@ export class CircularHeatmapComponent implements OnInit { return chart; } + setSectorCursor(svg: any, cursor: string, targetId: string): void { + let element = svg.select(cursor); + let path: string = ''; + if (targetId) { + if (targetId[0] != '#') targetId = '#' + targetId; + path = svg.select(targetId).attr('d'); + } + + svg.select(cursor).attr('d', path); + } + noActivitytoGrey(): void { for (var x = 0; x < this.ALL_CARD_DATA.length; x++) { if (this.ALL_CARD_DATA[x]['Done%'] == -1) { - d3.selectAll( - '#segment-' + - this.ALL_CARD_DATA[x]['SubDimension'].replace(/ /g, '-') + - '-' + - this.ALL_CARD_DATA[x]['Level'].replace(' ', '-') - ).attr('fill', '#DCDCDC'); + d3.select('#index-' + x).attr('fill', '#DCDCDC'); } } } @@ -778,6 +765,10 @@ export class CircularHeatmapComponent implements OnInit { this.showOverlay = false; } + toggleFilters() { + this.showFilters = !this.showFilters; + } + saveEditedYAMLfile() { this.setTeamsStatus(this.YamlObject, this.teamList, this.ALL_CARD_DATA); let yamlStr = yaml.dump(this.YamlObject); @@ -812,47 +803,39 @@ export class CircularHeatmapComponent implements OnInit { reColorHeatmap() { console.log('recolor'); + var teamsCount = this.teamVisible.length; + for (var index = 0; index < this.ALL_CARD_DATA.length; index++) { - let cntAll: number = 0; + var activities = this.ALL_CARD_DATA[index]['Activity']; + let cntAll: number = teamsCount * activities.length; let cntTrue: number = 0; var _self = this; - for (var i = 0; i < this.ALL_CARD_DATA[index]['Activity'].length; i++) { - var activityTeamList: any; - activityTeamList = - this.ALL_CARD_DATA[index]['Activity'][i]['teamsImplemented']; - ( - Object.keys(activityTeamList) as (keyof typeof activityTeamList)[] - ).forEach((key, index) => { - if (typeof key === 'string') { - if (this.teamVisible.includes(key)) { - if (activityTeamList[key] === true) { - cntTrue += 1; - } - cntAll += 1; - } + for (var i = 0; i < activities.length; i++) { + for (var teamname of this.teamVisible) { + if (activities[i]['teamsImplemented'][teamname]) { + cntTrue++; + // console.log(`Counting ${activities[i].activityName}: ${teamname} (${cntTrue})`); } - }); + } } + + var colorSector = d3 + .scaleLinear() + .domain([0, 1]) + .range(['white', 'green']); + if (cntAll !== 0) { this.ALL_CARD_DATA[index]['Done%'] = cntTrue / cntAll; + // console.log(`${this.ALL_CARD_DATA[index].SubDimension} ${this.ALL_CARD_DATA[index].Level} Done: ${cntTrue}/${cntAll} = ${(cntTrue / cntAll*100).toFixed(1)}%`); + d3.select('#index-' + index).attr('fill', function (p) { + return colorSector(_self.ALL_CARD_DATA[index]['Done%']); + }); } else { this.ALL_CARD_DATA[index]['Done%'] = -1; + // console.log(`${this.ALL_CARD_DATA[index].SubDimension} ${this.ALL_CARD_DATA[index].Level} None`); + d3.select('#index-' + index).attr('fill', '#DCDCDC'); } - var color = d3 - .scaleLinear() - .domain([0, 1]) - .range(['white', 'green']); - - d3.selectAll( - '#segment-' + - this.ALL_CARD_DATA[index]['SubDimension'].replace(/ /g, '-') + - '-' + - this.ALL_CARD_DATA[index]['Level'].replace(' ', '-') - ).attr('fill', function (p) { - return color(_self.ALL_CARD_DATA[index]['Done%']); - }); } - this.noActivitytoGrey(); } deleteLocalTeamsProgress() { diff --git a/src/app/component/sidenav-buttons/sidenav-buttons.component.ts b/src/app/component/sidenav-buttons/sidenav-buttons.component.ts index 01855215c..a48f9703d 100644 --- a/src/app/component/sidenav-buttons/sidenav-buttons.component.ts +++ b/src/app/component/sidenav-buttons/sidenav-buttons.component.ts @@ -7,8 +7,8 @@ import { Component } from '@angular/core'; }) export class SidenavButtonsComponent { Options: string[] = [ + 'Overview', 'Matrix', - 'Implementation Levels', 'Mappings', 'Usage', 'Teams', @@ -16,8 +16,8 @@ export class SidenavButtonsComponent { 'DSOMM User Day 2024', ]; Icons: string[] = [ - 'table_chart', 'pie_chart', + 'table_chart', 'timeline', 'description', 'people', @@ -25,8 +25,8 @@ export class SidenavButtonsComponent { 'school', ]; Routing: string[] = [ - '/', '/circular-heatmap', + '/matrix', '/mapping', '/usage', '/teams',