Pull request #8: Charts

Merge in RED/ui from charts to master

* commit 'bea47054b7dd8ed7a4837cbe62da3e3c9d01cd50':
  Finished charts on project listing
  Charts
This commit is contained in:
Timo Bejan 2020-10-13 12:49:32 +02:00
commit 064efe404a
20 changed files with 267 additions and 92 deletions

View File

@ -1,4 +1,4 @@
<div class="flex-row">
<div [className]="color + ' oval ' + size">{{initials}}</div>
<div *ngIf="withName" class="name clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
<div *ngIf="withName" class="clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
</div>

View File

@ -1,10 +1,5 @@
@import "../../../assets/styles/red-variables";
.name {
font-size: 13px;
line-height: 16px;
}
.flex-row {
flex-wrap: wrap;
gap: 12px;

View File

@ -1,6 +1,6 @@
<div class="rectangle-container" [ngClass]="{ small: small }">
<div *ngFor="let rect of config" [className]="'section-wrapper flex-' + rect.length">
<div [className]="'rectangle ' + rect.color "></div>
<div *ngIf="rect.title" class="subtitle">{{ rect.title }}</div>
<div *ngIf="rect.label" [ngClass]="labelClass">{{ rect.label }}</div>
</div>
</div>

View File

@ -38,20 +38,40 @@
.rectangle {
height: 4px;
&.grey {
background-color: $grey-4;
&.unassigned {
background-color: $grey-5;
}
&.yellow {
&.under-review {
background-color: $yellow-1;
}
&.blue {
background-color: $blue-1;
&.under-approval {
background-color: $red-1;
}
&.green {
background-color: $green-1;
&.approved {
background-color: $blue-2;
}
&.submitted {
background-color: $blue-3;
}
&.efsa {
background-color: $blue-4;
}
&.finished {
background-color: $green-2;
}
&.active {
background-color: $primary;
}
&.archived {
background-color: rgba($red-1, 0.1);
}
}
}

View File

@ -1,4 +1,5 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { Color } from '../../utils/types';
@Component({
selector: 'redaction-status-bar',
@ -10,13 +11,16 @@ export class StatusBarComponent implements OnInit {
@Input()
public config: {
length: number,
color: 'green' | 'blue' | 'red' | 'grey' | 'yellow',
title?: string,
color: Color,
label?: string,
}[] = [];
@Input()
public small = false;
@Input()
public labelClass = '';
constructor() {
}

View File

@ -84,7 +84,8 @@
<div *ngIf="infoTab" class="tab-content info-container">
<redaction-status-bar [small]="true"
[config]="[{ length: 1, title: 'Unassigned', color: 'grey'}]"></redaction-status-bar>
[labelClass]="'subtitle'"
[config]="[{ length: 1, label: 'Unassigned', color: 'unassigned'}]"></redaction-status-bar>
<div class="subtitle stats-subtitle mt-5">
<div>645</div>

View File

@ -65,8 +65,6 @@ redaction-pdf-viewer {
}
.assign-reviewer {
font-size: 13px;
line-height: 16px;
margin-left: 12px;
}
@ -92,8 +90,6 @@ redaction-pdf-viewer {
}
.page-number {
font-size: 13px;
line-height: 18px;
font-weight: 600;
}

View File

@ -20,7 +20,7 @@
<div class="left-container">
<div class="table-header">
<span class="subheading">
{{'projects.table-header.title.label'| translate:{length: appStateService.allProjects?.length || 0} }}
{{'projects.table-header.title.label'| translate:{ length: appStateService.allProjects?.length || 0 } }}
</span>
<div class="actions">
<div translate="projects.table-header.bulk-select.label"></div>
@ -57,7 +57,7 @@
</div>
<div class="stats-bar">
<redaction-status-bar
[config]="[{ color: 'yellow', length: 2}, { length: 1, color: 'green'}]"
[config]="[{ color: 'under-review', length: 2}, { length: 1, color: 'finished'}]"
></redaction-status-bar>
</div>
@ -79,8 +79,36 @@
</div>
<div class="right-fixed-container">
<redaction-simple-doughnut-chart [data]="projectsChartData" [strokeWidth]="15"></redaction-simple-doughnut-chart>
<div>
<redaction-simple-doughnut-chart [config]="projectsChartData"
[strokeWidth]="15"
[subtitle]="'Projects'"
></redaction-simple-doughnut-chart>
<div class="project-stats-container">
<div class="project-stats-item">
<mat-icon svgIcon="red:files"></mat-icon>
<div>
<div class="heading">1324</div>
<div>Analyzed pages</div>
</div>
</div>
<div class="project-stats-item">
<mat-icon svgIcon="red:user"></mat-icon>
<div>
<div class="heading">240</div>
<div>Total people</div>
</div>
</div>
</div>
</div>
<div>
<redaction-simple-doughnut-chart [config]="documentsChartData"
[strokeWidth]="15"
[subtitle]="'Total Documents'"
></redaction-simple-doughnut-chart>
</div>
</div>
</div>

View File

@ -15,3 +15,42 @@
color: $white;
}
}
.right-fixed-container {
display: flex;
width: 470px;
padding-top: 50px;
> div {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.project-stats-container {
width: fit-content;
.project-stats-item {
display: flex;
width: fit-content;
gap: 5px;
margin-top: 25px;
&:first-of-type {
margin-top: 50px;
}
mat-icon {
height: 16px;
width: 16px;
margin-top: 3px;
}
}
}
}
.left-container {
width: calc(100vw - #{$right-container-width} - 130px);
}

View File

@ -1,14 +1,14 @@
import {Component, OnInit} from '@angular/core';
import {Project, ProjectControllerService} from "@redaction/red-ui-http";
import {MatDialog} from "@angular/material/dialog";
import {AddEditProjectDialogComponent} from "./add-edit-project-dialog/add-edit-project-dialog.component";
import {ConfirmationDialogComponent} from "../../common/confirmation-dialog/confirmation-dialog.component";
import {TranslateService} from "@ngx-translate/core";
import {NotificationService} from "../../notification/notification.service";
import {AppStateService, ProjectWrapper} from "../../state/app-state.service";
import {UserService} from "../../user/user.service";
import {ProjectDetailsDialogComponent} from "../project-overview-screen/project-details-dialog/project-details-dialog.component";
import {DataSeries} from "../../simple-doughnut-chart/simple-doughnut-chart.component";
import { Component, OnInit } from '@angular/core';
import { Project, ProjectControllerService } from '@redaction/red-ui-http';
import { MatDialog } from '@angular/material/dialog';
import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edit-project-dialog.component';
import { ConfirmationDialogComponent } from '../../common/confirmation-dialog/confirmation-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { NotificationService } from '../../notification/notification.service';
import { AppStateService, ProjectWrapper } from '../../state/app-state.service';
import { UserService } from '../../user/user.service';
import { ProjectDetailsDialogComponent } from '../project-overview-screen/project-details-dialog/project-details-dialog.component';
import { DoughnutChartConfig } from '../../simple-doughnut-chart/simple-doughnut-chart.component';
@Component({
selector: 'redaction-project-listing-screen',
@ -16,7 +16,8 @@ import {DataSeries} from "../../simple-doughnut-chart/simple-doughnut-chart.comp
styleUrls: ['./project-listing-screen.component.scss']
})
export class ProjectListingScreenComponent implements OnInit {
projectsChartData: DataSeries [] = [];
projectsChartData: DoughnutChartConfig [] = [];
documentsChartData: DoughnutChartConfig [] = [];
constructor(
public readonly appStateService: AppStateService,
@ -34,8 +35,14 @@ export class ProjectListingScreenComponent implements OnInit {
ngOnInit(): void {
this.appStateService.reset();
this.projectsChartData = [
{value: this.activeProjects, color: '#DD4D50', label: 'active-projects'},
{value: this.inactiveProjects, color: '#f8eded', label: 'Archived'}];
{ value: this.activeProjects, color: 'active', label: 'active-projects' },
{ value: this.inactiveProjects, color: 'archived', label: 'Archived' }
];
this.documentsChartData = [
{ value: 20, color: 'unassigned', label: 'unassigned' },
{ value: 40, color: 'under-review', label: 'under review' },
{ value: 16, color: 'under-approval', label: 'under approval' },
]
}
openAddProjectDialog(project?: Project): void {
@ -55,7 +62,7 @@ export class ProjectListingScreenComponent implements OnInit {
$event.stopPropagation();
const dialogRef = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw',
maxWidth: '90vw'
});
dialogRef.afterClosed().subscribe(result => {
@ -84,7 +91,7 @@ export class ProjectListingScreenComponent implements OnInit {
}
get activeProjects() {
return this.appStateService.allProjects.reduce((i, p) => i+(p.project.status === Project.StatusEnum.ACTIVE ? 1 :0), 0)
return this.appStateService.allProjects.reduce((i, p) => i + (p.project.status === Project.StatusEnum.ACTIVE ? 1 : 0), 0);
}
get inactiveProjects() {

View File

@ -61,7 +61,7 @@
<div class=" status-container">
<div class="status-bar-wrapper">
<redaction-status-bar
[config]="[{ color: 'yellow', length: 1 }]"
[config]="[{ color: 'under-review', length: 1 }]"
></redaction-status-bar>
</div>
</div>
@ -110,7 +110,7 @@
></redaction-initials-avatar>
</div>
<div class="description mt-20">
<div class="mt-20">
{{ appStateService.activeProject.project.description }}
</div>

View File

@ -24,11 +24,6 @@
}
.project-details-container {
.description {
font-size: 13px;
line-height: 18px;
}
.members-container {
gap: 5px;
}

View File

@ -1,16 +1,30 @@
<svg attr.height="{{size}}" attr.width="{{size}}" attr.viewBox="0 0 {{size}} {{size}}" class="donut-chart">
<g *ngFor="let value of data; let i = index">
<circle attr.cx="{{cx}}"
attr.cy="{{cy}}"
attr.r="{{radius}}"
attr.stroke="{{data[i].color}}"
attr.stroke-width="{{strokeWidth}}"
attr.stroke-dasharray="{{adjustedCircumference}}"
attr.stroke-dashoffset="{{calculateStrokeDashOffset(value.value, circumference)}}"
fill="transparent"
attr.transform="{{returnCircleTransformValue(i)}}"/>
</g>
</svg>
<div *ngFor="let val of data">
{{val.value}} {{val.label}}
<div class="container">
<svg attr.height="{{size}}" attr.width="{{size}}" attr.viewBox="0 0 {{size}} {{size}}" class="donut-chart">
<g *ngFor="let value of parsedConfig; let i = index">
<circle attr.cx="{{cx}}"
attr.cy="{{cy}}"
attr.r="{{radius}}"
[class]="value.color"
attr.stroke-width="{{strokeWidth}}"
attr.stroke-dasharray="{{adjustedCircumference}}"
attr.stroke-dashoffset="{{calculateStrokeDashOffset(value.value, circumference)}}"
fill="transparent"
attr.transform="{{returnCircleTransformValue(i)}}" />
</g>
</svg>
<div class="text-container" [style]="'height: ' + size + 'px; width: ' + size + 'px;'">
<div class="heading-xl">{{ dataTotal }}</div>
<div class="projects-text mt-5">{{ subtitle }}</div>
</div>
<div class="mt-20 breakdown-container">
<div>
<div *ngFor="let val of parsedConfig">
<redaction-status-bar [small]="true"
[config]="[{ length: val.value, color: val.color, label: val.value + ' ' + val.label}]">
</redaction-status-bar>
</div>
</div>
</div>
</div>

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,69 @@
@import "../../assets/styles/red-variables";
.container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.text-container {
position: absolute;
top: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.breakdown-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
div {
width: fit-content;
display: flex;
flex-direction: column;
gap: 8px;
}
}
circle {
&.unassigned {
stroke: $grey-5;
}
&.under-review {
stroke: $yellow-1;
}
&.under-approval {
stroke: $red-1;
}
&.approved {
stroke: $blue-2;
}
&.submitted {
stroke: $blue-3;
}
&.efsa {
stroke: $blue-4;
}
&.finished {
stroke: $green-2;
}
&.active {
stroke: $primary;
}
&.archived {
stroke: rgba($red-1, 0.1);
}
}

View File

@ -1,13 +1,12 @@
import {Component, Input, OnInit} from '@angular/core';
import { Color } from '../utils/types';
export class DataSeries {
export class DoughnutChartConfig {
value: number;
color: string;
color: Color;
label: string;
}
@Component({
selector: 'redaction-simple-doughnut-chart',
templateUrl: './simple-doughnut-chart.component.html',
@ -15,18 +14,17 @@ export class DataSeries {
})
export class SimpleDoughnutChartComponent implements OnInit {
@Input() title: string;
@Input() subtitle: number;
@Input() data: DataSeries[] = [];
@Input() subtitle: string;
@Input() config: DoughnutChartConfig[] = [];
@Input() angleOffset = -90;
@Input() radius = 80;
@Input() radius = 85;
@Input() strokeWidth = 20;
chartData: any[] = [];
perimeter: number;
cx = 0;
cy = 0;
size = 0;
public chartData: any[] = [];
public perimeter: number;
public cx = 0;
public cy = 0;
public size = 0;
constructor() {
}
@ -47,11 +45,11 @@ export class SimpleDoughnutChartComponent implements OnInit {
};
get dataTotal() {
return this.data.map(v => v.value).reduce((acc, val) => acc + val, 0);
return this.config.map(v => v.value).reduce((acc, val) => acc + val, 0);
};
calculateChartData() {
this.data.forEach((dataVal) => {
this.config.forEach((dataVal) => {
const data = {
degrees: this.angleOffset,
}
@ -65,10 +63,6 @@ export class SimpleDoughnutChartComponent implements OnInit {
return circumference - strokeDiff;
}
degreesToRadians(angle) {
return angle * (Math.PI / 180);
}
dataPercentage(dataVal) {
return dataVal / this.dataTotal;
}
@ -77,4 +71,8 @@ export class SimpleDoughnutChartComponent implements OnInit {
return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`;
}
// Eliminate items with value = 0
public get parsedConfig() {
return this.config.filter((el) => el.value);
}
}

10
apps/red-ui/src/app/utils/types.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
export type Color =
'unassigned' |
'under-review' |
'under-approval' |
'approved' |
'submitted' |
'efsa' |
'finished' |
'active' |
'archived';

View File

@ -9,7 +9,7 @@
cursor: pointer;
color: $accent;
background: $white;
font-family: 'Inter', sans-serif;
font-family: Inter, sans-serif;
font-size: 13px;
letter-spacing: 0;
line-height: 14px;

View File

@ -5,8 +5,10 @@ html, body {
margin: 0;
padding: 0;
height: 100vh;
font-family: 'Inter', sans-serif;
font-family: Inter, sans-serif;
color: $accent;
font-size: 13px;
line-height: 16px;
}
.page-header {
@ -57,9 +59,6 @@ html, body {
}
.filters {
font-size: 13px;
line-height: 14px;
> div {
padding: 10px 14px;
}
@ -157,8 +156,6 @@ html, body {
.breadcrumb {
text-decoration: none;
color: $accent;
font-size: 13px;
line-height: 18px;
font-weight: 600;
@include line-clamp(1);
max-width: 320px;

View File

@ -10,7 +10,6 @@
.actions {
display: flex;
font-size: 13px;
div {
padding: 10px 14px;
@ -41,9 +40,7 @@
}
.table-item-title {
font-size: 13px;
font-weight: 600;
line-height: 18px;
@include line-clamp(1);
}

View File

@ -8,13 +8,18 @@ $dark: #000;
$grey-1: #283241;
$grey-2: #ECECEE;
$grey-3: #aaacb3;
$grey-3: #AAACB3;
$grey-4: #E2E4E9;
$grey-5: #D3D5DA;
$blue-1: #4875F7;
$blue-2: #48C9F7;
$blue-3: #5B97DB;
$blue-4: #374C81;
$red-1: #F65757;
$yellow-1: #FFB83B;
$green-1: #46CE7D;
$green-2: #5CE594;
$separator: rgba(226,228,233,0.9);