Merge pull request #49 in DEFINMA/definma-ui from f/pdf-export to master

* commit 'efd4e3f5e90d7e6efe1f21fe197a762954d2e9d0':
  Add timestamps to export file names
  Fix export button placement, finalize PDF layout
  Improve PDF export fonts
  Add most relevant content to PDF export
  Fix prediction result aggregation
  Add pdfmake dependency
This commit is contained in:
Hartenstein Ruben (PEA4-Fe) 2021-01-25 11:05:35 +01:00
commit 2d3782cc82
5 changed files with 800 additions and 83 deletions

707
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@
"flatpickr": "^4.6.3", "flatpickr": "^4.6.3",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"ng2-charts": "^2.3.2", "ng2-charts": "^2.3.2",
"pdfmake": "^0.1.70",
"quick-score": "0.0.8", "quick-score": "0.0.8",
"rxjs": "~6.5.5", "rxjs": "~6.5.5",
"str-compare": "^0.1.2", "str-compare": "^0.1.2",

View File

@ -60,12 +60,18 @@
<rb-form-radio name="multiple-samples" label="Multiple samples" [(ngModel)]="multipleSamples" [value]="true"> <rb-form-radio name="multiple-samples" label="Multiple samples" [(ngModel)]="multipleSamples" [value]="true">
</rb-form-radio> </rb-form-radio>
</div> </div>
<rb-icon-button icon="forward-right" mode="primary" (click)="exportCSV()" *ngIf="spectrumNames.length">
Export to CSV
</rb-icon-button>
</div> </div>
<!-- CSV export -->
<rb-icon-button icon="forward-right" mode="secondary" (click)="exportCSV()" *ngIf="spectrumNames.length" style="margin-right: 0.5rem">
Export to CSV
</rb-icon-button>
<!-- PDF exprot -->
<rb-icon-button icon="forward-right" mode="secondary" (click)="exportPDF()" *ngIf="spectrumNames.length">
Export to PDF
</rb-icon-button>
<div class="dpt-chart space-below"> <div class="dpt-chart space-below">
<canvas baseChart <canvas baseChart
class="dpt-chart" class="dpt-chart"

View File

@ -2,6 +2,7 @@
max-width: 800px; max-width: 800px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-top: 0.5rem;
} }
.file-input { .file-input {

View File

@ -5,8 +5,13 @@ import { animate, style, transition, trigger } from '@angular/animations';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { DataService } from '../services/data.service'; import { DataService } from '../services/data.service';
import { LoginService } from '../services/login.service';
import { ModelItemModel } from '../models/model-item.model'; import { ModelItemModel } from '../models/model-item.model';
import * as FileSaver from 'file-saver' import * as FileSaver from 'file-saver';
import * as pdfMake from "pdfmake/build/pdfmake";
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts.pdfMake.vfs;
@Component({ @Component({
@ -63,7 +68,8 @@ export class PredictionComponent implements OnInit {
constructor( constructor(
private api: ApiService, private api: ApiService,
public d: DataService public d: DataService,
public login: LoginService
) { ) {
this.chart[0] = cloneDeep(this.chartInit); this.chart[0] = cloneDeep(this.chartInit);
} }
@ -122,10 +128,154 @@ export class PredictionComponent implements OnInit {
this.result = undefined; this.result = undefined;
} }
exportCSV() { // Aggregates spectrum names and prediction values into an associative array
prepareExport() {
const zip = (a, b) => a.map((k, i) => [k, b[i]]); const zip = (a, b) => a.map((k, i) => [k, b[i]]);
const predictions = zip(this.spectrumNames, this.result.predictions.map(p => p[0].value)); const values = this.result.predictions
.map(prediction => prediction
.filter(field => field.category === 'Prediction')
.map(field => field.value));
return zip(this.spectrumNames, values);
}
// Generates a timestamp suitable for file naming
generateTimestamp() {
let d = new Date();
return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + '--' + d.getHours() + "-" + d.getMinutes();
}
// Converts the prediction results to a CSV file
exportCSV() {
const predictions = this.prepareExport();
const csv = predictions.map(line => line.join(";")).join("\n"); const csv = predictions.map(line => line.join(";")).join("\n");
FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), "predictions.csv"); FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'predictions-' + this.generateTimestamp() + '.csv');
}
// Converts the prediction results to a PDF file
exportPDF() {
const doc = {
content: [
{
text: 'DeFinMa - Decoding the Fingerprint of Materials by AI',
style: 'header'
},
{
table: {
widths: ['auto', '*', 'auto', 'auto', 'auto'],
body: [
[
{text: new Date().toLocaleDateString(), style: 'tableHeader'},
{text: 'Customer', style: 'tableHeader'},
{text: 'Security class', style: 'tableHeader'},
{text: 'Person in charge', style: 'tableHeader'},
{text: 'Phone', style: 'tableHeader'}],
[
this.activeGroup.group,
this.login.username,
'Intern',
{
stack: [
'CR/APS1-Lotter',
'CR/APS1-Lingenfelser'
]
},
{
stack: [
'0711/811-49017',
'0711/811-6897'
]
}
]
]
}
},
{
text: 'Prediction of ' + this.activeGroup.group + ' (' + this.activeGroup.models[this.activeModelIndex].name + ')*',
style: 'subheader'
},
{
text: this.result.mean.map(e => e.category + ' ' + e.value + ' ' + e.label + ' ' + (e.std !== '' ? (' (standard deviation: ' + e.std + ')') : ''))
},
{
table: {
widths: ['*', 'auto'],
body: [
[{text: 'Input Data / Sample Name', style: 'tableHeader'}, {text: 'Prediction*', style: 'tableHeader'}]
].concat(this.prepareExport())
}
},
{
text: 'Reference Data',
style: 'subheader'
},
{
image: document.getElementsByTagName('canvas')[0].toDataURL('image/png'),
width: 500
},
{
table: {
body: [[{
stack: [
{
text: '*Disclaimer: This tool is still under development and Testing',
style: 'subsubheader'
},
{
text: [
'The prediction and classification of material parameters are validated only for certain conditions.',
'These results may therefore under no circumstances be used to evaluate quality-relevant issues.',
'For more details please contact ',
{
text: 'CR/APS1-Lingenfelser',
link: 'mailto:dominic.lingenfelser@bosch.com'
},
'.'
]
},
]
}]],
},
margin: [25, 20]
},
{
table: {
widths: ['*', '*', 'auto'],
body: [
[{text: 'Pr\u00fcfung', style: 'tableHeader'}, {text: 'Freigabe', style: 'tableHeader'}, {text: 'Datum', style: 'tableHeader'}],
['CR/APS1-Lotter', 'CR/APS1-Lingenfelser', new Date().toLocaleDateString()]
],
}
}
],
footer: {
text: '\u00a9 Alle Rechte bei Robert Bosch GmbH, auch f\u00fcr den Fall von Schutzreichtsanmeldungen. Jede Verf\u00fcgungsbefugnis, wie Kopier- und Weitergaberecht, bei uns.',
fontSize: 8,
alignment: 'center'
},
styles: {
header: {
fontSize: 18,
bold: true,
margin: [0, 10]
},
subheader: {
fontSize: 15,
bold: true,
margin: [0, 8]
},
subsubheader: {
bold: true,
margin: [0, 5]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
}
},
pageMargins: [50, 50, 50, 15]
};
pdfMake.createPdf(doc).download('predictions-' + this.generateTimestamp() + '.pdf');
} }
} }