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">
|
<ng-container *ngIf="chartData.length || circleData.length">
|
||||||
<canvas
|
<canvas
|
||||||
*ngIf="type === 'bar' || type === 'stackedBar' || type === 'horizontalBar' || type === 'line'"
|
*ngIf="type === 'bar' || type === 'stackedBar' || type === 'horizontalBar' || type === 'line'"
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
.charts-wrapper {
|
.charts-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
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;
|
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.
|
* Fires an event, when the user clicks on the chart.
|
||||||
*/
|
*/
|
||||||
@ -135,6 +161,13 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
@Output()
|
@Output()
|
||||||
public hover = new EventEmitter<ChartEvent>();
|
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.
|
* The general data for the chart.
|
||||||
* This is only needed for `type == 'bar' || 'line'`
|
* This is only needed for `type == 'bar' || 'line'`
|
||||||
@ -191,6 +224,11 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
|
|
||||||
private _chartLegendSize: ChartLegendSize = 'middle';
|
private _chartLegendSize: ChartLegendSize = 'middle';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the value for `max-width`.
|
||||||
|
*/
|
||||||
|
private _size = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -47,12 +47,13 @@
|
|||||||
<div *ngIf="poll.stateHasVotes">
|
<div *ngIf="poll.stateHasVotes">
|
||||||
<h2 translate>Result</h2>
|
<h2 translate>Result</h2>
|
||||||
|
|
||||||
<div class="chart-wrapper"></div>
|
<div class="chart-wrapper" [ngClass]="{ flex: isVotedPoll }">
|
||||||
<mat-table [dataSource]="poll.tableData">
|
<mat-table [dataSource]="poll.tableData">
|
||||||
<ng-container matColumnDef="user" sticky>
|
<ng-container matColumnDef="user" sticky>
|
||||||
<mat-header-cell *matHeaderCellDef>{{ 'Candidates' | translate }}</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>{{ 'Candidates' | translate }}</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">{{ row.user }}</mat-cell>
|
<mat-cell *matCellDef="let row">{{ row.user }}</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<div *ngIf="!isVotedPoll">
|
||||||
<ng-container matColumnDef="yes">
|
<ng-container matColumnDef="yes">
|
||||||
<mat-header-cell *matHeaderCellDef>{{ 'Yes' | translate }}</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>{{ 'Yes' | translate }}</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">{{ row.yes }}</mat-cell>
|
<mat-cell *matCellDef="let row">{{ row.yes }}</mat-cell>
|
||||||
@ -67,38 +68,46 @@
|
|||||||
<mat-header-cell *matHeaderCellDef>{{ 'Abstain' | translate }}</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>{{ 'Abstain' | translate }}</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">{{ row.abstain }}</mat-cell>
|
<mat-cell *matCellDef="let row">{{ row.abstain }}</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-container matColumnDef="quorum">
|
<div *ngIf="isVotedPoll">
|
||||||
<mat-header-cell *matHeaderCellDef>{{ 'Quorum' | translate }}</mat-header-cell>
|
<ng-container matColumnDef="votes">
|
||||||
<mat-cell *matCellDef="let row"></mat-cell>
|
<mat-header-cell *matHeaderCellDef>{{ 'Votes' | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.yes }}</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="columnDefinitionOverview"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="columnDefinitionOverview"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: columnDefinitionOverview"></mat-row>
|
<mat-row *matRowDef="let row; columns: columnDefinitionOverview"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
|
<div class="chart-inner-wrapper">
|
||||||
<os-charts
|
<os-charts
|
||||||
*ngIf="chartDataSubject.value"
|
*ngIf="chartDataSubject.value"
|
||||||
[type]="chartType"
|
[type]="chartType"
|
||||||
[labels]="candidatesLabels"
|
[labels]="candidatesLabels"
|
||||||
|
[size]="isVotedPoll ? 70 : 100"
|
||||||
|
[legendPosition]="isVotedPoll ? 'right' : 'top'"
|
||||||
[showLegend]="true"
|
[showLegend]="true"
|
||||||
[data]="chartDataSubject"
|
[data]="chartDataSubject"
|
||||||
></os-charts>
|
></os-charts>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="poll.type === 'named' && votesDataSource.data">
|
<ng-container *ngIf="poll.type === 'named' && votesDataSource.data">
|
||||||
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter"/>
|
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter" />
|
||||||
<mat-table [dataSource]="votesDataSource">
|
<mat-table [dataSource]="votesDataSource">
|
||||||
<ng-container matColumnDef="users" sticky>
|
<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">
|
<mat-cell *matCellDef="let row">
|
||||||
<div *ngIf="row.user">{{ row.user.getFullName() }}</div>
|
<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>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container [matColumnDef]="'votes-' + option.user_id" *ngFor="let option of poll.options" sticky>
|
<ng-container [matColumnDef]="'votes-' + option.user_id" *ngFor="let option of poll.options" sticky>
|
||||||
<mat-header-cell *matHeaderCellDef>
|
<mat-header-cell *matHeaderCellDef>
|
||||||
<div *ngIf="option.user">{{ option.user.getFullName() }}</div>
|
<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-header-cell>
|
||||||
<mat-cell *matCellDef="let row">
|
<mat-cell *matCellDef="let row">
|
||||||
{{ row.votes[option.user_id] }}
|
{{ 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 candidatesLabels: string[] = [];
|
||||||
|
|
||||||
public get chartType(): ChartType {
|
public get chartType(): ChartType {
|
||||||
return 'horizontalBar';
|
return this._chartType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isVotedPoll(): boolean {
|
||||||
|
return this.poll.pollmethod === AssignmentPollMethods.Votes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get columnDefinitionOverview(): string[] {
|
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) {
|
if (this.poll.pollmethod === AssignmentPollMethods.YNA) {
|
||||||
columns.splice(3, 0, 'abstain');
|
columns.splice(3, 0, 'abstain');
|
||||||
}
|
}
|
||||||
@ -39,6 +43,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
|
|
||||||
public columnDefinitionPerName: string[];
|
public columnDefinitionPerName: string[];
|
||||||
|
|
||||||
|
private _chartType: ChartType = 'horizontalBar';
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
@ -62,7 +68,7 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
for (const vote of option.votes) {
|
for (const vote of option.votes) {
|
||||||
// if poll was pseudoanonymized, use a negative index to not interfere with
|
// if poll was pseudoanonymized, use a negative index to not interfere with
|
||||||
// possible named votes (although this should never happen)
|
// possible named votes (although this should never happen)
|
||||||
const userId = vote.user_id || i--;
|
const userId = vote.user_id || --i;
|
||||||
if (!votes[userId]) {
|
if (!votes[userId]) {
|
||||||
votes[userId] = {
|
votes[userId] = {
|
||||||
user: vote.user,
|
user: vote.user,
|
||||||
@ -81,6 +87,13 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected initChartData(): void {
|
||||||
|
if (this.isVotedPoll) {
|
||||||
|
this._chartType = 'doughnut';
|
||||||
|
this.chartDataSubject.next(this.poll.generateCircleChartData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected hasPerms(): boolean {
|
protected hasPerms(): boolean {
|
||||||
return this.operator.hasPerms('assignments.can_manage');
|
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.
|
* Sets the type of the shown chart, if votes are entered.
|
||||||
*/
|
*/
|
||||||
// public chartType = 'horizontalBar';
|
|
||||||
public abstract get chartType(): ChartType;
|
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.
|
* Opens dialog for editing the poll
|
||||||
*
|
|
||||||
* @param isChecked boolean, if the chart should show the amount of entered votes.
|
|
||||||
*/
|
*/
|
||||||
public changeChart(): void {
|
public openDialog(): void {
|
||||||
this.chartDataSubject.next(this.poll.generateChartData());
|
this.pollDialog.openDialog(this.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDeleted(): void {}
|
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.
|
* This checks, if the poll has votes.
|
||||||
*/
|
*/
|
||||||
private checkData(): void {
|
private checkData(): void {
|
||||||
if (this.poll.state === 3 || this.poll.state === 4) {
|
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) {
|
if (poll) {
|
||||||
this.poll = poll;
|
this.poll = poll;
|
||||||
this.updateBreadcrumbs();
|
this.updateBreadcrumbs();
|
||||||
this.checkData();
|
|
||||||
this.onPollLoaded();
|
this.onPollLoaded();
|
||||||
|
this.waitForOptions();
|
||||||
// wait for options to be loaded
|
this.checkData();
|
||||||
(function waitForOptions(): void {
|
|
||||||
if (!this.poll.options || !this.poll.options.length) {
|
|
||||||
setTimeout(waitForOptions.bind(this), 1);
|
|
||||||
} else {
|
|
||||||
this.onPollWithOptionsLoaded();
|
|
||||||
}
|
|
||||||
}.call(this));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -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 {
|
private waitForOptions(): void {
|
||||||
this.pollDialog.openDialog(this.poll);
|
if (!this.poll.options || !this.poll.options.length) {
|
||||||
|
setTimeout(() => this.waitForOptions(), 1);
|
||||||
|
} else {
|
||||||
|
this.onPollWithOptionsLoaded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user