added charts (#13)
This commit is contained in:
parent
918c856338
commit
c8e074ff4b
|
@ -17,6 +17,9 @@
|
||||||
<li class="nav-item" ngbDropdown [ngClass]="{ 'active': medicalHistory.isActive }">
|
<li class="nav-item" ngbDropdown [ngClass]="{ 'active': medicalHistory.isActive }">
|
||||||
<a routerLink="/medical-history" routerLinkActive="active" #medicalHistory="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'book-medical']"></fa-icon> Medical History</a>
|
<a routerLink="/medical-history" routerLinkActive="active" #medicalHistory="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'book-medical']"></fa-icon> Medical History</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" ngbDropdown [ngClass]="{ 'active': labs.isActive }">
|
||||||
|
<a routerLink="/labs" routerLinkActive="active" #labs="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'heart-pulse']"></fa-icon> Labs</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" ngbDropdown [ngClass]="{ 'active': sources.isActive }">
|
<li class="nav-item" ngbDropdown [ngClass]="{ 'active': sources.isActive }">
|
||||||
<a routerLink="/sources" routerLinkActive="active" #sources="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'hospital']"></fa-icon> Sources</a>
|
<a routerLink="/sources" routerLinkActive="active" #sources="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'hospital']"></fa-icon> Sources</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="row" >
|
<div class="row" >
|
||||||
<!-- Condition Header -->
|
<!-- Condition Header -->
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
{{observationTitle}}
|
<span routerLink="/source/{{observations[0]?.source_id}}/resource/{{observations[0]?.source_resource_id}}">{{observationTitle}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
{{observations[0] | fhirPath: "Observation.effectiveDateTime": "Observation.issued" | date}}
|
{{observations[0] | fhirPath: "Observation.effectiveDateTime": "Observation.issued" | date}}
|
||||||
|
@ -20,12 +20,27 @@
|
||||||
<div class="row pl-3">
|
<div class="row pl-3">
|
||||||
<div class="col-12 mt-3 mb-2">
|
<div class="col-12 mt-3 mb-2">
|
||||||
<p>
|
<p>
|
||||||
<strong>Test Code (loinc):</strong> {{observationCode}} <br/>
|
<strong>Short Name:</strong> {{observations[0] | fhirPath: "Observation.code.text"}} <br/>
|
||||||
|
<strong>Result:</strong> {{observations[0] | fhirPath: "Observation.valueQuantity.value"}} {{observations[0] | fhirPath: "Observation.valueQuantity.unit"}} <br/>
|
||||||
|
<br/>
|
||||||
<strong>Latest Test Date:</strong> {{observations[0] | fhirPath: "Observation.effectiveDateTime": "Observation.issued" | date}} <br/>
|
<strong>Latest Test Date:</strong> {{observations[0] | fhirPath: "Observation.effectiveDateTime": "Observation.issued" | date}} <br/>
|
||||||
<strong>Ordered By:</strong> {{observations[0] | fhirPath: "Observation.encounter.display"}} <br/>
|
<strong>Ordered By:</strong> {{observations[0] | fhirPath: "Observation.encounter.display"}} <br/>
|
||||||
<strong>Notes:</strong>
|
<strong>LOINC Code:</strong> {{observationCode}} <br/>
|
||||||
|
<strong>Notes:</strong> {{observations[0] | fhirPath: "Observation.note.text"}}
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<a class="cursor-pointer tx-indigo" (click)="collapse.toggle()">show all</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div #collapse="ngbCollapse" [ngbCollapse]="true">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li class="cursor-pointer tx-indigo" *ngFor="let observation of observations" routerLink="/source/{{observation?.source_id}}/resource/{{observation?.source_resource_id}}">Observation: {{observation | fhirPath: "Observation.effectiveDateTime": "Observation.issued" | date}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,20 +48,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 bg-gray-100">
|
<div class="col-6 bg-gray-100">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-12 mt-3">
|
||||||
<p>
|
<p>
|
||||||
<small>
|
<small>
|
||||||
Troponin I is a part of the troponin complex. It binds to actin in thin myofilaments to hold the actin-tropomyosin complex in place. Because of it myosin cannot bind actin in relaxed muscle. When calcium binds to the Troponin C it causes conformational changes which lead to dislocation of troponin I and finally tropomyosin leaves the binding site for myosin on actin leading to contraction of muscle. The letter I is given due to its inhibitory character.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<ng-container *ngFor="let observation of observations">
|
|
||||||
|
|
||||||
<div routerLink="/source/{{observation?.source_id}}/resource/{{observation?.source_resource_id}}" class="col-6 mt-3 mb-2 tx-indigo">
|
|
||||||
<strong>{{observation | fhirPath: "Observation.valueQuantity.value" }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
<div class="col-12">
|
||||||
|
<canvas baseChart [height]="chartHeight" [chartType]="'horizontalBar'" [datasets]="barChartData" [labels]="barChartLabels" [options]="barChartOptions"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import {Component, Input, OnInit} from '@angular/core';
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
||||||
|
import {ChartOptions} from 'chart.js';
|
||||||
|
// import { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';
|
||||||
|
// import { BaseChartDirective } from 'ng2-charts';
|
||||||
|
import * as fhirpath from 'fhirpath';
|
||||||
|
import {formatDate} from '@angular/common';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-report-labs-observation',
|
selector: 'app-report-labs-observation',
|
||||||
|
@ -12,10 +18,179 @@ export class ReportLabsObservationComponent implements OnInit {
|
||||||
@Input() observationCode: string
|
@Input() observationCode: string
|
||||||
@Input() observationTitle: string
|
@Input() observationTitle: string
|
||||||
|
|
||||||
|
// based on https://stackoverflow.com/questions/38889716/chartjs-2-stacked-bar-with-marker-on-top
|
||||||
|
// https://stackoverflow.com/questions/62711919/chart-js-horizontal-lines-per-bar
|
||||||
|
|
||||||
|
|
||||||
|
chartHeight = 45
|
||||||
|
|
||||||
|
barChartData =[
|
||||||
|
// {
|
||||||
|
// label: "Current",
|
||||||
|
// backgroundColor: 'rgba(255, 0, 128, 1)',
|
||||||
|
// data: [],
|
||||||
|
// xAxisID: "x-axis-current"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// label: "Reference",
|
||||||
|
// backgroundColor: 'rgba(99,189,50,0.2)',
|
||||||
|
// data: [],
|
||||||
|
// xAxisID: "x-axis-ref"
|
||||||
|
// },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "Reference",
|
||||||
|
data: [[55,102], [44,120]],
|
||||||
|
backgroundColor: "rgba(91, 71, 251,0.6)",
|
||||||
|
hoverBackgroundColor: "rgba(91, 71, 251,0.2)"
|
||||||
|
},{
|
||||||
|
label: "Current",
|
||||||
|
data: [[80,81], [130,131]],
|
||||||
|
borderColor: "rgba(0,0,0,1)",
|
||||||
|
backgroundColor: "rgba(0,0,0,1)",
|
||||||
|
hoverBackgroundColor: "rgba(0,0,0,1)",
|
||||||
|
minBarLength: 3,
|
||||||
|
barPercentage: 1,
|
||||||
|
tooltip: {
|
||||||
|
|
||||||
|
}
|
||||||
|
// id: "x-axis-current",
|
||||||
|
//important settings
|
||||||
|
|
||||||
|
//set the width of the line ( or point )
|
||||||
|
// pointRadius: 50,
|
||||||
|
// don´t show line betrween points
|
||||||
|
// showLine: false,
|
||||||
|
//change points of line chart to line style ( little bit confusin why it´s named point anyway )
|
||||||
|
// pointStyle: 'line',
|
||||||
|
|
||||||
|
//chart type
|
||||||
|
// type: "line",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
barChartLabels = [] // ["2020", "2018"] //["1","2","3","4","5","6","7","8"]
|
||||||
|
|
||||||
|
barChartOptions = {
|
||||||
|
legend:{
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
//add padding to fix tooltip cutoff
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 4,
|
||||||
|
top: 0,
|
||||||
|
bottom: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
stacked: true,
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
fontSize: 10,
|
||||||
|
min: 0,
|
||||||
|
// max: 80
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
scaleLabel:{
|
||||||
|
display: false,
|
||||||
|
labelString: "xaxis",
|
||||||
|
padding: 4,
|
||||||
|
},
|
||||||
|
// stacked: true,
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
fontSize: 10,
|
||||||
|
min: 0,
|
||||||
|
// max: 80
|
||||||
|
},
|
||||||
|
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
// xAxes: [{
|
||||||
|
// id: "x-axis-current",
|
||||||
|
// stacked: true,
|
||||||
|
// // barPercentage: 0.6,
|
||||||
|
// ticks: {
|
||||||
|
// beginAtZero:true,
|
||||||
|
// fontSize: 11
|
||||||
|
// }
|
||||||
|
// },{
|
||||||
|
// id: "x-axis-ref",
|
||||||
|
// stacked: true,
|
||||||
|
// ticks: {
|
||||||
|
// beginAtZero:true,
|
||||||
|
// fontSize: 11
|
||||||
|
// }
|
||||||
|
// // offset: true,
|
||||||
|
//
|
||||||
|
// // display: false,
|
||||||
|
// // gridLines: {
|
||||||
|
// // offsetGridLines: true
|
||||||
|
// // }
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
// legend: {
|
||||||
|
// display: false
|
||||||
|
// },
|
||||||
|
// elements: {
|
||||||
|
// point: {
|
||||||
|
// radius: 0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
} as ChartOptions
|
||||||
|
|
||||||
|
barChartColors = [
|
||||||
|
{
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
let currentValues = []
|
||||||
|
|
||||||
|
let referenceRanges = []
|
||||||
|
|
||||||
|
for(let observation of this.observations){
|
||||||
|
//get label
|
||||||
|
this.barChartLabels.push(
|
||||||
|
formatDate(fhirpath.evaluate(observation.resource_raw, "Observation.effectiveDateTime")[0], "mediumDate", "en-US", undefined)
|
||||||
|
)
|
||||||
|
|
||||||
|
//get current value
|
||||||
|
currentValues.push(fhirpath.evaluate(observation.resource_raw, "Observation.valueQuantity.value")[0])
|
||||||
|
|
||||||
|
//set chart x-axis label
|
||||||
|
let units = fhirpath.evaluate(observation.resource_raw, "Observation.valueQuantity.unit")[0]
|
||||||
|
if(units){
|
||||||
|
this.barChartOptions.scales.xAxes[0].scaleLabel.display = true
|
||||||
|
this.barChartOptions.scales.xAxes[0].scaleLabel.labelString = units
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//add low/high ref value blocks
|
||||||
|
referenceRanges.push([
|
||||||
|
fhirpath.evaluate(observation.resource_raw, "Observation.referenceRange.low.value")[0],
|
||||||
|
fhirpath.evaluate(observation.resource_raw, "Observation.referenceRange.high.value")[0]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.barChartData[0].data = referenceRanges
|
||||||
|
this.barChartData[1].data = currentValues.map(v => [v, v])
|
||||||
|
|
||||||
|
if(currentValues.length > 1){
|
||||||
|
this.chartHeight = 30 * currentValues.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="card card-dashboard-seven mb-3">
|
<div class="card card-dashboard-seven mb-3">
|
||||||
<div class="card-header tx-medium">
|
<div class="card-header tx-medium">
|
||||||
<div class="row" routerLink="/source/{{condition?.source_id}}/resource/{{condition?.source_resource_id}}">
|
<div class="row cursor-pointer" routerLink="/source/{{condition?.source_id}}/resource/{{condition?.source_resource_id}}">
|
||||||
<!-- Condition Header -->
|
<!-- Condition Header -->
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
{{condition | fhirPath: "Condition.code.text.first()":"Condition.code.coding.display.first()"}}
|
{{condition | fhirPath: "Condition.code.text.first()":"Condition.code.coding.display.first()"}}
|
||||||
|
@ -48,6 +48,15 @@
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="condition.related_resources.length > 0">
|
||||||
|
<a class="cursor-pointer tx-indigo" (click)="collapse.toggle()">show all</a>
|
||||||
|
<div #collapse="ngbCollapse" [ngbCollapse]="true">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li class="cursor-pointer tx-indigo" *ngFor="let resource of condition.related_resources" routerLink="/source/{{resource?.source_id}}/resource/{{resource?.source_resource_id}}">Resource: {{resource.source_resource_type}}/{{resource.source_resource_id}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { TreeModule } from '@circlon/angular-tree-component';
|
||||||
import {FilterPipe} from '../pipes/filter.pipe';
|
import {FilterPipe} from '../pipes/filter.pipe';
|
||||||
import { ReportMedicalHistoryConditionComponent } from './report-medical-history-condition/report-medical-history-condition.component';
|
import { ReportMedicalHistoryConditionComponent } from './report-medical-history-condition/report-medical-history-condition.component';
|
||||||
import { ReportLabsObservationComponent } from './report-labs-observation/report-labs-observation.component';
|
import { ReportLabsObservationComponent } from './report-labs-observation/report-labs-observation.component';
|
||||||
|
import { ChartsModule } from 'ng2-charts';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -50,6 +51,7 @@ import { ReportLabsObservationComponent } from './report-labs-observation/report
|
||||||
NgbModule,
|
NgbModule,
|
||||||
MomentModule,
|
MomentModule,
|
||||||
TreeModule,
|
TreeModule,
|
||||||
|
ChartsModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ComponentsSidebarComponent,
|
ComponentsSidebarComponent,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="az-content-body">
|
<div class="az-content-body">
|
||||||
|
|
||||||
<!-- Header Row -->
|
<!-- Header Row -->
|
||||||
<report-header [reportHeaderTitle]="'Medical History'"></report-header>
|
<report-header [reportHeaderTitle]="'Medical History'" [reportHeaderSubTitle]="'Organized by conditions, describes the scope and breadth of medical care'"></report-header>
|
||||||
|
|
||||||
|
|
||||||
<!-- Editor Button -->
|
<!-- Editor Button -->
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
<div class="az-content-body">
|
<div class="az-content-body">
|
||||||
|
|
||||||
<!-- Header Row -->
|
<!-- Header Row -->
|
||||||
<report-header [reportHeaderTitle]="'Labs'"></report-header>
|
<report-header [reportHeaderTitle]="'Labs'" [reportHeaderSubTitle]="'The Lab report turns data into meaningful information'" ></report-header>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ng-container *ngIf="!isEmptyReport; else emptyReport">
|
||||||
<!-- Observations Title -->
|
<!-- Observations Title -->
|
||||||
<div class="row mt-5 mb-3">
|
<div class="row mt-5 mb-3">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
@ -20,6 +20,25 @@
|
||||||
[observationCode]="observationGroup.key"
|
[observationCode]="observationGroup.key"
|
||||||
[observationTitle]="observationGroupTitles[observationGroup.key]"
|
[observationTitle]="observationGroupTitles[observationGroup.key]"
|
||||||
></app-report-labs-observation>
|
></app-report-labs-observation>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #emptyReport>
|
||||||
|
<div class="d-flex align-items-center" style="height:100%">
|
||||||
|
<div class="modal-body tx-center pd-y-20 pd-x-20">
|
||||||
|
<h4 class="tx-purple mg-b-20">No Lab Data Found!</h4>
|
||||||
|
<p class="mg-b-20 mg-x-20">
|
||||||
|
Fasten was unable to find any lab data from your connected sources. You may need to connect another source to import your Lab data.
|
||||||
|
</p>
|
||||||
|
<p class="mg-b-20 mg-x-20">
|
||||||
|
Click below to add a new healthcare provider to Fasten.
|
||||||
|
</p>
|
||||||
|
<button [routerLink]="'/sources'" type="button" class="btn btn-purple pd-x-25">Add Source</button>
|
||||||
|
</div><!-- modal-body -->
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,12 +14,17 @@ export class ReportLabsComponent implements OnInit {
|
||||||
observationGroups: {[key: string]: ResourceFhir[]} = {}
|
observationGroups: {[key: string]: ResourceFhir[]} = {}
|
||||||
observationGroupTitles: {[key: string]: string} = {}
|
observationGroupTitles: {[key: string]: string} = {}
|
||||||
|
|
||||||
|
loading = true
|
||||||
|
isEmptyReport = false
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fastenApi: FastenApiService,
|
private fastenApi: FastenApiService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.fastenApi.getResources("Observation").subscribe(results => {
|
this.fastenApi.getResources("Observation").subscribe(results => {
|
||||||
|
this.loading = false
|
||||||
|
results = results || []
|
||||||
console.log("ALL OBSERVATIONS", results)
|
console.log("ALL OBSERVATIONS", results)
|
||||||
|
|
||||||
//loop though all observations, group by "code.system": "http://loinc.org"
|
//loop though all observations, group by "code.system": "http://loinc.org"
|
||||||
|
@ -31,18 +36,21 @@ export class ReportLabsComponent implements OnInit {
|
||||||
if(!this.observationGroupTitles[observationGroup]){
|
if(!this.observationGroupTitles[observationGroup]){
|
||||||
this.observationGroupTitles[observationGroup] = fhirpath.evaluate(observation.resource_raw, "Observation.code.coding.where(system='http://loinc.org').first().display")[0]
|
this.observationGroupTitles[observationGroup] = fhirpath.evaluate(observation.resource_raw, "Observation.code.coding.where(system='http://loinc.org').first().display")[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: sort observation groups
|
this.isEmptyReport = !!!results.length
|
||||||
|
}, error => {
|
||||||
// this.observationGroups = results
|
this.loading = false
|
||||||
//
|
this.isEmptyReport = true
|
||||||
// //populate a lookup table with all resources
|
|
||||||
// for (let condition of this.conditions) {
|
|
||||||
// this.recPopulateResourceLookup(condition)
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
isEmpty(obj: any) {
|
||||||
|
return Object.keys(obj).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue