Longitudinal Data Viewer
No longer maintained. Reimplemented as the [docs:Flowsheet Module]
Tutors
Primary mentor: [~sy], Backup mentor: [~paul]
Abstract
One of the simplest and most powerful ways that a clinician visualizes data is by reviewing all results in reverse chronological order. While the data visualization "widget" shows graphical depictions of specific variables (e.g., trend lines), this "Flowsheet" project is more concerned with succinctly visualizing all available data for a single patient and, therefore, is more likely to take the form of web pages that render data in tables or lists. For example, a physician may be interested in finding the most recent results for a patient's kidney function. This could involve displaying many laboratory results, including serum sodium, potassium, bicarbonate, blood urea nitrogen, and creatinine levels; blood counts; and ultrasound results. For example, these could be displayed in lists and/or tables, starting with the most recent values.
http://archive.openmrs.org/wiki/Image:Flowsheet.jpg
Example of how one might render flowsheet data
Some ideas of how such a feature would be implemented:
- Start with a simple reverse chronological dump of patient data (e.g., select a patient and show all existing observations in reverse chronologic order)
- Allow clicking on a result to bring up more information about that result for the current patient (e.g., clicking on a patient's weight would bring up a dialog showing more information about that specific result along with additional information about weight and, possibly, a graph of weights for the patient)
- Create a simple search box to quickly navigate to data
- Add a date range selector to filter down to data within a given date rage
This project would require:
- Strong HTML, CSS, and JSP, and Java skills
- Learning how to make an OpenMRS module and work within the OpenMRS web application framework
- Comfort with Java and experience with (or willingness to learn) Hibernate and Spring
- Likely working with Velocity templates and/or AJAX/JavaScript libraries
Target
Successful completion of this project would at the minimum include:
- A new OpenMRS flowsheet module for longitudinal data review
- The ability for a user to select a patient and view their data in reverse chronological order
- Clicking on results brings up a dialog with more information relevant to the result (additional details of the specific result, relevant links, graph of all values for the patient, etc.)
- The ability to search for results by name and/or filter results by date range
Extra credit
- Begin to handle complex results such as sets and complex observations
- Handle abnormal flags
- Display some data in tabular form
- Render in pages vs. long form
Mockup
http://archive.openmrs.org/wiki/Image:GSoC_2009_Ideas_-_Flowsheet_mockup1.png
Planning
Here's some thoughts on how we are planning to implement the project and the progress.
- Installing this module will create a new tab in the patient dashboard --- Done
- As the initial step, all the data related to a patient will be listed in reverse chronological order
- GWT Integration to the module --- Done
- Representing UI objects in the GWT client side --- Done
- Choosing GXT/GWT widgets for displaying the data --- Done
- etc
- A date range selector will be added to filter data to particular period --- Done
(The above tasks are initially planned for mid-term goals and completed far before mid-term)
- Filter by concept type --- Done
- When clicked on an entry of record, it will pop up a window showing all the details of that particular observation --- Done
- The appropriate graphical view of an observation will be dynamically generated (eg: Graph etc) --- Done
- Addition of a flowsheet in the pop-up window --- Done
- Addition of abnormal flags etc --- Done
- Create a search box to search data by different filters --- Done
Enhancements
- UI enhancements
- Creating a double-slider widget for date range selection --- Done
- Layout data to reduce unnecessary space and display obs details on wider space --- Done
- Improving the Line Chart for numeric obs, by reducing extra- range space --- Done
- Highlighting the clicked obs entry in the flowsheet in the pop-up --- Done
- Include critical and normal values for numeric obs in the flowsheet --- Done
- Include a online-resource section to the pop-up --- Done
- According to the data in the pop-up reduce the space for line-chart by showing a thumbnail view ( expanding it should show the chart in bigger window)
- Improving the data transfer speed --- Done for now
Documentation
- Module Documentation --- Done
- Source code documentation --- Done
Future Enhancements - Long after the summer
- Optimization for large data set
- Interactive graph with the ability to zoom in and out
- Addition to more features to the pop-up window
- Addition of more links for resources related to the specific concept of the obs, to the pop-up window
In summary there will be a NEW way to view longitudinal data.
GWT-RPC Integration to the Module
http://archive.openmrs.org/wiki/Image:GWTDiagram.png
The Class Structures and Configurations
Service Interfacepackage org.openmrs.module.flowsheet.gwt.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("../../../moduleServlet/flowsheet/flowsheetService")
public interface FlowsheetService extends RemoteService{ //method definitions //String[] getObsData(String patientId); // ....
}
The synchronous Service interface lies in the client side of the GWT code. In this module, it resides under the package src/org.openmrs.module.flowsheet.gwt.client. Any Service interface should extends the com.google.gwt.user.client.rpc.RemoteService interface. All the RPC method calls should be defined in the service interface. The @RemoteServiceRelativePath annotation specifies the relative path of the Service. In this case, the compiled GWT code resides under </moduleResources/flowsheet/<generated-folder>. So if the relative path is given as /moduleServlet/flowsheet/flowsheetService, it will end up with http://localhost:8080/openmrs/moduleResources/flowsheet/generated-folder/moduleServlet/flowsheet/flowsheetService. To avoid that, the location ../../../moduleServlet/flowsheet/flowsheetService is used. This will end up in the correct location of the service : http://localhost:8080/openmrs/moduleServlet/flowsheet/flowsheetService
AsyncService Interfacepackage org.openmrs.module.flowsheet.gwt.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface FlowsheetServiceAsync {
//method definitions
void getObsData(String patientId, AsyncCallback<String[]> callback);
// ....
}
To make a remote call from the call, a asynchronous service interface should be defined in the client side. The interface should use the suffix Async with the name of the Service interface. The asynchronous method requires the caller to pass a callback object to get notified when the asynchronous call completes. Therefore the asynchronous methods do not have return types; void is returned by default. The method should have an additional parameter of the type AsyncCallback<return-type-of-the-service-method> .
Service Implementation Classpackage org.openmrs.module.flowsheet.gwt.server;
import org.openmrs.module.flowsheet.gwt.client.FlowsheetService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class FlowsheetServiceImpl extends RemoteServiceServlet implements FlowsheetService {
//method implementations
public String[] getObsData(String patientId)
// ....
}
Server side processing occurs in the service implementation class. This calls should has a suffix Impl to the service interface name. In addition it should extend com.google.gwt.user.server.rpc.RemoteServiceServlet class and implement the service interface. This implementation class should implement all the methods defined in the service interface. This class resides in the server-side of the GWT code. In the case of this module, it resides under web/src folder inside the package org.openmrs.module.flowsheet.gwt.server. This is the actual servlet which is accessed via the url http://localhost:8080/openmrs/moduleServlet/flowsheet/flowsheetService when the remote call is invoked in from the client. This servlet should be defined in the config.xml of the OpenMRS module as follows. <servlet>
<servlet-name>flowsheetService</servlet-name>
<servlet-class>
org.openmrs.module.flowsheet.gwt.server.FlowsheetServiceImpl
</servlet-class>
</servlet>
GWT Module Configuration File
In addition to these three main classes, the GWT module configuration file resides inside the folder src/org.openmrs.module.flowsheet.gwt.It is named as <Module-name>.gwt.xml and will have the following format.<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.2//EN"
"http://google-web-toolkit.googlecode.com/svn/tags/1.6.2/distro-source/core/src/gwt-module.dtd">
<module rename-to='mywebapp'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<!-- Inherit the default GWT style sheet. -->
<inherits name='com.google.gwt.user.theme.standard.Standard'/>
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->
<!-- Other module inherits -->
<!-- Specify the app entry point class. -->
<entry-point class='org.openmrs.module.flowsheet.gwt.client.Flowsheet'/>
</module>
GWT App Entry Point Class
When the GWT module is invoked, the processing starts at a enrty point class. This class is defined in the GWT module xml file inside the <entry-point> tag.The entry point class resides under src/org.openmrs.module.flowsheet.gwt.client. This entry point class should implement com.google.gwt.core.client.EntryPoint interface and should implement the onModuleLoad() method which is called when a GWT module loads. All the GWT UI code should resides inside this class. The code below shows the skeleton of the Entry Point class.package org.openmrs.module.flowsheet.gwt.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.*
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
public class Flowsheet implements EntryPoint {
private VerticalPanel mainPanel = new VerticalPa>nel();
private Label resultLabel = new Label();
private FlexTable table = new FlexTable();
/* more code here */
public void onModuleLoad() {
mainPanel.add(resultLabel);
table.setBorderWidth(2);
mainPanel.add(table);
RootPanel.get("webapp").add(mainPanel);
/* more code here */
String patientId = com.google.gwt.user.client.Window.Location.
getParameter("patientId");
/* The asynchronous call */
FlowsheetServiceAsync serviceAsync = GWT.create(FlowsheetService.class);
AsyncCallback<String[]> callback = new AsyncCallback<String[]>() {
public void onFailure(Throwable caught)
public void onSuccess(String[] result)
};
serviceAsync.getObsData(patientId, callback);
}
private void populateData(String[] data)
}
Addition to the build.xml to compile the GWT module
The following target additions should be made to the build.xml of the OpenMRS module to make it possible to compile the GWT module. <target name="gwtc" depends="javac" description="GWT compile to JavaScript">
<java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
<classpath>
<pathelement location="src"/>
<path refid="project.class.path"/>
</classpath>
<!-- add jvmarg -Xss16M or similar if you see a StackOverflowError -->
<jvmarg value="-Xmx256M"/>
<!-- Additional arguments like -style PRETTY or -logLevel DEBUG -->
<arg line="$
"/>
<arg value="org.openmrs.module.flowsheet.gwt.Flowsheet"/>
</java>
</target>
<target name="javac" depends="libs" description="Compile java source">
<mkdir dir="web/module/resources/war/WEB-INF/classes"/>
<javac srcdir="src" includes="**" encoding="utf-8"
destdir="web/module/resources/war/WEB-INF/classes"
source="1.5" target="1.5" nowarn="true"
debug="true" debuglevel="lines,vars,source">
<classpath refid="project.class.path"/>
</javac>
<copy todir="web/module/resources/war/WEB-INF/classes">
<fileset dir="src" excludes="*/.java"/>
</copy>
</target>
<target name="libs" description="Copy libs to WEB-INF/lib">
<mkdir dir="web/module/resources/war/WEB-INF/lib" />
<copy todir="web/module/resources/war/WEB-INF/lib" file="$
/gwt-servlet.jar" />
<copy todir="web/module/resources/war/WEB-INF/lib" file="lib-common/openmrs-api-1.7.0.13514-dev.jar" />
<copy todir="web/module/resources/war/WEB-INF/lib" file="lib-common/web-openmrs-api-1.7.0.13476-dev.jar" />
<!-- Add any additional server libs that need to be copied -->
</target>
When compiled, the GWT framework will generate the JavaScript file and resources under a folder generated. This generated folder is placed under web/modules/resources and the JavaScript file is accessed via <script type="text/javascript" language="javascript" src="/openmrs/moduleResources/flowsheet/mywebapp/mywebapp.nocache.js"> </script> in the JSP page of the module.
Representation of the Transfer Objects in the Client-Side
GWT allows the contents of a data object to be moved out of from one application and transmitted to another application via serialization. The data transfer objects should be serialized by either implementing java.io.Serializable interface or the IsSerializable interface provided by GWT.
The objects which are to be used in the GWT UI code, should have a representation in the client side in order to translated in to JavaScript. Since the main objects to be transfered are Obs, Encounters and Concept, they are represented in the client side as shown in the class diagram below.
References
Student Proposal
Blog Entries
http://umashanthi.blogspot.com/search/label/OpenMRS
Documentation
[docs:Flowsheet Module]