org.carewebframework.vista.ui.vitals.DisplayController.java Source code

Java tutorial

Introduction

Here is the source code for org.carewebframework.vista.ui.vitals.DisplayController.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at
 * http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is also subject to the terms of the Health-Related Additional
 * Disclaimer of Warranty and Limitation of Liability available at
 * http://www.carewebframework.org/licensing/disclaimer.
 */
package org.carewebframework.vista.ui.vitals;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import ca.uhn.fhir.model.dstu.resource.Patient;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.carewebframework.cal.api.patient.PatientContext;
import org.carewebframework.common.DateUtil;
import org.carewebframework.common.StrUtil;
import org.carewebframework.shell.plugins.IPluginEvent;
import org.carewebframework.shell.plugins.PluginContainer;
import org.carewebframework.ui.FrameworkController;
import org.carewebframework.ui.highcharts.Chart;
import org.carewebframework.ui.highcharts.DashStyle;
import org.carewebframework.ui.highcharts.DataPoint;
import org.carewebframework.ui.highcharts.DateTimeFormatOptions;
import org.carewebframework.ui.highcharts.Series;
import org.carewebframework.ui.highcharts.ZoomType;
import org.carewebframework.ui.zk.DateRangePicker;
import org.carewebframework.vista.api.util.VistAUtil;
import org.carewebframework.vista.mbroker.BrokerSession;
import org.carewebframework.vista.mbroker.FMDate;

import org.zkoss.zk.ui.Component;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listhead;
import org.zkoss.zul.Listheader;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Paging;
import org.zkoss.zul.Slider;
import org.zkoss.zul.Toolbar;

/**
 * Controller for vital measurement display.
 */
