added charts (#13)

This commit is contained in:
Jason Kulatunga 2022-12-22 18:20:56 -08:00 committed by GitHub
parent 918c856338
commit c8e074ff4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 269 additions and 38 deletions

View File

@ -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>&nbsp; Medical History</a> <a routerLink="/medical-history" routerLinkActive="active" #medicalHistory="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'book-medical']"></fa-icon>&nbsp; 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>&nbsp; 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>&nbsp; Sources</a> <a routerLink="/sources" routerLinkActive="active" #sources="routerLinkActive" class="nav-link"><fa-icon [icon]="['fas', 'hospital']"></fa-icon>&nbsp; Sources</a>
</li> </li>

View File

@ -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">
<p> <div class="col-12 mt-3">
<small> <p>
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. <small>
</small> 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.
</p> </small>
</p>
</div>
<div class="col-12">
<canvas baseChart [height]="chartHeight" [chartType]="'horizontalBar'" [datasets]="barChartData" [labels]="barChartLabels" [options]="barChartOptions"></canvas>
</div>
<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>
</ng-container>
</div> </div>
</div> </div>
</div> </div>

View File

@ -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
}
} }
} }

View File

@ -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>

View File

@ -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,

View File

@ -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 -->

View File

@ -3,23 +3,42 @@
<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">
<h1 class="az-dashboard-title">Observations</h1> <h1 class="az-dashboard-title">Observations</h1>
</div>
</div> </div>
</div>
<!-- Observations List --> <!-- Observations List -->
<app-report-labs-observation *ngFor="let observationGroup of observationGroups | keyvalue" <app-report-labs-observation *ngFor="let observationGroup of observationGroups | keyvalue"
[observations]="observationGroup.value" [observations]="observationGroup.value"
[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>

View File

@ -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;
}
} }