Adds a chart for assignment-poll-detail
This commit is contained in:
parent
c46369c6a7
commit
a0c3a28456
@ -1,4 +1,4 @@
|
||||
<div class="charts-wrapper">
|
||||
<div class="charts-wrapper" [ngClass]="[classes, hasPadding ? 'has-padding' : '']">
|
||||
<ng-container *ngIf="chartData.length || circleData.length">
|
||||
<canvas
|
||||
*ngIf="type === 'bar' || type === 'stackedBar' || type === 'horizontalBar' || type === 'line'"
|
||||
|
@ -1,4 +1,15 @@
|
||||
.charts-wrapper {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: auto;
|
||||
|
||||
&.has-padding {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 100 {
|
||||
.os-charts--#{$i} {
|
||||
width: unquote($string: $i + '%');
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,32 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
this.chartOptions.legend.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine, if the chart has some padding at the borders.
|
||||
*/
|
||||
@Input()
|
||||
public hasPadding = true;
|
||||
|
||||
/**
|
||||
* Optional passing a number as percentage value for `max-width`.
|
||||
* Range from 1 to 100.
|
||||
* Defaults to `100`.
|
||||
*/
|
||||
@Input()
|
||||
public set size(size: number) {
|
||||
if (size > 100) {
|
||||
size = 100;
|
||||
}
|
||||
if (size < 1) {
|
||||
size = 1;
|
||||
}
|
||||
this._size = size;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an event, when the user clicks on the chart.
|
||||
*/
|
||||
@ -135,6 +161,13 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
@Output()
|
||||
public hover = new EventEmitter<ChartEvent>();
|
||||
|
||||
/**
|
||||
* Returns a string to append to the `chart-wrapper's` classes.
|
||||
*/
|
||||
public get classes(): string {
|
||||
return 'os-charts os-charts--' + this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* The general data for the chart.
|
||||
* This is only needed for `type == 'bar' || 'line'`
|
||||
@ -191,6 +224,11 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
|
||||
private _chartLegendSize: ChartLegendSize = 'middle';
|
||||
|
||||
/**
|
||||
* Holds the value for `max-width`.
|
||||
*/
|
||||
private _size = 100;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -47,12 +47,13 @@
|
||||
<div *ngIf="poll.stateHasVotes">
|
||||
<h2 translate>Result</h2>
|
||||
|
||||
<div class="chart-wrapper"></div>
|
||||
<div class="chart-wrapper" [ngClass]="{ flex: isVotedPoll }">
|
||||
<mat-table [dataSource]="poll.tableData">
|
||||
<ng-container matColumnDef="user" sticky>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Candidates' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.user }}</mat-cell>
|
||||
</ng-container>
|
||||
<div *ngIf="!isVotedPoll">
|
||||
<ng-container matColumnDef="yes">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Yes' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.yes }}</mat-cell>
|
||||
@ -67,38 +68,46 @@
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Abstain' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.abstain }}</mat-cell>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container matColumnDef="quorum">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Quorum' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"></mat-cell>
|
||||
<div *ngIf="isVotedPoll">
|
||||
<ng-container matColumnDef="votes">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Votes' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.yes }}</mat-cell>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columnDefinitionOverview"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: columnDefinitionOverview"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<div class="chart-inner-wrapper">
|
||||
<os-charts
|
||||
*ngIf="chartDataSubject.value"
|
||||
[type]="chartType"
|
||||
[labels]="candidatesLabels"
|
||||
[size]="isVotedPoll ? 70 : 100"
|
||||
[legendPosition]="isVotedPoll ? 'right' : 'top'"
|
||||
[showLegend]="true"
|
||||
[data]="chartDataSubject"
|
||||
></os-charts>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="poll.type === 'named' && votesDataSource.data">
|
||||
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter" />
|
||||
<mat-table [dataSource]="votesDataSource">
|
||||
<ng-container matColumnDef="users" sticky>
|
||||
<mat-header-cell *matHeaderCellDef>{{ "User" | translate }}</mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'User' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<div *ngIf="row.user">{{ row.user.getFullName() }}</div>
|
||||
<div *ngIf="!row.user">{{ "Unknown user" | translate }}</div>
|
||||
<div *ngIf="!row.user">{{ 'Unknown user' | translate }}</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container [matColumnDef]="'votes-' + option.user_id" *ngFor="let option of poll.options" sticky>
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
<div *ngIf="option.user">{{ option.user.getFullName() }}</div>
|
||||
<div *ngIf="!option.user">{{ "Unknown user" | translate }}</div>
|
||||
<div *ngIf="!option.user">{{ 'Unknown user' | translate }}</div>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
{{ row.votes[option.user_id] }}
|
||||
|
@ -0,0 +1,15 @@
|
||||
.chart-wrapper {
|
||||
&.flex {
|
||||
display: flex;
|
||||
|
||||
.mat-table {
|
||||
flex: 2;
|
||||
.mat-column-votes {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.chart-inner-wrapper {
|
||||
flex: 3;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,11 +26,15 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
public candidatesLabels: string[] = [];
|
||||
|
||||
public get chartType(): ChartType {
|
||||
return 'horizontalBar';
|
||||
return this._chartType;
|
||||
}
|
||||
|
||||
public get isVotedPoll(): boolean {
|
||||
return this.poll.pollmethod === AssignmentPollMethods.Votes;
|
||||
}
|
||||
|
||||
public get columnDefinitionOverview(): string[] {
|
||||
const columns = ['user', 'yes', 'no', 'quorum'];
|
||||
const columns = this.isVotedPoll ? ['user', 'votes'] : ['user', 'yes', 'no'];
|
||||
if (this.poll.pollmethod === AssignmentPollMethods.YNA) {
|
||||
columns.splice(3, 0, 'abstain');
|
||||
}
|
||||
@ -39,6 +43,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
|
||||
public columnDefinitionPerName: string[];
|
||||
|
||||
private _chartType: ChartType = 'horizontalBar';
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
@ -62,7 +68,7 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
for (const vote of option.votes) {
|
||||
// if poll was pseudoanonymized, use a negative index to not interfere with
|
||||
// possible named votes (although this should never happen)
|
||||
const userId = vote.user_id || i--;
|
||||
const userId = vote.user_id || --i;
|
||||
if (!votes[userId]) {
|
||||
votes[userId] = {
|
||||
user: vote.user,
|
||||
@ -81,6 +87,13 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
protected initChartData(): void {
|
||||
if (this.isVotedPoll) {
|
||||
this._chartType = 'doughnut';
|
||||
this.chartDataSubject.next(this.poll.generateCircleChartData());
|
||||
}
|
||||
}
|
||||
|
||||
protected hasPerms(): boolean {
|
||||
return this.operator.hasPerms('assignments.can_manage');
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
/**
|
||||
* Sets the type of the shown chart, if votes are entered.
|
||||
*/
|
||||
// public chartType = 'horizontalBar';
|
||||
public abstract get chartType(): ChartType;
|
||||
|
||||
/**
|
||||
@ -123,12 +122,10 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
}
|
||||
|
||||
/**
|
||||
* This changes the data for the chart depending on the switch in the detail-view.
|
||||
*
|
||||
* @param isChecked boolean, if the chart should show the amount of entered votes.
|
||||
* Opens dialog for editing the poll
|
||||
*/
|
||||
public changeChart(): void {
|
||||
this.chartDataSubject.next(this.poll.generateChartData());
|
||||
public openDialog(): void {
|
||||
this.pollDialog.openDialog(this.poll);
|
||||
}
|
||||
|
||||
protected onDeleted(): void {}
|
||||
@ -167,12 +164,20 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes data for the shown chart.
|
||||
* Could be overwritten to implement custom chart data.
|
||||
*/
|
||||
protected initChartData(): void {
|
||||
this.chartDataSubject.next(this.poll.generateChartData());
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks, if the poll has votes.
|
||||
*/
|
||||
private checkData(): void {
|
||||
if (this.poll.state === 3 || this.poll.state === 4) {
|
||||
setTimeout(() => this.chartDataSubject.next(this.poll.generateChartData()));
|
||||
setTimeout(() => this.initChartData());
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,17 +192,9 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
if (poll) {
|
||||
this.poll = poll;
|
||||
this.updateBreadcrumbs();
|
||||
this.checkData();
|
||||
this.onPollLoaded();
|
||||
|
||||
// wait for options to be loaded
|
||||
(function waitForOptions(): void {
|
||||
if (!this.poll.options || !this.poll.options.length) {
|
||||
setTimeout(waitForOptions.bind(this), 1);
|
||||
} else {
|
||||
this.onPollWithOptionsLoaded();
|
||||
}
|
||||
}.call(this));
|
||||
this.waitForOptions();
|
||||
this.checkData();
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -205,10 +202,14 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens dialog for editing the poll
|
||||
* Waits until poll's options are loaded.
|
||||
*/
|
||||
public openDialog(): void {
|
||||
this.pollDialog.openDialog(this.poll);
|
||||
private waitForOptions(): void {
|
||||
if (!this.poll.options || !this.poll.options.length) {
|
||||
setTimeout(() => this.waitForOptions(), 1);
|
||||
} else {
|
||||
this.onPollWithOptionsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user