public class DisplayController extends FrameworkController
        implements PatientContext.IPatientContextEvent, IPluginEvent {

    private static final Log log = LogFactory.getLog(DisplayController.class);

    private static final long serialVersionUID = 1L;

    private static final String[] rangeSeries = new String[] { "low", "high" };

    private static final String DATE_FORMAT = "%d-%b-%y";

    private static final String TIME_FORMAT = DATE_FORMAT + " %H:%M";

    private Chart chart;

    private Listbox lstVitals;

    private Listhead hdrVitals;

    private Paging pgVitals;

    private Slider sldVitals;

    private Checkbox chkGrid;

    private Checkbox chkAge;

    private Checkbox chkPercentiles;

    private Checkbox chkZoom;

    private Combobox cboUnits;

    private Button btnEnterVitals;

    private DateRangePicker datRange;

    private Toolbar tbarPaging;

    private Patient patient;

    private int selectedRow = -1;

    private String selectedItem;

    private final Date today = new Date();

    private final Map<String, String> percentiles = new HashMap<String, String>();

    private List<String> tests;

    private BrokerSession broker;

    private String gridRPC;

    private String detailRPC;

    private final int maxCols = 5;

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        // chart.getRenderer().setBaseItemLabelsVisible(true);
        //chart.setPeriod(Chart.MINUTE);
        //chart.setDateFormat("dd-MMM-yy");
        chart.getXAxis().gridLineWidth = 0;
        chart.getYAxis().gridLineWidth = 0;
        chart.options.exporting.buttons_printButton.onclick = "cwf.print(this.container);";
        setDateFormats(chart.options.plotOptions.tooltip.dateTimeLabelFormats);
        setDateFormats(chart.getXAxis().dateTimeLabelFormats);
        broker = VistAUtil.getBrokerSession();
        cboUnits.setSelectedIndex(0);
        datRange.setSelectedIndex(0);
        btnEnterVitals.setVisible(EntryController.isEnabled());
        selectData("", "RGCWVM GRID", "RGCWVM DETAIL", null);
    }

    public void selectData(String test, String gridRPC, String detailRPC, List<String> tests) {
        this.gridRPC = gridRPC;
        this.detailRPC = detailRPC;
        this.tests = tests;
        committed();
        setSelectedRow(test);
    }

    private void setDateFormats(DateTimeFormatOptions dtlf) {
        dtlf.setDateFormats(DATE_FORMAT);
        dtlf.setTimeFormats(TIME_FORMAT);
    }

    private Date ageToDate(double age) {
        if (chkAge.isVisible() && chkAge.isChecked()) {
            return DateUtil.addDays(patient.getBirthDate().getValue(), (int) (age * 365.25 / 12.0), true);
        } else {
            return new Date();
        }
    }

    private double dateToAge(Date date) {
        double diff = date.getTime() - patient.getBirthDate().getValue().getTime();
        return diff / 2592000000.0;
    }

    private Listcell setValue(int col, int row, String value, Object object) {
        Listcell cell = getCell(col, row);
        cell.setLabel(value);
        cell.setValue(object);
        return cell;
    }

    private String getValue(int col, int row) {
        return getCell(col, row).getLabel();
    }

    private Object getObject(int col, int row) {
        return getCell(col, row).getValue();
    }

    private Listcell getCell(int col, int row) {
        Listitem item;

        while (row >= lstVitals.getItemCount()) {
            item = new Listitem();
            item.setVisible(false);
            item.setParent(lstVitals);
        }

        item = lstVitals.getItemAtIndex(row);

        while (col >= item.getChildren().size()) {
            Listcell cell = new Listcell("");
            cell.setParent(item);
        }

        return (Listcell) item.getChildren().get(col);
    }

    private Series findSeries(String seriesName, boolean forceCreate) {
        for (Series series : chart.options.series) {
            if (seriesName.equals(series.name)) {
                return series;
            }
        }

        if (!forceCreate) {
            return null;
        }

        Series series = chart.addSeries();
        series.name = seriesName;
        return series;
    }

    private DataPoint plotData(double xVal, String value, String seriesName, String id) {
        try {
            double yVal = Double.parseDouble(value);
            Series series = findSeries(seriesName, true);
            DataPoint dp = series.addDataPoint(xVal, yVal);
            dp.id = id;
            return dp;
        } catch (Exception e) {
            return null;
        }
    }

    private void plotRange(double xLow, double xHigh, String range) {
        final String pcs[] = StrUtil.split(range, "-", 2);

        for (int i = 0; i < 2; i++) {
            String seriesName = rangeSeries[i];
            Series series = findSeries(seriesName, true);
            DataPoint low = plotData(xLow, pcs[i], seriesName, null);
            DataPoint high = plotData(xHigh, pcs[i], seriesName, null);

            if (low == null || high == null) {
                chart.options.series.remove(series);
            } else {
                series.plotOptions.dashStyle = DashStyle.Dot;
                series.plotOptions.color = "darkgray";
                series.plotOptions.showInLegend = false;
                low.marker.enabled = false;
                high.marker.enabled = false;
            }
        }
    }

    private void plotPercentile(double xVal, String value, String series) {
        Series pctile = findSeries(series, true);
        DataPoint dp = plotData(xVal, value, series, series);

        if (pctile.plotOptions.dashStyle == null) {
            pctile.plotOptions.marker.enabled = false;
            pctile.plotOptions.enableMouseTracking = false;
            pctile.plotOptions.dashStyle = "50".equals(series) ? DashStyle.DashDot : DashStyle.Dash;
            pctile.plotOptions.lineWidth = 1;
        }
    }

    private void chartData() {
        Date dateHigh = null;
        Date dateLow = null;
        int row = selectedRow;
        boolean useAge = chkAge.isVisible() && chkAge.isChecked();
        chart.clear();
        int colcount = hdrVitals.getChildren().size() - 1;

        if (row < 0 || row >= lstVitals.getItemCount() || colcount < 0) {
            return;
        }

        boolean hasData = false;
        String testname = getValue(0, row);
        String testid = (String) getObject(0, row);
        boolean isBP = StringUtils.containsIgnoreCase(testname, "pressure");
        chart.getYAxis().title.text = (String) getObject(colcount, row);
        chart.getXAxis().title.text = useAge ? "age (months)" : null;
        chart.getXAxis().type = useAge ? "linear" : "datetime";

        for (int col = 1; col < colcount; col++) {
            Listheader hdr = (Listheader) hdrVitals.getChildren().get(col);
            FMDate date = (FMDate) hdr.getValue();

            if (date != null) {
                double xVal = useAge ? dateToAge(date) : date.getTime();
                String vals[] = StrUtil.split(getValue(col, row), ";");
                boolean newData = false;

                for (String val : vals) {
                    if (isBP) {
                        String pcs[] = StrUtil.split(val, "/");

                        if (pcs.length > 0) {
                            newData |= plotData(xVal, pcs[0], "Systolic", testname) != null;
                        }

                        if (pcs.length > 2) {
                            newData |= plotData(xVal, pcs[1], "Mean", testname) != null;
                            newData |= plotData(xVal, pcs[2], "Diastolic", testname) != null;
                        } else {
                            newData |= plotData(xVal, pcs[1], "Diastolic", testname) != null;
                        }
                    } else {
                        newData |= plotData(xVal, val, testname, testname) != null;
                    }
                }

                if (newData) {
                    hasData = true;
                    dateLow = dateLow == null ? date : date.getTime() < dateLow.getTime() ? date : dateLow;
                    dateHigh = dateHigh == null ? date : date.getTime() > dateHigh.getTime() ? date : dateHigh;
                }
            }
        }

        if (hasData) {
            double xLow = useAge ? dateToAge(dateLow) : dateLow.getTime();
            double xHigh = useAge ? dateToAge(dateHigh) : dateHigh.getTime();
            plotRange(xLow, xHigh, StrUtil.piece(getValue(colcount, row), " "));
            String pctileRPC = percentiles.get(testid);

            if (pctileRPC != null && chkPercentiles.isChecked()) {
                List<String> pctiles = broker.callRPCList(pctileRPC, null, testid, patient.getId().getIdPart(),
                        DateUtils.addDays(dateLow, -3000), DateUtils.addDays(dateHigh, 3000), getDefaultUnits());

                for (String pctile : pctiles) {
                    String pcs[] = StrUtil.split(pctile, StrUtil.U, 3);
                    FMDate date = new FMDate(pcs[1]);
                    plotPercentile(useAge ? dateToAge(date) : date.getTime(), pcs[2], pcs[0]);
                }
            }

            chart.run();
        }
    }

    /**
     * Load the string grid with data. Calls the grid RPC to retrieve data for the grid. Data is
     * returned in the following format:
     *
     * <pre>
     *
     * counts: test count^date count^result count
     * tests: control ien^test ien^test name^test abbrv^units^low norm^hi norm^percentile RPC
     * dates: date id^FM date results: date id^row #^value^result ien
     * 
     * For example:
     * 
     * 8^2^7
     * 
     * 3^3^TEMPERATURE^TMP^F^^^ 5^5^PULSE^PU^/min^60^100^
     * 15^15^RESPIRATIONS^RS^/min^^^ 4^4^BLOOD PRESSURE^BP^mmHg^90^150^
     * 1^1^HEIGHT^HT^in^^^CIAOCVVM PCTILE 2^2^WEIGHT^WT^lb^^^CIAOCVVM PCTILE
     * 21^21^PAIN^PA^^^^ 6^6^HEAD CIRCUMFERENCE^HC^in^^^CIAOCVVM PCTILE
     * 
     * 2^3041018.1446
     * 1^3041022.1446
     * 
     * 1^2^77^^2445227 1^4^101/65^^2445224 1^5^27^^2445222 2^5^26.5^^2445220
     * 1^6^16.5^^2445223 2^6^16^^2445218 1^8^17.5^^2445225
     * </pre>
     */
    private void loadGrid() {
        chart.clear();

        if (patient == null) {
            showMessage("No patient selected.");
            return;
        }

        Iterator<String> data = doRPC(gridRPC, datRange.getStartDate(), datRange.getEndDate(), tests).iterator();
        percentiles.clear();
        String[] pcs = StrUtil.split(data.next(), StrUtil.U, 3);
        int testcnt = StrUtil.toInt(pcs[0]);
        int datecnt = StrUtil.toInt(pcs[1]);
        int datacnt = StrUtil.toInt(pcs[2]);

        if (datacnt == 0 || datecnt == 0) {
            showMessage("No data available within selected range.");
            return;
        }

        initGrid(datecnt + 2, testcnt);
        // Populate test names and units
        for (int r = 0; r < testcnt; r++) {
            pcs = StrUtil.split(data.next(), StrUtil.U, 8);
            String range = pcs[5] + "-" + pcs[6];
            range = "-".equals(range) ? "" : range + " ";
            setValue(0, r, WordUtils.capitalizeFully(pcs[2]), pcs[0]).setStyle("font-weight:bold");
            setValue(datecnt + 1, r, range + pcs[4], pcs[4]).setStyle("font-style:italic");

            if (!pcs[7].isEmpty()) {
                percentiles.put(pcs[0], pcs[7]);
            }
        }
        // Populate date headers
        Map<String, Listheader> headers = new HashMap<String, Listheader>();

        for (int c = 1; c <= datecnt; c++) {
            pcs = StrUtil.split(data.next(), StrUtil.U, 2);
            FMDate date = new FMDate(pcs[1]);
            Listheader hdr = (Listheader) hdrVitals.getChildren().get(c);
            hdr.setLabel(date.toString());
            hdr.setValue(date);
            hdr.setParent(hdrVitals);
            headers.put(pcs[0], hdr);
        }
        // Populate data cells
        for (int i = 0; i < datacnt; i++) {
            pcs = StrUtil.split(data.next(), StrUtil.U, 3);
            int col = headers.get(pcs[0]).getColumnIndex();
            int row = StrUtil.toInt(pcs[1]) - 1;
            setValue(col, row, StrUtil.strAppend(getValue(col, row), pcs[2], "; "), null);
            lstVitals.getItemAtIndex(row).setVisible(true);
        }

        lstVitals.invalidate();
        setSelectedRow(selectedItem);
    }

    private void initGrid(int colcount, int rowcount) {
        lstVitals.getItems().clear();
        hdrVitals.getChildren().clear();

        if (colcount > 2 && rowcount > 0) {
            tbarPaging.setVisible(true);
            sldVitals.setMaxpos(colcount - 2);
            sldVitals.setCurpos(maxCols);
            pgVitals.setActivePage(0);
            pgVitals.setTotalSize(colcount - 2);
            pgVitals.setPageSize(maxCols + 1);
            getCell(colcount - 1, rowcount - 1);

            for (int i = 1; i <= colcount; i++) {
                Listheader lh = new Listheader();
                lh.setVisible(i <= maxCols || i == colcount);
                lh.setAlign(i == 1 ? "right" : i == colcount ? "left" : "center");
                lh.setHflex("1");
                hdrVitals.appendChild(lh);
            }
        } else {
            hdrVitals.appendChild(new Listheader());
            tbarPaging.setVisible(false);
        }
    }

    private void showMessage(String message) {
        initGrid(0, 0);
        Listcell cell = getCell(0, 0);
        cell.setLabel(message);
        cell.getParent().setVisible(true);
    }

    private void setSelectedRow(String test) {
        if (StringUtils.isEmpty(test)) {
            setSelectedRow(-1);
        } else {
            for (int i = 0; i < lstVitals.getItemCount(); i++) {
                if (test.equals(getValue(0, i))) {
                    setSelectedRow(i);
                    break;
                }
            }
        }
    }

    private void setSelectedRow(int index) {
        if (index < 0) {
            selectedRow = -1;
            selectedItem = "";
            lstVitals.clearSelection();
            chkPercentiles.setVisible(false);
        } else {
            selectedRow = index;
            selectedItem = getValue(0, index);
            lstVitals.setSelectedIndex(index);
            chkPercentiles.setVisible(percentiles.containsKey(getObject(0, index)));
        }

        chartData();
    }

    private int getDefaultUnits() {
        return cboUnits.getSelectedIndex() - 1;
    }

    private List<String> doRPC(String rpcName, Date date1, Date date2, List<String> tests) {
        // TODO: need to use encounter location
        return broker.callRPCList(rpcName, null, patient.getId().getIdPart(), date1, date2, 0, tests, 0,
                getDefaultUnits());
    }

    private void updatePaging() {
        int colcount = hdrVitals.getChildren().size() - 2;
        int maxcols = sldVitals.getCurpos() + 1;
        sldVitals.setTooltiptext(Integer.toString(maxcols));
        pgVitals.setActivePage(0);
        pgVitals.setPageSize(Math.min(maxcols, colcount));
        onPaging$pgVitals();
    }

    public void onScroll$sldVitals() {
        updatePaging();
    }

    public void onPaging$pgVitals() {
        int maxCols = sldVitals.getCurpos() + 1;
        int cols = hdrVitals.getChildren().size() - 1;
        int col1 = pgVitals.getActivePage() * maxCols;
        int col2 = col1 + maxCols;

        for (int col = 1; col < cols; col++) {
            ((Listheader) hdrVitals.getChildren().get(col)).setVisible(col > col1 && col <= col2);
        }
    }

    public void onSelect$lstVitals() {
        setSelectedRow(lstVitals.getSelectedIndex());
    }

    public void onSelectRange$datRange() {
        loadGrid();
    }

    public void onSelect$cboUnits() {
        loadGrid();
    }

    public void onCheck$chkGrid() {
        int w = chkGrid.isChecked() ? 1 : 0;
        chart.getXAxis().gridLineWidth = w;
        chart.getYAxis().gridLineWidth = w;
        chartData();
    }

    public void onCheck$chkAge() {
        chartData();
    }

    public void onCheck$chkPercentiles() {
        chartData();
    }

    public void onCheck$chkZoom() {
        chart.options.chart.zoomType = chkZoom.isChecked() ? ZoomType.xy : null;
        chartData();
    }

    /**
     * Called if the patient context change was canceled.
     */
    @Override
    public void canceled() {
    }

    /**
     * Called if the patient context was committed.
     */
    @Override
    public void committed() {
        patient = PatientContext.getActivePatient();
        chkAge.setVisible(patient != null && dateToAge(today) < 37);
        loadGrid();
    }

    /**
     * Called when a patient context change has been requested.
     *
     * @param silent If true, user interaction is not permitted.
     */
    @Override
    public String pending(boolean silent) {
        return null;
    }

    /**
     * The CareWeb framework will call this method whenever the component becomes activated
     * (visible).
     */
    @Override
    public void onActivate() {
    }

    /**
     * The CareWeb framework will call this method whenever the component becomes inactivated
     * (hidden).
     */
    @Override
    public void onInactivate() {
    }

    /**
     * The CareWeb framework will call this method whenever the component is initially loaded.
     *
     * @param container Reference to the plugin's container.
     */
    @Override
    public void onLoad(PluginContainer container) {
    }

    /**
     * The CareWeb framework will call this method whenever the component is unloaded.
     */
    @Override
    public void onUnload() {
    }

}