From f05108d823b7e8d8040ea2d13060d796b4f5f8b4 Mon Sep 17 00:00:00 2001 From: Ken Dunlap Date: Mon, 1 Dec 2014 17:23:54 -0500 Subject: [PATCH 01/21] Research Feature complete --- app/femr/business/helpers/DomainMapper.java | 35 + .../business/services/IResearchService.java | 18 +- .../business/services/ResearchService.java | 1031 ++++++++++++++++- app/femr/business/services/TriageService.java | 1 + .../common/models/ResearchFilterItem.java | 96 ++ .../common/models/ResearchGraphDataItem.java | 87 ++ app/femr/common/models/ResearchItem.java | 78 ++ app/femr/common/models/ResearchResult.java | 52 + .../data/models/IPatientPrescription.java | 1 - app/femr/data/models/Medication.java | 5 + .../ui/controllers/ResearchController.java | 57 +- .../ui/models/research/FilterViewModel.java | 136 +++ .../ui/views/admin/inventory/index.scala.html | 2 +- .../ui/views/research/generatedata.scala.html | 20 + app/femr/ui/views/research/index.scala.html | 213 +++- app/femr/util/calculations/dateUtils.java | 10 + app/femr/util/stringhelpers/StringUtils.java | 11 + conf/routes | 4 + conf/test.conf | 7 + project/Build.scala | 4 +- public/css/research.css | 640 ++++++++++ public/img/graph-loader.gif | Bin 0 -> 3208 bytes public/js/research/bar-graph.js | 114 ++ public/js/research/d3.min.js | 5 + public/js/research/d3.tip.js | 305 +++++ public/js/research/filter-menu.js | 665 +++++++++++ public/js/research/generateData.js | 127 ++ public/js/research/grouped-bar.js | 167 +++ public/js/research/line-graph.js | 156 +++ public/js/research/pie-graph.js | 273 +++++ public/js/research/research.js | 471 +++++++- public/js/research/saveSvgAsPng.js | 121 ++ public/js/research/scatter-plot.js | 106 ++ public/js/research/stacked-bar.js | 160 +++ public/js/research/table-chart.js | 56 + .../services/MockResearchService.java | 35 + .../business/services/MockTriageService.java | 2 + .../business/services/MockUserService.java | 132 ++- test/mock/femr/data/daos/MockRepository.java | 12 + test/mock/femr/data/models/MockHpiField.java | 37 - test/mock/femr/data/models/MockPatient.java | 26 +- .../data/models/MockPatientEncounter.java | 120 +- .../models/MockPatientEncounterHpiField.java | 73 -- .../models/MockPatientEncounterPmhField.java | 73 -- .../MockPatientEncounterTreatmentField.java | 74 -- .../data/models/MockPatientPrescription.java | 77 +- test/mock/femr/data/models/MockPmhField.java | 37 - .../femr/data/models/MockTreatmentField.java | 40 - test/mock/femr/data/models/MockUser.java | 44 +- .../services/ResearchServiceTest.java | 114 ++ .../controllers/ResearchControllerTest.java | 69 ++ .../femr/ui/views/home/ResearchIndexTest.java | 299 +++++ 52 files changed, 6052 insertions(+), 446 deletions(-) create mode 100644 app/femr/common/models/ResearchFilterItem.java create mode 100644 app/femr/common/models/ResearchGraphDataItem.java create mode 100644 app/femr/common/models/ResearchItem.java create mode 100644 app/femr/common/models/ResearchResult.java create mode 100644 app/femr/ui/models/research/FilterViewModel.java create mode 100644 app/femr/ui/views/research/generatedata.scala.html create mode 100644 conf/test.conf create mode 100644 public/img/graph-loader.gif create mode 100644 public/js/research/bar-graph.js create mode 100644 public/js/research/d3.min.js create mode 100644 public/js/research/d3.tip.js create mode 100644 public/js/research/filter-menu.js create mode 100644 public/js/research/generateData.js create mode 100644 public/js/research/grouped-bar.js create mode 100644 public/js/research/line-graph.js create mode 100644 public/js/research/pie-graph.js create mode 100644 public/js/research/saveSvgAsPng.js create mode 100644 public/js/research/scatter-plot.js create mode 100644 public/js/research/stacked-bar.js create mode 100644 public/js/research/table-chart.js create mode 100644 test/mock/femr/business/services/MockResearchService.java delete mode 100644 test/mock/femr/data/models/MockHpiField.java delete mode 100644 test/mock/femr/data/models/MockPatientEncounterHpiField.java delete mode 100644 test/mock/femr/data/models/MockPatientEncounterPmhField.java delete mode 100644 test/mock/femr/data/models/MockPatientEncounterTreatmentField.java delete mode 100644 test/mock/femr/data/models/MockPmhField.java delete mode 100644 test/mock/femr/data/models/MockTreatmentField.java create mode 100644 test/unit/app/femr/business/services/ResearchServiceTest.java create mode 100644 test/unit/app/femr/ui/controllers/ResearchControllerTest.java create mode 100644 test/unit/app/femr/ui/views/home/ResearchIndexTest.java diff --git a/app/femr/business/helpers/DomainMapper.java b/app/femr/business/helpers/DomainMapper.java index cc0e61b19..cf428fc60 100644 --- a/app/femr/business/helpers/DomainMapper.java +++ b/app/femr/business/helpers/DomainMapper.java @@ -23,6 +23,7 @@ import javax.inject.Provider; import femr.common.models.*; import femr.data.models.*; +import femr.ui.models.research.FilterViewModel; import femr.util.calculations.dateUtils; import femr.util.stringhelpers.StringUtils; import org.joda.time.DateTime; @@ -641,5 +642,39 @@ public IPhoto createPhoto(String description, String filePath) { return photo; } + /** + * Create research filter + * + * @param filterViewModel + * @return ResearchFilterItem + */ + public static ResearchFilterItem createResearchFilterItem(FilterViewModel filterViewModel){ + + ResearchFilterItem filterItem = new ResearchFilterItem(); + + filterItem.setPrimaryDataset(filterViewModel.getPrimaryDataset()); + filterItem.setSecondaryDataset(filterViewModel.getSecondaryDataset()); + filterItem.setGraphType(filterViewModel.getGraphType()); + filterItem.setStartDate(filterViewModel.getStartDate()); + filterItem.setEndDate(filterViewModel.getEndDate()); + + Integer groupFactor = filterViewModel.getGroupFactor(); + filterItem.setGroupFactor(groupFactor); + if( groupFactor != null && groupFactor > 0 ) { + + filterItem.setGroupPrimary(filterViewModel.isGroupPrimary()); + } + else{ + + filterItem.setGroupPrimary(false); + } + + filterItem.setRangeStart(filterViewModel.getRangeStart()); + filterItem.setRangeEnd(filterViewModel.getRangeEnd()); + + filterItem.setMedicationId(filterViewModel.getMedicationId()); + + return filterItem; + } } diff --git a/app/femr/business/services/IResearchService.java b/app/femr/business/services/IResearchService.java index 9c96346bd..8d62a7761 100644 --- a/app/femr/business/services/IResearchService.java +++ b/app/femr/business/services/IResearchService.java @@ -19,14 +19,22 @@ package femr.business.services; -/** - * Interface for the Research Service - */ -public interface IResearchService { - +import com.google.gson.JsonObject; +import femr.common.dto.ServiceResponse; +import femr.common.models.*; +import femr.ui.models.research.FilterViewModel; +import java.util.Date; +import java.util.List; +import java.util.Map; +/** + * Interface for the Research Service + */ +public interface IResearchService { + public ServiceResponse> getAllMedications(); + public ServiceResponse getGraphData(ResearchFilterItem filterItem); } diff --git a/app/femr/business/services/ResearchService.java b/app/femr/business/services/ResearchService.java index e4bf4ad64..448ccfbd7 100644 --- a/app/femr/business/services/ResearchService.java +++ b/app/femr/business/services/ResearchService.java @@ -18,21 +18,1044 @@ */ package femr.business.services; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.Query; +import com.avaje.ebean.SqlQuery; import com.google.inject.Inject; +import com.google.inject.Provider; +import femr.business.helpers.DomainMapper; +import femr.business.helpers.QueryProvider; +import femr.common.dto.ServiceResponse; +import femr.common.models.*; +import femr.data.daos.IRepository; +import femr.data.models.*; +import femr.util.calculations.dateUtils; +import femr.util.stringhelpers.StringUtils; +import org.apache.commons.lang3.text.WordUtils; -public class ResearchService implements IResearchService{ +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class ResearchService implements IResearchService { + + + + //repositories + private final IRepository chiefComplaintRepository; + private final IRepository patientRepository; + private final IRepository patientEncounterRepository; + private final IRepository patientEncounterVitalRepository; + private final IRepository userRepository; + private final IRepository vitalRepository; + private final IRepository prescriptionRepository; + private final IRepository medicationRepository; + private final Provider patientEncounterVitalProvider; + private final DomainMapper domainMapper; /** * Initializes the research service and injects the dependence */ @Inject - public ResearchService() { - //TODO: Inject research related repositories + public ResearchService(IRepository chiefComplaintRepository, + IRepository patientRepository, + IRepository patientEncounterRepository, + IRepository patientEncounterVitaRepository, + IRepository userRepository, + IRepository vitalRepository, + IRepository prescriptionRepository, + IRepository medicationRepository, + + Provider patientEncounterVitalProvider, + DomainMapper domainMapper) { + + this.chiefComplaintRepository = chiefComplaintRepository; + this.patientRepository = patientRepository; + this.patientEncounterRepository = patientEncounterRepository; + this.patientEncounterVitalRepository = patientEncounterVitaRepository; + this.userRepository = userRepository; + this.vitalRepository = vitalRepository; + this.prescriptionRepository = prescriptionRepository; + this.medicationRepository = medicationRepository; + this.patientEncounterVitalProvider = patientEncounterVitalProvider; + this.domainMapper = domainMapper; } - //TODO: Implement research business logic + @Override + public ServiceResponse getGraphData(ResearchFilterItem filters){ + + ServiceResponse response = new ServiceResponse<>(); + + String primaryDatasetName = filters.getPrimaryDataset(); + ResearchResult primaryItems = getDatasetItems(primaryDatasetName, filters); + + ResearchResult secondaryItems = new ResearchResult(); + String secondaryDatasetName = filters.getSecondaryDataset(); + if (!secondaryDatasetName.isEmpty()) { + + secondaryItems = getDatasetItems(secondaryDatasetName, filters); + } + + try{ + ResearchGraphDataItem graphDataItem = createResearchGraphItem(primaryItems, secondaryItems, filters); + response.setResponseObject(graphDataItem); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } + + @Override + public ServiceResponse> getAllMedications(){ + + ServiceResponse> response = new ServiceResponse<>(); + + try { + List medications = medicationRepository.findAll(Medication.class); + + Map medicationItems = new HashMap<>(); + + for (IMedication medication : medications) { + + medicationItems.put( + medication.getId(), + medication.getName() + ); + } + response.setResponseObject(medicationItems); + + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } + + public static ResearchGraphDataItem createResearchGraphItem(ResearchResult primaryResult, ResearchResult secondaryResult, ResearchFilterItem filters) { + + ResearchGraphDataItem graphModel = new ResearchGraphDataItem(); + List graphData = new ArrayList<>(); + Map groupedData = new HashMap<>(); + + String yAxisTitle = "Number of Patients"; + String xAxisTitle = WordUtils.capitalize(StringUtils.splitCamelCase(primaryResult.getDataType())); + String unitOfMeasurement = primaryResult.getUnitOfMeasurement(); + + List sortedPrimary = new ArrayList<>(); + Map primaryDataset = primaryResult.getDataset(); + Map secondaryDataset = secondaryResult.getDataset(); + int sampleSize = primaryDataset.size(); + float total = 0; + float rangeHigh = 0; + float rangeLow = 10000; + float median = 0; + + // Medications will not group correctly, just bundle and return + if( filters.getPrimaryDataset().equals("prescribedMeds") || + filters.getPrimaryDataset().equals("dispensedMeds") ){ + + for (Integer key : primaryDataset.keySet()) { + + // Make sure all secondary keys are set for all items + Float value = primaryDataset.get(key); + + ResearchItem currItem = new ResearchItem(); + currItem.setPrimaryName(Float.toString((float) key)); + currItem.setPrimaryValue(value); + + graphData.add(currItem); + } + + + graphModel.setAverage(0.0f); + graphModel.setMedian(median); + graphModel.setRangeLow(rangeLow); + graphModel.setRangeHigh(rangeHigh); + graphModel.setGraphData(graphData); + graphModel.setPrimaryValuemap(primaryResult.getValueMap()); + graphModel.setSecondaryValuemap(secondaryResult.getValueMap()); + graphModel.setyAxisTitle(yAxisTitle); + graphModel.setxAxisTitle(xAxisTitle); + graphModel.setUnitOfMeasurement(unitOfMeasurement); + return graphModel; + } + + + // total the individual patients based on their value + Map primaryGraphTotals = new HashMap<>(); + + // used to ensure all secondary keys are present in all items, just in case they have no data + Map secondaryKeyset = new HashMap<>(); + + for( Integer key : primaryDataset.keySet() ){ + + Float value = primaryDataset.get(key); + sortedPrimary.add(value); + + // Find current primary Value and add to builder map + ResearchItem currItem; + if( primaryGraphTotals.containsKey(value) ){ + + currItem = primaryGraphTotals.get(value); + float currItemTotal = currItem.getPrimaryValue() + 1; + currItem.setPrimaryValue(currItemTotal); + } + else{ + + currItem = new ResearchItem(); + currItem.setPrimaryValue(1); + } + currItem.setPrimaryName(value.toString()); + + // check for and add secondary item to total + if( secondaryDataset.containsKey(key) ) { + + // Get secondary item form current ResearchItem + Map secondaryItems = currItem.getSecondaryData(); + Float secondaryValue = secondaryDataset.get(key); + Float secondaryTotal = 1.0f; + String secondaryKey = secondaryValue.toString(); + + // Keep track of unique keys for secondary items + // want to make sure all possible keys are present in graphData + secondaryKeyset.put(secondaryKey, 0); + + if (secondaryItems.containsKey(secondaryKey)) { + + secondaryTotal = secondaryItems.get(secondaryKey); + secondaryTotal++; + secondaryItems.put(secondaryKey, secondaryTotal); + } + else{ + + secondaryItems.put(secondaryKey, 1.0f); + } + + // add secondary totals to currItem + currItem.setSecondaryData(secondaryItems); + } + + + primaryGraphTotals.put(value, currItem); + + // Calculate Stats while building data + // check range + if( value > rangeHigh ){ + rangeHigh = value; + } + if( value < rangeLow){ + rangeLow = value; + } + + // sum total for average + total += value; + + } + + // If no grouping and over 30 items, force groups of 10 + if( primaryGraphTotals.keySet().size() > 30 && !filters.isGroupPrimary() + && (filters.getGraphType().equals("pie") || filters.getGraphType().equals("stacked-bar") || filters.getGraphType().equals("grouped-bar") ) ){ + + filters.setGroupPrimary(true); + filters.setGroupFactor(10); + } + + // Build group key values -- don't bother if there will only be 1 group + // make happen automatically too? -- primaryGraphTotals.keySet().size() > 20 + if( filters.isGroupPrimary() && primaryGraphTotals.keySet().size() > filters.getGroupFactor() ) { + + int groupFactor = filters.getGroupFactor(); + int firstLowKey = (int)(rangeLow / groupFactor) * groupFactor; + int lastLowKey = (int)(rangeHigh / groupFactor) * groupFactor; + + //int totalGroups = (int)((lastLowKey-firstLowKey) / groupFactor); + List groupIndexes = new ArrayList(); + for( int low = firstLowKey; low <= lastLowKey; low+=groupFactor ){ + + int lowTemp = low; + if( low < filters.getRangeStart() ){ + + lowTemp = filters.getRangeStart().intValue(); + } + + int high = low + (groupFactor - 1); + if( high > filters.getRangeEnd() ){ + + high = filters.getRangeEnd().intValue(); + } + + String groupIndex = String.format("%d - %d", lowTemp, high); + groupIndexes.add(groupIndex); + + ResearchItem blankItem = new ResearchItem(groupIndex); + groupedData.put(groupIndex, blankItem); + } + + for (Float key : primaryGraphTotals.keySet()) { + + ResearchItem currItem = primaryGraphTotals.get(key); + + Float newVal = Float.parseFloat(currItem.getPrimaryName()); + Float newCount = currItem.getPrimaryValue(); + + int targetGroup = (int)(newVal / groupFactor) - (firstLowKey / groupFactor); + String groupIndex = groupIndexes.get(targetGroup); + + ResearchItem finalItem; + if( groupedData.containsKey(groupIndex) ){ + + // add current single item to existing group Item + finalItem = groupedData.get(groupIndex); + + Float currCount = finalItem.getPrimaryValue(); + currCount += newCount; + finalItem.setPrimaryValue(currCount); + + Map currSecondaryData = currItem.getSecondaryData(); + for( String sKey : currSecondaryData.keySet() ){ + + Float sCount = currSecondaryData.get(sKey); + + Map finalSecondaryData = finalItem.getSecondaryData(); + if( finalSecondaryData.containsKey(sKey) ){ + Float finalCount = finalSecondaryData.get(sKey); + finalCount += sCount; + finalSecondaryData.put(sKey, finalCount); + } + else{ + finalSecondaryData.put(sKey, sCount); + } + + finalItem.setSecondaryData(finalSecondaryData); + } + + } + else{ + + // haven't encountered item in range before, just set to current single item + finalItem = new ResearchItem(); + finalItem.setPrimaryName(groupIndex); + finalItem.setPrimaryValue(newCount); + finalItem.setSecondaryData(currItem.getSecondaryData()); + + } + // add item to grouped data + groupedData.put(groupIndex, finalItem); + } + + SortedSet keys = new TreeSet<>(new GroupedCompare()); + keys.addAll(groupedData.keySet()); + for (String key : keys) { + + // Make sure all secondary keys are set for all items + ResearchItem currItem = groupedData.get(key); + Map secondaryData = currItem.getSecondaryData(); + for (String secondaryKey : secondaryKeyset.keySet()) { + + if( !secondaryData.containsKey(secondaryKey) ){ + + secondaryData.put(secondaryKey, 0.0f); + } + } + + graphData.add(currItem); + } + + } + else{ + + SortedSet keys = new TreeSet<>(primaryGraphTotals.keySet()); + for (Float key : keys) { + + // Make sure all secondary keys are set for all items + ResearchItem currItem = primaryGraphTotals.get(key); + Map secondaryData = currItem.getSecondaryData(); + for (String secondaryKey : secondaryKeyset.keySet()) { + + if( !secondaryData.containsKey(secondaryKey) ){ + + secondaryData.put(secondaryKey, 0.0f); + } + } + + graphData.add(currItem); + } + } + + + + // Sort primary Data + Collections.sort(sortedPrimary); + + // Get Median Value from sorted list + float average = total / sampleSize; + + if( sampleSize > 1 ) { + if (sampleSize % 2 == 0) { + + int i = (sampleSize / 2) - 1; + int j = i + 1; + + // get vals i and j + float val1 = sortedPrimary.get(i); + float val2 = sortedPrimary.get(j); + + + //Integer key1 = primaryKeyList.get(i); + //Integer key2 = primaryKeyList.get(j); + + //float val1 = primaryDataset.get(key1); + //float val2 = primaryDataset.get(key2); + + median = (val1 + val2) / 2; + } else { + + int i = (int) Math.floor(sampleSize / 2); + + //Integer key = primaryKeyList.get(i); + //median = primaryDataset.get(key); + + median = sortedPrimary.get(i); + + } + } + else{ + + median = sortedPrimary.get(0); + } + + // build graph model item + + graphModel.setAverage(average); + graphModel.setMedian(median); + graphModel.setRangeLow(rangeLow); + graphModel.setRangeHigh(rangeHigh); + graphModel.setGraphData(graphData); + graphModel.setPrimaryValuemap(primaryResult.getValueMap()); + graphModel.setSecondaryValuemap(secondaryResult.getValueMap()); + graphModel.setyAxisTitle(yAxisTitle); + graphModel.setxAxisTitle(xAxisTitle); + graphModel.setUnitOfMeasurement(unitOfMeasurement); + + return graphModel; + + } + + + + private ResearchResult getDatasetItems(String datasetName, ResearchFilterItem filters){ + + switch(datasetName){ + + // Single Value Vital Items + case "weight": + case "temperature": + case "heartRate": + case "respiratoryRate": + case "oxygenSaturation": + case "glucose": + case "bloodPressureSystolic": + case "bloodPressureDiastolic": + return getPatientVitals(datasetName, filters); + + // Special Case Vital Item + case "height": + return getPatientHeights(filters); + + // Patient Specific Items + case "age": + case "gender": + case "pregnancyStatus": + case "pregnancyTime": + return getPatientAttribute(datasetName, filters); + + // Medication Items + case "prescribedMeds": + return getPrescribedMedications(filters); + + case "dispensedMeds": + return getDispensedMedications(filters); + + default: + + return new ResearchResult(); + } + + } + + + /** + * {@inheritDoc} + */ + public ResearchResult getPatientVitals(String vitalName, ResearchFilterItem filters) { + + String startDateString = filters.getStartDate(); + String endDateString = filters.getEndDate(); + Integer medicationID = filters.getMedicationId(); + + ResearchResult resultObj = new ResearchResult(); + Map resultItems = new HashMap<>(); + + Date startDateObj; + Date endDateObj; + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + + // Set Start Date to start of day + String startParseDate = startDateString + " 00:00:00"; + startDateObj = sqlFormat.parse(startParseDate); + + // Set End Date to end of day + String parseEndDate = endDateString + " 23:59:59"; + endDateObj = sqlFormat.parse(parseEndDate); + + } + catch(ParseException e){ + + startDateObj = new Date(); + endDateObj = new Date(); + } + + Query q = QueryProvider.getPatientEncounterVitalQuery(); + + if( medicationID > 0 ) { + + Query pQ = QueryProvider.getPatientPrescriptionQuery(); + + pQ.fetch("patientEncounter").where().eq("medication.id", medicationID); + List patientPrescriptions = prescriptionRepository.find(pQ); + + List scriptEncounterIds = new ArrayList<>(); + int i = 0; + for (IPatientPrescription script : patientPrescriptions) { + + scriptEncounterIds.add(script.getPatientEncounter().getId()); + } + + q.where().in("patientEncounterId", scriptEncounterIds); + } + + q.where() + .gt("dateTaken", sqlFormat.format(startDateObj)) + .lt("dateTaken", sqlFormat.format(endDateObj)) + .eq("vital.name", vitalName) + .orderBy("vital_value") + .findList(); + + List patientEncounterVitals = patientEncounterVitalRepository.find(q); + + + +/* + select t0.id c0, t0.user_id c1, t0.patient_encounter_id c2, t0.vital_value c3, t0.date_taken c4, + t1.id c5, t1.name c6, t1.data_type c7, t1.unit_of_measurement c8, t1.isDeleted c9 + from patient_encounter_vitals t0 + left outer join vitals as t1 on t1.id = t0.vital_id + left join patient_prescriptions as t2 on t2.encounter_id = t0.patient_encounter_id + where t0.date_taken > '2014-10-24 00:00:00' + and t0.date_taken < '2014-11-05 11:59:59' + and t1.name = 'temperature' + and t2.medication_id = 1 + order by vital_value + + select t0.id c0, t0.user_id c1, t0.patient_encounter_id c2, t0.vital_value c3, t0.date_taken c4 + from patient_encounter_vitals t0 + left outer join vitals t1 on t1.id = t0.vital_id + where t0.date_taken > ? + and t0.date_taken < ? + and t1.name = ? + order by vital_value +*/ + + + String unitOfMeasurement = ""; + for (IPatientEncounterVital eVital : patientEncounterVitals) { + + if( unitOfMeasurement.isEmpty() ) { + unitOfMeasurement = eVital.getVital().getUnitOfMeasurement(); + } + + Float vitalValue = eVital.getVitalValue(); + if( vitalValue >= filters.getRangeStart() && vitalValue <= filters.getRangeEnd() ) { + + resultItems.put( + eVital.getPatientEncounterId(), + vitalValue + ); + } + + } + + resultObj.setDataType(vitalName); + resultObj.setUnitOfMeasurement(unitOfMeasurement); + resultObj.setDataset(resultItems); + + return resultObj; + } + + /** + * {@inheritDoc} + */ + public ResearchResult getPatientAttribute(String attributeName, ResearchFilterItem filters) { + + String startDateString = filters.getStartDate(); + String endDateString = filters.getEndDate(); + Integer medicationID = filters.getMedicationId(); + + ResearchResult resultObj = new ResearchResult(); + Map resultItems = new HashMap<>(); + Map resultMap = new HashMap<>(); + + Date startDateObj; + Date endDateObj; + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + + // Set Start Date to start of day + String startParseDate = startDateString + " 00:00:00"; + startDateObj = sqlFormat.parse(startParseDate); + + // Set End Date to end of day + String parseEndDate = endDateString + " 23:59:59"; + endDateObj = sqlFormat.parse(parseEndDate); + + } + catch(ParseException e){ + + startDateObj = new Date(); + endDateObj = new Date(); + } + + // Get patients that had an encounter between startDate and endDate + // using dateOfTriageVisit for now -- might want to also check dateOfMedicalVisit & dateOfPharmacyVisit + Query q = QueryProvider.getPatientEncounterQuery(); + + if( medicationID > 0 ) { + + Query pQ = QueryProvider.getPatientPrescriptionQuery(); + + pQ.fetch("patientEncounter").where().eq("medication.id", medicationID); + List patientPrescriptions = prescriptionRepository.find(pQ); + + List scriptEncounterIds = new ArrayList<>(); + int i = 0; + for (IPatientPrescription script : patientPrescriptions) { + + scriptEncounterIds.add(script.getPatientEncounter().getId()); + } + + q.where().in("id", scriptEncounterIds); + } + + q.fetch("patient") + .where() + .gt("dateOfTriageVisit", sqlFormat.format(startDateObj)) + .lt("dateOfTriageVisit", sqlFormat.format(endDateObj)) + .orderBy("id") + .findList(); + + List encounters = patientEncounterRepository.find(q); + + String unitOfMeasurement = ""; + switch (attributeName) { + + case "age": + + unitOfMeasurement = "years"; + for (IPatientEncounter encounter : encounters) { + IPatient patient = encounter.getPatient(); + + Float age = (float) Math.floor(dateUtils.getAgeFloat(patient.getAge())); + + if( age >= filters.getRangeStart() && age <= filters.getRangeEnd() ) { + + resultItems.put( + encounter.getId(), + age + ); + } + } + break; + + case "gender": + + resultMap.put(0.0f, "Male"); + resultMap.put(1.0f, "Female"); + + for (IPatientEncounter encounter : encounters) { + IPatient patient = encounter.getPatient(); + + float gender = -1; + // Do case in-sensitve comparison to be safe + if (patient.getSex().matches("(?i:Male)")) { + gender = 0; + } else if (patient.getSex().matches("(?i:Female)")) { + gender = 1; + } + resultItems.put( + encounter.getId(), + gender + ); + } + break; + + case "pregnancyStatus": + + for (IPatientEncounter encounter : encounters) { + + resultMap.put(0.0f, "No"); + resultMap.put(1.0f, "Yes"); + + Integer wksPregnant = encounter.getWeeksPregnant(); + if (wksPregnant == null) wksPregnant = 0; + float pregnancyStatus = 0; + if (wksPregnant > 0) { + pregnancyStatus = 1; + } + resultItems.put( + encounter.getId(), + pregnancyStatus + ); + } + break; + + case "pregnancyTime": + + unitOfMeasurement = "weeks"; + for (IPatientEncounter encounter : encounters) { + + Integer weeksPregnant = encounter.getWeeksPregnant(); + if (weeksPregnant == null) weeksPregnant = 0; + + if( weeksPregnant >= filters.getRangeStart() && weeksPregnant <= filters.getRangeEnd() ) { + + resultItems.put( + encounter.getId(), + (float) weeksPregnant + ); + } + } + break; + } + + + resultObj.setDataType(attributeName); + resultObj.setUnitOfMeasurement(unitOfMeasurement); + resultObj.setDataset(resultItems); + resultObj.setValueMap(resultMap); + + return resultObj; + } + + + public ResearchResult getPatientHeights(ResearchFilterItem filters){ + + String startDateString = filters.getStartDate(); + String endDateString = filters.getEndDate(); + Integer medicationID = filters.getMedicationId(); + + ResearchResult resultObj = new ResearchResult(); + Map buildItems = new HashMap<>(); + + Date startDateObj; + Date endDateObj; + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + + // Set Start Date to start of day + String startParseDate = startDateString + " 00:00:00"; + startDateObj = sqlFormat.parse(startParseDate); + + // Set End Date to end of day + String parseEndDate = endDateString + " 23:59:59"; + endDateObj = sqlFormat.parse(parseEndDate); + + } + catch(ParseException e){ + + startDateObj = new Date(); + endDateObj = new Date(); + } + + + Query q = QueryProvider.getPatientEncounterVitalQuery(); + + List scriptEncounterIds = new ArrayList<>(); + if( medicationID > 0 ) { + + Query pQ = QueryProvider.getPatientPrescriptionQuery(); + + pQ.fetch("patientEncounter").where().eq("medication.id", medicationID); + List patientPrescriptions = prescriptionRepository.find(pQ); + + int i = 0; + for (IPatientPrescription script : patientPrescriptions) { + + scriptEncounterIds.add(script.getPatientEncounter().getId()); + } + + q.where().in("patientEncounterId", scriptEncounterIds); + } + + q.fetch("vital") + .where() + .gt("dateTaken", sqlFormat.format(startDateObj)) + .lt("dateTaken", sqlFormat.format(endDateObj)) + .eq("vital.name", "heightFeet") + .orderBy("patient_encounter_id") + .findList(); + List patientFeet = patientEncounterVitalRepository.find(q); + + q = QueryProvider.getPatientEncounterVitalQuery(); + + if( medicationID > 0 && scriptEncounterIds.size() > 0 ) { + + q.where().in("patientEncounterId", scriptEncounterIds); + } + q.fetch("vital") + .where() + .gt("dateTaken", sqlFormat.format(startDateObj)) + .lt("dateTaken", sqlFormat.format(endDateObj)) + .eq("vital.name", "heightInches") + .orderBy("patient_encounter_id") + .findList(); + List patientInches = patientEncounterVitalRepository.find(q); + + //Map researchItems = new HashMap<>(); + // Convert feet to inches + String unitOfMeasurement = "feet/inches"; + for (IPatientEncounterVital eVital : patientFeet) { + + float heightInches = 12 * eVital.getVitalValue(); + buildItems.put( + eVital.getPatientEncounterId(), + heightInches + ); + + } + + for(IPatientEncounterVital eVital : patientInches){ + + if( buildItems.containsKey(eVital.getPatientEncounterId()) ){ + + float heightInches = buildItems.get(eVital.getPatientEncounterId()); + heightInches = heightInches + eVital.getVitalValue(); + buildItems.put(eVital.getPatientEncounterId(), heightInches); + } + } + + Map resultItems = new HashMap<>(); + for( Integer key : buildItems.keySet() ){ + + Float value = buildItems.get(key); + if( value >= filters.getRangeStart() && value <= filters.getRangeEnd() ){ + + resultItems.put(key, value); + } + + } + + resultObj.setDataType("height"); + resultObj.setUnitOfMeasurement(unitOfMeasurement); + resultObj.setDataset(resultItems); + + return resultObj; + + } + + + + + public ResearchResult getPrescribedMedications(ResearchFilterItem filters){ + + String startDateString = filters.getStartDate(); + String endDateString = filters.getEndDate(); + + ResearchResult resultObj = new ResearchResult(); + Map resultItems = new HashMap<>(); + Map resultMap = new HashMap<>(); + + Date startDateObj; + Date endDateObj; + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + + // Set Start Date to start of day + String startParseDate = startDateString + " 00:00:00"; + startDateObj = sqlFormat.parse(startParseDate); + + // Set End Date to end of day + String parseEndDate = endDateString + " 23:59:59"; + endDateObj = sqlFormat.parse(parseEndDate); + + } + catch(ParseException e){ + + startDateObj = new Date(); + endDateObj = new Date(); + } + + + Query q = QueryProvider.getPatientPrescriptionQuery(); + q.fetch("medication") + .where() + .gt("dateTaken", sqlFormat.format(startDateObj)) + .lt("dateTaken", sqlFormat.format(endDateObj)) + .findList(); + + List patientMedication = prescriptionRepository.find(q); + + String unitOfMeasurement = ""; + for (IPatientPrescription prescription : patientMedication) { + + Integer key = prescription.getMedication().getId(); + + // Build prescription name map + if( !resultMap.containsKey((float)key) ){ + + resultMap.put((float)key, prescription.getMedication().getName()); + } + + Float count = 1.0f; + if( resultItems.containsKey(key) ){ + + count = resultItems.get(key) + 1.0f; + } + resultItems.put( + key, + count + ); + + } + + resultObj.setDataType("prescribedMeds"); + resultObj.setUnitOfMeasurement(unitOfMeasurement); + resultObj.setDataset(resultItems); + resultObj.setValueMap(resultMap); + + return resultObj; + } + + + // @TODO - need to make this work correctly + // want Medication Name (xAxis) vs Total Dispensed (yAxis) + // Need to tweak some things, this is different than # of patients + public ResearchResult getDispensedMedications(ResearchFilterItem filters){ + + String startDateString = filters.getStartDate(); + String endDateString = filters.getEndDate(); + + ResearchResult resultObj = new ResearchResult(); + Map resultItems = new HashMap<>(); + Map resultMap = new HashMap<>(); + + Date startDateObj; + Date endDateObj; + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + + // Set Start Date to start of day + String startParseDate = startDateString + " 00:00:00"; + startDateObj = sqlFormat.parse(startParseDate); + + // Set End Date to end of day + String parseEndDate = endDateString + " 23:59:59"; + endDateObj = sqlFormat.parse(parseEndDate); + + } + catch(ParseException e){ + + startDateObj = new Date(); + endDateObj = new Date(); + } + + Query q = QueryProvider.getPatientPrescriptionQuery(); + q.fetch("medication") + .where() + .gt("dateTaken", sqlFormat.format(startDateObj)) + .lt("dateTaken", sqlFormat.format(endDateObj)) + .findList(); + + List patientMedication = prescriptionRepository.find(q); + + String unitOfMeasurement = ""; + for (IPatientPrescription prescription : patientMedication) { + + Integer key = prescription.getMedication().getId(); + + // Build prescription name map + if( !resultMap.containsKey((float)key) ){ + + resultMap.put((float)key, prescription.getMedication().getName()); + } + + Float count = (float)prescription.getAmount(); + if( resultItems.containsKey(key) ){ + + count = resultItems.get(key) + count; + } + resultItems.put( + key, + count + ); + + } + + resultObj.setDataType("dispensedMeds"); + resultObj.setUnitOfMeasurement(unitOfMeasurement); + resultObj.setDataset(resultItems); + resultObj.setValueMap(resultMap); + + return resultObj; + + } + +} + + + +// Sorts Strings in format "float - float" by using the first float of the string +class GroupedCompare implements Comparator{ + + @Override + public int compare(String s1, String s2){ + + String[] s1Matches = s1.split("-"); + String s1First; + Float s1Val = 0.0f; + if( s1Matches.length > 1 ) { + s1First = s1Matches[0]; + } + else{ + s1First = s1; + } + s1Val = Float.parseFloat(s1First); + + String[] s2Matches = s2.split("-"); + String s2First; + Float s2Val = 0.0f; + if( s2Matches.length > 1 ) { + s2First = s2Matches[0]; + } + else{ + s2First = s2; + } + s2Val = Float.parseFloat(s2First); + + if( s1Val < s2Val ){ + + return -1; + } + else if( s1Val > s2Val ){ + + return 1; + } + return 0; + } } diff --git a/app/femr/business/services/TriageService.java b/app/femr/business/services/TriageService.java index 64f8ba4b9..7124faecd 100644 --- a/app/femr/business/services/TriageService.java +++ b/app/femr/business/services/TriageService.java @@ -187,6 +187,7 @@ public ServiceResponse createPatientEncounter(PatientEncou IUser nurseUser = userRepository.findOne(nurseQuery); + //find the age classification of the patient, if it exists ExpressionList patientAgeClassificationExpressionList = QueryProvider.getPatientAgeClassificationQuery() .where() diff --git a/app/femr/common/models/ResearchFilterItem.java b/app/femr/common/models/ResearchFilterItem.java new file mode 100644 index 000000000..1eeb9c8b2 --- /dev/null +++ b/app/femr/common/models/ResearchFilterItem.java @@ -0,0 +1,96 @@ +package femr.common.models; + + +public class ResearchFilterItem { + + private String primaryDataset; + private String secondaryDataset; + private String graphType; + private String startDate; + private String endDate; + private boolean groupPrimary; + private Integer groupFactor; + private Float rangeStart; + private Float rangeEnd; + private Integer medicationId; + + public String getPrimaryDataset() { + return primaryDataset; + } + + public void setPrimaryDataset(String primaryDataset) { + this.primaryDataset = primaryDataset; + } + + public String getSecondaryDataset() { + return secondaryDataset; + } + + public void setSecondaryDataset(String secondaryDataset) { + this.secondaryDataset = secondaryDataset; + } + + public String getGraphType() { + return graphType; + } + + public void setGraphType(String graphType) { + this.graphType = graphType; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public boolean isGroupPrimary() { return groupPrimary; } + + public void setGroupPrimary(boolean groupPrimary) { this.groupPrimary = groupPrimary; } + + public Integer getGroupFactor() { return groupFactor; } + + public void setGroupFactor(Integer groupFactor) { this.groupFactor = groupFactor; } + + public Float getRangeStart() { return rangeStart; } + + public void setRangeStart(Float rangeStart) { + + if (rangeStart == null) { + + this.rangeStart = Float.MIN_VALUE; + + } else { + + this.rangeStart = rangeStart; + } + } + + public Float getRangeEnd() { return rangeEnd; } + + public void setRangeEnd(Float rangeEnd) { + + if (rangeEnd == null) { + + this.rangeEnd = Float.MAX_VALUE; + + } else { + + this.rangeEnd = rangeEnd; + } + } + + public Integer getMedicationId() { return medicationId; } + + public void setMedicationId(Integer medicationId) { this.medicationId = medicationId; } +} diff --git a/app/femr/common/models/ResearchGraphDataItem.java b/app/femr/common/models/ResearchGraphDataItem.java new file mode 100644 index 000000000..a7f5e903e --- /dev/null +++ b/app/femr/common/models/ResearchGraphDataItem.java @@ -0,0 +1,87 @@ +package femr.common.models; + +import java.util.List; +import java.util.Map; + + +public class ResearchGraphDataItem { + + private float average; + private float median; + private float rangeLow; + private float rangeHigh; + private List graphData; + private String xAxisTitle; + private String yAxisTitle; + private String unitOfMeasurement; + private Map primaryValuemap; + private Map secondaryValuemap; + + public float getAverage() { + return average; + } + + public void setAverage(float average) { + this.average = average; + } + + public float getMedian() { + return median; + } + + public void setMedian(float median) { + this.median = median; + } + + public float getRangeLow() { + return rangeLow; + } + + public void setRangeLow(float rangeLow) { + this.rangeLow = rangeLow; + } + + public float getRangeHigh() { + return rangeHigh; + } + + public void setRangeHigh(float rangeHigh) { + this.rangeHigh = rangeHigh; + } + + public String getxAxisTitle() { + return xAxisTitle; + } + + public void setxAxisTitle(String xAxisTitle) { + this.xAxisTitle = xAxisTitle; + } + + public String getyAxisTitle() { return yAxisTitle; } + + public void setyAxisTitle(String yAxisTitle) { this.yAxisTitle = yAxisTitle; } + + public String getUnitOfMeasurement() { + return unitOfMeasurement; + } + + public void setUnitOfMeasurement(String unitOfMeasurement) { + this.unitOfMeasurement = unitOfMeasurement; + } + + public List getGraphData() { + return graphData; + } + + public void setGraphData(List graphData) { + this.graphData = graphData; + } + + public Map getPrimaryValuemap() { return primaryValuemap; } + + public void setPrimaryValuemap(Map primaryValuemap) { this.primaryValuemap = primaryValuemap; } + + public Map getSecondaryValuemap() { return secondaryValuemap; } + + public void setSecondaryValuemap(Map secondaryValuemap) { this.secondaryValuemap = secondaryValuemap; } +} diff --git a/app/femr/common/models/ResearchItem.java b/app/femr/common/models/ResearchItem.java new file mode 100644 index 000000000..4874504ea --- /dev/null +++ b/app/femr/common/models/ResearchItem.java @@ -0,0 +1,78 @@ +package femr.common.models; + +import java.util.HashMap; +import java.util.Map; + +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +public class ResearchItem { + + + private String primaryName; + private float primaryValue; + private Map secondaryData; + + public ResearchItem(){ + + primaryName = ""; + primaryValue = 0.0f; + secondaryData = new HashMap<>(); + } + + public ResearchItem(String name){ + + primaryName = name; + primaryValue = 0.0f; + secondaryData = new HashMap<>(); + } + + public String getPrimaryName() { + return primaryName; + } + + + public void setPrimaryName(String primaryName) { + this.primaryName = primaryName; + } + + public float getPrimaryValue() { + return primaryValue; + } + + public void setPrimaryValue(float primaryValue) { + this.primaryValue = primaryValue; + } + + + public Map getSecondaryData() { + + if( secondaryData != null ) { + return secondaryData; + } + else{ + return new HashMap(); + } + } + + public void setSecondaryData(Map secondaryData) { + this.secondaryData = secondaryData; + } + +} + diff --git a/app/femr/common/models/ResearchResult.java b/app/femr/common/models/ResearchResult.java new file mode 100644 index 000000000..ccb076c60 --- /dev/null +++ b/app/femr/common/models/ResearchResult.java @@ -0,0 +1,52 @@ +package femr.common.models; + + +import java.util.HashMap; +import java.util.Map; + +public class ResearchResult { + + private String dataType; + private String unitOfMeasurement; + private Map dataset; + + // Used for non-number items - map float to String + private Map valueMap; + + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public String getUnitOfMeasurement() { + return unitOfMeasurement; + } + + public void setUnitOfMeasurement(String unitOfMeasurement) { + this.unitOfMeasurement = unitOfMeasurement; + } + + public Map getDataset() { + + if( dataset == null ){ + + return new HashMap(); + } + return dataset; + } + + public void setDataset(Map dataset) { + this.dataset = dataset; + } + + public Map getValueMap() { + return valueMap; + } + + public void setValueMap(Map valueMap) { + this.valueMap = valueMap; + } +} diff --git a/app/femr/data/models/IPatientPrescription.java b/app/femr/data/models/IPatientPrescription.java index 3ca712f4b..6e61149d8 100644 --- a/app/femr/data/models/IPatientPrescription.java +++ b/app/femr/data/models/IPatientPrescription.java @@ -24,7 +24,6 @@ public interface IPatientPrescription { int getId(); - IMedication getMedication(); void setMedication(IMedication medication); diff --git a/app/femr/data/models/Medication.java b/app/femr/data/models/Medication.java index 9de8cb8c8..4cd6a38f5 100644 --- a/app/femr/data/models/Medication.java +++ b/app/femr/data/models/Medication.java @@ -46,6 +46,11 @@ public class Medication implements IMedication { inverseJoinColumns = {@JoinColumn(name = "medication_active_drugs_id", referencedColumnName = "id")}) private List medicationActiveDrugs; + /*@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinColumn(name = "patient_encounters") + private PatientEncounter patientEncounter; +*/ + @Override public int getId() { return id; diff --git a/app/femr/ui/controllers/ResearchController.java b/app/femr/ui/controllers/ResearchController.java index 2296d7a8d..7ca1b4e42 100644 --- a/app/femr/ui/controllers/ResearchController.java +++ b/app/femr/ui/controllers/ResearchController.java @@ -1,20 +1,31 @@ package femr.ui.controllers; - +import com.google.gson.Gson; import com.google.inject.Inject; +import femr.business.helpers.DomainMapper; import femr.common.dto.CurrentUser; import femr.business.services.IResearchService; import femr.business.services.ISessionService; +import femr.common.dto.ServiceResponse; +import femr.common.models.ResearchFilterItem; +import femr.common.models.ResearchGraphDataItem; import femr.data.models.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.views.html.research.index; +import femr.ui.views.html.research.generatedata; +import femr.ui.models.research.FilterViewModel; +import play.data.Form; import play.mvc.Controller; import play.mvc.Result; import play.mvc.Security; +import java.text.SimpleDateFormat; +import java.util.*; + + /** * This is the controller for the research page, it is currently not supported. * Research was designed before combining of some tables in the database @@ -22,6 +33,9 @@ @Security.Authenticated(FEMRAuthenticated.class) @AllowedRoles({Roles.RESEARCHER}) public class ResearchController extends Controller { + + private final Form FilterViewModelForm = Form.form(FilterViewModel.class); + private IResearchService researchService; private ISessionService sessionService; @@ -38,9 +52,48 @@ public ResearchController(ISessionService sessionService, IResearchService resea } public Result indexGet() { + + // There isn't really a request here, should this be different? + FilterViewModel filterViewModel = FilterViewModelForm.bindFromRequest().get(); + + // Set Default Start (30 Days Ago) and End Date (Today) + Calendar today = Calendar.getInstance(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + filterViewModel.setEndDate(dateFormat.format(today.getTime())); + today.add(Calendar.DAY_OF_MONTH, -30); + filterViewModel.setStartDate(dateFormat.format(today.getTime())); + + ServiceResponse> medResponse = researchService.getAllMedications(); + if( !medResponse.hasErrors() ) { + filterViewModel.setMedicationsList(medResponse.getResponseObject()); + } + CurrentUser currentUserSession = sessionService.getCurrentUserSession(); + return ok(index.render(currentUserSession, filterViewModel)); + } + + public Result generateData() { + FilterViewModel viewModel = FilterViewModelForm.bindFromRequest().get(); + CurrentUser currentUserSession = sessionService.getCurrentUserSession(); + return ok(generatedata.render(currentUserSession, viewModel)); + } + + public Result getGraphPost(){ + FilterViewModel filterViewModel = FilterViewModelForm.bindFromRequest().get(); - return ok(index.render(currentUserSession)); + ResearchFilterItem researchFilterItem = DomainMapper.createResearchFilterItem(filterViewModel); + + ServiceResponse response = researchService.getGraphData(researchFilterItem); + ResearchGraphDataItem graphModel = new ResearchGraphDataItem(); + if( !response.hasErrors() ) { + + graphModel = response.getResponseObject(); + } + + Gson gson = new Gson(); + String jsonString = gson.toJson(graphModel); + return ok(jsonString); } + } diff --git a/app/femr/ui/models/research/FilterViewModel.java b/app/femr/ui/models/research/FilterViewModel.java new file mode 100644 index 000000000..310268ceb --- /dev/null +++ b/app/femr/ui/models/research/FilterViewModel.java @@ -0,0 +1,136 @@ +package femr.ui.models.research; + +import play.data.validation.ValidationError; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class FilterViewModel { + + private String primaryDataset; + private String secondaryDataset; + private String graphType; + private String startDate; + private String endDate; + private boolean groupPrimary; + private Integer groupFactor; + private Float rangeStart; + private Float rangeEnd; + private Integer medicationId; + private Map medicationsList; + + /* + public List validate() { + + List errors = new ArrayList(); + + if (primaryDataset == null || primaryDataset.length() == 0 ) { + errors.add(new ValidationError("primaryDataset", "Choose a primary dataset")); + } + + Date startDateObj; + Date endDateObj; + Date today = new Date(); + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + + // Set Start Date to start of day + String startParseDate = startDate + " 00:00:00"; + startDateObj = sqlFormat.parse(startParseDate); + + // Set End Date to end of day + String parseEndDate = endDate + " 23:59:59"; + endDateObj = sqlFormat.parse(parseEndDate); + + } + catch(ParseException e){ + + startDateObj = new Date(); + endDateObj = new Date(); + } + + if( startDateObj.getTime() > endDateObj.getTime() ){ + + errors.add(new ValidationError("startDate", "Start Date cannot be after End Date")); + } + if( startDateObj.getTime() > today.getTime() ){ + + errors.add(new ValidationError("startDate", "Start Date cannot be in the future")); + } + if( endDateObj.getTime() > today.getTime() ){ + + errors.add(new ValidationError("endDate", "End Date cannot be in the future")); + } + + return errors.isEmpty() ? null : errors; + } + */ + + public String getPrimaryDataset() { + return primaryDataset; + } + + public void setPrimaryDataset(String primaryDataset) { + this.primaryDataset = primaryDataset; + } + + public String getSecondaryDataset() { + return secondaryDataset; + } + + public void setSecondaryDataset(String secondaryDataset) { + this.secondaryDataset = secondaryDataset; + } + + public String getGraphType() { + return graphType; + } + + public void setGraphType(String graphType) { + this.graphType = graphType; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public boolean isGroupPrimary() { return groupPrimary; } + + public void setGroupPrimary(boolean groupPrimary) { this.groupPrimary = groupPrimary; } + + public Integer getGroupFactor() { return groupFactor; } + + public void setGroupFactor(Integer groupFactor) { this.groupFactor = groupFactor; } + + public Float getRangeStart() { return rangeStart; } + + public void setRangeStart(Float rangeStart) { this.rangeStart = rangeStart; } + + public Float getRangeEnd() { return rangeEnd; } + + public void setRangeEnd(Float rangeEnd) { this.rangeEnd = rangeEnd; } + + public Integer getMedicationId() { return medicationId; } + + public void setMedicationId(Integer medicationId) { this.medicationId = medicationId; } + + public Map getMedicationsList() { return medicationsList; } + + public void setMedicationsList(Map medicationsList) { this.medicationsList = medicationsList; } +} diff --git a/app/femr/ui/views/admin/inventory/index.scala.html b/app/femr/ui/views/admin/inventory/index.scala.html index bdda7f4a2..24f3c1348 100644 --- a/app/femr/ui/views/admin/inventory/index.scala.html +++ b/app/femr/ui/views/admin/inventory/index.scala.html @@ -37,7 +37,7 @@
- +
diff --git a/app/femr/ui/views/research/generatedata.scala.html b/app/femr/ui/views/research/generatedata.scala.html new file mode 100644 index 000000000..e4de90551 --- /dev/null +++ b/app/femr/ui/views/research/generatedata.scala.html @@ -0,0 +1,20 @@ +@(currentUser: femr.common.dto.CurrentUser, viewModel:femr.ui.models.research.FilterViewModel) + +@import femr.ui.views.html.layouts.main +@import femr.ui.controllers.routes.ResearchController + + @additionalStyles = { + + } + @additionalScripts = { + + + } + +@main("Research", currentUser, styles = additionalStyles, scripts = additionalScripts) { + +

Generate Test Data

+ How many did you want to generate:
+ + +} diff --git a/app/femr/ui/views/research/index.scala.html b/app/femr/ui/views/research/index.scala.html index ca500ec30..fb72e74ce 100644 --- a/app/femr/ui/views/research/index.scala.html +++ b/app/femr/ui/views/research/index.scala.html @@ -1,14 +1,223 @@ -@(currentUser: femr.common.dto.CurrentUser) +@(currentUser: femr.common.dto.CurrentUser, viewModel:femr.ui.models.research.FilterViewModel) @import femr.ui.views.html.layouts.main +@import femr.ui.controllers.routes.ResearchController @additionalStyles = { } @additionalScripts = { + + + + + + + + + + + } @main("Research", currentUser, styles = additionalStyles, scripts = additionalScripts) { -

Future Research Stuff

+ + + } diff --git a/app/femr/util/calculations/dateUtils.java b/app/femr/util/calculations/dateUtils.java index fbba83fb6..d46e887b7 100644 --- a/app/femr/util/calculations/dateUtils.java +++ b/app/femr/util/calculations/dateUtils.java @@ -58,6 +58,16 @@ public static String getAge(Date born) { return Integer.toString(monthsInt/12) + " YO"; } + public static float getAgeFloat(Date born) { + LocalDate birthdate = new LocalDate(born); + LocalDate now = new LocalDate(); + Months months = Months.monthsBetween(birthdate, now); + int monthsInt = months.getMonths(); + float result = (float) monthsInt; + return result/12; + } + + public static String getFriendlyDate(DateTime dateTime){ DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM d, yyyy - HH:mm:ss"); String dtStr = dateTime.toString(fmt); diff --git a/app/femr/util/stringhelpers/StringUtils.java b/app/femr/util/stringhelpers/StringUtils.java index e50fbf9a1..492c834ad 100644 --- a/app/femr/util/stringhelpers/StringUtils.java +++ b/app/femr/util/stringhelpers/StringUtils.java @@ -81,4 +81,15 @@ public static String ToSimpleDate(Date dt) return ""; } + public static String splitCamelCase(String s) { + return s.replaceAll( + String.format("%s|%s|%s", + "(?<=[A-Z])(?=[A-Z][a-z])", + "(?<=[^A-Z])(?=[A-Z])", + "(?<=[A-Za-z])(?=[^A-Za-z])" + ), + " " + ); + } + } diff --git a/conf/routes b/conf/routes index 3fd2e84ef..1094c05cb 100644 --- a/conf/routes +++ b/conf/routes @@ -49,6 +49,10 @@ GET /medical @femr.ui.controllers.MedicalControl POST /medical @femr.ui.controllers.MedicalController.indexPost() #Research GET /research @femr.ui.controllers.ResearchController.indexGet() +POST /research/graph @femr.ui.controllers.ResearchController.getGraphPost() +GET /research/generateData @femr.ui.controllers.ResearchController.generateData() + + #Sessions GET /logout @femr.ui.controllers.SessionsController.delete() POST /login/reset @femr.ui.controllers.SessionsController.editPasswordPost() diff --git a/conf/test.conf b/conf/test.conf new file mode 100644 index 000000000..d4b41ed3b --- /dev/null +++ b/conf/test.conf @@ -0,0 +1,7 @@ +include "application.conf" + +db.default.user="femr" +db.default.password="PnhcTUQ9xpraJf7e" + +#Session time out, in minutes +sessionTimeout=2880 \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index 716683252..1ec71be31 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -22,8 +22,8 @@ object ApplicationBuild extends Build { val main = Project(appName, file(".")).enablePlugins(play.PlayJava).settings( - /*javacOptions += "-Xlint:deprecation",*/ //use when searching for deprecated API usage - /*javacOptions += "-Xlint:unchecked",*/ //use when you want to display java warnings + javacOptions += "-Xlint:deprecation", //*/ //use when searching for deprecated API usage + javacOptions += "-Xlint:unchecked", //*/ //use when you want to display java warnings version := appVersion, scalaVersion := currentScalaVersion, libraryDependencies ++= appDependencies, diff --git a/public/css/research.css b/public/css/research.css index e69de29bb..287209313 100644 --- a/public/css/research.css +++ b/public/css/research.css @@ -0,0 +1,640 @@ +/* + * Sidebar + */ + +/* Hide for mobile, show later */ +.sidebar { + display: none; +} + +@media (min-width: 768px) { + + .sidebar { + position: fixed; + top: 50px; + bottom: 0; + left: 0; + z-index: 1000; + display: block; + padding: 20px; + background-color: #000; + border-right: 1px solid #6f6f6f; + } +} + +/* Sidebar navigation */ +.nav-sidebar { + margin-right: -20px; + margin-bottom: 20px; + margin-left: -20px; + border-top: 1px solid #6f6f6f; +} +.nav-sidebar li.menu-item { + position: relative; + border-bottom: 1px solid #6f6f6f; +} +.nav-sidebar li.controls{ + + border: none; + padding: 10px; + color: #eeeeee; + border-bottom: 1px solid #6f6f6f; +} + +.nav-sidebar li.controls label{ + + font-weight: normal; + color: #cccccc; +} + +.nav-sidebar li.controls span.group{ + + display: block; + white-space: nowrap; + padding: 0; +} + +.nav-sidebar li.controls span.group input[type="checkbox"]{ + + margin-right: 8px; +} + +.nav-sidebar li.controls span.group input[type="text"]{ + + display: inline; + width: 30px; + text-align: center; + margin-left: 2px; +} + +.nav-sidebar li.controls span.filter{ + + display: block; + white-space: nowrap; + margin-bottom: 10px; +} + +.nav-sidebar li.controls span.filter .glyphicon-remove { + + cursor: pointer; + color: #BB1D1D; +} + +.nav-sidebar li.controls span.filter label{ + + display: block; + width: 100%; +} + +.nav-sidebar li.controls span.filter input[type="text"]{ + + width: 50px; +} + +.nav-sidebar li.controls span.filter input#rangeStart{ + + margin-left: 5px; +} + +.nav-sidebar li.controls span.medication{ + + display: block; + white-space: nowrap; + margin-bottom: 15px; +} + +.nav-sidebar li.controls span.medication select{ + + display: block; + clear: both; + margin-left: 10px; +} + +.nav-sidebar li.submit{ + + padding: 20px 10px +} + +.nav-sidebar li.submit input.clear{ + + background-color: #ccc; + color: #333; + padding: 3px 8px; + font-size: 1.1em; +} +.nav-sidebar li.submit input.clear:hover { + + background-color: #9f9f9f; +} +.nav-sidebar li.submit input.submit{ + + background-color: rgb(44, 158, 255); + color: #ffffff; + padding: 3px 8px; + font-size: 1.1em; +} + +.nav-sidebar li.submit input.submit:hover{ + + background-color: rgb(17, 106, 181); +} +.nav-sidebar li.menu-item .val{ + + position: absolute; + bottom: 0.3em; + left: 10px; + color: #7f7f7f; + display: block; + font-size: .9em; +} + +.nav-sidebar li.menu-item .glyphicon{ + + position: absolute; + top: 50%; + right: 8px; + color: #6f6f6f; + font-size: 14px; + line-height: 20px; + margin-top: -10px; +} + +.nav-sidebar li.menu-item > a { + + padding: 0.5em 10px 1.6em; + color: #ccc; +} +.nav-sidebar li.menu-item > a.noval{ + + padding-bottom: 0.5em; +} + + +.nav-sidebar li.menu-item > a:hover{ + + cursor: pointer; + background-color: #000; + color: #ccc; +} + + +.nav-sidebar > li.active > a, +.nav-sidebar > li.active > a:hover, +.nav-sidebar > li.active > a:focus{ + + background-color: #333333; + color: #fff; + cursor: pointer; +} +.nav-sidebar li.menu-item span:hover{ + cursor: pointer; +} +.nav-sidebar .submenu{ + + background-color: black; + position: absolute; + left: 100%; + top: 0; + display: none; + padding: 0; + margin: 0 0 0 1px; + min-width: 160px; + list-style: none; +} + +.nav-sidebar .submenu.active{ + + display: block; +} +.nav-sidebar .submenu li{ + + color: #ffffff; +} +.nav-sidebar .submenu li a{ + + display: block; + white-space: nowrap; + padding: 3px 20px; + color: #cfcfcf; +} +.nav-sidebar .submenu li a:hover { + + text-decoration: none; + background-color: #333333; + color: #fff; + cursor: pointer; +} +.nav-sidebar .submenu li a.disabled { + + color: #333333; +} +.nav-sidebar .submenu li a.disabled:hover{ + + background-color: transparent; + color: #333333; +} +.nav-sidebar .submenu li a.clear{ + + border-top: 1px solid #4f4f4f; + border-bottom: 1px solid #4f4f4f; + position: relative; + color: #ffffff; +} +.nav-sidebar .submenu li a.clear .glyphicon{ + + position: relative;; + float: left; + top: auto; + right: auto; + margin: -1px 5px 0 0; + color: #BB1D1D; +} +.nav-sidebar .submenu li.title{ + + background-color: #1f1f1f; + border-top: 1px solid #4f4f4f; + border-bottom: 1px solid #4f4f4f; + padding: 5px 10px; +} + +/* +.nav-sidebar .submenu li #save{ + + background-color: #1f1f1f; + border-top: 1px solid #4f4f4f; + border-bottom: 1px solid #4f4f4f; + padding: 5px 10px; +} +*/ + +#gfilter-menu .submenu{ + + padding-bottom: 10px; +} + +#gfilter-menu .submenu li{ + + padding: 10px 30px; +} + +#filter-errors{ + + margin-right: -10px; + margin-bottom: 10px; + margin-left: -10px; + +} + +#filter-errors ul{ + + margin: 0; + padding: 0; + list-style: none; +} + +#filter-errors ul li { + + list-style: none; + font-size: 0.9em; + color: #BB1D1D; +} + +/* + * Main content + */ + +.main { + padding: 20px 10px; +} + +.main .page-header { + margin-top: 0; +} + +/* Table Styles */ + +#table-container{ + + +} + +.table{ + + width: auto; +} + +.table td{ + + background-color: #fff; + border-right: 1px solid #dddddd; +} + +.table th{ + + background-color: #ccc; + color: #000; +} + + +.legend-bg{ + + background-color: #efefef; +} + +.chart-container{ + + background-color: #fff; + box-shadow: 1px 2px 8px #333; + position: relative; +} + +.chart-container .loading{ + + display: none; + position: absolute; + top: 50%; + left: 50%; + margin-left: -16px; + margin-top: -16px; +} + +.save-image-cont{ + + float: right; + margin-right: 1em; +} + +.save-image-cont .options{ + + display: none; + position: absolute; + top: 50%; + left: 50%; + + margin-left: -200px; + margin-top: -60px; + + width: 400px; + height: 120px; + background: #6f6f6f; + border: 1px solid #9f9f9f; + padding: 10px; + box-shadow: 1px 1px 5px #333333; +} + +.save-image-cont .options .close{ + + position: absolute; + top: 0px; + right: 5px; + opacity: 1; +} + +.save-image-cont .options .close span{ + + font-size: 12px; + color: #BB1D1D; + cursor: pointer; +} + +.save-image-cont .options p{ + + color: #ffffff; + font-size: 1.2em; + margin-bottom: 18px; +} + +.save-image-cont .options a.image-size-selection{ + + color: #000000; + background-color: #efefef; + display: block; + float: left; + margin-left: 20px; + border-radius: 4px; + border: 1px solid #9f9f9f; + padding: 5px 12px; + text-align: center; +} + +.save-image-cont .options a.image-size-selection:hover{ + + text-decoration: none; +} + + + +/* Graph Styles */ + +.graph-instructions{ + + padding: 1em; +} + +.graph-area{ + + display: none; +} + +#legend{ + + width: 200px; + height: 300px; + overflow: auto; + display: none; +} + +.graph-header{ + + padding: 1em; + background-color: #fff; + margin-bottom: 1em; + overflow: hidden; +} + +.graph-header .stat{ + + font-size: 1.1em; + margin: 0 0 5px 0; + display: none; +} + +.chart{ + + font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; + background-color: #ffffff; + width: 100%; +} + +.d3-tip { + line-height: 1; + font-weight: bold; + padding: 12px; + background: rgba(0, 0, 0, 0.8); + color: #eeeeee; + border-radius: 2px; +} + +.d3-tip span{ + + color: #ffffff; +} + +.d3-tip span.name{ + + display: block; + text-align: center; + margin-bottom: 8px; + font-size: 1.2em; +} + +.d3-tip span.val{ + + display: block; + +} + +/* Creates a small triangle extender for the tooltip */ +.d3-tip:after { + box-sizing: border-box; + display: inline; + font-size: 10px; + width: 100%; + line-height: 1; + color: rgba(0, 0, 0, 0.8); + content: "\25BC"; + position: absolute; + text-align: center; +} + +/* Style northward tooltips differently */ +.d3-tip.n:after { + margin: -1px 0 0 0; + top: 100%; + left: 0; +} + + + +*[data-labelbg=true] {filter:url(#bg-color)} + +.label-text { + alignment-baseline: middle; + font-size: 14px; + fill: #ffffff; + font-weight: 300; + padding: 5px; +} +.label-line { + stroke-width: 1; + stroke: #4f4f4f; +} +.label-circle { + fill: #4f4f4f; +} + +.label_group .label{ + + display: none; +} + +.legend .item{ + + cursor: pointer; +} + +.legend text { + + font-size: 12px; + font-weight: bold; + shape-rendering: crispEdges; +} + +/* Bar Chart */ + +.bar { + fill: #4682B4; +} + +.bar:hover { + fill: #386284; +} + +/* Line Chart */ + +.line { + fill: none; + stroke: #4682B4; + stroke-width: 1.5px; +} + +.overlay { + fill: none; + pointer-events: all; +} + +.focus { + fill: #ffffff; +} + +.focus circle { + fill: red; + stroke: #3f3f3f; +} + +.focus text{ + + font-size: 16px; + fill: #2f2f2f; +} + +/* Scatter Plot */ + +.dot { + fill: #4682B4; + stroke: #3f3f3f; + cursor: pointer; +} + +.dot:hover{ + + fill: red; +} + +/* Axis */ + +.axis text { + font-size: 10px; +} + +@media (min-width: 992px){ + + .axis text { + font-size: 12px; + } +} + + + +.axis path, +.axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.axis text.title{ + + font-size: 14px; + font-weight: bold; + shape-rendering: crispEdges; +} + +#svgdataurl{ display: none; } +#pngdataurl{ display: none; } + + +footer p{ + + text-align: right; +} \ No newline at end of file diff --git a/public/img/graph-loader.gif b/public/img/graph-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c2f7c058836808f7f1f5f9a4bdf37d4e5f9284a GIT binary patch literal 3208 zcmc(ic~Dc=9>*`aOO|`};l05I0veErPzymNWmO?SfPgFlg5pAq3huH91c9)G1Y`|i z4JZjDhG<1Z5!{L(f?6D`8``H2sCB`;j(xVT^L)Wphk5_LnZEPqnRDmP=X1Wl@66!` z`n$Ttvj7(G763kc_y7RFrAwCz3JSWqx*8f9xLj^@boA)x=);E(&z?OyXU-f5f{bVW zSl0ix;3aK}PuV15r6r~$u;RDIr*GdCFLF%Wxp^00{VN2}j0dehpey_$SMt2W{1!QK zKojHE!m014ehNU3O@{&#81Ry?oX#6DZ$$v0J3e>A35z_WBvJ<_#BKo;WU| zlhe}qUa=5u3mwW&3lJ7s?M1x36dY=FTw|QvgGz$IR&C=!53NBJpfA=OKGM`_RmbT% znZm9NNG{r+6zds~vIJC01Jq2Sfd~xI=Y0{MfaQy zn2ZzlCpfx2_e$RKF6Y3;lDC^Ctng6>y!>|IX5edIqlS+PO-?8+ z`B&W3L?HdjNFjrNI!Jn^_xX`vLz8IS;`MxK?2dHilQLyLW(Kk1FgksCojERsJ!?iEkw+`1cDYq6akXxle%?Jr<{{=0nz`Kk-S^@n0J8?VXMIkDd80qP5Zm)#`}B9q`aYD-x25 zc@QMAn3TmSh+$G`MJqYrrZlSzXqzXwkxe}q+R{=~MXl6{TMe0tZ;lxDwHaEwS~Tn) z%Z4-bbN=m#CC+_Hj=V@B(_K9qdqPDt^t)b6FaB0hLKPppyu1i6y5o8OFfai$3|@Hf z;}f9$JoCBho5!)9?In}=Wi7?^t?W>oEX>UIsE7wEM6JuV|urBCNX|~_fosA>efw^cee6+8#zdilg;yU=9%o2Tr8vKU(UXB z3kWh_IQ#Dlz2mDX28*Vsv~^2N0@-2rA5dndqT#a_FD7Mja*;&mNGTuQl4hBa#RlbU zyNJdn0&7;IFttot;-xlVx#2#Rt0hHS8Yc?$hTuI$Ax^85FTg>Ou?^asn^v zc4TamL;dN)1SL|wK5J+}IUv2WZek)s&{URu5`W(KbZO#xJ-h7I%bmY@-Nh&FUD-3b zWYh3hA$_f%(+^E&|9Jfl`pIECdq1scZFL2~(DjE!P`xQick6HdB~DW0RW%CJs%Egc z5*vQ&0+H<+8=2yLP{*8J|AcQU5HKERhC^Yc8+NlT`wE?W{KMilM$MR*u`F^Vg|y0P zH$vvm4^8ofIt;5X%DqHWn*2F7FBENb*Qjev#6oN7p$rX0Wr+o zs`8{oPY+ryQp?#Sq!&YSG)vgY_Gs^!%G7))-)}L!8*2e#qa^10fs}hSj~-QC@-4P~ z6qFe9!gDNk%%gbp7$K<>c~-GPNqH$TKYQ-6`*N1g%+J>kPgn4EssJL|j0Ip5#AY?s zRM6Erzwp(Dilg}V_^V)%qWGU*#U9ns-X-MKYl| zwFePZV^uR!FKtm8+&~Gt)DlKfaDSp(XD8Bx>sdSsxd$cN6#7_!m=A>Xob*j5%IRbb zL+IeOburN9EZZ>Z9V|2W!Ll&m3Wh3Gp-TYt&PcD{jknNG3RUzoTSoVzE3-^Q04Zo> zo;@!8+wSODeZ97yngE&Z;n_3~QezZYX6lH()hmh|!W>Kvk9*v*4a;;;uE^_s5$88j z@v}80$2lr=(S2WP{rV(s;4ea&y7i}<7XxY=T&X^_9@OJUZ0sn8#??REOF5?yT1o`- zcy532%O{1)9c9x=V!U)kdGqd6mgst zjK)D-dV{YE!y_F;(H;WUcZBDP7GSpl>Q%HuunND8;a5kUr6+R98O-cNL&bM=ik$%oZJ^bN~{`Ou$DNS@CB>aXDEiy1~>dAVzrxJXf|%q~{3 zV+sT$5OlN3ch~51Ia#f2Dy#?LDRKz$p>(uvXKchk3lKrb!5U$BE`ni$=yiZPfK&CDbpRi{y#a8x>Lvn-cH8Z2YFcxCWPvAg{g4_(vBgWOcI!oCDiIr*tgFD z0>S>ZbG=}lo*<*B9x-NM2+WPPzk!bHFPppF5E{UBX{72*x15C{|HfBzB=y)?!u4((=0EgFLA_ z6`T@*qVPu%h`}%=g4~IcPci+B9@-2D7oZGStf5opdO-$lH-c!vJHV>+`Sv#v^E=-M zy2;5mj{xJ#ck$qxWMVRMnc%^tr=x`E2j(mK&uiab@cCNZ3*; z{}ciWc1dFPu?S2#l*O}QL#Hy~RyUEaitnx6%8J5aG?N#&&2ooOFi*BoP^rKruGE6e zcty2q{Z3UiqprS6E6a4e(ctyDh^*`q;E_{?+fE^2WEl1@`Khci${^T>BfB-uBvB zWRm+Rso1^=^H?Vo|byTTbgxVWRzkrjj8ud(@m}8ax_s zY?YdiajB#$UkG9tIz0b*bBDr_s}UX3GqXvExGLdpADx_i0 20 ){ + + xAxis.tickValues(xScale.domain().filter(function(d, i) { return !(i % 6); })) + } + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("left") + .tickFormat(d3.format("d")); + + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([-10, 0]) + .html(function(d) { + return '' + mapGraphData(d.primaryName, valueMap) + ' Patients: ' + d.primaryValue + ''; + }); + + var chart = d3.select("#"+containerIDString) + .attr("width", containerWidth) + .attr("height", containerHeight) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + chart.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + graphHeight + ")") + .call(xAxis) + .append("text") + .attr("class", "title") + .attr("x", graphWidth / 2 ) + .attr("y", 0 + margin.bottom) + .style("text-anchor", "middle") + .attr("dy", "-5px") + .text(xAxisTitle); + + chart.call(tip); + + chart.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("class", "title") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margin.left) + .attr("x", 0 - (graphHeight / 2)) + .attr("dy", "1.25em") + .style("text-anchor", "middle") + .text("Number of Patients"); + + chart.selectAll(".bar") + .data(graphData) + .enter().append("rect") + .attr("class", "bar") + .attr("x", function(d) { return xScale(mapGraphData(d.primaryName, valueMap)); }) + .attr("y", function(d) { return yScale(d.primaryValue); }) + .attr("height", function(d) { return graphHeight - yScale(d.primaryValue); }) + //.attr("width", function(d) { return xScale(d.primaryName); }) + .attr("width", xScale.rangeBand()) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); + }; + + return publicObject; + +})(); \ No newline at end of file diff --git a/public/js/research/d3.min.js b/public/js/research/d3.min.js new file mode 100644 index 000000000..88550ae51 --- /dev/null +++ b/public/js/research/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n,t){return t>n?-1:n>t?1:n>=t?0:0/0}function t(n){return null!=n&&!isNaN(n)}function e(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function r(n){return n.length}function u(n){for(var t=1;n*t%1;)t*=10;return t}function i(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function o(){}function a(n){return ia+n in this}function c(n){return n=ia+n,n in this&&delete this[n]}function s(){var n=[];return this.forEach(function(t){n.push(t)}),n}function l(){var n=0;for(var t in this)t.charCodeAt(0)===oa&&++n;return n}function f(){for(var n in this)if(n.charCodeAt(0)===oa)return!1;return!0}function h(){}function g(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function p(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.substring(1);for(var e=0,r=aa.length;r>e;++e){var u=aa[e]+t;if(u in n)return u}}function v(){}function d(){}function m(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function U(n){return sa(n,da),n}function j(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t0&&(n=n.substring(0,a));var s=ya.get(n);return s&&(n=s,c=Y),a?t?u:r:t?v:i}function O(n,t){return function(e){var r=Zo.event;Zo.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{Zo.event=r}}}function Y(n,t){var e=O(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function I(){var n=".dragsuppress-"+ ++Ma,t="click"+n,e=Zo.select(Wo).on("touchmove"+n,y).on("dragstart"+n,y).on("selectstart"+n,y);if(xa){var r=Bo.style,u=r[xa];r[xa]="none"}return function(i){function o(){e.on(t,null)}e.on(n,null),xa&&(r[xa]=u),i&&(e.on(t,function(){y(),o()},!0),setTimeout(o,0))}}function Z(n,t){t.changedTouches&&(t=t.changedTouches[0]);var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>_a&&(Wo.scrollX||Wo.scrollY)){e=Zo.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var u=e[0][0].getScreenCTM();_a=!(u.f||u.e),e.remove()}return _a?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var i=n.getBoundingClientRect();return[t.clientX-i.left-n.clientLeft,t.clientY-i.top-n.clientTop]}function V(){return Zo.event.changedTouches[0].identifier}function X(){return Zo.event.target}function $(){return Wo}function B(n){return n>0?1:0>n?-1:0}function W(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function J(n){return n>1?0:-1>n?ba:Math.acos(n)}function G(n){return n>1?Sa:-1>n?-Sa:Math.asin(n)}function K(n){return((n=Math.exp(n))-1/n)/2}function Q(n){return((n=Math.exp(n))+1/n)/2}function nt(n){return((n=Math.exp(2*n))-1)/(n+1)}function tt(n){return(n=Math.sin(n/2))*n}function et(){}function rt(n,t,e){return this instanceof rt?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof rt?new rt(n.h,n.s,n.l):mt(""+n,yt,rt):new rt(n,t,e)}function ut(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new gt(u(n+120),u(n),u(n-120))}function it(n,t,e){return this instanceof it?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof it?new it(n.h,n.c,n.l):n instanceof at?st(n.l,n.a,n.b):st((n=xt((n=Zo.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new it(n,t,e)}function ot(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new at(e,Math.cos(n*=Aa)*t,Math.sin(n)*t)}function at(n,t,e){return this instanceof at?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof at?new at(n.l,n.a,n.b):n instanceof it?ot(n.l,n.c,n.h):xt((n=gt(n)).r,n.g,n.b):new at(n,t,e)}function ct(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=lt(u)*ja,r=lt(r)*Ha,i=lt(i)*Fa,new gt(ht(3.2404542*u-1.5371385*r-.4985314*i),ht(-.969266*u+1.8760108*r+.041556*i),ht(.0556434*u-.2040259*r+1.0572252*i))}function st(n,t,e){return n>0?new it(Math.atan2(e,t)*Ca,Math.sqrt(t*t+e*e),n):new it(0/0,0/0,n)}function lt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function ft(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function ht(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function gt(n,t,e){return this instanceof gt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof gt?new gt(n.r,n.g,n.b):mt(""+n,gt,ut):new gt(n,t,e)}function pt(n){return new gt(n>>16,255&n>>8,255&n)}function vt(n){return pt(n)+""}function dt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function mt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(_t(u[0]),_t(u[1]),_t(u[2]))}return(i=Ia.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.substring(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,c=15&i,c=c<<4|c):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,c=255&i)),t(o,a,c))}function yt(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),new rt(r,u,c)}function xt(n,t,e){n=Mt(n),t=Mt(t),e=Mt(e);var r=ft((.4124564*n+.3575761*t+.1804375*e)/ja),u=ft((.2126729*n+.7151522*t+.072175*e)/Ha),i=ft((.0193339*n+.119192*t+.9503041*e)/Fa);return at(116*u-16,500*(r-u),200*(u-i))}function Mt(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function _t(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function bt(n){return"function"==typeof n?n:function(){return n}}function wt(n){return n}function St(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),kt(t,e,n,r)}}function kt(n,t,e,r){function u(){var n,t=c.status;if(!t&&c.responseText||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return o.error.call(i,r),void 0}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=Zo.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,s=null;return!Wo.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=Zo.event;Zo.event=n;try{o.progress.call(i,c)}finally{Zo.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(s=n,i):s},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(Xo(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var l in a)c.setRequestHeader(l,a[l]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=s&&(c.responseType=s),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},Zo.rebind(i,o,"on"),null==r?i:i.get(Et(r))}function Et(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function At(){var n=Ct(),t=Nt()-n;t>24?(isFinite(t)&&(clearTimeout($a),$a=setTimeout(At,t)),Xa=0):(Xa=1,Wa(At))}function Ct(){var n=Date.now();for(Ba=Za;Ba;)n>=Ba.t&&(Ba.f=Ba.c(n-Ba.t)),Ba=Ba.n;return n}function Nt(){for(var n,t=Za,e=1/0;t;)t.f?t=n?n.n=t.n:Za=t.n:(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Tt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r?function(n){for(var t=n.length,u=[],i=0,o=r[0];t>0&&o>0;)u.push(n.substring(t-=o,t+o)),o=r[i=(i+1)%r.length];return u.reverse().join(e)}:wt;return function(n){var e=Ga.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"",c=e[4]||"",s=e[5],l=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1;switch(h&&(h=+h.substring(1)),(s||"0"===r&&"="===o)&&(s=r="0",o="=",f&&(l-=Math.floor((l-1)/4))),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=Ka.get(g)||qt;var y=s&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):a;if(0>p){var c=Zo.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x=n.lastIndexOf("."),M=0>x?n:n.substring(0,x),_=0>x?"":t+n.substring(x+1);!s&&f&&(M=i(M));var b=v.length+M.length+_.length+(y?0:u.length),w=l>b?new Array(b=l-b+1).join(r):"";return y&&(M=i(w+M)),u+=v,n=M+_,("<"===o?u+n+w:">"===o?w+u+n:"^"===o?w.substring(0,b>>=1)+u+n+w.substring(b):u+(y?n:w+n))+e}}}function qt(n){return n+""}function Rt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Dt(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new nc(e-1)),1),e}function i(n,e){return t(n=new nc(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{nc=Rt;var r=new Rt;return r._=n,o(r,t,e)}finally{nc=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Pt(n);return c.floor=c,c.round=Pt(r),c.ceil=Pt(u),c.offset=Pt(i),c.range=a,n}function Pt(n){return function(t,e){try{nc=Rt;var r=new Rt;return r._=t,n(r,e)._}finally{nc=Date}}}function Ut(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++aa;){if(r>=s)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=N[o in ec?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){b.lastIndex=0;var r=b.exec(t.substring(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){M.lastIndex=0;var r=M.exec(t.substring(e));return r?(n.w=_.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.substring(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.substring(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,C.c.toString(),t,r)}function c(n,t,r){return e(n,C.x.toString(),t,r)}function s(n,t,r){return e(n,C.X.toString(),t,r)}function l(n,t,e){var r=x.get(t.substring(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{nc=Rt;var t=new nc;return t._=n,r(t)}finally{nc=Date}}var r=t(n);return e.parse=function(n){try{nc=Rt;var t=r.parse(n);return t&&t._}finally{nc=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=re;var x=Zo.map(),M=Ht(v),_=Ft(v),b=Ht(d),w=Ft(d),S=Ht(m),k=Ft(m),E=Ht(y),A=Ft(y);p.forEach(function(n,t){x.set(n.toLowerCase(),t)});var C={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return jt(n.getDate(),t,2)},e:function(n,t){return jt(n.getDate(),t,2)},H:function(n,t){return jt(n.getHours(),t,2)},I:function(n,t){return jt(n.getHours()%12||12,t,2)},j:function(n,t){return jt(1+Qa.dayOfYear(n),t,3)},L:function(n,t){return jt(n.getMilliseconds(),t,3)},m:function(n,t){return jt(n.getMonth()+1,t,2)},M:function(n,t){return jt(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return jt(n.getSeconds(),t,2)},U:function(n,t){return jt(Qa.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return jt(Qa.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return jt(n.getFullYear()%100,t,2)},Y:function(n,t){return jt(n.getFullYear()%1e4,t,4)},Z:te,"%":function(){return"%"}},N={a:r,A:u,b:i,B:o,c:a,d:Wt,e:Wt,H:Gt,I:Gt,j:Jt,L:ne,m:Bt,M:Kt,p:l,S:Qt,U:Yt,w:Ot,W:It,x:c,X:s,y:Vt,Y:Zt,Z:Xt,"%":ee};return t}function jt(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Ht(n){return new RegExp("^(?:"+n.map(Zo.requote).join("|")+")","i")}function Ft(n){for(var t=new o,e=-1,r=n.length;++e68?1900:2e3)}function Bt(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Wt(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function Jt(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function Gt(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function Kt(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function Qt(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ne(n,t,e){rc.lastIndex=0;var r=rc.exec(t.substring(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function te(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=~~(ua(t)/60),u=ua(t)%60;return e+jt(r,"0",2)+jt(u,"0",2)}function ee(n,t,e){uc.lastIndex=0;var r=uc.exec(t.substring(e,e+1));return r?e+r[0].length:-1}function re(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,c=Math.cos(t),s=Math.sin(t),l=i*s,f=u*c+l*Math.cos(a),h=l*o*Math.sin(a);lc.add(Math.atan2(h,f)),r=n,u=c,i=s}var t,e,r,u,i;fc.point=function(o,a){fc.point=n,r=(t=o)*Aa,u=Math.cos(a=(e=a)*Aa/2+ba/4),i=Math.sin(a)},fc.lineEnd=function(){n(t,e)}}function le(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function fe(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function he(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function ge(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function pe(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function ve(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function de(n){return[Math.atan2(n[1],n[0]),G(n[2])]}function me(n,t){return ua(n[0]-t[0])a;++a)u.point((e=n[a])[0],e[1]);return u.lineEnd(),void 0}var c=new Ee(e,n,null,!0),s=new Ee(e,null,c,!1);c.o=s,i.push(c),o.push(s),c=new Ee(r,n,null,!1),s=new Ee(r,null,c,!0),c.o=s,i.push(c),o.push(s)}}),o.sort(t),ke(i),ke(o),i.length){for(var a=0,c=e,s=o.length;s>a;++a)o[a].e=c=!c;for(var l,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;l=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,s=l.length;s>a;++a)u.point((f=l[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){l=g.p.z;for(var a=l.length-1;a>=0;--a)u.point((f=l[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,l=g.z,p=!p}while(!g.v);u.lineEnd()}}}function ke(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(_||(i.polygonStart(),_=!0),i.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Ce))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:s,polygonStart:function(){y.point=l,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=s,g=Zo.merge(g);var n=Le(m,p);g.length?(_||(i.polygonStart(),_=!0),Se(g,ze,n,e,i)):n&&(_||(i.polygonStart(),_=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),_&&(i.polygonEnd(),_=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},x=Ne(),M=t(x),_=!1;return y}}function Ce(n){return n.length>1}function Ne(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:v,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function ze(n,t){return((n=n.x)[0]<0?n[1]-Sa-ka:Sa-n[1])-((t=t.x)[0]<0?t[1]-Sa-ka:Sa-t[1])}function Le(n,t){var e=n[0],r=n[1],u=[Math.sin(e),-Math.cos(e),0],i=0,o=0;lc.reset();for(var a=0,c=t.length;c>a;++a){var s=t[a],l=s.length;if(l)for(var f=s[0],h=f[0],g=f[1]/2+ba/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===l&&(d=0),n=s[d];var m=n[0],y=n[1]/2+ba/4,x=Math.sin(y),M=Math.cos(y),_=m-h,b=_>=0?1:-1,w=b*_,S=w>ba,k=p*x;if(lc.add(Math.atan2(k*b*Math.sin(w),v*M+k*Math.cos(w))),i+=S?_+b*wa:_,S^h>=e^m>=e){var E=he(le(f),le(n));ve(E);var A=he(u,E);ve(A);var C=(S^_>=0?-1:1)*G(A[2]);(r>C||r===C&&(E[0]||E[1]))&&(o+=S^_>=0?1:-1)}if(!d++)break;h=m,p=x,v=M,f=n}}return(-ka>i||ka>i&&0>lc)^1&o}function Te(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?ba:-ba,c=ua(i-e);ua(c-ba)0?Sa:-Sa),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=ba&&(ua(e-u)ka?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function Re(n,t,e,r){var u;if(null==n)u=e*Sa,r.point(-ba,u),r.point(0,u),r.point(ba,u),r.point(ba,0),r.point(ba,-u),r.point(0,-u),r.point(-ba,-u),r.point(-ba,0),r.point(-ba,u);else if(ua(n[0]-t[0])>ka){var i=n[0]i}function e(n){var e,i,c,s,l;return{lineStart:function(){s=c=!1,l=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?ba:-ba),h):0;if(!e&&(s=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(me(e,g)||me(p,g))&&(p[0]+=ka,p[1]+=ka,v=t(p[0],p[1]))),v!==c)l=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(l=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&me(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return l|(s&&c)<<1}}}function r(n,t,e){var r=le(n),u=le(t),o=[1,0,0],a=he(r,u),c=fe(a,a),s=a[0],l=c-s*s;if(!l)return!e&&n;var f=i*c/l,h=-i*s/l,g=he(o,a),p=pe(o,f),v=pe(a,h);ge(p,v);var d=g,m=fe(p,d),y=fe(d,d),x=m*m-y*(fe(p,p)-1);if(!(0>x)){var M=Math.sqrt(x),_=pe(d,(-m-M)/y);if(ge(_,p),_=de(_),!e)return _;var b,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(b=w,w=S,S=b);var A=S-w,C=ua(A-ba)A;if(!C&&k>E&&(b=k,k=E,E=b),N?C?k+E>0^_[1]<(ua(_[0]-w)ba^(w<=_[0]&&_[0]<=S)){var z=pe(d,(-m+M)/y);return ge(z,p),[_,de(z)]}}}function u(t,e){var r=o?n:ba-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=ua(i)>ka,c=sr(n,6*Aa);return Ae(t,e,c,o?[0,-n]:[-ba,n-ba])}function Pe(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,s=o.y,l=a.x,f=a.y,h=0,g=1,p=l-c,v=f-s;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-s,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-s,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:s+h*v}),1>g&&(u.b={x:c+g*p,y:s+g*v}),u}}}}}}function Ue(n,t,e,r){function u(r,u){return ua(r[0]-n)0?0:3:ua(r[0]-e)0?2:1:ua(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,s=a[0];c>o;++o)i=a[o],s[1]<=r?i[1]>r&&W(s,i,n)>0&&++t:i[1]<=r&&W(s,i,n)<0&&--t,s=i;return 0!==t}function s(i,a,c,s){var l=0,f=0;if(null==i||(l=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do s.point(0===l||3===l?n:e,l>1?r:t);while((l=(l+c+4)%4)!==f)}else s.point(a[0],a[1])}function l(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){l(n,t)&&a.point(n,t)}function h(){N.point=p,d&&d.push(m=[]),S=!0,w=!1,_=b=0/0}function g(){v&&(p(y,x),M&&w&&A.rejoin(),v.push(A.buffer())),N.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-kc,Math.min(kc,n)),t=Math.max(-kc,Math.min(kc,t));var e=l(n,t);if(d&&m.push([n,t]),S)y=n,x=t,M=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:_,y:b},b:{x:n,y:t}};C(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}_=n,b=t,w=e}var v,d,m,y,x,M,_,b,w,S,k,E=a,A=Ne(),C=Pe(n,t,e,r),N={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=Zo.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),s(null,null,1,a),a.lineEnd()),u&&Se(v,i,t,s,a),a.polygonEnd()),v=d=m=null}};return N}}function je(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function He(n){var t=0,e=ba/3,r=tr(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ba/180,e=n[1]*ba/180):[180*(t/ba),180*(e/ba)]},u}function Fe(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,G((i-(n*n+e*e)*u*u)/(2*u))]},e}function Oe(){function n(n,t){Ac+=u*n-r*t,r=n,u=t}var t,e,r,u;Tc.point=function(i,o){Tc.point=n,t=r=i,e=u=o},Tc.lineEnd=function(){n(t,e)}}function Ye(n,t){Cc>n&&(Cc=n),n>zc&&(zc=n),Nc>t&&(Nc=t),t>Lc&&(Lc=t)}function Ie(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Ze(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Ze(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Ze(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Ve(n,t){pc+=n,vc+=t,++dc}function Xe(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);mc+=o*(t+n)/2,yc+=o*(e+r)/2,xc+=o,Ve(t=n,e=r)}var t,e;Rc.point=function(r,u){Rc.point=n,Ve(t=r,e=u)}}function $e(){Rc.point=Ve}function Be(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);mc+=o*(r+n)/2,yc+=o*(u+t)/2,xc+=o,o=u*n-r*t,Mc+=o*(r+n),_c+=o*(u+t),bc+=3*o,Ve(r=n,u=t)}var t,e,r,u;Rc.point=function(i,o){Rc.point=n,Ve(t=r=i,e=u=o)},Rc.lineEnd=function(){n(t,e)}}function We(n){function t(t,e){n.moveTo(t,e),n.arc(t,e,o,0,wa)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:v};return a}function Je(n){function t(n){return(a?r:e)(n)}function e(t){return Qe(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){x=0/0,S.point=i,t.lineStart()}function i(e,r){var i=le([e,r]),o=n(e,r);u(x,M,y,_,b,w,x=o[0],M=o[1],y=e,_=i[0],b=i[1],w=i[2],a,t),t.point(x,M)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=s,S.lineEnd=l}function s(n,t){i(f=n,h=t),g=x,p=M,v=_,d=b,m=w,S.point=i}function l(){u(x,M,y,_,b,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,x,M,_,b,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,s,l,f,h,g,p,v,d,m){var y=l-t,x=f-e,M=y*y+x*x;if(M>4*i&&d--){var _=a+g,b=c+p,w=s+v,S=Math.sqrt(_*_+b*b+w*w),k=Math.asin(w/=S),E=ua(ua(w)-1)i||ua((y*z+x*L)/M-.5)>.3||o>a*g+c*p+s*v)&&(u(t,e,r,a,c,s,C,N,E,_/=S,b/=S,w,d,m),m.point(C,N),u(C,N,E,_,b,w,l,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Aa),a=16; +return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function Ge(n){var t=Je(function(t,e){return n([t*Ca,e*Ca])});return function(n){return er(t(n))}}function Ke(n){this.stream=n}function Qe(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function nr(n){return tr(function(){return n})()}function tr(n){function t(n){return n=a(n[0]*Aa,n[1]*Aa),[n[0]*h+c,s-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(s-n[1])/h),n&&[n[0]*Ca,n[1]*Ca]}function r(){a=je(o=ir(m,y,x),i);var n=i(v,d);return c=g-n[0]*h,s=p+n[1]*h,u()}function u(){return l&&(l.valid=!1,l=null),t}var i,o,a,c,s,l,f=Je(function(n,t){return n=i(n,t),[n[0]*h+c,s-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,y=0,x=0,M=Sc,_=wt,b=null,w=null;return t.stream=function(n){return l&&(l.valid=!1),l=er(M(o,f(_(n)))),l.valid=!0,l},t.clipAngle=function(n){return arguments.length?(M=null==n?(b=n,Sc):De((b=+n)*Aa),u()):b},t.clipExtent=function(n){return arguments.length?(w=n,_=n?Ue(n[0][0],n[0][1],n[1][0],n[1][1]):wt,u()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Aa,d=n[1]%360*Aa,r()):[v*Ca,d*Ca]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Aa,y=n[1]%360*Aa,x=n.length>2?n[2]%360*Aa:0,r()):[m*Ca,y*Ca,x*Ca]},Zo.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function er(n){return Qe(n,function(t,e){n.point(t*Aa,e*Aa)})}function rr(n,t){return[n,t]}function ur(n,t){return[n>ba?n-wa:-ba>n?n+wa:n,t]}function ir(n,t,e){return n?t||e?je(ar(n),cr(t,e)):ar(n):t||e?cr(t,e):ur}function or(n){return function(t,e){return t+=n,[t>ba?t-wa:-ba>t?t+wa:t,e]}}function ar(n){var t=or(n);return t.invert=or(-n),t}function cr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*r+a*u;return[Math.atan2(c*i-l*o,a*r-s*u),G(l*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*i-c*o;return[Math.atan2(c*i+s*o,a*r+l*u),G(l*r-a*u)]},e}function sr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=lr(e,u),i=lr(e,i),(o>0?i>u:u>i)&&(u+=o*wa)):(u=n+o*wa,i=n-.5*c);for(var s,l=u;o>0?l>i:i>l;l-=c)a.point((s=de([e,-r*Math.cos(l),-r*Math.sin(l)]))[0],s[1])}}function lr(n,t){var e=le(t);e[0]-=n,ve(e);var r=J(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-ka)%(2*Math.PI)}function fr(n,t,e){var r=Zo.range(n,t-ka,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function hr(n,t,e){var r=Zo.range(n,t-ka,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function gr(n){return n.source}function pr(n){return n.target}function vr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),s=u*Math.sin(n),l=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(tt(r-t)+u*o*tt(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*l,u=e*s+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Ca,Math.atan2(o,Math.sqrt(r*r+u*u))*Ca]}:function(){return[n*Ca,t*Ca]};return p.distance=h,p}function dr(){function n(n,u){var i=Math.sin(u*=Aa),o=Math.cos(u),a=ua((n*=Aa)-t),c=Math.cos(a);Dc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Pc.point=function(u,i){t=u*Aa,e=Math.sin(i*=Aa),r=Math.cos(i),Pc.point=n},Pc.lineEnd=function(){Pc.point=Pc.lineEnd=v}}function mr(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function yr(n,t){function e(n,t){o>0?-Sa+ka>t&&(t=-Sa+ka):t>Sa-ka&&(t=Sa-ka);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ba/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=B(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Sa]},e):Mr}function xr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return ua(u)u;u++){for(;r>1&&W(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function Er(n,t){return n[0]-t[0]||n[1]-t[1]}function Ar(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Cr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],s=e[1],l=t[1]-c,f=r[1]-s,h=(a*(c-s)-f*(u-i))/(f*o-a*l);return[u+h*o,c+h*l]}function Nr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function zr(){Gr(this),this.edge=this.site=this.circle=null}function Lr(n){var t=Bc.pop()||new zr;return t.site=n,t}function Tr(n){Yr(n),Vc.remove(n),Bc.push(n),Gr(n)}function qr(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Tr(n);for(var c=i;c.circle&&ua(e-c.circle.x)l;++l)s=a[l],c=a[l-1],Br(s.edge,c.site,s.site,u);c=a[0],s=a[f-1],s.edge=Xr(c.site,s.site,null,u),Or(c),Or(s)}function Rr(n){for(var t,e,r,u,i=n.x,o=n.y,a=Vc._;a;)if(r=Dr(a,o)-i,r>ka)a=a.L;else{if(u=i-Pr(a,o),!(u>ka)){r>-ka?(t=a.P,e=a):u>-ka?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Lr(n);if(Vc.insert(t,c),t||e){if(t===e)return Yr(t),e=Lr(t.site),Vc.insert(c,e),c.edge=e.edge=Xr(t.site,c.site),Or(t),Or(e),void 0;if(!e)return c.edge=Xr(t.site,c.site),void 0;Yr(t),Yr(e);var s=t.site,l=s.x,f=s.y,h=n.x-l,g=n.y-f,p=e.site,v=p.x-l,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,x=v*v+d*d,M={x:(d*y-g*x)/m+l,y:(h*x-v*y)/m+f};Br(e.edge,s,p,M),c.edge=Xr(s,n,null,M),e.edge=Xr(n,p,null,M),Or(t),Or(e)}}function Dr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,s=c-t;if(!s)return a;var l=a-r,f=1/i-1/s,h=l/s;return f?(-h+Math.sqrt(h*h-2*f*(l*l/(-2*s)-c+s/2+u-i/2)))/f+r:(r+a)/2}function Pr(n,t){var e=n.N;if(e)return Dr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ur(n){this.site=n,this.edges=[]}function jr(n){for(var t,e,r,u,i,o,a,c,s,l,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Zc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)l=a[o].end(),r=l.x,u=l.y,s=a[++o%c].start(),t=s.x,e=s.y,(ua(r-t)>ka||ua(u-e)>ka)&&(a.splice(o,0,new Wr($r(i.site,l,ua(r-f)ka?{x:f,y:ua(t-f)ka?{x:ua(e-p)ka?{x:h,y:ua(t-h)ka?{x:ua(e-g)=-Ea)){var g=c*c+s*s,p=l*l+f*f,v=(f*g-s*p)/h,d=(c*p-l*g)/h,f=d+a,m=Wc.pop()||new Fr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,x=$c._;x;)if(m.yd||d>=a)return;if(h>p){if(i){if(i.y>=s)return}else i={x:d,y:c};e={x:d,y:s}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=s)return}else i={x:(c-u)/r,y:c};e={x:(s-u)/r,y:s}}else{if(i){if(i.yg){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.xi&&(u=t.substring(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,c.push({i:o,x:lu(e,r)})),i=Kc.lastIndex;return ir;++r)a[(e=c[r]).i]=e.x(n);return a.join("")})}function hu(n,t){for(var e,r=Zo.interpolators.length;--r>=0&&!(e=Zo.interpolators[r](n,t)););return e}function gu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(hu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function pu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function vu(n){return function(t){return 1-n(1-t)}}function du(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function mu(n){return n*n}function yu(n){return n*n*n}function xu(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Mu(n){return function(t){return Math.pow(t,n)}}function _u(n){return 1-Math.cos(n*Sa)}function bu(n){return Math.pow(2,10*(n-1))}function wu(n){return 1-Math.sqrt(1-n*n)}function Su(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/wa*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*wa/t)}}function ku(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Eu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Au(n,t){n=Zo.hcl(n),t=Zo.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return ot(e+i*n,r+o*n,u+a*n)+""}}function Cu(n,t){n=Zo.hsl(n),t=Zo.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return ut(e+i*n,r+o*n,u+a*n)+""}}function Nu(n,t){n=Zo.lab(n),t=Zo.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ct(e+i*n,r+o*n,u+a*n)+""}}function zu(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Lu(n){var t=[n.a,n.b],e=[n.c,n.d],r=qu(t),u=Tu(t,e),i=qu(Ru(e,t,-u))||0;t[0]*e[1]180?l+=360:l-s>180&&(s+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:lu(s,l)})):l&&r.push(r.pop()+"rotate("+l+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:lu(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:lu(g[0],p[0])},{i:e-2,x:lu(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i=0;)e.push(u[r])}function Bu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++oe;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function ii(n){return n.reduce(oi,0)}function oi(n,t){return n+t[1]}function ai(n,t){return ci(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function ci(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function si(n){return[Zo.min(n),Zo.max(n)]}function li(n,t){return n.value-t.value}function fi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function hi(n,t){n._pack_next=t,t._pack_prev=n}function gi(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function pi(n){function t(n){l=Math.min(n.x-n.r,l),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(s=e.length)){var e,r,u,i,o,a,c,s,l=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(vi),r=e[0],r.x=-r.r,r.y=0,t(r),s>1&&(u=e[1],u.x=u.r,u.y=0,t(u),s>2))for(i=e[2],yi(r,u,i),t(i),fi(r,i),r._pack_prev=i,fi(i,u),u=r._pack_next,o=3;s>o;o++){yi(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(gi(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!gi(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.ro;o++)i=e[o],i.x-=m,i.y-=y,x=Math.max(x,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=x,e.forEach(di)}}function vi(n){n._pack_next=n._pack_prev=n}function di(n){delete n._pack_next,delete n._pack_prev}function mi(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Si(n,t,e){return n.a.parent===t.parent?n.a:e}function ki(n){return 1+Zo.max(n,function(n){return n.y})}function Ei(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Ai(n){var t=n.children;return t&&t.length?Ai(t[0]):n}function Ci(n){var t,e=n.children;return e&&(t=e.length)?Ci(e[t-1]):n}function Ni(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function zi(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Li(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ti(n){return n.rangeExtent?n.rangeExtent():Li(n.range())}function qi(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Ri(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Di(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:ss}function Pi(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Pi:qi,c=r?Uu:Pu;return o=u(n,t,c,e),a=u(t,n,c,hu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(zu)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Oi(n,t)},i.tickFormat=function(t,e){return Yi(n,t,e)},i.nice=function(t){return Hi(n,t),u()},i.copy=function(){return Ui(n,t,e,r)},u()}function ji(n,t){return Zo.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Hi(n,t){return Ri(n,Di(Fi(n,t)[2]))}function Fi(n,t){null==t&&(t=10);var e=Li(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Oi(n,t){return Zo.range.apply(Zo,Fi(n,t))}function Yi(n,t,e){var r=Fi(n,t);if(e){var u=Ga.exec(e);if(u.shift(),"s"===u[8]){var i=Zo.formatPrefix(Math.max(ua(r[0]),ua(r[1])));return u[7]||(u[7]="."+Ii(i.scale(r[2]))),u[8]="f",e=Zo.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+Zi(u[8],r)),e=u.join("")}else e=",."+Ii(r[2])+"f";return Zo.format(e)}function Ii(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Zi(n,t){var e=Ii(t[2]);return n in ls?Math.abs(e-Ii(Math.max(ua(t[0]),ua(t[1]))))+ +("e"!==n):e-2*("%"===n)}function Vi(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Ri(r.map(u),e?Math:hs);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Li(r),o=[],a=n[0],c=n[1],s=Math.floor(u(a)),l=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(l-s)){if(e){for(;l>s;s++)for(var h=1;f>h;h++)o.push(i(s)*h);o.push(i(s))}else for(o.push(i(s));s++0;h--)o.push(i(s)*h);for(s=0;o[s]c;l--);o=o.slice(s,l)}return o},o.tickFormat=function(n,t){if(!arguments.length)return fs;arguments.length<2?t=fs:"function"!=typeof t&&(t=Zo.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return Vi(n.copy(),t,e,r)},ji(o,n)}function Xi(n,t,e){function r(t){return n(u(t))}var u=$i(t),i=$i(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Oi(e,n)},r.tickFormat=function(n,t){return Yi(e,n,t)},r.nice=function(n){return r.domain(Hi(e,n))},r.exponent=function(o){return arguments.length?(u=$i(t=o),i=$i(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Xi(n.copy(),t,e)},ji(r,n)}function $i(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Bi(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return Zo.range(n.length).map(function(n){return t+e*n})}var u,i,a;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new o;for(var i,a=-1,c=r.length;++an?[0/0,0/0]:[n>0?o[n-1]:e[0],nt?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return Ji(n,t,e)},u()}function Gi(n,t){function e(e){return e>=e?t[Zo.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return Gi(n,t)},e}function Ki(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Oi(n,t)},t.tickFormat=function(t,e){return Yi(n,t,e)},t.copy=function(){return Ki(n)},t}function Qi(n){return n.innerRadius}function no(n){return n.outerRadius}function to(n){return n.startAngle}function eo(n){return n.endAngle}function ro(n){function t(t){function o(){s.push("M",i(n(l),a))}for(var c,s=[],l=[],f=-1,h=t.length,g=bt(e),p=bt(r);++f1&&u.push("H",r[0]),u.join("")}function ao(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var s=2;s9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function So(n){return n.length<3?uo(n):n[0]+ho(n,wo(n))}function ko(n){for(var t,e,r,u=-1,i=n.length;++ue?s():(u.active=e,i.event&&i.event.start.call(n,l,t),i.tween.forEach(function(e,r){(r=r.call(n,l,t))&&v.push(r)}),Zo.timer(function(){return p.c=c(r||1)?we:c,1},0,a),void 0)}function c(r){if(u.active!==e)return s();for(var o=r/g,a=f(o),c=v.length;c>0;)v[--c].call(n,a); +return o>=1?(i.event&&i.event.end.call(n,l,t),s()):void 0}function s(){return--u.count?delete u[e]:delete n.__transition__,1}var l=n.__data__,f=i.ease,h=i.delay,g=i.duration,p=Ba,v=[];return p.t=h+a,r>=h?o(r-h):(p.c=o,void 0)},0,a)}}function Uo(n,t){n.attr("transform",function(n){return"translate("+t(n)+",0)"})}function jo(n,t){n.attr("transform",function(n){return"translate(0,"+t(n)+")"})}function Ho(n){return n.toISOString()}function Fo(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=Zo.bisect(Us,u);return i==Us.length?[t.year,Fi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Us[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=Oo(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Oo(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Li(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Oo(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Fo(n.copy(),t,e)},ji(r,n)}function Oo(n){return new Date(n)}function Yo(n){return JSON.parse(n.responseText)}function Io(n){var t=$o.createRange();return t.selectNode($o.body),t.createContextualFragment(n.responseText)}var Zo={version:"3.4.11"};Date.now||(Date.now=function(){return+new Date});var Vo=[].slice,Xo=function(n){return Vo.call(n)},$o=document,Bo=$o.documentElement,Wo=window;try{Xo(Bo.childNodes)[0].nodeType}catch(Jo){Xo=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}try{$o.createElement("div").style.setProperty("opacity",0,"")}catch(Go){var Ko=Wo.Element.prototype,Qo=Ko.setAttribute,na=Ko.setAttributeNS,ta=Wo.CSSStyleDeclaration.prototype,ea=ta.setProperty;Ko.setAttribute=function(n,t){Qo.call(this,n,t+"")},Ko.setAttributeNS=function(n,t,e){na.call(this,n,t,e+"")},ta.setProperty=function(n,t,e){ea.call(this,n,t+"",e)}}Zo.ascending=n,Zo.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},Zo.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=e);)e=void 0;for(;++ur&&(e=r)}else{for(;++u=e);)e=void 0;for(;++ur&&(e=r)}return e},Zo.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=e);)e=void 0;for(;++ue&&(e=r)}else{for(;++u=e);)e=void 0;for(;++ue&&(e=r)}return e},Zo.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i=e);)e=u=void 0;for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=e);)e=void 0;for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},Zo.sum=function(n,t){var e,r=0,u=n.length,i=-1;if(1===arguments.length)for(;++i1&&(e=e.map(r)),e=e.filter(t),e.length?Zo.quantile(e.sort(n),.5):void 0};var ra=e(n);Zo.bisectLeft=ra.left,Zo.bisect=Zo.bisectRight=ra.right,Zo.bisector=function(t){return e(1===t.length?function(e,r){return n(t(e),r)}:t)},Zo.shuffle=function(n){for(var t,e,r=n.length;r;)e=0|Math.random()*r--,t=n[r],n[r]=n[e],n[e]=t;return n},Zo.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},Zo.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},Zo.zip=function(){if(!(u=arguments.length))return[];for(var n=-1,t=Zo.min(arguments,r),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var ua=Math.abs;Zo.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/e)throw new Error("infinite range");var r,i=[],o=u(ua(e)),a=-1;if(n*=o,t*=o,e*=o,0>e)for(;(r=n+e*++a)>t;)i.push(r/o);else for(;(r=n+e*++a)=i.length)return r?r.call(u,a):e?a.sort(e):a;for(var s,l,f,h,g=-1,p=a.length,v=i[c++],d=new o;++g=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(Zo.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},Zo.set=function(n){var t=new h;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},i(h,{has:a,add:function(n){return this[ia+n]=!0,n},remove:function(n){return n=ia+n,n in this&&delete this[n]},values:s,size:l,empty:f,forEach:function(n){for(var t in this)t.charCodeAt(0)===oa&&n.call(this,t.substring(1))}}),Zo.behavior={},Zo.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},Zo.event=null,Zo.requote=function(n){return n.replace(ca,"\\$&")};var ca=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},la=function(n,t){return t.querySelector(n)},fa=function(n,t){return t.querySelectorAll(n)},ha=Bo.matches||Bo[p(Bo,"matchesSelector")],ga=function(n,t){return ha.call(n,t)};"function"==typeof Sizzle&&(la=function(n,t){return Sizzle(n,t)[0]||null},fa=Sizzle,ga=Sizzle.matchesSelector),Zo.selection=function(){return ma};var pa=Zo.selection.prototype=[];pa.select=function(n){var t,e,r,u,i=[];n=b(n);for(var o=-1,a=this.length;++o=0&&(e=n.substring(0,t),n=n.substring(t+1)),va.hasOwnProperty(e)?{space:va[e],local:n}:n}},pa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=Zo.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(S(t,n[t]));return this}return this.each(S(n,t))},pa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=A(n)).length,u=-1;if(t=e.classList){for(;++ur){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(z(e,n[e],t));return this}if(2>r)return Wo.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(z(n,t,e))},pa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(L(t,n[t]));return this}return this.each(L(n,t))},pa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},pa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},pa.append=function(n){return n=T(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},pa.insert=function(n,t){return n=T(n),t=b(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},pa.remove=function(){return this.each(function(){var n=this.parentNode;n&&n.removeChild(this)})},pa.data=function(n,t){function e(n,e){var r,u,i,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new o,y=new o,x=[];for(r=-1;++rr;++r)p[r]=q(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,c.push(p),s.push(g),l.push(v)}var r,u,i=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return _(u)},pa.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},pa.sort=function(n){n=D.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},pa.size=function(){var n=0;return this.each(function(){++n}),n};var da=[];Zo.selection.enter=U,Zo.selection.enter.prototype=da,da.append=pa.append,da.empty=pa.empty,da.node=pa.node,da.call=pa.call,da.size=pa.size,da.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(F(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(F(n,t,e))};var ya=Zo.map({mouseenter:"mouseover",mouseleave:"mouseout"});ya.forEach(function(n){"on"+n in $o&&ya.remove(n)});var xa="onselectstart"in $o?null:p(Bo.style,"userSelect"),Ma=0;Zo.mouse=function(n){return Z(n,x())};var _a=/WebKit/.test(Wo.navigator.userAgent)?-1:0;Zo.touches=function(n,t){return arguments.length<2&&(t=x().touches),t?Xo(t).map(function(t){var e=Z(n,t);return e.identifier=t.identifier,e}):[]},Zo.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",i)}function t(n,t,u,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-x[0],e=r[1]-x[1],p|=n|e,x=r,g({type:"drag",x:r[0]+s[0],y:r[1]+s[1],dx:n,dy:e}))}function c(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&Zo.event.target===f),g({type:"dragend"}))}var s,l=this,f=Zo.event.target,h=l.parentNode,g=e.of(l,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=Zo.select(u()).on(i+d,a).on(o+d,c),y=I(),x=t(h,v);r?(s=r.apply(l,arguments),s=[s.x-x[0],s.y-x[1]]):s=[0,0],g({type:"dragstart"})}}var e=M(n,"drag","dragstart","dragend"),r=null,u=t(v,Zo.mouse,$,"mousemove","mouseup"),i=t(V,Zo.touch,X,"touchmove","touchend");return n.origin=function(t){return arguments.length?(r=t,n):r},Zo.rebind(n,e,"on")};var ba=Math.PI,wa=2*ba,Sa=ba/2,ka=1e-6,Ea=ka*ka,Aa=ba/180,Ca=180/ba,Na=Math.SQRT2,za=2,La=4;Zo.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=Q(v),o=i/(za*h)*(e*nt(Na*t+v)-K(v));return[r+o*s,u+o*l,i*e/Q(Na*t+v)]}return[r+n*s,u+n*l,i*Math.exp(Na*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],s=o-r,l=a-u,f=s*s+l*l,h=Math.sqrt(f),g=(c*c-i*i+La*f)/(2*i*za*h),p=(c*c-i*i-La*f)/(2*c*za*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Na;return e.duration=1e3*y,e},Zo.behavior.zoom=function(){function n(n){n.on(A,s).on(Ra+".zoom",f).on("dblclick.zoom",h).on(z,l)}function t(n){return[(n[0]-S.x)/S.k,(n[1]-S.y)/S.k]}function e(n){return[n[0]*S.k+S.x,n[1]*S.k+S.y]}function r(n){S.k=Math.max(E[0],Math.min(E[1],n))}function u(n,t){t=e(t),S.x+=n[0]-t[0],S.y+=n[1]-t[1]}function i(){_&&_.domain(x.range().map(function(n){return(n-S.x)/S.k}).map(x.invert)),w&&w.domain(b.range().map(function(n){return(n-S.y)/S.k}).map(b.invert))}function o(n){n({type:"zoomstart"})}function a(n){i(),n({type:"zoom",scale:S.k,translate:[S.x,S.y]})}function c(n){n({type:"zoomend"})}function s(){function n(){l=1,u(Zo.mouse(r),h),a(s)}function e(){f.on(C,null).on(N,null),g(l&&Zo.event.target===i),c(s)}var r=this,i=Zo.event.target,s=L.of(r,arguments),l=0,f=Zo.select(Wo).on(C,n).on(N,e),h=t(Zo.mouse(r)),g=I();H.call(r),o(s)}function l(){function n(){var n=Zo.touches(g);return h=S.k,n.forEach(function(n){n.identifier in v&&(v[n.identifier]=t(n))}),n}function e(){var t=Zo.event.target;Zo.select(t).on(M,i).on(_,f),b.push(t);for(var e=Zo.event.changedTouches,o=0,c=e.length;c>o;++o)v[e[o].identifier]=null;var s=n(),l=Date.now();if(1===s.length){if(500>l-m){var h=s[0],g=v[h.identifier];r(2*S.k),u(h,g),y(),a(p)}m=l}else if(s.length>1){var h=s[0],x=s[1],w=h[0]-x[0],k=h[1]-x[1];d=w*w+k*k}}function i(){for(var n,t,e,i,o=Zo.touches(g),c=0,s=o.length;s>c;++c,i=null)if(e=o[c],i=v[e.identifier]){if(t)break;n=e,t=i}if(i){var l=(l=e[0]-n[0])*l+(l=e[1]-n[1])*l,f=d&&Math.sqrt(l/d);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+i[0])/2,(t[1]+i[1])/2],r(f*h)}m=null,u(n,t),a(p)}function f(){if(Zo.event.touches.length){for(var t=Zo.event.changedTouches,e=0,r=t.length;r>e;++e)delete v[t[e].identifier];for(var u in v)return void n()}Zo.selectAll(b).on(x,null),w.on(A,s).on(z,l),k(),c(p)}var h,g=this,p=L.of(g,arguments),v={},d=0,x=".zoom-"+Zo.event.changedTouches[0].identifier,M="touchmove"+x,_="touchend"+x,b=[],w=Zo.select(g).on(A,null).on(z,e),k=I();H.call(g),e(),o(p)}function f(){var n=L.of(this,arguments);d?clearTimeout(d):(g=t(p=v||Zo.mouse(this)),H.call(this),o(n)),d=setTimeout(function(){d=null,c(n)},50),y(),r(Math.pow(2,.002*Ta())*S.k),u(p,g),a(n)}function h(){var n=L.of(this,arguments),e=Zo.mouse(this),i=t(e),s=Math.log(S.k)/Math.LN2;o(n),r(Math.pow(2,Zo.event.shiftKey?Math.ceil(s)-1:Math.floor(s)+1)),u(e,i),a(n),c(n)}var g,p,v,d,m,x,_,b,w,S={x:0,y:0,k:1},k=[960,500],E=qa,A="mousedown.zoom",C="mousemove.zoom",N="mouseup.zoom",z="touchstart.zoom",L=M(n,"zoomstart","zoom","zoomend");return n.event=function(n){n.each(function(){var n=L.of(this,arguments),t=S;Ss?Zo.select(this).transition().each("start.zoom",function(){S=this.__chart__||{x:0,y:0,k:1},o(n)}).tween("zoom:zoom",function(){var e=k[0],r=k[1],u=e/2,i=r/2,o=Zo.interpolateZoom([(u-S.x)/S.k,(i-S.y)/S.k,e/S.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),c=e/r[2];this.__chart__=S={x:u-r[0]*c,y:i-r[1]*c,k:c},a(n)}}).each("end.zoom",function(){c(n)}):(this.__chart__=S,o(n),a(n),c(n))})},n.translate=function(t){return arguments.length?(S={x:+t[0],y:+t[1],k:S.k},i(),n):[S.x,S.y]},n.scale=function(t){return arguments.length?(S={x:S.x,y:S.y,k:+t},i(),n):S.k},n.scaleExtent=function(t){return arguments.length?(E=null==t?qa:[+t[0],+t[1]],n):E},n.center=function(t){return arguments.length?(v=t&&[+t[0],+t[1]],n):v},n.size=function(t){return arguments.length?(k=t&&[+t[0],+t[1]],n):k},n.x=function(t){return arguments.length?(_=t,x=t.copy(),S={x:0,y:0,k:1},n):_},n.y=function(t){return arguments.length?(w=t,b=t.copy(),S={x:0,y:0,k:1},n):w},Zo.rebind(n,L,"on")};var Ta,qa=[0,1/0],Ra="onwheel"in $o?(Ta=function(){return-Zo.event.deltaY*(Zo.event.deltaMode?120:1)},"wheel"):"onmousewheel"in $o?(Ta=function(){return Zo.event.wheelDelta},"mousewheel"):(Ta=function(){return-Zo.event.detail},"MozMousePixelScroll");Zo.color=et,et.prototype.toString=function(){return this.rgb()+""},Zo.hsl=rt;var Da=rt.prototype=new et;Da.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new rt(this.h,this.s,this.l/n)},Da.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new rt(this.h,this.s,n*this.l)},Da.rgb=function(){return ut(this.h,this.s,this.l)},Zo.hcl=it;var Pa=it.prototype=new et;Pa.brighter=function(n){return new it(this.h,this.c,Math.min(100,this.l+Ua*(arguments.length?n:1)))},Pa.darker=function(n){return new it(this.h,this.c,Math.max(0,this.l-Ua*(arguments.length?n:1)))},Pa.rgb=function(){return ot(this.h,this.c,this.l).rgb()},Zo.lab=at;var Ua=18,ja=.95047,Ha=1,Fa=1.08883,Oa=at.prototype=new et;Oa.brighter=function(n){return new at(Math.min(100,this.l+Ua*(arguments.length?n:1)),this.a,this.b)},Oa.darker=function(n){return new at(Math.max(0,this.l-Ua*(arguments.length?n:1)),this.a,this.b)},Oa.rgb=function(){return ct(this.l,this.a,this.b)},Zo.rgb=gt;var Ya=gt.prototype=new et;Ya.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new gt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new gt(u,u,u)},Ya.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new gt(n*this.r,n*this.g,n*this.b)},Ya.hsl=function(){return yt(this.r,this.g,this.b)},Ya.toString=function(){return"#"+dt(this.r)+dt(this.g)+dt(this.b)};var Ia=Zo.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Ia.forEach(function(n,t){Ia.set(n,pt(t))}),Zo.functor=bt,Zo.xhr=St(wt),Zo.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=kt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(l>=s)return o;if(u)return u=!1,i;var t=l;if(34===n.charCodeAt(t)){for(var e=t;e++l;){var r=n.charCodeAt(l++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(l)&&(++l,++a);else if(r!==c)continue;return n.substring(t,l-a)}return n.substring(t)}for(var r,u,i={},o={},a=[],s=n.length,l=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();(!t||(h=t(h,f++)))&&a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new h,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},Zo.csv=Zo.dsv(",","text/csv"),Zo.tsv=Zo.dsv(" ","text/tab-separated-values"),Zo.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=x().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return Z(n,r)};var Za,Va,Xa,$a,Ba,Wa=Wo[p(Wo,"requestAnimationFrame")]||function(n){setTimeout(n,17)};Zo.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};Va?Va.n=i:Za=i,Va=i,Xa||($a=clearTimeout($a),Xa=1,Wa(At))},Zo.timer.flush=function(){Ct(),Nt()},Zo.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var Ja=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Lt);Zo.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=Zo.round(n,zt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),Ja[8+e/3]};var Ga=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,Ka=Zo.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=Zo.round(n,zt(n,t))).toFixed(Math.max(0,Math.min(20,zt(n*(1+1e-15),t))))}}),Qa=Zo.time={},nc=Date;Rt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){tc.setUTCDate.apply(this._,arguments)},setDay:function(){tc.setUTCDay.apply(this._,arguments)},setFullYear:function(){tc.setUTCFullYear.apply(this._,arguments)},setHours:function(){tc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){tc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){tc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){tc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){tc.setUTCSeconds.apply(this._,arguments)},setTime:function(){tc.setTime.apply(this._,arguments)}};var tc=Date.prototype;Qa.year=Dt(function(n){return n=Qa.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),Qa.years=Qa.year.range,Qa.years.utc=Qa.year.utc.range,Qa.day=Dt(function(n){var t=new nc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),Qa.days=Qa.day.range,Qa.days.utc=Qa.day.utc.range,Qa.dayOfYear=function(n){var t=Qa.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=Qa[n]=Dt(function(n){return(n=Qa.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=Qa.year(n).getDay();return Math.floor((Qa.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});Qa[n+"s"]=e.range,Qa[n+"s"].utc=e.utc.range,Qa[n+"OfYear"]=function(n){var e=Qa.year(n).getDay();return Math.floor((Qa.dayOfYear(n)+(e+t)%7)/7)}}),Qa.week=Qa.sunday,Qa.weeks=Qa.sunday.range,Qa.weeks.utc=Qa.sunday.utc.range,Qa.weekOfYear=Qa.sundayOfYear;var ec={"-":"",_:" ",0:"0"},rc=/^\s*\d+/,uc=/^%/;Zo.locale=function(n){return{numberFormat:Tt(n),timeFormat:Ut(n)}};var ic=Zo.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});Zo.format=ic.numberFormat,Zo.geo={},ue.prototype={s:0,t:0,add:function(n){ie(n,this.t,oc),ie(oc.s,this.s,this),this.s?this.t+=oc.t:this.s=oc.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var oc=new ue;Zo.geo.stream=function(n,t){n&&ac.hasOwnProperty(n.type)?ac[n.type](n,t):oe(n,t)};var ac={Feature:function(n,t){oe(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ba+n:n,fc.lineStart=fc.lineEnd=fc.point=v}};Zo.geo.bounds=function(){function n(n,t){x.push(M=[l=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=le([t*Aa,e*Aa]);if(m){var u=he(m,r),i=[u[1],-u[0],0],o=he(i,u);ve(o),o=de(o);var c=t-p,s=c>0?1:-1,v=o[0]*Ca*s,d=ua(c)>180;if(d^(v>s*p&&s*t>v)){var y=o[1]*Ca;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>s*p&&s*t>v)){var y=-o[1]*Ca;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t):h>=l?(l>t&&(l=t),t>h&&(h=t)):t>p?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t)}else n(t,e);m=r,p=t}function e(){_.point=t}function r(){M[0]=l,M[1]=h,_.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=ua(r)>180?r+(r>0?360:-360):r}else v=n,d=e;fc.point(n,e),t(n,e)}function i(){fc.lineStart()}function o(){u(v,d),fc.lineEnd(),ua(y)>ka&&(l=-(h=180)),M[0]=l,M[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function s(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nlc?(l=-(h=180),f=-(g=90)):y>ka?g=90:-ka>y&&(f=-90),M[0]=l,M[1]=h}};return function(n){g=h=-(l=f=1/0),x=[],Zo.geo.stream(n,_);var t=x.length;if(t){x.sort(c);for(var e,r=1,u=x[0],i=[u];t>r;++r)e=x[r],s(e[0],u)||s(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e); +for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,l=e[0],h=u[1])}return x=M=null,1/0===l||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[l,f],[h,g]]}}(),Zo.geo.centroid=function(n){hc=gc=pc=vc=dc=mc=yc=xc=Mc=_c=bc=0,Zo.geo.stream(n,wc);var t=Mc,e=_c,r=bc,u=t*t+e*e+r*r;return Ea>u&&(t=mc,e=yc,r=xc,ka>gc&&(t=pc,e=vc,r=dc),u=t*t+e*e+r*r,Ea>u)?[0/0,0/0]:[Math.atan2(e,t)*Ca,G(r/Math.sqrt(u))*Ca]};var hc,gc,pc,vc,dc,mc,yc,xc,Mc,_c,bc,wc={sphere:v,point:ye,lineStart:Me,lineEnd:_e,polygonStart:function(){wc.lineStart=be},polygonEnd:function(){wc.lineStart=Me}},Sc=Ae(we,Te,Re,[-ba,-ba/2]),kc=1e9;Zo.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ue(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(Zo.geo.conicEqualArea=function(){return He(Fe)}).raw=Fe,Zo.geo.albers=function(){return Zo.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},Zo.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=Zo.geo.albers(),o=Zo.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=Zo.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var s=i.scale(),l=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[l-.455*s,f-.238*s],[l+.455*s,f+.238*s]]).stream(c).point,r=o.translate([l-.307*s,f+.201*s]).clipExtent([[l-.425*s+ka,f+.12*s+ka],[l-.214*s-ka,f+.234*s-ka]]).stream(c).point,u=a.translate([l-.205*s,f+.212*s]).clipExtent([[l-.214*s+ka,f+.166*s+ka],[l-.115*s-ka,f+.234*s-ka]]).stream(c).point,n},n.scale(1070)};var Ec,Ac,Cc,Nc,zc,Lc,Tc={point:v,lineStart:v,lineEnd:v,polygonStart:function(){Ac=0,Tc.lineStart=Oe},polygonEnd:function(){Tc.lineStart=Tc.lineEnd=Tc.point=v,Ec+=ua(Ac/2)}},qc={point:Ye,lineStart:v,lineEnd:v,polygonStart:v,polygonEnd:v},Rc={point:Ve,lineStart:Xe,lineEnd:$e,polygonStart:function(){Rc.lineStart=Be},polygonEnd:function(){Rc.point=Ve,Rc.lineStart=Xe,Rc.lineEnd=$e}};Zo.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),Zo.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Ec=0,Zo.geo.stream(n,u(Tc)),Ec},n.centroid=function(n){return pc=vc=dc=mc=yc=xc=Mc=_c=bc=0,Zo.geo.stream(n,u(Rc)),bc?[Mc/bc,_c/bc]:xc?[mc/xc,yc/xc]:dc?[pc/dc,vc/dc]:[0/0,0/0]},n.bounds=function(n){return zc=Lc=-(Cc=Nc=1/0),Zo.geo.stream(n,u(qc)),[[Cc,Nc],[zc,Lc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||Ge(n):wt,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Ie:new We(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(Zo.geo.albersUsa()).context(null)},Zo.geo.transform=function(n){return{stream:function(t){var e=new Ke(t);for(var r in n)e[r]=n[r];return e}}},Ke.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},Zo.geo.projection=nr,Zo.geo.projectionMutator=tr,(Zo.geo.equirectangular=function(){return nr(rr)}).raw=rr.invert=rr,Zo.geo.rotation=function(n){function t(t){return t=n(t[0]*Aa,t[1]*Aa),t[0]*=Ca,t[1]*=Ca,t}return n=ir(n[0]%360*Aa,n[1]*Aa,n.length>2?n[2]*Aa:0),t.invert=function(t){return t=n.invert(t[0]*Aa,t[1]*Aa),t[0]*=Ca,t[1]*=Ca,t},t},ur.invert=rr,Zo.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=ir(-n[0]*Aa,-n[1]*Aa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ca,n[1]*=Ca}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=sr((t=+r)*Aa,u*Aa),n):t},n.precision=function(r){return arguments.length?(e=sr(t*Aa,(u=+r)*Aa),n):u},n.angle(90)},Zo.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Aa,u=n[1]*Aa,i=t[1]*Aa,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),s=Math.cos(u),l=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=s*l-c*f*a)*e),c*l+s*f*a)},Zo.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return Zo.range(Math.ceil(i/d)*d,u,d).map(h).concat(Zo.range(Math.ceil(s/m)*m,c,m).map(g)).concat(Zo.range(Math.ceil(r/p)*p,e,p).filter(function(n){return ua(n%d)>ka}).map(l)).concat(Zo.range(Math.ceil(a/v)*v,o,v).filter(function(n){return ua(n%m)>ka}).map(f))}var e,r,u,i,o,a,c,s,l,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(s).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],s=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),s>c&&(t=s,s=c,c=t),n.precision(y)):[[i,s],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,l=fr(a,o,90),f=hr(r,e,y),h=fr(s,c,90),g=hr(i,u,y),n):y},n.majorExtent([[-180,-90+ka],[180,90-ka]]).minorExtent([[-180,-80-ka],[180,80+ka]])},Zo.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=gr,u=pr;return n.distance=function(){return Zo.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},Zo.geo.interpolate=function(n,t){return vr(n[0]*Aa,n[1]*Aa,t[0]*Aa,t[1]*Aa)},Zo.geo.length=function(n){return Dc=0,Zo.geo.stream(n,Pc),Dc};var Dc,Pc={sphere:v,point:v,lineStart:dr,lineEnd:v,polygonStart:v,polygonEnd:v},Uc=mr(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(Zo.geo.azimuthalEqualArea=function(){return nr(Uc)}).raw=Uc;var jc=mr(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},wt);(Zo.geo.azimuthalEquidistant=function(){return nr(jc)}).raw=jc,(Zo.geo.conicConformal=function(){return He(yr)}).raw=yr,(Zo.geo.conicEquidistant=function(){return He(xr)}).raw=xr;var Hc=mr(function(n){return 1/n},Math.atan);(Zo.geo.gnomonic=function(){return nr(Hc)}).raw=Hc,Mr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Sa]},(Zo.geo.mercator=function(){return _r(Mr)}).raw=Mr;var Fc=mr(function(){return 1},Math.asin);(Zo.geo.orthographic=function(){return nr(Fc)}).raw=Fc;var Oc=mr(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(Zo.geo.stereographic=function(){return nr(Oc)}).raw=Oc,br.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Sa]},(Zo.geo.transverseMercator=function(){var n=_r(br),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=br,Zo.geom={},Zo.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=bt(e),i=bt(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(Er),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var s=kr(a),l=kr(c),f=l[0]===s[0],h=l[l.length-1]===s[s.length-1],g=[];for(t=s.length-1;t>=0;--t)g.push(n[a[s[t]][2]]);for(t=+f;t=r&&s.x<=i&&s.y>=u&&s.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];l.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/ka)*ka,y:Math.round(o(n,t)/ka)*ka,i:t}})}var r=wr,u=Sr,i=r,o=u,a=Jc;return n?t(n):(t.links=function(n){return tu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return tu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Hr),c=-1,s=a.length,l=a[s-1].edge,f=l.l===o?l.r:l.l;++c=s,h=r>=l,g=(h<<1)+f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=ou()),f?u=s:a=s,h?o=l:c=l,i(n,t,e,r,u,o,a,c)}var l,f,h,g,p,v,d,m,y,x=bt(a),M=bt(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)l=n[g],l.xm&&(m=l.x),l.y>y&&(y=l.y),f.push(l.x),h.push(l.y);else for(g=0;p>g;++g){var _=+x(l=n[g],g),b=+M(l,g);v>_&&(v=_),d>b&&(d=b),_>m&&(m=_),b>y&&(y=b),f.push(_),h.push(b)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=ou();if(k.add=function(n){i(k,n,+x(n,++g),+M(n,g),v,d,m,y)},k.visit=function(n){au(n,k,v,d,m,y)},g=-1,null==t){for(;++g=0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=ns.get(e)||Qc,r=ts.get(r)||wt,pu(r(e.apply(null,Vo.call(arguments,1))))},Zo.interpolateHcl=Au,Zo.interpolateHsl=Cu,Zo.interpolateLab=Nu,Zo.interpolateRound=zu,Zo.transform=function(n){var t=$o.createElementNS(Zo.ns.prefix.svg,"g");return(Zo.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Lu(e?e.matrix:es)})(n)},Lu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var es={a:1,b:0,c:0,d:1,e:0,f:0};Zo.interpolateTransform=Du,Zo.layout={},Zo.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/d){if(p>c){var s=t.charge/c;n.px-=i*s,n.py-=o*s}return!0}if(t.point&&c&&p>c){var s=t.pointCharge/c;n.px-=i*s,n.py-=o*s}}return!t.charge}}function t(n){n.px=Zo.event.x,n.py=Zo.event.y,a.resume()}var e,r,u,i,o,a={},c=Zo.dispatch("start","tick","end"),s=[1,1],l=.9,f=rs,h=us,g=-30,p=is,v=.1,d=.64,m=[],y=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,x,M,_=m.length,b=y.length;for(e=0;b>e;++e)a=y[e],f=a.source,h=a.target,x=h.x-f.x,M=h.y-f.y,(p=x*x+M*M)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,x*=p,M*=p,h.x-=x*(d=f.weight/(h.weight+f.weight)),h.y-=M*d,f.x+=x*(d=1-d),f.y+=M*d);if((d=r*v)&&(x=s[0]/2,M=s[1]/2,e=-1,d))for(;++e<_;)a=m[e],a.x+=(x-a.x)*d,a.y+=(M-a.y)*d;if(g)for(Vu(t=Zo.geom.quadtree(m),r,o),e=-1;++e<_;)(a=m[e]).fixed||t.visit(n(a));for(e=-1;++e<_;)a=m[e],a.fixed?(a.x=a.px,a.y=a.py):(a.x-=(a.px-(a.px=a.x))*l,a.y-=(a.py-(a.py=a.y))*l);c.tick({type:"tick",alpha:r})},a.nodes=function(n){return arguments.length?(m=n,a):m},a.links=function(n){return arguments.length?(y=n,a):y},a.size=function(n){return arguments.length?(s=n,a):s},a.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,a):f},a.distance=a.linkDistance,a.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,a):h},a.friction=function(n){return arguments.length?(l=+n,a):l},a.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,a):g},a.chargeDistance=function(n){return arguments.length?(p=n*n,a):Math.sqrt(p)},a.gravity=function(n){return arguments.length?(v=+n,a):v},a.theta=function(n){return arguments.length?(d=n*n,a):Math.sqrt(d)},a.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),Zo.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=y[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,s=o.length;++at;++t)(r=m[t]).index=t,r.weight=0;for(t=0;l>t;++t)r=y[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;l>t;++t)u[t]=+f.call(this,y[t],t);else for(t=0;l>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;l>t;++t)i[t]=+h.call(this,y[t],t);else for(t=0;l>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=Zo.behavior.drag().origin(wt).on("dragstart.force",Ou).on("drag.force",t).on("dragend.force",Yu)),arguments.length?(this.on("mouseover.force",Iu).on("mouseout.force",Zu).call(e),void 0):e},Zo.rebind(a,c,"on")};var rs=20,us=1,is=1/0;Zo.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(s=e.call(n,i,i.depth))&&(c=s.length)){for(var c,s,l;--c>=0;)o.push(l=s[c]),l.parent=i,l.depth=i.depth+1;r&&(i.value=0),i.children=s}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return Bu(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=Gu,e=Wu,r=Ju;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&($u(t,function(n){n.children&&(n.value=0)}),Bu(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},Zo.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,s=-1;for(r=t.value?r/t.value:0;++sg;++g)for(u.call(n,s[0][g],p=v[g],l[0][g][1]),h=1;d>h;++h)u.call(n,s[h][g],p+=l[h-1][g][1],l[h][g][1]);return a}var t=wt,e=ei,r=ri,u=ti,i=Qu,o=ni;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:as.get(t)||ei,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:cs.get(t)||ri,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var as=Zo.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(ui),i=n.map(ii),o=Zo.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,s=[],l=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],s.push(e)):(c+=i[e],l.push(e));return l.reverse().concat(s)},reverse:function(n){return Zo.range(n.length).reverse()},"default":ei}),cs=Zo.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,s,l=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=s=0,e=1;h>e;++e){for(t=0,u=0;l>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];l>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,s>c&&(s=c)}for(e=0;h>e;++e)g[e]-=s;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ri});Zo.layout.histogram=function(){function n(n,i){for(var o,a,c=[],s=n.map(e,this),l=r.call(this,s,i),f=u.call(this,l,s,i),i=-1,h=s.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=l[0]&&a<=l[1]&&(o=c[Zo.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=si,u=ai;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=bt(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return ci(n,t)}:bt(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},Zo.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],s=u[1],l=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Bu(a,function(n){n.r=+l(n.value)}),Bu(a,pi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/s))/2;Bu(a,function(n){n.r+=f}),Bu(a,pi),Bu(a,function(n){n.r-=f})}return mi(a,c/2,s/2,t?1:1/Math.max(2*a.r/c,2*a.r/s)),o}var t,e=Zo.layout.hierarchy().sort(li),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Xu(n,e)},Zo.layout.tree=function(){function n(n,u){var l=o.call(this,n,u),f=l[0],h=t(f);if(Bu(h,e),h.parent.m=-h.z,$u(h,r),s)$u(f,i);else{var g=f,p=f,v=f;$u(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=c[0]/(p.x+a(p,g)/2+d),y=c[1]/(v.depth||1);$u(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return l}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){wi(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,c=u.parent.children[0],s=u.m,l=i.m,f=o.m,h=c.m;o=_i(o),u=Mi(u),o&&u;)c=Mi(c),i=_i(i),i.a=n,r=o.z+f-u.z-s+a(o._,u._),r>0&&(bi(Si(o,n,e),n,r),s+=r,l+=r),f+=o.m,s+=u.m,h+=c.m,l+=i.m;o&&!_i(i)&&(i.t=o,i.m+=f-l),u&&!Mi(c)&&(c.t=u,c.m+=s-h,e=n)}return e}function i(n){n.x*=c[0],n.y=n.depth*c[1]}var o=Zo.layout.hierarchy().sort(null).value(null),a=xi,c=[1,1],s=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(s=null==(c=t)?i:null,n):s?null:c},n.nodeSize=function(t){return arguments.length?(s=null==(c=t)?null:i,n):s?c:null},Xu(n,o)},Zo.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],s=0;Bu(c,function(n){var t=n.children;t&&t.length?(n.x=Ei(t),n.y=ki(t)):(n.x=o?s+=e(n,o):0,n.y=0,o=n)});var l=Ai(c),f=Ci(c),h=l.x-e(l,f)/2,g=f.x+e(f,l)/2;return Bu(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=Zo.layout.hierarchy().sort(null).value(null),e=xi,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Xu(n,t)},Zo.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,s=f(e),l=[],h=i.slice(),p=1/0,v="slice"===g?s.dx:"dice"===g?s.dy:"slice-dice"===g?1&e.depth?s.dy:s.dx:Math.min(s.dx,s.dy);for(n(h,s.dx*s.dy/e.value),l.area=0;(c=h.length)>0;)l.push(o=h[c-1]),l.area+=o.area,"squarify"!==g||(a=r(l,v))<=p?(h.pop(),p=a):(l.area-=l.pop().area,u(l,v,s,!1),v=Math.min(s.dx,s.dy),l.length=l.area=0,p=1/0);l.length&&(u(l,v,s,!0),l.length=l.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++oe&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,s=e.y,l=t?c(n.area/t):0;if(t==e.dx){for((r||l>e.dy)&&(l=e.dy);++ie.dx)&&(l=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=Zo.random.normal.apply(Zo,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=Zo.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},Zo.scale={};var ss={floor:wt,ceil:wt};Zo.scale.linear=function(){return Ui([0,1],[0,1],hu,!1)};var ls={s:1,g:1,p:1,r:1,e:1};Zo.scale.log=function(){return Vi(Zo.scale.linear().domain([0,1]),10,!0,[1,10])};var fs=Zo.format(".0e"),hs={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};Zo.scale.pow=function(){return Xi(Zo.scale.linear(),1,[0,1])},Zo.scale.sqrt=function(){return Zo.scale.pow().exponent(.5)},Zo.scale.ordinal=function(){return Bi([],{t:"range",a:[[]]})},Zo.scale.category10=function(){return Zo.scale.ordinal().range(gs)},Zo.scale.category20=function(){return Zo.scale.ordinal().range(ps)},Zo.scale.category20b=function(){return Zo.scale.ordinal().range(vs)},Zo.scale.category20c=function(){return Zo.scale.ordinal().range(ds)};var gs=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(vt),ps=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(vt),vs=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(vt),ds=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(vt);Zo.scale.quantile=function(){return Wi([],[])},Zo.scale.quantize=function(){return Ji(0,1,[0,1])},Zo.scale.threshold=function(){return Gi([.5],[0,1])},Zo.scale.identity=function(){return Ki([0,1])},Zo.svg={},Zo.svg.arc=function(){function n(){var n=t.apply(this,arguments),i=e.apply(this,arguments),o=r.apply(this,arguments)+ms,a=u.apply(this,arguments)+ms,c=(o>a&&(c=o,o=a,a=c),a-o),s=ba>c?"0":"1",l=Math.cos(o),f=Math.sin(o),h=Math.cos(a),g=Math.sin(a); +return c>=ys?n?"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"M0,"+n+"A"+n+","+n+" 0 1,0 0,"+-n+"A"+n+","+n+" 0 1,0 0,"+n+"Z":"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"Z":n?"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L"+n*h+","+n*g+"A"+n+","+n+" 0 "+s+",0 "+n*l+","+n*f+"Z":"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L0,0"+"Z"}var t=Qi,e=no,r=to,u=eo;return n.innerRadius=function(e){return arguments.length?(t=bt(e),n):t},n.outerRadius=function(t){return arguments.length?(e=bt(t),n):e},n.startAngle=function(t){return arguments.length?(r=bt(t),n):r},n.endAngle=function(t){return arguments.length?(u=bt(t),n):u},n.centroid=function(){var n=(t.apply(this,arguments)+e.apply(this,arguments))/2,i=(r.apply(this,arguments)+u.apply(this,arguments))/2+ms;return[Math.cos(i)*n,Math.sin(i)*n]},n};var ms=-Sa,ys=wa-ka;Zo.svg.line=function(){return ro(wt)};var xs=Zo.map({linear:uo,"linear-closed":io,step:oo,"step-before":ao,"step-after":co,basis:po,"basis-open":vo,"basis-closed":mo,bundle:yo,cardinal:fo,"cardinal-open":so,"cardinal-closed":lo,monotone:So});xs.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Ms=[0,2/3,1/3,0],_s=[0,1/3,2/3,0],bs=[0,1/6,2/3,1/6];Zo.svg.line.radial=function(){var n=ro(ko);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},ao.reverse=co,co.reverse=ao,Zo.svg.area=function(){return Eo(wt)},Zo.svg.area.radial=function(){var n=Eo(ko);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},Zo.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),s=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,s)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,s.r,s.p0)+r(s.r,s.p1,s.a1-s.a0)+u(s.r,s.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)+ms,l=s.call(n,u,r)+ms;return{r:i,a0:o,a1:l,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(l),i*Math.sin(l)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ba)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=gr,o=pr,a=Ao,c=to,s=eo;return n.radius=function(t){return arguments.length?(a=bt(t),n):a},n.source=function(t){return arguments.length?(i=bt(t),n):i},n.target=function(t){return arguments.length?(o=bt(t),n):o},n.startAngle=function(t){return arguments.length?(c=bt(t),n):c},n.endAngle=function(t){return arguments.length?(s=bt(t),n):s},n},Zo.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=gr,e=pr,r=Co;return n.source=function(e){return arguments.length?(t=bt(e),n):t},n.target=function(t){return arguments.length?(e=bt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},Zo.svg.diagonal.radial=function(){var n=Zo.svg.diagonal(),t=Co,e=n.projection;return n.projection=function(n){return arguments.length?e(No(t=n)):t},n},Zo.svg.symbol=function(){function n(n,r){return(ws.get(t.call(this,n,r))||To)(e.call(this,n,r))}var t=Lo,e=zo;return n.type=function(e){return arguments.length?(t=bt(e),n):t},n.size=function(t){return arguments.length?(e=bt(t),n):e},n};var ws=Zo.map({circle:To,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*As)),e=t*As;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Es),e=t*Es/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Es),e=t*Es/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});Zo.svg.symbolTypes=ws.keys();var Ss,ks,Es=Math.sqrt(3),As=Math.tan(30*Aa),Cs=[],Ns=0;Cs.call=pa.call,Cs.empty=pa.empty,Cs.node=pa.node,Cs.size=pa.size,Zo.transition=function(n){return arguments.length?Ss?n.transition():n:ma.transition()},Zo.transition.prototype=Cs,Cs.select=function(n){var t,e,r,u=this.id,i=[];n=b(n);for(var o=-1,a=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return qo(u,this.id)},Cs.tween=function(n,t){var e=this.id;return arguments.length<2?this.node().__transition__[e].tween.get(n):P(this,null==t?function(t){t.__transition__[e].tween.remove(n)}:function(r){r.__transition__[e].tween.set(n,t)})},Cs.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Du:hu,a=Zo.ns.qualify(n);return Ro(this,"attr."+n,t,a.local?i:u)},Cs.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=Zo.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Cs.style=function(n,t,e){function r(){this.style.removeProperty(n)}function u(t){return null==t?r:(t+="",function(){var r,u=Wo.getComputedStyle(this,null).getPropertyValue(n);return u!==t&&(r=hu(u,t),function(t){this.style.setProperty(n,r(t),e)})})}var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return Ro(this,"style."+n,t,u)},Cs.styleTween=function(n,t,e){function r(r,u){var i=t.call(this,r,u,Wo.getComputedStyle(this,null).getPropertyValue(n));return i&&function(t){this.style.setProperty(n,i(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Cs.text=function(n){return Ro(this,"text",n,Do)},Cs.remove=function(){return this.each("end.transition",function(){var n;this.__transition__.count<2&&(n=this.parentNode)&&n.removeChild(this)})},Cs.ease=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].ease:("function"!=typeof n&&(n=Zo.ease.apply(Zo,arguments)),P(this,function(e){e.__transition__[t].ease=n}))},Cs.delay=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].delay:P(this,"function"==typeof n?function(e,r,u){e.__transition__[t].delay=+n.call(e,e.__data__,r,u)}:(n=+n,function(e){e.__transition__[t].delay=n}))},Cs.duration=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].duration:P(this,"function"==typeof n?function(e,r,u){e.__transition__[t].duration=Math.max(1,n.call(e,e.__data__,r,u))}:(n=Math.max(1,n),function(e){e.__transition__[t].duration=n}))},Cs.each=function(n,t){var e=this.id;if(arguments.length<2){var r=ks,u=Ss;Ss=e,P(this,function(t,r,u){ks=t.__transition__[e],n.call(t,t.__data__,r,u)}),ks=r,Ss=u}else P(this,function(r){var u=r.__transition__[e];(u.event||(u.event=Zo.dispatch("start","end"))).on(n,t)});return this},Cs.transition=function(){for(var n,t,e,r,u=this.id,i=++Ns,o=[],a=0,c=this.length;c>a;a++){o.push(n=[]);for(var t=this[a],s=0,l=t.length;l>s;s++)(e=t[s])&&(r=Object.create(e.__transition__[u]),r.delay+=r.duration,Po(e,s,i,r)),n.push(e)}return qo(o,i)},Zo.svg.axis=function(){function n(n){n.each(function(){var n,s=Zo.select(this),l=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):wt:t,p=s.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",ka),d=Zo.transition(p.exit()).style("opacity",ka).remove(),m=Zo.transition(p.order()).style("opacity",1),y=Ti(f),x=s.selectAll(".domain").data([0]),M=(x.enter().append("path").attr("class","domain"),Zo.transition(x));v.append("line"),v.append("text");var _=v.select("line"),b=m.select("line"),w=p.select("text").text(g),S=v.select("text"),k=m.select("text");switch(r){case"bottom":n=Uo,_.attr("y2",u),S.attr("y",Math.max(u,0)+o),b.attr("x2",0).attr("y2",u),k.attr("x",0).attr("y",Math.max(u,0)+o),w.attr("dy",".71em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+i+"V0H"+y[1]+"V"+i);break;case"top":n=Uo,_.attr("y2",-u),S.attr("y",-(Math.max(u,0)+o)),b.attr("x2",0).attr("y2",-u),k.attr("x",0).attr("y",-(Math.max(u,0)+o)),w.attr("dy","0em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+-i+"V0H"+y[1]+"V"+-i);break;case"left":n=jo,_.attr("x2",-u),S.attr("x",-(Math.max(u,0)+o)),b.attr("x2",-u).attr("y2",0),k.attr("x",-(Math.max(u,0)+o)).attr("y",0),w.attr("dy",".32em").style("text-anchor","end"),M.attr("d","M"+-i+","+y[0]+"H0V"+y[1]+"H"+-i);break;case"right":n=jo,_.attr("x2",u),S.attr("x",Math.max(u,0)+o),b.attr("x2",u).attr("y2",0),k.attr("x",Math.max(u,0)+o).attr("y",0),w.attr("dy",".32em").style("text-anchor","start"),M.attr("d","M"+i+","+y[0]+"H0V"+y[1]+"H"+i)}if(f.rangeBand){var E=f,A=E.rangeBand()/2;l=f=function(n){return E(n)+A}}else l.rangeBand?l=f:d.call(n,f);v.call(n,l),m.call(n,f)})}var t,e=Zo.scale.linear(),r=zs,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Ls?t+"":zs,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var zs="bottom",Ls={top:1,right:1,bottom:1,left:1};Zo.svg.brush=function(){function n(i){i.each(function(){var i=Zo.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=i.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),i.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=i.selectAll(".resize").data(p,wt);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Ts[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,f=Zo.transition(i),h=Zo.transition(o);c&&(l=Ti(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),e(f)),s&&(l=Ti(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),r(f)),t(f)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+l[+/e$/.test(n)]+","+f[+/^s/.test(n)]+")"})}function e(n){n.select(".extent").attr("x",l[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",l[1]-l[0])}function r(n){n.select(".extent").attr("y",f[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",f[1]-f[0])}function u(){function u(){32==Zo.event.keyCode&&(C||(x=null,z[0]-=l[1],z[1]-=f[1],C=2),y())}function p(){32==Zo.event.keyCode&&2==C&&(z[0]+=l[1],z[1]+=f[1],C=0,y())}function v(){var n=Zo.mouse(_),u=!1;M&&(n[0]+=M[0],n[1]+=M[1]),C||(Zo.event.altKey?(x||(x=[(l[0]+l[1])/2,(f[0]+f[1])/2]),z[0]=l[+(n[0]p?(u=r,r=p):u=p),v[0]!=r||v[1]!=u?(e?o=null:i=null,v[0]=r,v[1]=u,!0):void 0}function m(){v(),S.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),Zo.select("body").style("cursor",null),L.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),N(),w({type:"brushend"})}var x,M,_=this,b=Zo.select(Zo.event.target),w=a.of(_,arguments),S=Zo.select(_),k=b.datum(),E=!/^(n|s)$/.test(k)&&c,A=!/^(e|w)$/.test(k)&&s,C=b.classed("extent"),N=I(),z=Zo.mouse(_),L=Zo.select(Wo).on("keydown.brush",u).on("keyup.brush",p);if(Zo.event.changedTouches?L.on("touchmove.brush",v).on("touchend.brush",m):L.on("mousemove.brush",v).on("mouseup.brush",m),S.interrupt().selectAll("*").interrupt(),C)z[0]=l[0]-z[0],z[1]=f[0]-z[1];else if(k){var T=+/w$/.test(k),q=+/^n/.test(k);M=[l[1-T]-z[0],f[1-q]-z[1]],z[0]=l[T],z[1]=f[q]}else Zo.event.altKey&&(x=z.slice());S.style("pointer-events","none").selectAll(".resize").style("display",null),Zo.select("body").style("cursor",b.style("cursor")),w({type:"brushstart"}),v()}var i,o,a=M(n,"brushstart","brush","brushend"),c=null,s=null,l=[0,0],f=[0,0],h=!0,g=!0,p=qs[0];return n.event=function(n){n.each(function(){var n=a.of(this,arguments),t={x:l,y:f,i:i,j:o},e=this.__chart__||t;this.__chart__=t,Ss?Zo.select(this).transition().each("start.brush",function(){i=e.i,o=e.j,l=e.x,f=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=gu(l,t.x),r=gu(f,t.y);return i=o=null,function(u){l=t.x=e(u),f=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){i=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,p=qs[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,p=qs[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(h=!!t[0],g=!!t[1]):c?h=!!t:s&&(g=!!t),n):c&&s?[h,g]:c?h:s?g:null},n.extent=function(t){var e,r,u,a,h;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),i=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(h=e,e=r,r=h),(e!=l[0]||r!=l[1])&&(l=[e,r])),s&&(u=t[0],a=t[1],c&&(u=u[1],a=a[1]),o=[u,a],s.invert&&(u=s(u),a=s(a)),u>a&&(h=u,u=a,a=h),(u!=f[0]||a!=f[1])&&(f=[u,a])),n):(c&&(i?(e=i[0],r=i[1]):(e=l[0],r=l[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(h=e,e=r,r=h))),s&&(o?(u=o[0],a=o[1]):(u=f[0],a=f[1],s.invert&&(u=s.invert(u),a=s.invert(a)),u>a&&(h=u,u=a,a=h))),c&&s?[[e,u],[r,a]]:c?[e,r]:s&&[u,a])},n.clear=function(){return n.empty()||(l=[0,0],f=[0,0],i=o=null),n},n.empty=function(){return!!c&&l[0]==l[1]||!!s&&f[0]==f[1]},Zo.rebind(n,a,"on")};var Ts={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},qs=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Rs=Qa.format=ic.timeFormat,Ds=Rs.utc,Ps=Ds("%Y-%m-%dT%H:%M:%S.%LZ");Rs.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Ho:Ps,Ho.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Ho.toString=Ps.toString,Qa.second=Dt(function(n){return new nc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),Qa.seconds=Qa.second.range,Qa.seconds.utc=Qa.second.utc.range,Qa.minute=Dt(function(n){return new nc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),Qa.minutes=Qa.minute.range,Qa.minutes.utc=Qa.minute.utc.range,Qa.hour=Dt(function(n){var t=n.getTimezoneOffset()/60;return new nc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),Qa.hours=Qa.hour.range,Qa.hours.utc=Qa.hour.utc.range,Qa.month=Dt(function(n){return n=Qa.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),Qa.months=Qa.month.range,Qa.months.utc=Qa.month.utc.range;var Us=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],js=[[Qa.second,1],[Qa.second,5],[Qa.second,15],[Qa.second,30],[Qa.minute,1],[Qa.minute,5],[Qa.minute,15],[Qa.minute,30],[Qa.hour,1],[Qa.hour,3],[Qa.hour,6],[Qa.hour,12],[Qa.day,1],[Qa.day,2],[Qa.week,1],[Qa.month,1],[Qa.month,3],[Qa.year,1]],Hs=Rs.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",we]]),Fs={range:function(n,t,e){return Zo.range(Math.ceil(n/e)*e,+t,e).map(Oo)},floor:wt,ceil:wt};js.year=Qa.year,Qa.scale=function(){return Fo(Zo.scale.linear(),js,Hs)};var Os=js.map(function(n){return[n[0].utc,n[1]]}),Ys=Ds.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",we]]);Os.year=Qa.year.utc,Qa.scale.utc=function(){return Fo(Zo.scale.linear(),Os,Ys)},Zo.text=St(function(n){return n.responseText}),Zo.json=function(n,t){return kt(n,"application/json",Yo,t)},Zo.html=function(n,t){return kt(n,"text/html",Io,t)},Zo.xml=St(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(Zo):"object"==typeof module&&module.exports&&(module.exports=Zo),this.d3=Zo}(); \ No newline at end of file diff --git a/public/js/research/d3.tip.js b/public/js/research/d3.tip.js new file mode 100644 index 000000000..718909cf0 --- /dev/null +++ b/public/js/research/d3.tip.js @@ -0,0 +1,305 @@ +// d3.tip +// Copyright (c) 2013 Justin Palmer +// +// Tooltips for d3.js SVG visualizations + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module with d3 as a dependency. + define(['d3'], factory) + } else if (typeof module === 'object' && module.exports) { + // CommonJS + module.exports = function(d3) { + d3.tip = factory(d3) + return d3.tip + } + } else { + // Browser global. + root.d3.tip = factory(root.d3) + } +}(this, function (d3) { + + // Public - contructs a new tooltip + // + // Returns a tip + return function() { + var direction = d3_tip_direction, + offset = d3_tip_offset, + html = d3_tip_html, + node = initNode(), + svg = null, + point = null, + target = null + + function tip(vis) { + svg = getSVGNode(vis) + point = svg.createSVGPoint() + document.body.appendChild(node) + } + + // Public - show the tooltip on the screen + // + // Returns a tip + tip.show = function() { + var args = Array.prototype.slice.call(arguments) + if(args[args.length - 1] instanceof SVGElement) target = args.pop() + + var content = html.apply(this, args), + poffset = offset.apply(this, args), + dir = direction.apply(this, args), + nodel = d3.select(node), + i = directions.length, + coords, + scrollTop = document.documentElement.scrollTop || document.body.scrollTop, + scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft + + nodel.html(content) + .style({ opacity: 1, 'pointer-events': 'all' }) + + while(i--) nodel.classed(directions[i], false) + coords = direction_callbacks.get(dir).apply(this) + nodel.classed(dir, true).style({ + top: (coords.top + poffset[0]) + scrollTop + 'px', + left: (coords.left + poffset[1]) + scrollLeft + 'px' + }) + + return tip + } + + // Public - hide the tooltip + // + // Returns a tip + tip.hide = function() { + var nodel = d3.select(node) + nodel.style({ opacity: 0, 'pointer-events': 'none' }) + return tip + } + + // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. + // + // n - name of the attribute + // v - value of the attribute + // + // Returns tip or attribute value + tip.attr = function(n, v) { + if (arguments.length < 2 && typeof n === 'string') { + return d3.select(node).attr(n) + } else { + var args = Array.prototype.slice.call(arguments) + d3.selection.prototype.attr.apply(d3.select(node), args) + } + + return tip + } + + // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. + // + // n - name of the property + // v - value of the property + // + // Returns tip or style property value + tip.style = function(n, v) { + if (arguments.length < 2 && typeof n === 'string') { + return d3.select(node).style(n) + } else { + var args = Array.prototype.slice.call(arguments) + d3.selection.prototype.style.apply(d3.select(node), args) + } + + return tip + } + + // Public: Set or get the direction of the tooltip + // + // v - One of n(north), s(south), e(east), or w(west), nw(northwest), + // sw(southwest), ne(northeast) or se(southeast) + // + // Returns tip or direction + tip.direction = function(v) { + if (!arguments.length) return direction + direction = v == null ? v : d3.functor(v) + + return tip + } + + // Public: Sets or gets the offset of the tip + // + // v - Array of [x, y] offset + // + // Returns offset or + tip.offset = function(v) { + if (!arguments.length) return offset + offset = v == null ? v : d3.functor(v) + + return tip + } + + // Public: sets or gets the html value of the tooltip + // + // v - String value of the tip + // + // Returns html value or tip + tip.html = function(v) { + if (!arguments.length) return html + html = v == null ? v : d3.functor(v) + + return tip + } + + function d3_tip_direction() { return 'n' } + function d3_tip_offset() { return [0, 0] } + function d3_tip_html() { return ' ' } + + var direction_callbacks = d3.map({ + n: direction_n, + s: direction_s, + e: direction_e, + w: direction_w, + nw: direction_nw, + ne: direction_ne, + sw: direction_sw, + se: direction_se + }), + + directions = direction_callbacks.keys() + + function direction_n() { + var bbox = getScreenBBox() + return { + top: bbox.n.y - node.offsetHeight, + left: bbox.n.x - node.offsetWidth / 2 + } + } + + function direction_s() { + var bbox = getScreenBBox() + return { + top: bbox.s.y, + left: bbox.s.x - node.offsetWidth / 2 + } + } + + function direction_e() { + var bbox = getScreenBBox() + return { + top: bbox.e.y - node.offsetHeight / 2, + left: bbox.e.x + } + } + + function direction_w() { + var bbox = getScreenBBox() + return { + top: bbox.w.y - node.offsetHeight / 2, + left: bbox.w.x - node.offsetWidth + } + } + + function direction_nw() { + var bbox = getScreenBBox() + return { + top: bbox.nw.y - node.offsetHeight, + left: bbox.nw.x - node.offsetWidth + } + } + + function direction_ne() { + var bbox = getScreenBBox() + return { + top: bbox.ne.y - node.offsetHeight, + left: bbox.ne.x + } + } + + function direction_sw() { + var bbox = getScreenBBox() + return { + top: bbox.sw.y, + left: bbox.sw.x - node.offsetWidth + } + } + + function direction_se() { + var bbox = getScreenBBox() + return { + top: bbox.se.y, + left: bbox.e.x + } + } + + function initNode() { + var node = d3.select(document.createElement('div')) + node.style({ + position: 'absolute', + top: 0, + opacity: 0, + 'pointer-events': 'none', + 'box-sizing': 'border-box' + }) + + return node.node() + } + + function getSVGNode(el) { + el = el.node() + if(el.tagName.toLowerCase() === 'svg') + return el + + return el.ownerSVGElement + } + + // Private - gets the screen coordinates of a shape + // + // Given a shape on the screen, will return an SVGPoint for the directions + // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), + // sw(southwest). + // + // +-+-+ + // | | + // + + + // | | + // +-+-+ + // + // Returns an Object {n, s, e, w, nw, sw, ne, se} + function getScreenBBox() { + var targetel = target || d3.event.target; + + while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) { + targetel = targetel.parentNode; + } + + var bbox = {}, + matrix = targetel.getScreenCTM(), + tbbox = targetel.getBBox(), + width = tbbox.width, + height = tbbox.height, + x = tbbox.x, + y = tbbox.y + + point.x = x + point.y = y + bbox.nw = point.matrixTransform(matrix) + point.x += width + bbox.ne = point.matrixTransform(matrix) + point.y += height + bbox.se = point.matrixTransform(matrix) + point.x -= width + bbox.sw = point.matrixTransform(matrix) + point.y -= height / 2 + bbox.w = point.matrixTransform(matrix) + point.x += width + bbox.e = point.matrixTransform(matrix) + point.x -= width / 2 + point.y -= height / 2 + bbox.n = point.matrixTransform(matrix) + point.y += height + bbox.s = point.matrixTransform(matrix) + + return bbox + } + + return tip + }; + +})); + diff --git a/public/js/research/filter-menu.js b/public/js/research/filter-menu.js new file mode 100644 index 000000000..91a5ab4fd --- /dev/null +++ b/public/js/research/filter-menu.js @@ -0,0 +1,665 @@ + +var filterMenuModule = (function(){ + + var activeSubMenu = null; + var filterValues = { + + dataset1: null, + dataset2: null, + graphType: null, + startDate: null, + endDate: null, + groupFactor: false, + rangeStart: null, + rangeEnd: null + }; + + var filterFields = { + dataset1: $("#primaryDataset"), + dataset2: $("#secondaryDataset"), + graphType: $("#graphType"), + startDate: $("#startDate"), + endDate: $("#endDate"), + groupPrimary: $("#groupPrimary"), + groupFactor: $("#groupPrimaryData"), + rangeStart: $("#rangeStart"), + rangeEnd: $("#rangeEnd"), + medicationId: $("#medicationId") + }; + + var filterMenus = { + errors: $("#filter-errors"), + dataset1: $("#gdata1-menu"), + dataset2: $("#gdata2-menu"), + graphType: $("#gtype-menu"), + filter: $("#gfilter-menu") + }; + + + var closeSubMenu = function(){ + + if( activeSubMenu == null ) return; + + $(activeSubMenu).removeClass("active"); + $(activeSubMenu).children("ul.submenu").hide(); + + activeSubMenu = null; + }; + + var openSubMenu = function(){ + + if( activeSubMenu == null ) return; + + // reset any previously shown menu + $(".menu-item").removeClass("active").find("ul.submenu").hide(); + + $(activeSubMenu).addClass("active"); + $(activeSubMenu).children("ul.submenu").show(); + }; + + var clearFilterPrimary = function(){ + + $(filterFields.rangeStart).val(""); + $(filterFields.rangeEnd).val(""); + }; + + var clearGraphOptions = function(){ + + // Clear Values + filterValues.dataset1 = null; + filterValues.dataset2 = null; + filterValues.graphType = null; + filterValues.startDate = null; + filterValues.endDate = null; + + // Clear Form Fields + $(filterFields.dataset1).val(''); + $(filterFields.dataset2).val(''); + $(filterFields.graphType).val(''); + + // Set Date to previous month + $(filterFields.startDate).val(''); + $(filterFields.endDate).val(''); + + // Clear Visual Fields + $(filterMenus.dataset1).find(".val").text(""); + $(filterMenus.dataset2).find(".val").text(""); + $(filterMenus.graphType).find(".val").text(""); + + // Set Default EndDate to today + var defaultEndDate = new Date(); + if( Object.prototype.toString.call(defaultEndDate) === "[object Date]" && !isNaN(defaultEndDate.getTime()) ) { + + var monthNum = defaultEndDate.getUTCMonth() + 1; + var dayNum = defaultEndDate.getUTCDate(); + if (dayNum < 10 && dayNum >= 0) dayNum = '0' + dayNum.toString(); + var yearNum = defaultEndDate.getUTCFullYear(); + + var endDateInputString = yearNum + '-' + monthNum + '-' + dayNum; + var endDateString = monthNum + '/' + dayNum + '/' + yearNum; + + // Set End Date in text and Date input item + $(filterMenus.filter).find(".val").find(".date").find(".end").text(endDateString); + $(filterFields.endDate).val(endDateInputString); + filterValues.endDate = endDateString; + + + } + else{ + + $(filterMenus.filter).find(".val").find(".date").find(".end").text(""); + } + + // make default startDate of 30 days ago + var defaultStartDate = new Date(defaultEndDate.getTime() - 30*24*60*60*1000); + // date field is in format yyyy-MM-dd --> view String like mm/dd/yyyy + if( Object.prototype.toString.call(defaultStartDate) === "[object Date]" && !isNaN(defaultStartDate.getTime()) ) { + + var monthNum = defaultStartDate.getUTCMonth() + 1; + var dayNum = defaultStartDate.getUTCDate(); + if (dayNum < 10 && dayNum >= 0) dayNum = '0' + dayNum.toString(); + var yearNum = defaultStartDate.getUTCFullYear(); + + var startDateInputString = yearNum + '-' + monthNum + '-' + dayNum; + var startDateString = monthNum + '/' + dayNum + '/' + yearNum; + + // Set Start Date in text and Date input item + $(filterMenus.filter).find(".val").find(".date").find(".start").text(startDateString); + $(filterFields.startDate).val(startDateInputString); + filterValues.startDate = startDateString; + + + } + else{ + + $(filterMenus.filter).find(".val").find(".date").find(".start").text(""); + } + + $(filterFields.rangeStart).val(""); + $(filterFields.rangeEnd).val(""); + $(filterFields.medicationId).val(-1); + $(filterFields.groupPrimary).prop('checked', false); + $(filterFields.groupFactor).val("10"); + + }; + + var saveAsImage = function(scaleFactor, imageName){ + + scaleFactor = typeof scaleFactor !== 'undefined' ? scaleFactor : 1; + + saveSvgAsPng(document.getElementById("graph"), imageName, scaleFactor); + + closeSubMenu(); + return false; + }; + + var showImageOptions = function(){ + + $(".save-image-cont").find(".options").show(); + return false; + }; + + var hideImageOptions = function(){ + + $(".save-image-cont").find(".options").hide(); + return false; + }; + + var chooseImageSize = function(){ + + $(".save-image-cont").find(".options").hide(); + + var aspectRatio = 5/2.5; + + // get Image Size + var imageSize = $(this).data("imagesize"); + console.log(imageSize); + + var currWidth = $(".main").width(); + + var graphType = graphLoaderModule.getGraphType(); + var imageName = graphType + "-chart-"+imageSize+".png" + + // figure out scaleFactor + if( imageSize == "small" ){ + + //(700x350) + var scaleFactor = 700 / currWidth; + saveAsImage(scaleFactor, imageName); + } + else if( imageSize == "medium" ){ + + // (1000x500) + var scaleFactor = 1000 / currWidth; + saveAsImage(scaleFactor, imageName); + } + // default to large + else{ + + // (1200x600) + var scaleFactor = 1200 / currWidth; + saveAsImage(scaleFactor, imageName); + } + + // Save Image + + + return false; + }; + + var updateAvailableFilterChoices = function(){ + + // run after any filter change + // make sure what is selected is still valid + // enable/disable invalid choices + + // Check currently selected dataset2 validity + // -- clear if not valid + if( filterValues.dataset2 != null && + !allowedFilterValues.isSecondaryDataAllowed(filterValues.dataset1, filterValues.dataset2) ){ + + // clear Dataset2 + filterValues.dataset2 = null; + $(filterMenus.dataset2).find(".val").text(""); + $(filterFields.dataset2).val(""); + } + + // Check currently selected graphType validity + // - clear if not valid + // -- is set + // -- allowed for dataset1 + // -- dataset1 and dataset2 are set and graph is for combinable + if( filterValues.graphType != null ){ + + if( filterValues.dataset1 != null && filterValues.dataset2 != null && + !allowedFilterValues.isCombinableGraph(filterValues.graphType) ){ + + // clear Graph Type + filterValues.graphType = null; + $(filterMenus.graphType).find(".val").text(""); + $(filterFields.graphType).val(""); + + } + else if( filterValues.dataset2 == null && + !allowedFilterValues.isGraphTypeAllowed(filterValues.dataset1, filterValues.graphType) ){ + + // clear Graph Type + filterValues.graphType = null; + $(filterMenus.graphType).find(".val").text(""); + $(filterFields.graphType).val(""); + } + + if( filterValues.graphType == "line" || filterValues.graphType == "scatter" ){ + $(filterFields.groupPrimary).attr("disabled",true); + } + else{ + $(filterFields.groupPrimary).removeAttr("disabled"); + } + } + + + // Disable Secondary Data as needed + $(filterMenus.dataset2).find(".submenu").find("a").not(".clear").each(function () { + if (!allowedFilterValues.isSecondaryDataAllowed(filterValues.dataset1, $(this).data("dname2"))) { + $(this).addClass('disabled'); + } + else { + $(this).removeClass('disabled'); + } + }); + + + // Disable Graph Types as needed + if( filterValues.dataset2 == null ) { + + $(filterMenus.graphType).find(".submenu").find("a").not(".clear").each(function () { + if (!allowedFilterValues.isGraphTypeAllowed(filterValues.dataset1, $(this).data("gtype"))) { + $(this).addClass('disabled'); + } + else { + $(this).removeClass('disabled'); + } + }); + } + else{ + + // Disable All Types but Combined + $(filterMenus.graphType).find(".submenu").find("a").not(".clear").each(function () { + if( !allowedFilterValues.isCombinableGraph($(this).data("gtype")) ){ + $(this).addClass('disabled'); + } + else { + $(this).removeClass('disabled'); + } + }); + + } + + }; + + var chooseDataSet1 = function(){ + + if( $(this).hasClass('clear') ){ + + // clear Dataset2 + filterValues.dataset1 = null; + $(filterMenus.dataset1).find(".val").text(""); + $(filterFields.dataset1).val(""); + } + else { + + var newVal = $(this).data("dname1"); + if (newVal != filterValues.dataset1) { + + // Set New Dataset 1 value + filterValues.dataset1 = newVal; + $(filterMenus.dataset1).find(".val").text($(this).text()); + $(filterFields.dataset1).val(filterValues.dataset1); + } + + // set group to true when selecting age for the first time + if( filterValues.dataset1 == "age" ){ + + filterValues.groupFactor = true; + //$(filterFields.groupFactor).attr("checked",true); + $(filterFields.groupFactor).prop('checked', true); + } + else{ + + filterValues.groupFactor = false; + //$(filterFields.groupFactor).attr("checked",false); + $(filterFields.groupFactor).prop('checked', false); + } + } + + updateAvailableFilterChoices(); + closeSubMenu(); + return false; + }; + + var chooseDataSet2 = function(){ + + // do nothing for disabled secondary types + if( $(this).hasClass('disabled') ) return false; + + if( $(this).hasClass('clear') ){ + + // clear Dataset2 + filterValues.dataset2 = null; + $(filterMenus.dataset2).find(".val").text(""); + $(filterFields.dataset2).val(""); + } + else { + var newVal = $(this).data("dname2"); + if (newVal != filterValues.dataset2) { + + filterValues.dataset2 = newVal; + $(filterMenus.dataset2).find(".val").text($(this).text()); + $(filterFields.dataset2).val(filterValues.dataset2); + } + } + + updateAvailableFilterChoices(); + closeSubMenu(); + return false; + }; + + var chooseStartDate = function(){ + + var dateString = $(filterFields.startDate).val(); + // date field is in format yyyy-MM-dd --> convert to Date object and build string like mm/dd/yyyy + var startDate = new Date(dateString); + if( Object.prototype.toString.call(startDate) === "[object Date]" && !isNaN(startDate.getTime()) ) { + + var monthNum = startDate.getUTCMonth() + 1; + var dayNum = startDate.getUTCDate(); + if (dayNum < 10 && dayNum >= 0) dayNum = '0' + dayNum.toString(); + var yearNum = startDate.getUTCFullYear(); + var startDateString = monthNum + '/' + dayNum + '/' + yearNum; + $(filterMenus.filter).find(".val").find(".date").find(".start").text(startDateString); + filterValues.startDate = startDateString; + } + else{ + + $(filterMenus.filter).find(".val").find(".date").find(".start").text(""); + } + + }; + + var chooseEndDate = function(){ + + var dateString = $(filterFields.endDate).val(); + // date field is in format yyyy-MM-dd --> convert to Date object and build string like mm/dd/yyyy + var endDate = new Date(dateString); + if( Object.prototype.toString.call(endDate) === "[object Date]" && !isNaN(endDate.getTime()) ) { + + var monthNum = endDate.getUTCMonth() + 1; + var dayNum = endDate.getUTCDate(); + if (dayNum < 10 && dayNum >= 0) dayNum = '0' + dayNum.toString(); + var yearNum = endDate.getUTCFullYear(); + var endDateString = monthNum + '/' + dayNum + '/' + yearNum; + $(filterMenus.filter).find(".val").find(".date").find(".end").text(endDateString); + filterValues.endDate = endDateString; + } + else{ + $(filterMenus.filter).find(".val").find(".date").find(".end").text(""); + } + }; + + var chooseGraphType = function(){ + + // do nothing for disabled secondary types + if( $(this).hasClass('disabled') ) return false; + + if( $(this).hasClass('clear') ){ + + // clear Graph Type + filterValues.graphType = null; + $(filterMenus.graphType).find(".val").text(""); + $(filterFields.graphType).val(""); + } + else { + + var newVal = $(this).data("gtype"); + if (newVal != filterValues.graphType) { + + filterValues.graphType = newVal; + $(filterMenus.graphType).find(".val").text($(this).text()); + $(filterFields.graphType).val(filterValues.graphType); + } + } + + updateAvailableFilterChoices(); + closeSubMenu(); + return false; + }; + + var chooseGroupPrimary = function(){ + + filterValues.groupFactor = $(this).prop('checked'); + return false; + }; + + var changeRangeValues = function(){ + + filterValues.rangeStart = $(filterFields.rangeStart).val(); + filterValues.rangeEnd = $(filterFields.rangeEnd).val(); + + console.log("Range Changed"); + + return false; + }; + + var optionLinkClick = function(evt){ + + // do nothing is tab is within submenu + if($(evt.target).parents('ul.submenu').length) { + return; + } + + activeSubMenu = $(this); + if( $(activeSubMenu).hasClass("active") ) { + closeSubMenu(); + } + else{ + openSubMenu(); + } + return false; + }; + + var checkFilterValid = function(){ + + var errors = []; + //var filtersAreValid = true; + + // Dataset 1 has valid value + if( !allowedFilterValues.isPrimaryDataValid(filterValues.dataset1) ){ + + //filtersAreValid = false; + errors.push("Choose valid Primary Dataset"); + } + + // Dataset 2 has valid value - based on dataset1 + if( filterValues.dataset2 != null ){ + if( !allowedFilterValues.isSecondaryDataAllowed(filterValues.dataset1, filterValues.dataset2) ) { + //filtersAreValid = false; + errors.push("Choose valid Secondary Dataset"); + } + } + + // Graph Type has valid value + if( filterValues.dataset2 != null && + !allowedFilterValues.isCombinableGraph(filterValues.graphType) ){ + + //filtersAreValid = false; + errors.push("Choose valid Graph Type"); + } + else if(filterValues.dataset2 == null && + !allowedFilterValues.isGraphTypeAllowed(filterValues.dataset1, filterValues.graphType) ){ + + //filtersAreValid = false; + errors.push("Choose valid Graph Type"); + } + + // Start Date has value + // Start Date is before or equal to today + if( filterValues.startDate != null ){ + + var startDate = new Date(filterValues.startDate); + if( Object.prototype.toString.call(startDate) === "[object Date]" && !isNaN(startDate.getTime()) ) { + + var today = new Date(); + if( startDate.getTime() > today.getTime() ){ + + errors.push("Start Date cannot be in the future") + } + } + else{ + errors.push("Invalid Start Date"); + } + } + + // End Date is after Start Date + // End Date is 30 days or less from Start Date + if( filterValues.endDate != null ){ + + var endDate = new Date(filterValues.endDate); + if( Object.prototype.toString.call(endDate) === "[object Date]" && !isNaN(endDate.getTime()) ) { + + var today = new Date(); + var startDate = new Date(filterValues.startDate); + if( endDate.getTime() > today.getTime() ){ + + errors.push("End Date cannot be in the future"); + } + else if( Object.prototype.toString.call(startDate) === "[object Date]" && !isNaN(startDate.getTime()) ){ + + + // if StartDate is more than 1 year before end date + var startTime = startDate.getTime() + (365 * 24 * 60 * 60 * 1000); + //console.log(startTime+" < "+endDate.getTime()); + + if( endDate.getTime() < startDate.getTime() ){ + errors.push("End Date is before Start Date"); + } + else if( startTime < endDate.getTime() ){ + + errors.push("Date Range max is 1 year"); + } + } + } + else{ + errors.push("Invalid End Date"); + } + } + + console.log(filterValues); + if( filterValues.rangeStart != null ) { + + if( isNaN(+filterValues.rangeStart) || !isFinite(filterValues.rangeStart) ){ + + errors.push("Range Start is not a number"); + } + } + + if( filterValues.rangeEnd != null ) { + + if( isNaN(+filterValues.rangeEnd) || !isFinite(filterValues.rangeEnd) ){ + + errors.push("Range End is not a number"); + } + } + + if( filterValues.rangeStart != null && filterValues.rangeStart != "" && + filterValues.rangeEnd != null && filterValues.rangeEnd != "" ){ + + if( parseFloat(filterValues.rangeStart) > parseFloat(filterValues.rangeEnd) ){ + + errors.push("Range Start is larger than End"); + } + } + + if( filterFields.groupPrimary && filterFields.groupFactor < 1 ){ + + errors.push("Group Factor is less than 1"); + } + + // clear errors + $(filterMenus.errors).html(""); + // show error list on page + if( errors.length > 0 ){ + + var errorList = "
    "; + for (i = 0; i < errors.length; ++i) { + + errorList += "
  • " + errors[i] + "
  • "; + } + errorList += "
"; + $(filterMenus.errors).append(errorList); + return (false); + } + else{ + + return (true); + } + + }; + + var getGraph = function(){ + + closeSubMenu(); + + // Validate before submitting + var filtersAreValid = checkFilterValid(); + if( filtersAreValid ) { + // Get Filter values from form hidden fields + var graphType = $(filterValues.graphType).val(); + var postData = $("#graph-options").serialize(); + //console.log(postData); + graphLoaderModule.loadGraph(filterValues.graphType, postData); + } + + // stop html form post + return false; + }; + + var publicObject = {}; + publicObject.getPrimaryDataset = function(){ return filterValues.dataset1; }; + publicObject.getSecondaryDataset = function(){ return filterValues.dataset2; }; + publicObject.isPrimaryDataGrouped = function(){ return filterValues.groupFactor; }; + publicObject.getRangeStart = function(){ return filterValues.rangeStart; }; + publicObject.getRangeEnd = function(){ return filterValues.rangeEnd; }; + publicObject.init = function() { + + // Register Actions + //$(".menu-item").find("a.opt-link").click(optionLinkClick); + $(".menu-item").click(optionLinkClick); + $(filterMenus.dataset1).find(".submenu").find("a").click(chooseDataSet1); + $(filterMenus.dataset2).find(".submenu").find("a").click(chooseDataSet2); + $(filterMenus.graphType).find(".submenu").find("a").click(chooseGraphType); + + $(filterFields.startDate).change(chooseStartDate); + $(filterFields.endDate).change(chooseEndDate); + $(filterFields.groupFactor).change(chooseGroupPrimary); + $(filterFields.rangeStart).change(changeRangeValues); + $(filterFields.rangeEnd).change(changeRangeValues); + $(filterFields.startDate).trigger("change"); + $(filterFields.endDate).trigger("change"); + + $("#filter-primary-clear").click(clearFilterPrimary); + $("#clear-button").click(clearGraphOptions); + $("#submit-button").click(getGraph); + + $("#save-button").click(showImageOptions); + $(".save-image-cont").find(".options").find(".image-size-selection").click(chooseImageSize); + $(".save-image-cont").find(".options").find(".close").click(hideImageOptions) + + // stop form submission + $("#graph-options").attr("onsubmit", "return false;"); + + updateAvailableFilterChoices(); + }; + + return publicObject; + +})(); diff --git a/public/js/research/generateData.js b/public/js/research/generateData.js new file mode 100644 index 000000000..085283096 --- /dev/null +++ b/public/js/research/generateData.js @@ -0,0 +1,127 @@ +jQuery(document).ready(function(){ + + + //*** Loop Start +// for (var i=0; i<10; i++) { +// // Generate new vitals/demographics every time - maybe change patient name +// var postData = { +// +// firstName: (randomString()), +// lastName: (randomString()), +// address: (randomInt(100, 2000)) + ' address', +// city: 'anywhere', +// age: (randomInt(1950, 2000)) + '-' + (randomInt(1, 12)) + '-' + (randomInt(1, 12)), +// sex: (randomGender()), +// +// bloodPressureSystolic: (randomInt(110, 150)), +// bloodPressureDiastolic: (randomInt(60, 100)), +// heartRate: (randomInt(10, 60)), +// temperature: (randomInt(92, 101)), +// respiratoryRate: (randomInt(10, 22)), +// oxygenSaturation: (randomInt(80, 100)), +// heightFeet: (randomInt(0, 7)), +// heightInches: (randomInt(0, 12)), +// weight: (randomInt(92, 101)), +// glucose: null, +// chiefComplaint: null, +// weeksPregnant: null +// +// // Add the rest of the form fields here +// }; +// +// $.post("/triage?id=0", postData, function (responseData) { +// +// console.log(postData); +// +// }); + //} + //*** Loop End + +}); + +function generate(){ + var howMany = document.getElementById("field1").value + for (var i=0; i 20 ){ + + xAxis.tickValues(x0.domain().filter(function(d, i) { return !(i % 6); })) + } + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left") + .tickFormat(d3.format("d")); + + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([-10, 0]) + .html(function(d) { + return ""+mapGraphData(d.secondaryName, secondaryValueMap)+", "+ + mapGraphData(d.primaryName, primaryValueMap) +" "+measurementUnits+ + ": " + d.value + " Patients"; + }); + + var svg = d3.select("#"+containerIDString) + .attr("width", containerWidth) + .attr("height", containerHeight) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + svg.call(tip); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + graphHeight + ")") + .call(xAxis) + .append("text") + .attr("class", "title") + .attr("x", graphWidth / 2 ) + .attr("y", 0 + margin.bottom) + .style("text-anchor", "middle") + .attr("dy", "-5px") + .text(xAxisTitle); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("class", "title") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margin.left) + .attr("x", 0 - (graphHeight / 2)) + .attr("dy", "1.25em") + .style("text-anchor", "middle") + .text("Number of Patients"); + + var bar = svg.selectAll(".bar") + .data(graphData) + .enter().append("g") + .attr("class", "g") + .attr("transform", function(d) { return "translate(" + x0(mapGraphData(d.primaryName, primaryValueMap)) + ",0)"; }); + + bar.selectAll("rect") + .data(function(d) { return d.groups; }) + .enter().append("rect") + .attr("width", x1.rangeBand()) + .attr("x", function(d) { return x1(d.secondaryName); }) + .attr("y", function(d) { return y(d.value); }) + .attr("height", function(d) { return graphHeight - y(d.value); }) + .style("fill", function(d) { return colors(d.secondaryName); }) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); + + var legend = svg.selectAll(".legend") + .data(secondaryKeys.slice().reverse()) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); + + legend.append("rect") + .attr("x", graphWidth - 18) + .attr("width", 18) + .attr("height", 18) + .style("fill", colors); + + legend.append("text") + .attr("x", graphWidth - 24) + .attr("y", 9) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(function(d) { return mapGraphData(d, secondaryValueMap); }); + + }; + + return publicObject; + +})(); + diff --git a/public/js/research/line-graph.js b/public/js/research/line-graph.js new file mode 100644 index 000000000..c6e487aae --- /dev/null +++ b/public/js/research/line-graph.js @@ -0,0 +1,156 @@ + +/* + * + * Line Graph + * + */ + +var lineGraphModule = (function(){ + + var xAxisTitle = ""; + var measurementUnits = ""; + var graphData = []; + var valueMap = {}; + + var publicObject = {}; + publicObject.setGraphData = function(jsonData, xTitle, unitOfMeasurement){ + + graphData = jsonData.graphData; + valueMap = jsonData.primaryValuemap; + xAxisTitle = xTitle; + measurementUnits = unitOfMeasurement; + }; + + publicObject.buildGraph = function(containerIDString){ + + var margin = {top: 20, right: 30, bottom: 50, left: 60}; + + // keep 3/2 width/height ratio + var aspectRatio = 5/3; + var containerWidth = $("#"+containerIDString).width(); + var containerHeight = containerWidth / aspectRatio; + + // Calculate height/width taking margin into account + var graphWidth = containerWidth - margin.right - margin.left; + var graphHeight = containerHeight - margin.top - margin.bottom; + + var xScale = d3.scale.linear() + .domain(d3.extent(graphData, function(d) { return parseInt(d.primaryName); })) + .nice() + .range([0, graphWidth]); + + var yScale = d3.scale.linear() + .domain(d3.extent(graphData, function(d) { return d.primaryValue; })) + //.domain([0, d3.max(graphData, function(d) { return d.primaryValue; })]) + .range([graphHeight, 0]); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("left") + .tickFormat(d3.format("d")); + + var line = d3.svg.line() + .x(function(d) { return xScale(d.primaryName); }) + .y(function(d) { return yScale(d.primaryValue); }); + + var chart = d3.select("#"+containerIDString) + .attr("width", containerWidth) + .attr("height", containerHeight) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + chart.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + graphHeight + ")") + .call(xAxis) + .append("text") + .attr("class", "title") + .attr("x", graphWidth / 2 ) + .attr("y", 0 + margin.bottom) + .style("text-anchor", "middle") + .attr("dy", "-5px") + .text(xAxisTitle); + + chart.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("class", "title") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margin.left) + .attr("x", 0 - (graphHeight / 2)) + .attr("dy", "1.25em") + .style("text-anchor", "middle") + .text("Number of Patients"); + + chart.append("path") + .datum(graphData) + .attr("class", "line") + .attr("d", line); + + var focus = chart.append("g") + .attr("class", "focus") + .style("display", "none"); + + focus.append("circle") + .attr("r", 4.5); + + focus.append("text") + .attr("x", 9) + .attr("dy", ".35em"); + + chart.append("rect") + .attr("class", "overlay") + .attr("width", graphWidth) + .attr("height", graphHeight) + .on("mouseover", function() { focus.style("display", null); }) + .on("mouseout", function() { focus.style("display", "none"); }) + .on("mousemove", mousemove); + + var bisectValue = d3.bisector(function(d){ return d.primaryName; }).left; + + function mousemove() { + + var x0 = xScale.invert(d3.mouse(this)[0]), + i = bisectValue(graphData, x0, 1); + + if( i < graphData.length ) { + + var d0 = graphData[i - 1], + d1 = graphData[i], + d = x0 - d0.primaryValue > d1.primaryValue - x0 ? d1 : d0; + + var xTranslate = xScale(d.primaryName); + var yTranslate = yScale(d.primaryValue); + + focus.attr("transform", "translate(" + xTranslate + "," + yTranslate + ")"); + + //console.log(d3.mouse(this)); + //console.log(graphWidth); + if( d3.mouse(this)[0] > (graphWidth/2) ) { + console.log("right"); + focus.select("text") + .attr('text-anchor', 'end') + .attr("dx", "-1em") + .text(mapGraphData(d.primaryName, valueMap) + " " + measurementUnits + ": " + d.primaryValue + " patients"); + + } + else{ + focus.select("text") + .attr('text-anchor', 'start') + .attr("dx", "0") + .text(mapGraphData(d.primaryName, valueMap) + " " + measurementUnits + ": " + d.primaryValue + " patients"); + } + } + } + }; + + return publicObject; + +})(); + + diff --git a/public/js/research/pie-graph.js b/public/js/research/pie-graph.js new file mode 100644 index 000000000..873b72bc8 --- /dev/null +++ b/public/js/research/pie-graph.js @@ -0,0 +1,273 @@ +/* + * + * + * Pie Graph + * + */ + +var pieGraphModule = (function(){ + + var xAxisTitle = ""; + var measurementUnits = ""; + var graphData = []; + var valueMap = {}; + var label_ids = {}; + var arc_ids = {}; + + var publicObject = {}; + publicObject.setGraphData = function(jsonData, xTitle, unitOfMeasurement){ + + graphData = jsonData.graphData; + valueMap = jsonData.primaryValuemap; + xAxisTitle = xTitle; + measurementUnits = unitOfMeasurement; + }; + + + publicObject.buildGraph = function(containerIDString){ + + var margin = {top: 20, right: 30, bottom: 50, left: 60}; + + // keep 3/2 width/height ratio on container + var aspectRatio = 5/2.5; + var containerWidth = $("#"+containerIDString).width(); + var containerHeight = containerWidth / aspectRatio; + + // Calculate height/width taking margin into account + var graphWidth = containerWidth - margin.right - margin.left; + var graphHeight = containerHeight - margin.top - margin.bottom; + + var pieWidth = graphHeight-20; + var pieHeight = graphHeight-20; + var outerRadius = pieWidth / 2; + var innerRadius = 0; + var textOffset = 14; + + + var arc = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(outerRadius); + + var arcOver = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(outerRadius + 10); + + var pie = d3.layout.pie() + .value(function(d){ return d.primaryValue; } ); + var color = d3.scale.category20(); + + var pied_data = pie(graphData); + + var pieLeftTranslate = (graphWidth/2)+margin.left; + var pieTopTranslate = (graphHeight/2)+margin.top; + var chart = d3.select("#"+containerIDString) + .attr("width", containerWidth) + .attr("height", containerHeight) + .append("g") + .attr("transform", "translate(" + pieLeftTranslate + "," + pieTopTranslate + ")"); + + //Set up Arcs + var arcs = chart.selectAll("g.arc") + .data(pied_data) + .enter() + .append("g") + .attr("class", "arc") + .each(function(d, i){ + + var labelId = "label-"+i; + var arcId = "arc-"+i; + arc_ids[d.data.primaryName] = arcId; + label_ids[d.data.primaryName] = labelId; + + }) + .attr('id', function(d){ return arc_ids[d.data.primaryName]; }) + .on("mouseover", function(d) { + d3.select(this).select("path").transition() + .duration(200) + .attr("d", arcOver); + + var labelId = label_ids[d.data.primaryName]; + $("#"+labelId).show(); + + }) + .on("mouseout", function(d) { + d3.select(this).select("path").transition() + .duration(100) + .attr("d", arc); + + var labelId = label_ids[d.data.primaryName]; + $("#"+labelId).hide(); + + }); + + arcs.append("path") + .attr("fill", function(d, i) { return color(i); }) + .attr("data-legend", function(d, i){ return mapGraphData(d.data.primaryName, valueMap); }) + .attr("d", arc); + + + var legend = d3.select("#"+containerIDString) + .append("g") + .attr("class", "legend") + .attr("width", outerRadius) + //.attr("height", outerRadius * 2) + .attr("transform", "translate(20,"+margin.top+")") + .selectAll("g.item") + .data(graphData) + .enter().append("g") + .attr("class", "item") + .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }) + .on("mouseover", function(d) { + + var arcID = arc_ids[d.primaryName]; + d3.select("#"+arcID).select("path").transition() + .duration(200) + .attr("d", arcOver); + + var labelId = label_ids[d.primaryName]; + $("#"+labelId).show(); + }) + .on("mouseout", function(d) { + + var arcID = arc_ids[d.primaryName]; + d3.select("#"+arcID).select("path").transition() + .duration(100) + .attr("d", arc); + + var labelId = label_ids[d.primaryName]; + $("#"+labelId).hide(); + + }); + + legend.append("rect") + .attr("width", 18) + .attr("height", 18) + .style("fill", function(d, i) { return color(i); }); + + + legend.append("text") + .attr("x", 24) + .attr("y", 9) + .attr("dy", ".35em") + .text(function(d) { return mapGraphData(d.primaryName, valueMap)+" "+measurementUnits; }); + + + //GROUP FOR LABELS + var label_group = d3.select("#"+containerIDString).append("g") + .attr("class", "label_group") + .attr("transform", "translate(" + pieLeftTranslate + "," + pieTopTranslate + ")"); + + + //DRAW LABELS WITH PERCENTAGE VALUES + //* + var enteringLabels = label_group.selectAll("g.label") + .data(pied_data) + .enter(); + var labelGroups = enteringLabels.append("g") + .attr("class", "label") + .attr("id", function(d){ + + var labelId = label_ids[d.data.primaryName]; + return labelId; + }); + + labelGroups.append("circle").attr({ + x: 0, + y: 0, + r: 2, + fill: "#333", + transform: function (d, i) { + centroid = arc.centroid(d); + return "translate(" + arc.centroid(d) + ")"; + }, + 'class': "label-circle" + }); + + cDim = { + height: graphWidth, + width: graphWidth, + labelRadius: graphWidth/4 + }; + + var textLines = labelGroups.append("line").attr({ + x1: function (d, i) { + return arc.centroid(d)[0]; + }, + y1: function (d, i) { + return arc.centroid(d)[1]; + }, + x2: function (d, i) { + centroid = arc.centroid(d); + midAngle = Math.atan2(centroid[1], centroid[0]); + x = Math.cos(midAngle) * cDim.labelRadius; + return x; + }, + y2: function (d, i) { + centroid = arc.centroid(d); + midAngle = Math.atan2(centroid[1], centroid[0]); + y = Math.sin(midAngle) * cDim.labelRadius; + return y; + }, + 'class': "label-line" + }); + + var textLabels = labelGroups.append("text").attr({ + x: function (d, i) { + centroid = arc.centroid(d); + midAngle = Math.atan2(centroid[1], centroid[0]); + x = Math.cos(midAngle) * cDim.labelRadius; + sign = (x > 0) ? 1 : -1; + labelX = x + (5 * sign); + return labelX; + }, + y: function (d, i) { + centroid = arc.centroid(d); + midAngle = Math.atan2(centroid[1], centroid[0]); + y = Math.sin(midAngle) * cDim.labelRadius; + return y; + }, + 'data-labelbg': "true", + 'text-anchor': function (d, i) { + centroid = arc.centroid(d); + midAngle = Math.atan2(centroid[1], centroid[0]); + x = Math.cos(midAngle) * cDim.labelRadius; + return (x > 0) ? "start" : "end"; + }, + 'class': 'label-text', + 'with-space-preserve': true, + 'xml:space': 'preserve', + "dominant-baseline": "central" + }) + .text(function (d) { + + var label = " "+mapGraphData(d.data.primaryName, valueMap)+" "+measurementUnits+": "+d.data.primaryValue+" patients "; + return label; + }); + //.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve"); + + + /* + var filter = '' + + '' + + '' + + ''; + */ + var filter = d3.select("#"+containerIDString) + .append("filter") + .attr("x", "0") + .attr("y", "0") + .attr("width", "1") + .attr("height", "1") + .attr("id", "bg-color") + + filter.append("feFlood") + .attr("flood-color", "#333333"); + + filter.append("feComposite") + .attr("in", "SourceGraphic"); + + }; + + return publicObject; + +})(); \ No newline at end of file diff --git a/public/js/research/research.js b/public/js/research/research.js index a2fb5d2c9..816287da8 100644 --- a/public/js/research/research.js +++ b/public/js/research/research.js @@ -1,4 +1,473 @@ -$(document).ready(function () { +var allowedFilterValues = (function(){ + var allowedValues = { + + age: { + graphTypes: ['bar','line','pie','scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + gender: { + graphTypes: ['bar','pie', 'table'], + secondaryData: [] + }, + pregnancyStatus: { + graphTypes: ['bar','pie','table'], + secondaryData: [] + }, + pregnancyTime: { + graphTypes: ['bar','line','pie','scatter','table'], + secondaryData: [] + }, + height: { + graphTypes: ['bar','pie','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + weight: { + graphTypes: ['bar','line','pie', 'scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + dispensedMeds: { + graphTypes: ['bar','pie','table'], + secondaryData: [] + }, + prescribedMeds: { + graphTypes: ['bar','pie','table'], + secondaryData: [] + }, + bloodPressureSystolic: { + graphTypes: ['line','scatter','table'], + secondaryData: [] + }, + bloodPressureDiastolic: { + graphTypes: ['line','scatter','table'], + secondaryData: [] + }, + temperature: { + graphTypes: ['bar','line','scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + oxygenSaturation: { + graphTypes: ['bar','line','scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + heartRate: { + graphTypes: ['bar','line','scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + respiratoryRate: { + graphTypes: ['bar','line','scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + }, + glucose: { + graphTypes: ['bar','line','scatter','table'], + secondaryData: ['gender', 'pregnancyStatus'] + } + }; + + var combinableGraphs = ["stacked-bar", "grouped-bar"]; + + var publicObject = {}; + publicObject.isPrimaryDataValid = function(dataset){ + + if( allowedValues[dataset] != undefined ){ + + return true; + } + else return false; + }; + publicObject.isGraphTypeAllowed = function(dataset, graphType){ + + if( allowedValues[dataset] != undefined ){ + var found = false; + allowedValues[dataset].graphTypes.forEach(function(val){ + if( val === graphType ){ + + found = true; + return; + } + }); + return found; + } + }; + publicObject.isSecondaryDataAllowed = function(dataset, secondaryData){ + + if( allowedValues[dataset] != undefined ){ + + var found = false; + allowedValues[dataset].secondaryData.forEach(function(val){ + if( val === secondaryData ){ + + found = true; + return; + } + }); + return found; + } + }; + publicObject.isCombinableGraph = function(graphType){ + + var found = false; + combinableGraphs.forEach(function(val){ + + if( val == graphType ){ + + found = true; + return; + } + }); + return found; + }; + return publicObject; + +})(); + + +var graphLoaderModule = (function(){ + + var graphType = 'bar'; + var graphContainerId = "graph"; + var primaryDataType = ""; + var secondaryDataType = ""; + var jsonData; + + var statisticsFields = { + median: $("#median"), + average: $("#average"), + range: $("#range") + }; + + var showGraphLoadingIcon = function(){ + + $(".chart-container").find(".loading").show(); + }; + + var hideGraphLoadingIcon = function(){ + + $(".chart-container").find(".loading").hide(); + }; + + var publicObject = {}; + publicObject.loadGraph = function(newGraphType, postData){ + + showGraphLoadingIcon(); + + graphType = newGraphType; + + //console.log(postData); + + // remove any previous graph + d3.selectAll("svg > *").remove(); + $("#range").find(".val").text(""); + $("#average").find(".val").text(""); + $("#median").find(".val").text(""); + + // post graph + $.post("/research/graph", postData, function (rawData) { + + jsonData = jQuery.parseJSON(rawData); + + if( typeof jsonData.graphData == 'undefined' || jsonData.graphData.length == 0 ){ + + // show error + hideGraphLoadingIcon(); + alert("No patients match the chosen filters"); + + return; + } + + primaryDataType = filterMenuModule.getPrimaryDataset(); + secondaryDataType = filterMenuModule.getSecondaryDataset(); + + $(".graph-instructions").hide(); + $(".graph-area").show(); + + console.log(jsonData); + + var xAxisTitle = ""; + if( "xAxisTitle" in jsonData ) { + xAxisTitle = jsonData.xAxisTitle; + } + + var unitOfMeasurement = ""; + if( "unitOfMeasurement" in jsonData ) { + + unitOfMeasurement = jsonData.unitOfMeasurement; + if( unitOfMeasurement.length > 0 ) { + xAxisTitle += " (" + unitOfMeasurement + ")"; + } + } + + $("#graph").show(); + $("#table-container").hide(); + + // remove any previous graph + d3.selectAll("svg > *").remove(); + + switch(graphType){ + + case 'line': + + lineGraphModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + lineGraphModule.buildGraph(graphContainerId); + break; + + case 'scatter': + scatterGraphModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + scatterGraphModule.buildGraph(graphContainerId); + break; + + case 'pie': + pieGraphModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + pieGraphModule.buildGraph(graphContainerId); + break; + + case 'stacked-bar': + stackedBarGraphModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + stackedBarGraphModule.buildGraph(graphContainerId); + break; + + case 'grouped-bar': + groupedBarGraphModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + groupedBarGraphModule.buildGraph(graphContainerId); + break; + + case 'bar': + barGraphModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + barGraphModule.buildGraph(graphContainerId); + break; + + case 'table': + default: + $("#graph").hide(); + $("#table-container").show(); + tableChartModule.setGraphData(jsonData, xAxisTitle, unitOfMeasurement); + tableChartModule.buildGraph(); + } + + if( typeof jsonData.primaryValuemap != "undefined" && + Object.keys(jsonData.primaryValuemap).length > 0){ + + // If there is a value map, statistics are not valid + $(statisticsFields.median).hide(); + $(statisticsFields.average).hide(); + $(statisticsFields.range).hide(); + } + else { + + $(statisticsFields.median).show(); + $(statisticsFields.average).show(); + $(statisticsFields.range).show(); + + // Grab Statistics + if ("median" in jsonData) { + + var median = jsonData.median; + //console.log(median); + if( filterMenuModule.getPrimaryDataset() == "height" ){ + + var median = inchesToFeetInches(median); + } + else { + + median = parseFloat(median).toFixed(2); + } + + $(statisticsFields.median).find(".val").text(median + " " + unitOfMeasurement); + } + else { + $(statisticsFields.median).find(".val").text("n/a"); + } + + if ("average" in jsonData) { + + var average = jsonData.average; + //console.log(average); + if( filterMenuModule.getPrimaryDataset() == "height" ){ + + var average = inchesToFeetInches(average); + } + else { + + average = parseFloat(average).toFixed(2); + } + + $(statisticsFields.average).find(".val").text(average + " " + unitOfMeasurement); + } + else { + $(statisticsFields.average).find(".val").text("n/a"); + } + + if (("rangeLow" in jsonData) && ("rangeHigh" in jsonData)) { + + var rangeLow = jsonData.rangeLow; + //console.log(rangeLow); + if( filterMenuModule.getPrimaryDataset() == "height" ){ + + var rangeLow = inchesToFeetInches(rangeLow); + } + else { + + rangeLow = parseFloat(rangeLow).toFixed(2); + } + + var rangeHigh = jsonData.rangeHigh; + //console.log(rangeHigh); + if( filterMenuModule.getPrimaryDataset() == "height" ){ + + var rangeHigh = inchesToFeetInches(rangeHigh); + } + else { + + rangeHigh = parseFloat(rangeHigh).toFixed(2); + } + + $(statisticsFields.range).find(".val").text(rangeLow + " - " + rangeHigh + " " + unitOfMeasurement); + } + else { + $(statisticsFields.range).find(".val").text("n/a"); + } + } + + hideGraphLoadingIcon(); + }); + + }; + + publicObject.reloadGraph = function(newContainerId){ + + var thisContainerId = graphContainerId; + if( typeof newContainerId !== "undefined" ){ + + thisContainerId = newContainerId; + } + + if( jsonData != null ) { + switch (graphType) { + + case 'line': + lineGraphModule.buildGraph(thisContainerId); + break; + + case 'scatter': + scatterGraphModule.buildGraph(thisContainerId); + break; + + case 'pie': + pieGraphModule.buildGraph(thisContainerId); + break; + + case 'stacked-bar': + stackedBarGraphModule.buildGraph(thisContainerId); + break; + + case 'grouped-bar': + groupedBarGraphModule.buildGraph(thisContainerId); + break; + + case 'bar': + barGraphModule.buildGraph(thisContainerId); + break; + + default: + // do nothing + } + } + }; + publicObject.getGraphType = function (){ return graphType; }; + publicObject.getPrimaryDataType = function (){ return primaryDataType; }; + publicObject.getSecondaryDataType = function (){ return secondaryDataType; }; + publicObject.init = function(){ + + // do any initialization that might be needed + }; + + return publicObject; +})(); + + +jQuery(document).ready(function(){ + + filterMenuModule.init(); + graphLoaderModule.init(); + + // Detect changes in main container width, redraw chart + var lastChartWidth = $(".main").width(); + $(window).on("resize", function() { + var currChartWidth = $(".main").width(); + if( lastChartWidth != currChartWidth ) { + + graphLoaderModule.reloadGraph(); + lastChartWidth = currChartWidth; + } + }).trigger("resize"); }); + + +Array.prototype.inArray = function(comparer) { + for(var i=0; i < this.length; i++) { + if(comparer(this[i])) return true; + } + return false; +}; + +// adds an element to the array if it does not already exist using a comparer +// function +Array.prototype.pushIfNotExist = function(element, comparer) { + if (!this.inArray(comparer)) { + this.push(element); + } +}; + +function inchesToFeetInches(inches){ + + if( inches == 0 ) return '0"'; + + var feet = parseInt(inches / 12); + var inch = parseInt(inches % 12); + var str = ""; + + if( feet > 0 ) { + str = feet + "'"; + } + str += inch + '"'; + + return str; +} + + +function mapGraphData(name, datamap){ + + var toRet = name; + + // Do nothing if value map does not exist + if( typeof datamap != "undefined" ) { + + if (Object.keys(datamap).length > 0) { + + if (name in datamap) { + toRet = datamap[name]; + } + } + } + + if( graphLoaderModule.getPrimaryDataType() == "height" && datamap == null ){ + + if( toRet.search("-") ){ + + var splitVals = toRet.split("-"); + var convVals = []; + splitVals.forEach(function(val){ + + convVals.push(inchesToFeetInches(val)); + }); + + toRet = convVals.join(" - "); + } + else { + toRet = inchesToFeetInches(toRet); + } + } + return toRet; +}; + diff --git a/public/js/research/saveSvgAsPng.js b/public/js/research/saveSvgAsPng.js new file mode 100644 index 000000000..dbe170454 --- /dev/null +++ b/public/js/research/saveSvgAsPng.js @@ -0,0 +1,121 @@ +(function() { + var out$ = typeof exports != 'undefined' && exports || this; + + var doctype = ''; + + function inlineImages(callback) { + var images = document.querySelectorAll('svg image'); + var left = images.length; + if (left == 0) { + callback(); + } + for (var i = 0; i < images.length; i++) { + (function(image) { + if (image.getAttribute('xlink:href')) { + var href = image.getAttribute('xlink:href').value; + if (/^http/.test(href) && !(new RegExp('^' + window.location.host).test(href))) { + throw new Error("Cannot render embedded images linking to external hosts."); + } + } + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + var img = new Image(); + img.src = image.getAttribute('xlink:href'); + img.onload = function() { + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + image.setAttribute('xlink:href', canvas.toDataURL('image/png')); + left--; + if (left == 0) { + callback(); + } + } + })(images[i]); + } + } + + function styles(dom) { + var css = ""; + var sheets = document.styleSheets; + for (var i = 0; i < sheets.length; i++) { + var rules = sheets[i].cssRules; + if (rules != null) { + for (var j = 0; j < rules.length; j++) { + var rule = rules[j]; + if (typeof(rule.style) != "undefined") { + css += rule.selectorText + " { " + rule.style.cssText + " }\n"; + } + } + } + } + + var s = document.createElement('style'); + s.setAttribute('type', 'text/css'); + s.innerHTML = ""; + + var defs = document.createElement('defs'); + defs.appendChild(s); + return defs; + } + + out$.svgAsDataUri = function(el, scaleFactor, cb) { + scaleFactor = scaleFactor || 1; + + inlineImages(function() { + var outer = document.createElement("div"); + var clone = el.cloneNode(true); + var width = parseInt( + clone.getAttribute('width') + || clone.style.width + || out$.getComputedStyle(el).getPropertyValue('width') + ); + var height = parseInt( + clone.getAttribute('height') + || clone.style.height + || out$.getComputedStyle(el).getPropertyValue('height') + ); + + var xmlns = "http://www.w3.org/2000/xmlns/"; + + clone.setAttribute("version", "1.1"); + clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg"); + clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink"); + clone.setAttribute("width", width * scaleFactor); + clone.setAttribute("height", height * scaleFactor); + clone.setAttribute("viewBox", "0 0 " + width + " " + height); + outer.appendChild(clone); + + clone.insertBefore(styles(clone), clone.firstChild); + var svg = doctype + outer.innerHTML; + var uri = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg))); + + + if (cb) { + cb(uri); + } + }); + } + + out$.saveSvgAsPng = function(el, name, scaleFactor) { + out$.svgAsDataUri(el, scaleFactor, function(uri) { + + var image = new Image(); + image.src = uri; + image.onload = function() { + var canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + var context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); + + var a = document.createElement('a'); + a.download = name; + a.href = canvas.toDataURL('image/png'); + document.body.appendChild(a); + a.click(); + } + + }); + } +})(); \ No newline at end of file diff --git a/public/js/research/scatter-plot.js b/public/js/research/scatter-plot.js new file mode 100644 index 000000000..be528956d --- /dev/null +++ b/public/js/research/scatter-plot.js @@ -0,0 +1,106 @@ +/* + * + * Scatterplot Graph + * + */ + +var scatterGraphModule = (function(){ + + var xAxisTitle = ""; + var measurementUnits = ""; + var graphData = []; + var valueMap = {}; + + var publicObject = {}; + publicObject.setGraphData = function(jsonData, xTitle, unitOfMeasurement){ + + graphData = jsonData.graphData; + valueMap = jsonData.primaryValuemap; + xAxisTitle = xTitle; + measurementUnits = unitOfMeasurement; + }; + + publicObject.buildGraph = function(containerIDString){ + + var margin = {top: 20, right: 30, bottom: 50, left: 60}; + + // keep 3/2 width/height ratio + var aspectRatio = 5/2.5; + var containerWidth = $("#"+containerIDString).width(); + var containerHeight = containerWidth / aspectRatio; + + // Calculate height/width taking margin into account + var graphWidth = containerWidth - margin.right - margin.left; + var graphHeight = containerHeight - margin.top - margin.bottom; + + var xScale = d3.scale.linear() + .domain([d3.min(graphData, function(d) { return parseInt(d.primaryName); })-1, d3.max(graphData, function(d) { return parseInt(d.primaryName); })+1]) + .range([0, graphWidth]); + + var yScale = d3.scale.linear() + .domain([d3.min(graphData, function(d) { return d.primaryValue; })-1, d3.max(graphData, function(d) { return d.primaryValue; })+1]) + .range([graphHeight, 0]); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("left") + .tickFormat(d3.format("d")); + + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([-10, 0]) + .html(function(d) { + return '' + d.primaryName + ' '+measurementUnits+' Patients: ' + d.primaryValue + ''; + }); + + var chart = d3.select(".chart") + .attr("width", containerWidth) + .attr("height", containerHeight) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + chart.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + graphHeight + ")") + .call(xAxis) + .append("text") + .attr("class", "title") + .attr("x", graphWidth / 2 ) + .attr("y", 0 + margin.bottom) + .style("text-anchor", "middle") + .attr("dy", "-5px") + .text(xAxisTitle); + + chart.call(tip); + + chart.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("class", "title") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margin.left) + .attr("x", 0 - (graphHeight / 2)) + .attr("dy", "1.25em") + .style("text-anchor", "middle") + .text("Number of Patients"); + + chart.selectAll(".dot") + .data(graphData) + .enter().append("circle") + .attr("class", "dot") + .attr("r", 4.5) + .attr("cx", function(d){ return xScale(d.primaryName)}) + .attr("cy", function(d){ return yScale(d.primaryValue)}) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); + + }; + + return publicObject; + +})(); diff --git a/public/js/research/stacked-bar.js b/public/js/research/stacked-bar.js new file mode 100644 index 000000000..2e0335e62 --- /dev/null +++ b/public/js/research/stacked-bar.js @@ -0,0 +1,160 @@ +/* + * + * Stacked Bar Graph + * + */ + +var stackedBarGraphModule = (function(){ + + var xAxisTitle = ""; + var measurementUnits = ""; + var graphData = []; + var primaryValueMap = {}; + var secondaryValueMap = {}; + + var publicObject = {}; + publicObject.setGraphData = function(jsonData, xTitle, unitOfMeasurement){ + + graphData = jsonData.graphData; + primaryValueMap = jsonData.primaryValuemap; + secondaryValueMap = jsonData.secondaryValuemap; + xAxisTitle = xTitle; + measurementUnits = unitOfMeasurement; + }; + + publicObject.buildGraph = function(containerIDString){ + + var margin = {top: 20, right: 30, bottom: 50, left: 60}; + + // keep 3/2 width/height ratio + var aspectRatio = 5/2.5; + var containerWidth = $("#"+containerIDString).width(); + var containerHeight = containerWidth / aspectRatio; + + // Calculate height/width taking margin into account + var graphWidth = containerWidth - margin.right - margin.left; + var graphHeight = containerHeight - margin.top - margin.bottom; + + //Easy colors accessible via a 20-step ordinal scale + var colors = d3.scale.category10(); + + // get possible colors from keys in first element + colors.domain(d3.keys(graphData[0].secondaryData)); + + graphData.forEach(function(d) { + var y0 = 0; + d.groups = colors.domain().map(function(secondaryName) { + return { + primaryName: d.primaryName, secondaryName: secondaryName, y0: y0, y1: y0 += +d.secondaryData[secondaryName] + }; + }); + d.total = d.groups[d.groups.length - 1].y1; + }); + + //Set up scales + var xScale = d3.scale.ordinal() + .domain(graphData.map(function(d) { return mapGraphData(d.primaryName, primaryValueMap); })) + .rangeRoundBands([0, graphWidth], 0.15); + + var yScale = d3.scale.linear() + .domain([0, d3.max(graphData, function(d) { return d.total; })]) + .range([graphHeight, 0]); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient("bottom"); + + if( Object.keys(graphData).length > 20 ){ + + xAxis.tickValues(xScale.domain().filter(function(d, i) { return !(i % 6); })) + } + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("left") + .tickFormat(d3.format("d")); + + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([-10, 0]) + .html(function(d) { + var val = d.y1 - d.y0; + return ""+mapGraphData(d.secondaryName, secondaryValueMap)+", "+ + mapGraphData(d.primaryName, primaryValueMap) +" "+measurementUnits+ + ": " + val + " Patients"; + }); + + + //Create SVG element + var chart = d3.select("#"+containerIDString) + .attr("width", containerWidth) + .attr("height", containerHeight) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + chart.call(tip); + + chart.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + graphHeight + ")") + .call(xAxis) + .append("text") + .attr("class", "title") + .attr("x", graphWidth / 2 ) + .attr("y", 0 + margin.bottom) + .style("text-anchor", "middle") + .attr("dy", "-5px") + .text(xAxisTitle); + + chart.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("class", "title") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margin.left) + .attr("x", 0 - (graphHeight / 2)) + .attr("dy", "1.25em") + .style("text-anchor", "middle") + .text("Number of Patients"); + + var state = chart.selectAll(".bar") + .data(graphData) + .enter().append("g") + .attr("class", "g") + .attr("transform", function(d) { return "translate(" + xScale(mapGraphData(d.primaryName, primaryValueMap)) + ",0)"; }); + + state.selectAll("rect") + .data(function(d) { return d.groups; }) + .enter().append("rect") + .attr("width", xScale.rangeBand()) + .attr("y", function(d) { return yScale(d.y1); }) + .attr("height", function(d) { return yScale(d.y0) - yScale(d.y1); }) + .style("fill", function(d) { return colors(d.secondaryName); }) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); + + var legend = chart.selectAll(".legend") + .data(colors.domain().slice().reverse()) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); + + legend.append("rect") + .attr("x", graphWidth - 18) + .attr("width", 18) + .attr("height", 18) + .style("fill", colors); + + legend.append("text") + .attr("x", graphWidth - 24) + .attr("y", 9) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(function(d) { return mapGraphData(d, secondaryValueMap); }); + + }; + + return publicObject; + +})(); \ No newline at end of file diff --git a/public/js/research/table-chart.js b/public/js/research/table-chart.js new file mode 100644 index 000000000..c23d1ca98 --- /dev/null +++ b/public/js/research/table-chart.js @@ -0,0 +1,56 @@ +/* + * + * Bar Graph + * + */ + +var tableChartModule = (function(){ + + var xAxisTitle = ""; + var measurementUnits = ""; + var graphData = []; + var valueMap = {}; + + + var publicObject = {}; + publicObject.setGraphData = function(jsonData, xTitle, unitOfMeasurement){ + + graphData = jsonData.graphData; + valueMap = jsonData.primaryValuemap; + xAxisTitle = xTitle; + measurementUnits = unitOfMeasurement; + }; + + publicObject.buildGraph = function(){ + + var table = $("#table-container"); + $(table).html(''); + + var table_html = '' + + '' + + '' + + '' + + '' + + '' + + ''; + + table_html += ''; + + $.each(graphData, function (key, obj) { + + table_html += '' + + '' + + '' + + ''; + + }); + + table_html += ''; + + $(table).append(table_html); + + }; + + return publicObject; + +})(); \ No newline at end of file diff --git a/test/mock/femr/business/services/MockResearchService.java b/test/mock/femr/business/services/MockResearchService.java new file mode 100644 index 000000000..2d89d46f9 --- /dev/null +++ b/test/mock/femr/business/services/MockResearchService.java @@ -0,0 +1,35 @@ +package mock.femr.business.services; + +import femr.common.dto.ServiceResponse; +import femr.common.models.ResearchFilterItem; +import femr.common.models.ResearchGraphDataItem; + + +public class MockResearchService { + + public ServiceResponse getGraphData(ResearchFilterItem filters){ + + ResearchGraphDataItem graphDataItem = new ResearchGraphDataItem(); + + if( filters.getPrimaryDataset().equals("age") ) { + + graphDataItem.setAverage(1.0f); + graphDataItem.setMedian(1.0f); + + } + else if( filters.getPrimaryDataset().equals("gender") ){ + + + + } + + + + ServiceResponse response = new ServiceResponse<>(); + response.setResponseObject(graphDataItem); + + return response; + } + + +} diff --git a/test/mock/femr/business/services/MockTriageService.java b/test/mock/femr/business/services/MockTriageService.java index aab67fb6b..e57d31ba8 100644 --- a/test/mock/femr/business/services/MockTriageService.java +++ b/test/mock/femr/business/services/MockTriageService.java @@ -1,3 +1,4 @@ +/* package mock.femr.business.services; import femr.common.dto.ServiceResponse; @@ -44,3 +45,4 @@ public ServiceResponse getDateOfTriageCheckIn(int encounterId) { return null; } } +*/ diff --git a/test/mock/femr/business/services/MockUserService.java b/test/mock/femr/business/services/MockUserService.java index 1ae8fef80..f5d5b727c 100644 --- a/test/mock/femr/business/services/MockUserService.java +++ b/test/mock/femr/business/services/MockUserService.java @@ -1,9 +1,14 @@ package mock.femr.business.services; +import com.avaje.ebean.ExpressionList; +import femr.business.helpers.DomainMapper; +import femr.business.helpers.QueryProvider; import femr.common.dto.ServiceResponse; import femr.business.services.IUserService; +import femr.common.models.UserItem; import femr.data.models.IRole; import femr.data.models.IUser; +import femr.data.models.User; import java.util.List; @@ -17,8 +22,37 @@ public class MockUserService implements IUserService { public boolean findByIdWasCalled = false; @Override - public ServiceResponse createUser(IUser user) { - return null; + public ServiceResponse createUser(UserItem user, String password) { + + /* + ServiceResponse response = new ServiceResponse<>(); + try { + + ExpressionList query = QueryProvider.getRoleQuery() + .where() + .in("name", user.getRoles()); + List roles = roleRepository.find(query); + + + IUser newUser = domainMapper.createUser(user, password, false, false, roles); + encryptAndSetUserPassword(newUser); + + + if (userExistsWithEmail(user.getEmail())) { + response.addError("", "A user already exists with that email address."); + return response; + } + + newUser = userRepository.create(newUser); + response.setResponseObject(DomainMapper.createUserItem(newUser)); + } catch (Exception ex) { + response.addError("", ex.getMessage()); + } + */ + + ServiceResponse response = new ServiceResponse<>(); + response.setResponseObject(null); + return response; } @Override @@ -36,8 +70,98 @@ public IUser findById(int id) { } @Override - public ServiceResponse> findAllUsers(){ - return new ServiceResponse<>(); + public ServiceResponse findUser(int id) { + + /* + ServiceResponse response = new ServiceResponse<>(); + ExpressionList query = QueryProvider.getUserQuery().fetch("roles").where().eq("id", id); + try { + IUser user = userRepository.findOne(query); + UserItem userItem; + userItem = DomainMapper.createUserItem(user); + response.setResponseObject(userItem); + } catch (Exception ex) { + response.addError("", ex.getMessage()); + } + return response; + */ + + ServiceResponse response = new ServiceResponse<>(); + response.setResponseObject(null); + return response; + } + + @Override + public ServiceResponse> findAllUsers(){ + + ServiceResponse> response = new ServiceResponse<>(); + response.setResponseObject(null); + return response; + } + + @Override + public ServiceResponse updateUser(UserItem userItem, String newPassword) { + + /* + ServiceResponse response = new ServiceResponse<>(); + if (userItem == null) { + response.addError("", "send a user"); + return response; + } + ExpressionList query = QueryProvider.getUserQuery().where().eq("id", userItem.getId()); + ExpressionList roleQuery = QueryProvider.getRoleQuery() + .where() + .ne("name", "SuperUser"); + + List allRoles = roleRepository.find(roleQuery); + + try { + IUser user = userRepository.findOne(query); + if (StringUtils.isNotNullOrWhiteSpace(newPassword)) { + user.setPassword(newPassword); + encryptAndSetUserPassword(user); + } + user.setFirstName(userItem.getFirstName()); + user.setLastName(userItem.getLastName()); + user.setNotes(userItem.getNotes()); + List newRoles = new ArrayList<>(); + for (IRole role : allRoles){ + if (userItem.getRoles().contains(role.getName())) + newRoles.add(role); + } + user.setRoles(newRoles); + user.setPasswordReset(userItem.isPasswordReset()); + user = userRepository.update(user); + response.setResponseObject(DomainMapper.createUserItem(user)); + } catch (Exception ex) { + response.addError("", ex.getMessage()); + } + */ + + ServiceResponse response = new ServiceResponse<>(); + response.setResponseObject(null); + return response; + } + + @Override + public ServiceResponse toggleUser(int id) { + /* + ServiceResponse response = new ServiceResponse<>(); + ExpressionList query = QueryProvider.getUserQuery().where().eq("id", id); + try { + IUser user = userRepository.findOne(query); + user.setDeleted(!user.getDeleted()); + user = userRepository.update(user); + response.setResponseObject(DomainMapper.createUserItem(user)); + } catch (Exception ex) { + response.addError("", ex.getMessage()); + } + */ + + ServiceResponse response = new ServiceResponse<>(); + response.setResponseObject(null); + return response; + } @Override diff --git a/test/mock/femr/data/daos/MockRepository.java b/test/mock/femr/data/daos/MockRepository.java index 5081cd57e..896aa9f82 100644 --- a/test/mock/femr/data/daos/MockRepository.java +++ b/test/mock/femr/data/daos/MockRepository.java @@ -64,4 +64,16 @@ public T update(T entity) { entityPassedIn = entity; return entityPassedIn; } + + @Override + public void delete(List entities){ + + + } + + @Override + public void delete(T entity){ + + + } } diff --git a/test/mock/femr/data/models/MockHpiField.java b/test/mock/femr/data/models/MockHpiField.java deleted file mode 100644 index a405ad05f..000000000 --- a/test/mock/femr/data/models/MockHpiField.java +++ /dev/null @@ -1,37 +0,0 @@ -package mock.femr.data.models; - -import femr.data.models.IHpiField; - -public class MockHpiField implements IHpiField{ - private int id = 0; - private String name = ""; - - @Override - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Override - public String getName() { - return name; - } - - @Override - public void setName(String name) { - this.name = name; - } - - @Override - public Boolean getDeleted() { - return null; - } - - @Override - public void setDeleted(Boolean deleted) { - - } -} diff --git a/test/mock/femr/data/models/MockPatient.java b/test/mock/femr/data/models/MockPatient.java index 5a5adc2ce..a6cfa0a8f 100644 --- a/test/mock/femr/data/models/MockPatient.java +++ b/test/mock/femr/data/models/MockPatient.java @@ -1,18 +1,22 @@ package mock.femr.data.models; import femr.data.models.IPatient; +import femr.data.models.IPhoto; +import femr.data.models.Photo; import java.util.Date; public class MockPatient implements IPatient { - private int id = 0; - private int userId = 0; - private String firstName = ""; - private String lastName = ""; + + private int id; + private int userId; + private String firstName; + private String lastName; private Date age; - private String sex = ""; - private String address = ""; - private String city = ""; + private String sex; + private String address; + private String city; + private Photo photo; @Override public int getId() { @@ -95,12 +99,12 @@ public void setId(int id) { } @Override - public Integer getPhotoId() { - return null; + public IPhoto getPhoto() { + return photo; } @Override - public void setPhotoId(Integer id) { - + public void setPhoto(IPhoto photo) { + this.photo = (Photo) photo; } } diff --git a/test/mock/femr/data/models/MockPatientEncounter.java b/test/mock/femr/data/models/MockPatientEncounter.java index cae176aa5..b9c800cad 100644 --- a/test/mock/femr/data/models/MockPatientEncounter.java +++ b/test/mock/femr/data/models/MockPatientEncounter.java @@ -1,15 +1,24 @@ package mock.femr.data.models; -import femr.data.models.IPatientEncounter; +import femr.data.models.*; +import org.joda.time.DateTime; + +import java.util.ArrayList; +import java.util.List; public class MockPatientEncounter implements IPatientEncounter { - private int id = 0; - private int patientId = 0; - private int userId = 0; - private String dateOfVisit = ""; - private String chiefComplaint = ""; - private Integer weeksPregnant = 0; + private int id; + private Patient patient; + private User nurse; + private DateTime dateOfTriageVisit; + private List chiefComplaints; + private Integer weeksPregnant; + private DateTime dateOfMedicalVisit; + private DateTime dateOfPharmacyVisit; + private User doctor; + private User pharmacist; + private PatientAgeClassification patientAgeClassification; @Override public int getId() { @@ -17,53 +26,110 @@ public int getId() { } @Override - public int getPatientId() { - return patientId; + public IPatient getPatient() { + return patient; } @Override - public void setPatientId(int patientId) { - this.patientId = patientId; + public void setPatient(IPatient patient) { + this.patient = (Patient) patient; } @Override - public int getUserId() { - return userId; + public List getChiefComplaints() { + List temp = new ArrayList<>(); + for (ChiefComplaint cc : chiefComplaints) { + temp.add(cc); + } + return temp; } @Override - public void setUserId(int userId) { - this.userId = userId; + public void setChiefComplaints(List chiefComplaints) { + for (IChiefComplaint cc : chiefComplaints) { + this.chiefComplaints.add((ChiefComplaint) cc); + } } @Override - public String getDateOfTriageVisit() { - return dateOfVisit; + public Integer getWeeksPregnant() { + return weeksPregnant; } @Override - public void setDateOfVisit(String dateOfVisit) { - this.dateOfVisit = dateOfVisit; + public void setWeeksPregnant(Integer weeksPregnant) { + this.weeksPregnant = weeksPregnant; } @Override - public String getChiefComplaint() { - return chiefComplaint; + public DateTime getDateOfTriageVisit() { + return dateOfTriageVisit; } @Override - public void setChiefComplaint(String chiefComplaint) { - this.chiefComplaint = chiefComplaint; + public void setDateOfTriageVisit(DateTime dateOfTriageVisit) { + this.dateOfTriageVisit = dateOfTriageVisit; } @Override - public Integer getWeeksPregnant() { - return weeksPregnant; + public DateTime getDateOfMedicalVisit() { + return dateOfMedicalVisit; } @Override - public void setWeeksPregnant(Integer weeksPregnant) { - this.weeksPregnant = weeksPregnant; + public void setDateOfMedicalVisit(DateTime dateOfMedicalVisit) { + this.dateOfMedicalVisit = dateOfMedicalVisit; + } + + @Override + public DateTime getDateOfPharmacyVisit() { + return dateOfPharmacyVisit; + } + + @Override + public void setDateOfPharmacyVisit(DateTime dateOfPharmacyVisit) { + this.dateOfPharmacyVisit = dateOfPharmacyVisit; + } + + @Override + public IUser getDoctor() { + return doctor; + } + + @Override + public void setDoctor(IUser doctor) { + this.doctor = (User) doctor; + } + + @Override + public IUser getPharmacist() { + return pharmacist; + } + + @Override + public void setPharmacist(IUser pharmacist) { + this.pharmacist = (User) pharmacist; + } + + @Override + public IUser getNurse() { + return nurse; } + + @Override + public void setNurse(IUser nurse) { + this.nurse = (User) nurse; + } + + @Override + public IPatientAgeClassification getPatientAgeClassification() { + return patientAgeClassification; + } + + @Override + public void setPatientAgeClassification(IPatientAgeClassification patientAgeClassification) { + this.patientAgeClassification = (PatientAgeClassification) patientAgeClassification; + } + } diff --git a/test/mock/femr/data/models/MockPatientEncounterHpiField.java b/test/mock/femr/data/models/MockPatientEncounterHpiField.java deleted file mode 100644 index 47813efe0..000000000 --- a/test/mock/femr/data/models/MockPatientEncounterHpiField.java +++ /dev/null @@ -1,73 +0,0 @@ -package mock.femr.data.models; - -import femr.data.models.IHpiField; -import femr.common.models.IPatientEncounterHpiField; -import org.joda.time.DateTime; - -public class MockPatientEncounterHpiField implements IPatientEncounterHpiField { - - private int id = 0; - private int userId = 0; - private int patientEncounterId = 0; - private MockHpiField hpiField = null; - private String hpiFieldValue = ""; - private DateTime dateTaken; - - @Override - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Override - public int getUserId() { - return userId; - } - - @Override - public void setUserId(int userId) { - this.userId = userId; - } - - @Override - public int getPatientEncounterId() { - return patientEncounterId; - } - - @Override - public void setPatientEncounterId(int patientEncounterId) { - this.patientEncounterId = patientEncounterId; - } - - @Override - public IHpiField getHpiField() { - return hpiField; - } - - @Override - public void setHpiField(IHpiField hpiField) { - this.hpiField = (MockHpiField)hpiField; - } - - @Override - public String getHpiFieldValue() { - return hpiFieldValue; - } - - @Override - public void setHpiFieldValue(String hpiFieldValue) { - this.hpiFieldValue = hpiFieldValue; - } - - @Override - public DateTime getDateTaken() { - return dateTaken; - } - - public void setDateTaken(DateTime dateTaken) { - this.dateTaken = dateTaken; - } -} diff --git a/test/mock/femr/data/models/MockPatientEncounterPmhField.java b/test/mock/femr/data/models/MockPatientEncounterPmhField.java deleted file mode 100644 index 753bc6be4..000000000 --- a/test/mock/femr/data/models/MockPatientEncounterPmhField.java +++ /dev/null @@ -1,73 +0,0 @@ -package mock.femr.data.models; - -import femr.common.models.IPatientEncounterPmhField; -import femr.data.models.IPmhField; -import org.joda.time.DateTime; - -public class MockPatientEncounterPmhField implements IPatientEncounterPmhField { - private int id; - private int userId; - private int patientEncounterId; - private MockPmhField pmhField; - private String pmhFieldValue; - private DateTime dateTaken; - - @Override - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Override - public int getUserId() { - return userId; - } - - @Override - public void setUserId(int userId) { - this.userId = userId; - } - - @Override - public int getPatientEncounterId() { - return patientEncounterId; - } - - @Override - public void setPatientEncounterId(int patientEncounterId) { - this.patientEncounterId = patientEncounterId; - } - - @Override - public IPmhField getPmhField() { - return pmhField; - } - - @Override - public void setPmhField(IPmhField pmhField) { - this.pmhField = (MockPmhField)pmhField; - } - - @Override - public String getPmhFieldValue() { - return pmhFieldValue; - } - - @Override - public void setPmhFieldValue(String pmhFieldValue) { - this.pmhFieldValue = pmhFieldValue; - } - - @Override - public DateTime getDateTaken() { - return dateTaken; - } - - @Override - public void setDateTaken(DateTime dateTaken) { - this.dateTaken = dateTaken; - } -} diff --git a/test/mock/femr/data/models/MockPatientEncounterTreatmentField.java b/test/mock/femr/data/models/MockPatientEncounterTreatmentField.java deleted file mode 100644 index 7d94f96cf..000000000 --- a/test/mock/femr/data/models/MockPatientEncounterTreatmentField.java +++ /dev/null @@ -1,74 +0,0 @@ -package mock.femr.data.models; - -import femr.common.models.IPatientEncounterTreatmentField; -import femr.data.models.ITreatmentField; -import org.joda.time.DateTime; - -public class MockPatientEncounterTreatmentField implements IPatientEncounterTreatmentField{ - - private int id = 0; - private int userId = 0; - private int patientEncounterId = 0; - private MockTreatmentField treatmentField = null; - private String treatmentFieldValue = ""; - private DateTime dateTaken; - - @Override - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Override - public int getUserId() { - return userId; - } - - @Override - public void setUserId(int userId) { - this.userId = userId; - } - - @Override - public int getPatientEncounterId() { - return patientEncounterId; - } - - @Override - public void setPatientEncounterId(int patientEncounterId) { - this.patientEncounterId = patientEncounterId; - } - - @Override - public ITreatmentField getTreatmentField() { - return treatmentField; - } - - @Override - public void setTreatmentField(ITreatmentField treatmentField) { - this.treatmentField = (MockTreatmentField)treatmentField; - } - - @Override - public String getTreatmentFieldValue() { - return treatmentFieldValue; - } - - @Override - public void setTreatmentFieldValue(String treatmentFieldValue) { - this.treatmentFieldValue = treatmentFieldValue; - } - - @Override - public DateTime getDateTaken() { - return dateTaken; - } - - @Override - public void setDateTaken(DateTime dateTaken) { - this.dateTaken = dateTaken; - } -} diff --git a/test/mock/femr/data/models/MockPatientPrescription.java b/test/mock/femr/data/models/MockPatientPrescription.java index 3cded5db0..2b942d154 100644 --- a/test/mock/femr/data/models/MockPatientPrescription.java +++ b/test/mock/femr/data/models/MockPatientPrescription.java @@ -1,20 +1,19 @@ package mock.femr.data.models; -import femr.data.models.IPatientPrescription; +import femr.data.models.*; import org.joda.time.DateTime; public class MockPatientPrescription implements IPatientPrescription { - private int id = 0; - private int encounterId = 0; - private int userId = 0; - private int amount = 0; - private Boolean replaced = false; - private String reason = ""; - private Integer replacementId = 0; - private String medicationName = ""; - private DateTime dateTaken; - public void setId(int id){this.id = id;} + private int id; + private PatientEncounter patientEncounter; + private Medication medication; + private MedicationAdministration medicationAdministration; + private User physician; + private int amount; + private DateTime dateTaken; + private Integer replacementId; + private String specialInstructions; @Override public int getId() { @@ -22,53 +21,53 @@ public int getId() { } @Override - public int getEncounterId() { - return encounterId; + public IPatientEncounter getPatientEncounter() { + return patientEncounter; } @Override - public void setEncounterId(int encounterId) { - this.encounterId = encounterId; + public void setPatientEncounter(IPatientEncounter patientEncounter) { + this.patientEncounter = (PatientEncounter) patientEncounter; } @Override - public int getUserId() { - return userId; + public IMedication getMedication() { + return medication; } @Override - public void setUserId(int userId) { - this.userId = userId; + public void setMedication(IMedication medication) { + this.medication = (Medication) medication; } @Override - public int getAmount() { - return amount; + public IMedicationAdministration getMedicationAdministration() { + return medicationAdministration; } @Override - public void setAmount(int amount) { - this.amount = amount; + public void setMedicationAdministration(IMedicationAdministration medicationAdministration) { + this.medicationAdministration = (MedicationAdministration) medicationAdministration; } @Override - public Boolean getReplaced() { - return replaced; + public IUser getPhysician() { + return physician; } @Override - public void setReplaced(Boolean replaced) { - this.replaced = replaced; + public void setPhysician(IUser physician) { + this.physician = (User) physician; } @Override - public String getReason() { - return reason; + public int getAmount() { + return amount; } @Override - public void setReason(String reason) { - this.reason = reason; + public void setAmount(int amount) { + this.amount = amount; } @Override @@ -82,22 +81,22 @@ public void setReplacementId(Integer replacementId) { } @Override - public String getMedicationName() { - return medicationName; + public DateTime getDateTaken() { + return dateTaken; } @Override - public void setMedicationName(String medicationName) { - this.medicationName = medicationName; + public void setDateTaken(DateTime dateTaken) { + this.dateTaken = dateTaken; } @Override - public DateTime getDateTaken() { - return dateTaken; + public String getSpecialInstructions() { + return specialInstructions; } @Override - public void setDateTaken(DateTime dateTaken) { - this.dateTaken = dateTaken; + public void setSpecialInstructions(String specialInstructions) { + this.specialInstructions = specialInstructions; } } diff --git a/test/mock/femr/data/models/MockPmhField.java b/test/mock/femr/data/models/MockPmhField.java deleted file mode 100644 index 4ba8eaad0..000000000 --- a/test/mock/femr/data/models/MockPmhField.java +++ /dev/null @@ -1,37 +0,0 @@ -package mock.femr.data.models; - -import femr.data.models.IPmhField; - -public class MockPmhField implements IPmhField { - private int id = 0; - private String name = ""; - @Override - public int getId() { - return id; - } - - @Override - public String getName() { - return name; - } - - @Override - public void setName(String name) { - this.name = name; - - } - - @Override - public Boolean getDeleted() { - return null; - } - - @Override - public void setDeleted(Boolean deleted) { - - } - - public void setId(int id) { - this.id = id; - } -} diff --git a/test/mock/femr/data/models/MockTreatmentField.java b/test/mock/femr/data/models/MockTreatmentField.java deleted file mode 100644 index 56f3dc8f5..000000000 --- a/test/mock/femr/data/models/MockTreatmentField.java +++ /dev/null @@ -1,40 +0,0 @@ -package mock.femr.data.models; - -import femr.data.models.ITreatmentField; - -/** - * Created by Kevin on 2/9/14. - */ -public class MockTreatmentField implements ITreatmentField { - private int id = 0; - private String name = ""; - - @Override - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Override - public String getName() { - return name; - } - - @Override - public void setName(String name) { - this.name = name; - } - - @Override - public Boolean getDeleted() { - return null; - } - - @Override - public void setDeleted(Boolean deleted) { - - } -} diff --git a/test/mock/femr/data/models/MockUser.java b/test/mock/femr/data/models/MockUser.java index 9e8e4b8a5..980693e06 100644 --- a/test/mock/femr/data/models/MockUser.java +++ b/test/mock/femr/data/models/MockUser.java @@ -4,16 +4,21 @@ import femr.data.models.IUser; import org.joda.time.DateTime; +import java.util.ArrayList; import java.util.List; public class MockUser implements IUser { - public int id = 0; - public String firstName = ""; - public String lastName = ""; - public String email = ""; - public String password = ""; - public List roles; + private int id; + private String firstName; + private String lastName; + private String email; + private String password; + private List roles; + private DateTime lastLogin; + private Boolean deleted; + private Boolean passwordReset; + private String notes; @Override public int getId() { @@ -66,8 +71,11 @@ public List getRoles() { } @Override - public void setRoles(List roles) { - this.roles = roles; + public void setRoles(List roles) { + this.roles = new ArrayList<>(); + for (IRole role : roles){ + this.roles.add(role); + } } @Override @@ -77,31 +85,41 @@ public void addRole(IRole role) { @Override public DateTime getLastLogin() { - return null; + return lastLogin; } @Override public void setLastLogin(DateTime lastLogin) { - + this.lastLogin = lastLogin; } @Override public Boolean getDeleted() { - return null; + return deleted; } @Override public void setDeleted(Boolean deleted) { - + this.deleted = deleted; } @Override public Boolean getPasswordReset() { - return null; + return passwordReset; } @Override public void setPasswordReset(Boolean passwordReset) { + this.passwordReset = passwordReset; + } + @Override + public String getNotes() { + return notes; + } + + @Override + public void setNotes(String notes) { + this.notes = notes; } } diff --git a/test/unit/app/femr/business/services/ResearchServiceTest.java b/test/unit/app/femr/business/services/ResearchServiceTest.java new file mode 100644 index 000000000..221ec64ca --- /dev/null +++ b/test/unit/app/femr/business/services/ResearchServiceTest.java @@ -0,0 +1,114 @@ +package unit.app.femr.business.services; + +import com.google.inject.Provider; +import femr.business.helpers.DomainMapper; +import femr.business.services.IResearchService; +import femr.business.services.ResearchService; +import femr.common.dto.ServiceResponse; +import femr.data.daos.IRepository; +import femr.data.daos.Repository; +import femr.data.models.*; +import mock.femr.data.daos.MockRepository; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; + +import static org.fest.assertions.Assertions.assertThat; + +public class ResearchServiceTest { + + private IResearchService researchService; + + private Repository mockPatientIRepository; + private Repository mockPatientEncounterIRepository; + private Repository mockPatientEncounterVitalIRepository; + + private Repository mockChiefComplaintIRepository; + private Repository mockUserIRepository; + private Repository mockVitalIRepository; + private Repository mockPrescriptionIRepository; + private Repository mockMedicationIRepository; + + private Provider mockPatientEncounterVitalProvider; + private DomainMapper domainMapper; + + private Provider chiefComplaintProvider; + private Provider medicationProvider; + private Provider medicationActiveDrugNameProvider; + private Provider medicationActiveDrugProvider; + private Provider medicationMeasurementUnitProvider; + private Provider medicationFormProvider; + private Provider patientProvider; + private Provider patientAgeClassificationProvider; + private Provider patientEncounterPhotoProvider; + private Provider patientEncounterProvider; + private Provider patientEncounterTabFieldProvider; + private Provider patientEncounterVitalProvider; + private Provider patientPrescriptionProvider; + + private Provider photoProvider; + private Provider roleProvider; + private Provider tabFieldProvider; + private Provider tabFieldSizeProvider; + private Provider tabFieldTypeProvider; + private Provider tabProvider; + private Provider userProvider; + private Provider vitalProvider; + + + @Before + public void setUp() { + + mockPatientIRepository = new Repository<>(); + mockPatientEncounterIRepository = new Repository<>(); + mockChiefComplaintIRepository = new Repository<>(); + mockUserIRepository = new Repository<>(); + mockVitalIRepository = new Repository<>(); + mockPrescriptionIRepository = new Repository<>(); + mockMedicationIRepository = new Repository<>(); + mockPatientEncounterVitalIRepository = new Repository<>(); + + domainMapper = new DomainMapper(chiefComplaintProvider, + medicationProvider, + medicationActiveDrugNameProvider, + medicationFormProvider, + medicationActiveDrugProvider, + medicationMeasurementUnitProvider, + patientProvider, + patientAgeClassificationProvider, + patientEncounterPhotoProvider, + patientEncounterProvider, + patientEncounterTabFieldProvider, + patientEncounterVitalProvider, + patientPrescriptionProvider, + photoProvider, + roleProvider, + tabFieldProvider, + tabFieldSizeProvider, + tabFieldTypeProvider, + tabProvider, + userProvider, + vitalProvider); + + researchService = new ResearchService(mockChiefComplaintIRepository, + mockPatientIRepository, + mockPatientEncounterIRepository, + mockPatientEncounterVitalIRepository, + mockUserIRepository, + mockVitalIRepository, + mockPrescriptionIRepository, + mockMedicationIRepository, + mockPatientEncounterVitalProvider, + domainMapper); + + } + + @Test + public void getAllMedicationsCheck(){ + + //ServiceResponse> medResponse = researchService.getAllMedications(); + //assertThat(medResponse.getResponseObject().size() > 1); + } + +} diff --git a/test/unit/app/femr/ui/controllers/ResearchControllerTest.java b/test/unit/app/femr/ui/controllers/ResearchControllerTest.java new file mode 100644 index 000000000..413e1cfa2 --- /dev/null +++ b/test/unit/app/femr/ui/controllers/ResearchControllerTest.java @@ -0,0 +1,69 @@ +package unit.app.femr.ui.controllers; + + +import femr.ui.models.research.FilterViewModel; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; +import play.mvc.Result; +import play.libs.F; +import play.test.FakeRequest; +import play.test.TestBrowser; + +import java.util.HashMap; +import java.util.Map; + +import static org.fest.assertions.Assertions.assertThat; +import static play.mvc.Http.Status.OK; +import static play.test.Helpers.*; + +public class ResearchControllerTest{ + + @Before + public void setUp() { + + + } + + @Test + public void indexGetCheck(){ + + /* + Result result = callAction(route()); + assertThat(status(result)).isEqualTo(OK); + assertThat(contentType(result)).isEqualTo("text/html"); + assertThat(charset(result)).isEqualTo("utf-8"); + assertThat(contentAsString(result)).contains("hello, world"); + */ + } + + @Test + public void getGraphPostCheck(){ + + FilterViewModel filters = new FilterViewModel(); + + filters.setPrimaryDataset("age"); + + + + } + + + @Test + public void runInBrowser() { + + /* + Map map = new HashMap<>(); + + running(testServer(9000), HtmlUnitDriver.class, new F.Callback() { + + public void invoke(TestBrowser browser) { + browser.goTo("http://localhost:9000"); + assertThat(browser.$("body").getTexts().get(0)).isEqualTo("hello, world"); + } + }); + */ + } + +} + diff --git a/test/unit/app/femr/ui/views/home/ResearchIndexTest.java b/test/unit/app/femr/ui/views/home/ResearchIndexTest.java new file mode 100644 index 000000000..9d9ca9c96 --- /dev/null +++ b/test/unit/app/femr/ui/views/home/ResearchIndexTest.java @@ -0,0 +1,299 @@ +package unit.app.femr.ui.views.home; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.*; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import play.libs.F; +import play.test.FakeApplication; +import play.test.TestBrowser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static play.test.Helpers.*; + +public class ResearchIndexTest { + + public static FakeApplication app; + + private WebDriver driver; + private String baseUrl; + private boolean acceptNextAlert = true; + private StringBuffer verificationErrors = new StringBuffer(); + + List primaryDatasets; + List secondaryDatasets; + List graphTypes; + + public ResearchIndexTest(){ + + primaryDatasets = new ArrayList<>(); + primaryDatasets.add("age"); + primaryDatasets.add("gender"); + primaryDatasets.add("height"); + primaryDatasets.add("weight"); + primaryDatasets.add("pregnancyStatus"); + primaryDatasets.add("pregnancyTime"); + primaryDatasets.add("prescribedMeds"); + primaryDatasets.add("dispensedMeds"); + primaryDatasets.add("temperature"); + primaryDatasets.add("bloodPressureSystolic"); + primaryDatasets.add("bloodPressureDiastolic"); + primaryDatasets.add("heartRate"); + primaryDatasets.add("respirations"); + primaryDatasets.add("oxygenSaturation"); + primaryDatasets.add("glucose"); + + secondaryDatasets = new ArrayList<>(); + secondaryDatasets.add("age"); + secondaryDatasets.add("gender"); + secondaryDatasets.add("pregnancyStatus"); + + graphTypes = new ArrayList<>(); + graphTypes.add("bar"); + graphTypes.add("pie"); + graphTypes.add("line"); + graphTypes.add("scatter"); + graphTypes.add("stacked-bar"); + graphTypes.add("grouped-bar"); + graphTypes.add("table"); + + } + + + @Before + public void setUp() throws Exception { + + Map settings = new HashMap(); + settings.put("db.default.url", "jdbc:mysql://localhost/femr?characterEncoding=UTF-8"); + settings.put("db.default.user", "femr"); + settings.put("db.default.password", "PnhcTUQ9xpraJf7e"); + + app = fakeApplication(settings); + + baseUrl = "http://localhost:3333/"; + System.setProperty("webdriver.chrome.driver", "/Users/dev/Desktop/Selenium/chromedriver"); + driver = new ChromeDriver(); + driver.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS); + + running(testServer(3333, app), driver, new F.Callback() { + public void invoke(TestBrowser browser) { + + runAllTests(); + } + + }); + + + } + + + @Test + public void sampleTest(){ + + + } + + public void runAllTests() { + + try { + testLogin(); + testSingleDataset(); + testDoubleDataSet(); + } + catch (Exception e){ + + System.out.println("** Exception Found **"); + System.out.println(e.getMessage()); + } + } + + public void testLogin() throws Exception { + + driver.get(baseUrl); + driver.findElement(By.name("email")).sendKeys("kdunlap4918@gmail.com"); + driver.findElement(By.name("password")).sendKeys("password"); + driver.findElement(By.cssSelector("input[type=\"submit\"]")).click(); + + WebDriverWait driverWait = new WebDriverWait(driver, 30); + driverWait.until(ExpectedConditions.visibilityOfElementLocated(By.linkText("Research"))); + + driver.findElement(By.linkText("Research")).click(); + assertEquals("Research | fEMR - The free EMR", driver.getTitle()); + + driverWait.until(ExpectedConditions.visibilityOfElementLocated(By.linkText("Primary Dataset"))); + } + + public void testSingleDataset() throws Exception { + + for( String name : primaryDatasets ){ + + try { + driver.findElement(By.linkText("Primary Dataset")).click(); + WebElement primaryLink = driver.findElement(By.xpath("//a[@data-dname1=\"" + name + "\" and (not(@class) or @class!=\"disabled\")]")); + primaryLink.click(); + } + catch(NoSuchElementException e){ + + driver.findElement(By.linkText("Primary Dataset")).click(); + continue; + } + + for( String graph : graphTypes ) { + + driver.findElement(By.linkText("Graph Type")).click(); + WebElement graphNode = null; + try{ + + graphNode = driver.findElement(By.xpath("//a[@data-gtype=\"" + graph + "\" and (not(@class) or @class!=\"disabled\")]")); + } + catch(NoSuchElementException e){ + + driver.findElement(By.linkText("Graph Type")).click(); + continue; + } + + if (graphNode != null) { + + graphNode.click(); + driver.findElement(By.id("submit-button")).click(); + + if (!graph.equals("table")){ + + // Wait for graph to load - g elements will show up? + WebDriverWait driverWait = new WebDriverWait(driver, 10); + driverWait.until(ExpectedConditions.visibilityOfElementLocated(By.tagName("g"))); + + driver.findElement(By.id("save-button")).click(); + driver.findElement(By.xpath("//a[@data-imagesize=\"small\"]")).click(); + } + } + + } + } + + } + + public void testDoubleDataSet(){ + + for( String name : primaryDatasets ){ + + try { + driver.findElement(By.linkText("Primary Dataset")).click(); + WebElement primaryLink = driver.findElement(By.xpath("//a[@data-dname1=\"" + name + "\" and (not(@class) or @class!=\"disabled\")]")); + primaryLink.click(); + } + catch(NoSuchElementException e){ + + System.out.println("Primary Not Found"); + driver.findElement(By.linkText("Primary Dataset")).click(); + continue; + } + + for( String name2 : secondaryDatasets ){ + + try { + + driver.findElement(By.linkText("Secondary Dataset")).click(); + WebElement secondaryLink = driver.findElement(By.xpath("//a[@data-dname2=\"" + name2 + "\" and (not(@class) or @class!=\"disabled\")]")); + secondaryLink.click(); + } + catch(NoSuchElementException e){ + + System.out.println("Secondary Not Found"); + driver.findElement(By.linkText("Secondary Dataset")).click(); + continue; + } + + for( String graph : graphTypes ) { + + + driver.findElement(By.linkText("Graph Type")).click(); + WebElement graphNode = null; + try{ + + graphNode = driver.findElement(By.xpath("//a[@data-gtype=\"" + graph + "\" and (not(@class) or @class!=\"disabled\")]")); + } + catch(NoSuchElementException e){ + + System.out.println("Graph Not Found"); + driver.findElement(By.linkText("Graph Type")).click(); + continue; + } + + if (graphNode != null) { + + graphNode.click(); + driver.findElement(By.id("submit-button")).click(); + + if (!graph.equals("table")){ + // Wait for graph to load - g elements will show up? + WebDriverWait driverWait = new WebDriverWait(driver, 10); + driverWait.until(ExpectedConditions.visibilityOfElementLocated(By.tagName("g"))); + + driver.findElement(By.id("save-button")).click(); + driver.findElement(By.xpath("//a[@data-imagesize=\"small\"]")).click(); + } + } + } + + } + } + } + + + @After + public void tearDown() throws Exception { + + String verificationErrorString = verificationErrors.toString(); + if (!"".equals(verificationErrorString)) { + fail(verificationErrorString); + } + + //Helpers.stop(app); + driver.quit(); + } + + private boolean isElementPresent(By by) { + try { + driver.findElement(by); + return true; + } catch (NoSuchElementException e) { + return false; + } + } + + private boolean isAlertPresent() { + try { + driver.switchTo().alert(); + return true; + } catch (NoAlertPresentException e) { + return false; + } + } + + private String closeAlertAndGetItsText() { + try { + Alert alert = driver.switchTo().alert(); + String alertText = alert.getText(); + if (acceptNextAlert) { + alert.accept(); + } else { + alert.dismiss(); + } + return alertText; + } finally { + acceptNextAlert = true; + } + } + +} From 886316a9578bdd7d51f78ff4f0f35848b55fdf8a Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Thu, 11 Dec 2014 18:01:23 -0500 Subject: [PATCH 02/21] update contributors --- CONTRIBUTORS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 309342c78..eaea7a301 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,4 +12,7 @@ These people have contributed to fEMR's design and implementation: * [Priyesh Pandya](https://github.com/priyeshp) * [Danny Reinheimer](https://github.com/codeitandloadit) * [Cohen Carlisle](https://github.com/Cohen-Carlisle) -* [Brandon Dane](https://github.com/b6025) \ No newline at end of file +* [Brandon Dane](https://github.com/b6025) +* [Ken Dunlap](https://github.com/kdunlap) +* [Arslan Gondal](https://github.com/unfixed) +* [Khoa Le](https://github.com/khoal) \ No newline at end of file From ed7595034391b79bbcf818d2d6e8fddbcdb72f2a Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Wed, 17 Dec 2014 18:45:12 -0500 Subject: [PATCH 03/21] update development to keep up with master --- app/femr/ui/views/home/index.scala.html | 2 +- app/femr/ui/views/partials/footer.scala.html | 2 +- app/femr/ui/views/triage/index.scala.html | 16 +- project/Build.scala | 2 +- public/js/admin/users.js | 1 - public/js/libraries/exif.js | 830 ++++++++++++++++++ .../jquery.jWindowCrop.js | 10 +- public/js/libraries/megapix-image.js | 261 ++++++ .../js/{triage => libraries}/touchit-1.0.0.js | 0 public/js/shared/femr.js | 9 + public/js/triage/triage.js | 121 ++- 11 files changed, 1225 insertions(+), 29 deletions(-) create mode 100644 public/js/libraries/exif.js rename public/js/{triage => libraries}/jquery.jWindowCrop.js (97%) create mode 100644 public/js/libraries/megapix-image.js rename public/js/{triage => libraries}/touchit-1.0.0.js (100%) diff --git a/app/femr/ui/views/home/index.scala.html b/app/femr/ui/views/home/index.scala.html index daf8d87a9..985263a2d 100644 --- a/app/femr/ui/views/home/index.scala.html +++ b/app/femr/ui/views/home/index.scala.html @@ -5,7 +5,7 @@ @top = {
-

Welcome to fEMR 2.1.0, @currentUser.getFirstName!

+

Welcome to fEMR 2.1.2, @currentUser.getFirstName!

@*

This is a placeholder landing page. As more features are developed, this page will change over time.

*@

Please select a tab at the top to get started!

diff --git a/app/femr/ui/views/partials/footer.scala.html b/app/femr/ui/views/partials/footer.scala.html index a17f311f7..8ec69ccb1 100644 --- a/app/femr/ui/views/partials/footer.scala.html +++ b/app/femr/ui/views/partials/footer.scala.html @@ -1,4 +1,4 @@

-

fEMR 2.1.0 © 2014

+

fEMR 2.1.2 © 2014

\ No newline at end of file diff --git a/app/femr/ui/views/triage/index.scala.html b/app/femr/ui/views/triage/index.scala.html index b22d678eb..092f738dc 100644 --- a/app/femr/ui/views/triage/index.scala.html +++ b/app/femr/ui/views/triage/index.scala.html @@ -11,14 +11,15 @@ } @additionalScripts = { - - + + + + } - @main("Triage", currentUser, styles = additionalStyles, scripts = additionalScripts, search = search("triage")) {
@@ -40,14 +41,14 @@

General Info

- @inputDate("Birth Date", "age", if(viewModel != null) viewModel.getPatient.getBirth else null) - OR @inputAge("Age", "Years", "years", "Months", "months", if(viewModel != null) viewModel.getPatient.getAge else null) OR + @inputDate("Birth Date", "age", if(viewModel != null) viewModel.getPatient.getBirth else null) + OR
@for((key, valyew) <- viewModel.getPossibleAgeClassifications) { - + } @@ -217,5 +218,4 @@

Vitals

-} - +} \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index 1ec71be31..f7477ab85 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -5,7 +5,7 @@ import play.Play.autoImport._ object ApplicationBuild extends Build { val appName = "fEMR" - val appVersion = "2.1.0" + val appVersion = "2.1.2"//doesn't auto update everything in the code when this is changed val currentScalaVersion = "2.11.2" val appDependencies = Seq( diff --git a/public/js/admin/users.js b/public/js/admin/users.js index fc6c68424..055e92f3b 100644 --- a/public/js/admin/users.js +++ b/public/js/admin/users.js @@ -96,7 +96,6 @@ var createUsers = { //validate roles var isARoleChecked = false; $.each(document.forms["createForm"].elements["roles[]"], function () { - console.log($(this).is(':checked')); if ($(this).is(':checked')) { isARoleChecked = true; } diff --git a/public/js/libraries/exif.js b/public/js/libraries/exif.js new file mode 100644 index 000000000..0bb45180f --- /dev/null +++ b/public/js/libraries/exif.js @@ -0,0 +1,830 @@ +/** + * This file was retrieved from: + * https://github.com/jseidelin/exif-js + * + * The MIT License (MIT) + + Copyright (c) 2008 Jacob Seidelin + + Permission is hereby granted, free of charge, to any person obtaining a copy of this + software and associated documentation files (the "Software"), to deal in the Software + without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + */ + +(function() { + + var debug = false; + + var root = this; + + var EXIF = function(obj) { + if (obj instanceof EXIF) return obj; + if (!(this instanceof EXIF)) return new EXIF(obj); + this.EXIFwrapped = obj; + }; + + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = EXIF; + } + exports.EXIF = EXIF; + } else { + root.EXIF = EXIF; + } + + var ExifTags = EXIF.Tags = { + + // version tags + 0x9000 : "ExifVersion", // EXIF version + 0xA000 : "FlashpixVersion", // Flashpix format version + + // colorspace tags + 0xA001 : "ColorSpace", // Color space information tag + + // image configuration + 0xA002 : "PixelXDimension", // Valid width of meaningful image + 0xA003 : "PixelYDimension", // Valid height of meaningful image + 0x9101 : "ComponentsConfiguration", // Information about channels + 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel + + // user information + 0x927C : "MakerNote", // Any desired information written by the manufacturer + 0x9286 : "UserComment", // Comments by user + + // related file + 0xA004 : "RelatedSoundFile", // Name of related sound file + + // date and time + 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated + 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally + 0x9290 : "SubsecTime", // Fractions of seconds for DateTime + 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal + 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized + + // picture-taking conditions + 0x829A : "ExposureTime", // Exposure time (in seconds) + 0x829D : "FNumber", // F number + 0x8822 : "ExposureProgram", // Exposure program + 0x8824 : "SpectralSensitivity", // Spectral sensitivity + 0x8827 : "ISOSpeedRatings", // ISO speed rating + 0x8828 : "OECF", // Optoelectric conversion factor + 0x9201 : "ShutterSpeedValue", // Shutter speed + 0x9202 : "ApertureValue", // Lens aperture + 0x9203 : "BrightnessValue", // Value of brightness + 0x9204 : "ExposureBias", // Exposure bias + 0x9205 : "MaxApertureValue", // Smallest F number of lens + 0x9206 : "SubjectDistance", // Distance to subject in meters + 0x9207 : "MeteringMode", // Metering mode + 0x9208 : "LightSource", // Kind of light source + 0x9209 : "Flash", // Flash status + 0x9214 : "SubjectArea", // Location and area of main subject + 0x920A : "FocalLength", // Focal length of the lens in mm + 0xA20B : "FlashEnergy", // Strobe energy in BCPS + 0xA20C : "SpatialFrequencyResponse", // + 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit + 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit + 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution + 0xA214 : "SubjectLocation", // Location of subject in image + 0xA215 : "ExposureIndex", // Exposure index selected on camera + 0xA217 : "SensingMethod", // Image sensor type + 0xA300 : "FileSource", // Image source (3 == DSC) + 0xA301 : "SceneType", // Scene type (1 == directly photographed) + 0xA302 : "CFAPattern", // Color filter array geometric pattern + 0xA401 : "CustomRendered", // Special processing + 0xA402 : "ExposureMode", // Exposure mode + 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual + 0xA404 : "DigitalZoomRation", // Digital zoom ratio + 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) + 0xA406 : "SceneCaptureType", // Type of scene + 0xA407 : "GainControl", // Degree of overall image gain adjustment + 0xA408 : "Contrast", // Direction of contrast processing applied by camera + 0xA409 : "Saturation", // Direction of saturation processing applied by camera + 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera + 0xA40B : "DeviceSettingDescription", // + 0xA40C : "SubjectDistanceRange", // Distance to subject + + // other tags + 0xA005 : "InteroperabilityIFDPointer", + 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image + }; + + var TiffTags = EXIF.TiffTags = { + 0x0100 : "ImageWidth", + 0x0101 : "ImageHeight", + 0x8769 : "ExifIFDPointer", + 0x8825 : "GPSInfoIFDPointer", + 0xA005 : "InteroperabilityIFDPointer", + 0x0102 : "BitsPerSample", + 0x0103 : "Compression", + 0x0106 : "PhotometricInterpretation", + 0x0112 : "Orientation", + 0x0115 : "SamplesPerPixel", + 0x011C : "PlanarConfiguration", + 0x0212 : "YCbCrSubSampling", + 0x0213 : "YCbCrPositioning", + 0x011A : "XResolution", + 0x011B : "YResolution", + 0x0128 : "ResolutionUnit", + 0x0111 : "StripOffsets", + 0x0116 : "RowsPerStrip", + 0x0117 : "StripByteCounts", + 0x0201 : "JPEGInterchangeFormat", + 0x0202 : "JPEGInterchangeFormatLength", + 0x012D : "TransferFunction", + 0x013E : "WhitePoint", + 0x013F : "PrimaryChromaticities", + 0x0211 : "YCbCrCoefficients", + 0x0214 : "ReferenceBlackWhite", + 0x0132 : "DateTime", + 0x010E : "ImageDescription", + 0x010F : "Make", + 0x0110 : "Model", + 0x0131 : "Software", + 0x013B : "Artist", + 0x8298 : "Copyright" + }; + + var GPSTags = EXIF.GPSTags = { + 0x0000 : "GPSVersionID", + 0x0001 : "GPSLatitudeRef", + 0x0002 : "GPSLatitude", + 0x0003 : "GPSLongitudeRef", + 0x0004 : "GPSLongitude", + 0x0005 : "GPSAltitudeRef", + 0x0006 : "GPSAltitude", + 0x0007 : "GPSTimeStamp", + 0x0008 : "GPSSatellites", + 0x0009 : "GPSStatus", + 0x000A : "GPSMeasureMode", + 0x000B : "GPSDOP", + 0x000C : "GPSSpeedRef", + 0x000D : "GPSSpeed", + 0x000E : "GPSTrackRef", + 0x000F : "GPSTrack", + 0x0010 : "GPSImgDirectionRef", + 0x0011 : "GPSImgDirection", + 0x0012 : "GPSMapDatum", + 0x0013 : "GPSDestLatitudeRef", + 0x0014 : "GPSDestLatitude", + 0x0015 : "GPSDestLongitudeRef", + 0x0016 : "GPSDestLongitude", + 0x0017 : "GPSDestBearingRef", + 0x0018 : "GPSDestBearing", + 0x0019 : "GPSDestDistanceRef", + 0x001A : "GPSDestDistance", + 0x001B : "GPSProcessingMethod", + 0x001C : "GPSAreaInformation", + 0x001D : "GPSDateStamp", + 0x001E : "GPSDifferential" + }; + + var StringValues = EXIF.StringValues = { + ExposureProgram : { + 0 : "Not defined", + 1 : "Manual", + 2 : "Normal program", + 3 : "Aperture priority", + 4 : "Shutter priority", + 5 : "Creative program", + 6 : "Action program", + 7 : "Portrait mode", + 8 : "Landscape mode" + }, + MeteringMode : { + 0 : "Unknown", + 1 : "Average", + 2 : "CenterWeightedAverage", + 3 : "Spot", + 4 : "MultiSpot", + 5 : "Pattern", + 6 : "Partial", + 255 : "Other" + }, + LightSource : { + 0 : "Unknown", + 1 : "Daylight", + 2 : "Fluorescent", + 3 : "Tungsten (incandescent light)", + 4 : "Flash", + 9 : "Fine weather", + 10 : "Cloudy weather", + 11 : "Shade", + 12 : "Daylight fluorescent (D 5700 - 7100K)", + 13 : "Day white fluorescent (N 4600 - 5400K)", + 14 : "Cool white fluorescent (W 3900 - 4500K)", + 15 : "White fluorescent (WW 3200 - 3700K)", + 17 : "Standard light A", + 18 : "Standard light B", + 19 : "Standard light C", + 20 : "D55", + 21 : "D65", + 22 : "D75", + 23 : "D50", + 24 : "ISO studio tungsten", + 255 : "Other" + }, + Flash : { + 0x0000 : "Flash did not fire", + 0x0001 : "Flash fired", + 0x0005 : "Strobe return light not detected", + 0x0007 : "Strobe return light detected", + 0x0009 : "Flash fired, compulsory flash mode", + 0x000D : "Flash fired, compulsory flash mode, return light not detected", + 0x000F : "Flash fired, compulsory flash mode, return light detected", + 0x0010 : "Flash did not fire, compulsory flash mode", + 0x0018 : "Flash did not fire, auto mode", + 0x0019 : "Flash fired, auto mode", + 0x001D : "Flash fired, auto mode, return light not detected", + 0x001F : "Flash fired, auto mode, return light detected", + 0x0020 : "No flash function", + 0x0041 : "Flash fired, red-eye reduction mode", + 0x0045 : "Flash fired, red-eye reduction mode, return light not detected", + 0x0047 : "Flash fired, red-eye reduction mode, return light detected", + 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode", + 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", + 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", + 0x0059 : "Flash fired, auto mode, red-eye reduction mode", + 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode", + 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode" + }, + SensingMethod : { + 1 : "Not defined", + 2 : "One-chip color area sensor", + 3 : "Two-chip color area sensor", + 4 : "Three-chip color area sensor", + 5 : "Color sequential area sensor", + 7 : "Trilinear sensor", + 8 : "Color sequential linear sensor" + }, + SceneCaptureType : { + 0 : "Standard", + 1 : "Landscape", + 2 : "Portrait", + 3 : "Night scene" + }, + SceneType : { + 1 : "Directly photographed" + }, + CustomRendered : { + 0 : "Normal process", + 1 : "Custom process" + }, + WhiteBalance : { + 0 : "Auto white balance", + 1 : "Manual white balance" + }, + GainControl : { + 0 : "None", + 1 : "Low gain up", + 2 : "High gain up", + 3 : "Low gain down", + 4 : "High gain down" + }, + Contrast : { + 0 : "Normal", + 1 : "Soft", + 2 : "Hard" + }, + Saturation : { + 0 : "Normal", + 1 : "Low saturation", + 2 : "High saturation" + }, + Sharpness : { + 0 : "Normal", + 1 : "Soft", + 2 : "Hard" + }, + SubjectDistanceRange : { + 0 : "Unknown", + 1 : "Macro", + 2 : "Close view", + 3 : "Distant view" + }, + FileSource : { + 3 : "DSC" + }, + + Components : { + 0 : "", + 1 : "Y", + 2 : "Cb", + 3 : "Cr", + 4 : "R", + 5 : "G", + 6 : "B" + } + }; + + function addEvent(element, event, handler) { + if (element.addEventListener) { + element.addEventListener(event, handler, false); + } else if (element.attachEvent) { + element.attachEvent("on" + event, handler); + } + } + + function imageHasData(img) { + return !!(img.exifdata); + } + + + function base64ToArrayBuffer(base64, contentType) { + contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' + base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); + var binary = atob(base64); + var len = binary.length; + var buffer = new ArrayBuffer(len); + var view = new Uint8Array(buffer); + for (var i = 0; i < len; i++) { + view[i] = binary.charCodeAt(i); + } + return buffer; + } + + function objectURLToBlob(url, callback) { + var http = new XMLHttpRequest(); + http.open("GET", url, true); + http.responseType = "blob"; + http.onload = function(e) { + if (this.status == 200 || this.status === 0) { + callback(this.response); + } + }; + http.send(); + } + + function getImageData(img, callback) { + function handleBinaryFile(binFile) { + var data = findEXIFinJPEG(binFile); + var iptcdata = findIPTCinJPEG(binFile); + img.exifdata = data || {}; + img.iptcdata = iptcdata || {}; + if (callback) { + callback.call(img); + } + } + + if (img.src) { + if (/^data\:/i.test(img.src)) { // Data URI + var arrayBuffer = base64ToArrayBuffer(img.src); + handleBinaryFile(arrayBuffer); + + } else if (/^blob\:/i.test(img.src)) { // Object URL + var fileReader = new FileReader(); + fileReader.onload = function(e) { + handleBinaryFile(e.target.result); + }; + objectURLToBlob(img.src, function (blob) { + fileReader.readAsArrayBuffer(blob); + }); + } else { + var http = new XMLHttpRequest(); + http.onload = function() { + if (this.status == 200 || this.status === 0) { + handleBinaryFile(http.response); + } else { + throw "Could not load image"; + } + http = null; + }; + http.open("GET", img.src, true); + http.responseType = "arraybuffer"; + http.send(null); + } + } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) { + var fileReader = new FileReader(); + fileReader.onload = function(e) { + if (debug) console.log("Got file of length " + e.target.result.byteLength); + handleBinaryFile(e.target.result); + }; + + fileReader.readAsArrayBuffer(img); + } + } + + function findEXIFinJPEG(file) { + var dataView = new DataView(file); + + if (debug) console.log("Got file of length " + file.byteLength); + if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { + if (debug) console.log("Not a valid JPEG"); + return false; // not a valid jpeg + } + + var offset = 2, + length = file.byteLength, + marker; + + while (offset < length) { + if (dataView.getUint8(offset) != 0xFF) { + if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset)); + return false; // not a valid marker, something is wrong + } + + marker = dataView.getUint8(offset + 1); + if (debug) console.log(marker); + + // we could implement handling for other markers here, + // but we're only looking for 0xFFE1 for EXIF data + + if (marker == 225) { + if (debug) console.log("Found 0xFFE1 marker"); + + return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); + + // offset += 2 + file.getShortAt(offset+2, true); + + } else { + offset += 2 + dataView.getUint16(offset+2); + } + + } + + } + + function findIPTCinJPEG(file) { + var dataView = new DataView(file); + + if (debug) console.log("Got file of length " + file.byteLength); + if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { + if (debug) console.log("Not a valid JPEG"); + return false; // not a valid jpeg + } + + var offset = 2, + length = file.byteLength; + + + var isFieldSegmentStart = function(dataView, offset){ + return ( + dataView.getUint8(offset) === 0x38 && + dataView.getUint8(offset+1) === 0x42 && + dataView.getUint8(offset+2) === 0x49 && + dataView.getUint8(offset+3) === 0x4D && + dataView.getUint8(offset+4) === 0x04 && + dataView.getUint8(offset+5) === 0x04 + ); + }; + + while (offset < length) { + + if ( isFieldSegmentStart(dataView, offset )){ + + // Get the length of the name header (which is padded to an even number of bytes) + var nameHeaderLength = dataView.getUint8(offset+7); + if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1; + // Check for pre photoshop 6 format + if(nameHeaderLength === 0) { + // Always 4 + nameHeaderLength = 4; + } + + var startOffset = offset + 8 + nameHeaderLength; + var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength); + + return readIPTCData(file, startOffset, sectionLength); + + break; + + } + + + // Not the marker, continue searching + offset++; + + } + + } + var IptcFieldMap = { + 0x78 : 'caption', + 0x6E : 'credit', + 0x19 : 'keywords', + 0x37 : 'dateCreated', + 0x50 : 'byline', + 0x55 : 'bylineTitle', + 0x7A : 'captionWriter', + 0x69 : 'headline', + 0x74 : 'copyright', + 0x0F : 'category' + }; + function readIPTCData(file, startOffset, sectionLength){ + var dataView = new DataView(file); + var data = {}; + var fieldValue, fieldName, dataSize, segmentType, segmentSize; + var segmentStartPos = startOffset; + while(segmentStartPos < startOffset+sectionLength) { + if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){ + segmentType = dataView.getUint8(segmentStartPos+2); + if(segmentType in IptcFieldMap) { + dataSize = dataView.getInt16(segmentStartPos+3); + segmentSize = dataSize + 5; + fieldName = IptcFieldMap[segmentType]; + fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize); + // Check if we already stored a value with this name + if(data.hasOwnProperty(fieldName)) { + // Value already stored with this name, create multivalue field + if(data[fieldName] instanceof Array) { + data[fieldName].push(fieldValue); + } + else { + data[fieldName] = [data[fieldName], fieldValue]; + } + } + else { + data[fieldName] = fieldValue; + } + } + + } + segmentStartPos++; + } + return data; + } + + + + function readTags(file, tiffStart, dirStart, strings, bigEnd) { + var entries = file.getUint16(dirStart, !bigEnd), + tags = {}, + entryOffset, tag, + i; + + for (i=0;i 4 ? valueOffset : (entryOffset + 8); + vals = []; + for (n=0;n 4 ? valueOffset : (entryOffset + 8); + return getStringFromDB(file, offset, numValues-1); + + case 3: // short, 16 bit int + if (numValues == 1) { + return file.getUint16(entryOffset + 8, !bigEnd); + } else { + offset = numValues > 2 ? valueOffset : (entryOffset + 8); + vals = []; + for (n=0;n= 1) { percent = base.minPercent; @@ -130,7 +131,9 @@ } function handleMouseDown(event) { event.preventDefault(); //some browsers do image dragging themselves - base.isDragging = true; + if (!patientPhotoFeature.config.overrideIsDragging){ + base.isDragging = true; + } base.dragMouseCoords = {x: event.pageX, y: event.pageY}; base.dragImageCoords = {x: parseInt(base.$image.css('left')), y: parseInt(base.$image.css('top'))} if(base.options.smartControls) base.$frame.find('.jwc_controls').fadeIn('fast'); @@ -139,7 +142,6 @@ base.isDragging = false; hidePanel(); } - function handleMouseMove(event) { if(base.isDragging) { var xDif = event.pageX - base.dragMouseCoords.x; diff --git a/public/js/libraries/megapix-image.js b/public/js/libraries/megapix-image.js new file mode 100644 index 000000000..07c122f63 --- /dev/null +++ b/public/js/libraries/megapix-image.js @@ -0,0 +1,261 @@ +/** + * Mega pixel image rendering library for iOS6 Safari + * + * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel), + * which causes unexpected subsampling when drawing it in canvas. + * By using this library, you can safely render the image with proper stretching. + * + * Copyright (c) 2012 Shinichi Tomita + * Released under the MIT license + */ +(function() { + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be subsampled in rendering. + */ + function detectSubsampling(img) { + var iw = img.naturalWidth, ih = img.naturalHeight; + if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image + var canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, -iw + 1, 0); + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering edge pixel or not. + // if alpha value is 0 image is not covering, hence subsampled. + return ctx.getImageData(0, 0, 1, 1).data[3] === 0; + } else { + return false; + } + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into canvas for some images. + */ + function detectVerticalSquash(img, iw, ih) { + var canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = ih; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + var data = ctx.getImageData(0, 0, 1, ih).data; + // search image edge pixel position in case it is squashed vertically. + var sy = 0; + var ey = ih; + var py = ih; + while (py > sy) { + var alpha = data[(py - 1) * 4 + 3]; + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + py = (ey + sy) >> 1; + } + var ratio = (py / ih); + return (ratio===0)?1:ratio; + } + + /** + * Rendering image element (with resizing) and get its data URL + */ + function renderImageToDataURL(img, options, doSquash) { + var canvas = document.createElement('canvas'); + renderImageToCanvas(img, canvas, options, doSquash); + return canvas.toDataURL("image/jpeg", options.quality || 0.8); + } + + /** + * Rendering image element (with resizing) into the canvas element + */ + function renderImageToCanvas(img, canvas, options, doSquash) { + var iw = img.naturalWidth, ih = img.naturalHeight; + if (!(iw+ih)) return; + var width = options.width, height = options.height; + var ctx = canvas.getContext('2d'); + ctx.save(); + transformCoordinate(canvas, ctx, width, height, options.orientation); + var subsampled = detectSubsampling(img); + if (subsampled) { + iw /= 2; + ih /= 2; + } + var d = 1024; // size of tiling canvas + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + var tmpCtx = tmpCanvas.getContext('2d'); + var vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1; + var dw = Math.ceil(d * width / iw); + var dh = Math.ceil(d * height / ih / vertSquashRatio); + var sy = 0; + var dy = 0; + while (sy < ih) { + var sx = 0; + var dx = 0; + while (sx < iw) { + tmpCtx.clearRect(0, 0, d, d); + tmpCtx.drawImage(img, -sx, -sy); + ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + } + + /** + * Transform canvas coordination according to specified frame size and orientation + * Orientation value is from EXIF tag + */ + function transformCoordinate(canvas, ctx, width, height, orientation) { + switch (orientation) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + default: + canvas.width = width; + canvas.height = height; + } + switch (orientation) { + case 2: + // horizontal flip + ctx.translate(width, 0); + ctx.scale(-1, 1); + break; + case 3: + // 180 rotate left + ctx.translate(width, height); + ctx.rotate(Math.PI); + break; + case 4: + // vertical flip + ctx.translate(0, height); + ctx.scale(1, -1); + break; + case 5: + // vertical flip + 90 rotate right + ctx.rotate(0.5 * Math.PI); + ctx.scale(1, -1); + break; + case 6: + // 90 rotate right + ctx.rotate(0.5 * Math.PI); + ctx.translate(0, -height); + break; + case 7: + // horizontal flip + 90 rotate right + ctx.rotate(0.5 * Math.PI); + ctx.translate(width, -height); + ctx.scale(-1, 1); + break; + case 8: + // 90 rotate left + ctx.rotate(-0.5 * Math.PI); + ctx.translate(-width, 0); + break; + default: + break; + } + } + + var URL = window.URL && window.URL.createObjectURL ? window.URL : + window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : + null; + + /** + * MegaPixImage class + */ + function MegaPixImage(srcImage) { + if (window.Blob && srcImage instanceof Blob) { + if (!URL) { throw Error("No createObjectURL function found to create blob url"); } + var img = new Image(); + img.src = URL.createObjectURL(srcImage); + this.blob = srcImage; + srcImage = img; + } + if (!srcImage.naturalWidth && !srcImage.naturalHeight) { + var _this = this; + srcImage.onload = srcImage.onerror = function() { + var listeners = _this.imageLoadListeners; + if (listeners) { + _this.imageLoadListeners = null; + for (var i=0, len=listeners.length; i maxWidth) { + width = maxWidth; + height = (imgHeight * width / imgWidth) << 0; + } + if (maxHeight && height > maxHeight) { + height = maxHeight; + width = (imgWidth * height / imgHeight) << 0; + } + var opt = { width : width, height : height }; + for (var k in options) opt[k] = options[k]; + + var tagName = target.tagName.toLowerCase(); + if (tagName === 'img') { + target.src = renderImageToDataURL(this.srcImage, opt, doSquash); + } else if (tagName === 'canvas') { + renderImageToCanvas(this.srcImage, target, opt, doSquash); + } + if (typeof this.onrender === 'function') { + this.onrender(target); + } + if (callback) { + callback(); + } + if (this.blob) { + this.blob = null; + URL.revokeObjectURL(this.srcImage.src); + } + }; + + /** + * Export class to global + */ + if (typeof define === 'function' && define.amd) { + define([], function() { return MegaPixImage; }); // for AMD loader + } else { + this.MegaPixImage = MegaPixImage; + } + +})(); diff --git a/public/js/triage/touchit-1.0.0.js b/public/js/libraries/touchit-1.0.0.js similarity index 100% rename from public/js/triage/touchit-1.0.0.js rename to public/js/libraries/touchit-1.0.0.js diff --git a/public/js/shared/femr.js b/public/js/shared/femr.js index 0a16b8b53..aeb5829d5 100644 --- a/public/js/shared/femr.js +++ b/public/js/shared/femr.js @@ -1,3 +1,12 @@ +$(document).on('blur', 'input, textarea', function () { + //this is a hacky fix for the navigation bar dropping on iPads. After the keyboard + //appears and disappears, the navigation bar would fall to the center of the screen and + //fix itself after scrolling. + setTimeout(function () { + window.scrollTo(document.body.scrollLeft, document.body.scrollTop); + }, 0); +}); + $(document).ready(function () { $('.hamburger').click(function(){ var navMenu = $('.navigationItemsWrap'); diff --git a/public/js/triage/triage.js b/public/js/triage/triage.js index 8409d561b..6a554e7d5 100644 --- a/public/js/triage/triage.js +++ b/public/js/triage/triage.js @@ -4,7 +4,8 @@ var patientPhotoFeature = { windowWidth: 250, windowHeight: 250, imageElement: $('#patientPhoto'), - isNewPhoto: false + isNewPhoto: false, + overrideIsDragging: false }, photo: { @@ -24,7 +25,7 @@ var patientPhotoFeature = { jwc.destroyCrop(); //Create new crop window - jwc = $(patientPhotoFeature.config.imageElement).jWindowCrop({ + jwc = $(patientPhotoFeature.config.imageElement).jWindowCrop({ targetWidth: patientPhotoFeature.config.windowWidth, targetHeight: patientPhotoFeature.config.windowHeight, loadingText: '', @@ -32,10 +33,13 @@ var patientPhotoFeature = { zoomOutId: 'btnZoomOut', onChange: function (result) { + patientPhotoFeature.photo.x = result.cropX; patientPhotoFeature.photo.y = result.cropY; patientPhotoFeature.photo.width = result.cropW; patientPhotoFeature.photo.height = result.cropH; + + patientPhotoFeature.cropAndReplace(); } }); jwc.touchit(); //This invokes the "touchit" module which translates mouse/click @@ -48,6 +52,10 @@ var patientPhotoFeature = { $('#patientPhotoDiv').hide(); $('#photoInputFormDiv').show(); $(patientPhotoFeature.config.imageElement).show(); + /* + No zooming allowed + */ + $('#zoomControls').hide(); } else { $('#patientPhotoDiv').show(); @@ -112,7 +120,12 @@ var patientPhotoFeature = { } }, + /** + * Takes the photo from the canvas and crops it. Then sets it back on the canvas. + * This helps reduce the amount of bandwidth, especially when a poor signal is detected. + */ cropAndReplace: function () { + if (patientPhotoFeature.config.isNewPhoto === true) { var canvas = document.getElementById('patientPhotoCanvas'), context = canvas.getContext('2d'); @@ -121,19 +134,100 @@ var patientPhotoFeature = { canvas.width = 255; var img = document.getElementById('patientPhoto'); + + var orientation = 1; + EXIF.getData(img, function () { + orientation = EXIF.getTag(this, "Orientation"); + }); + + + //sx, sy, sw, and sh identify the area of the photo that is going to + //be cropped. This is because a user can drag the photo around and choose + //which part they want to be saved. var sx = patientPhotoFeature.photo.x; var sy = patientPhotoFeature.photo.y; var sw = patientPhotoFeature.photo.width; var sh = patientPhotoFeature.photo.height; - context.drawImage(img, sx, sy, sw, sh, 0, 0, 255, 255); - - //specify the quality downgrade, but how much?!??!? - var dataURL = canvas.toDataURL("image/jpeg", 0.5); - $('#photoInputCropped').val(dataURL); - $('#photoInput').remove();//remove file upload from DOM so it's not submitted in POST + switch (orientation) { + case 1: + //patientPhotoFeature.drawImageIOSFix(context, img, sx, sy, sw, sh, 0, 0, 255, 255); + if (sx >= 2) { + //fixes a bug where iOS devices will spit out a black image. + //This only happens when the user has scrolled all the way to + //the right, but sx is always subtracted by 2 (negative not allowed). + sx = sx - 2; + } + context.drawImage(img, sx, sy, sw, sh, 0, 0, 255, 255); + + break; + case 3: + //this occurs in iOS landscape mode, but only when the camera is on the left + context.translate(canvas.width, canvas.height); + context.rotate(180 * Math.PI / 180); + //get the width of the actual picture that was uploaded + var real_width = img.naturalWidth; + //get the width of the html element + var imgElement_width = img.width; + var pixel_density_sx = real_width / imgElement_width; + //250 = jwc frame size + var max_sx = (imgElement_width - 250) * pixel_density_sx; + //a 180 degree rotation introduces a different spot for sx + sx = max_sx - sx; + if (sx < 0) { + //just in case the calculation is off by a small decimal + sx = 0; + } + if (sx >= 2) { + //fixes a bug where iOS devices will spit out a black image. + //This only happens when the user has scrolled all the way to + //the right, but sx is always subtracted by 2 (negative not allowed). + sx = sx - 2; + } + context.drawImage(img, sx, sy, sw, sh, 0, 0, 255, 255); + break; + case 6: + patientPhotoFeature.config.overrideIsDragging = true; + //here you will find iOS in portrait mode with bizzare behavior, + //we use a special library to take care of the vertical squashing bug + var fileInput = document.getElementById('photoInput'); + var file = fileInput.files[0]; + // MegaPixImage constructor accepts File/Blob object. + var mpImg = new MegaPixImage(file); + //context.drawImage(img, sx, sy, sw, sh, 0, 0, 255, 255); + // Render resized image into canvas element. + mpImg.render(canvas, { width: 255, height: 255, orientation: 6, sx: sx, sy: sy }); + break; + case 8: + //this case occurs in android portrait mode and android isn't fucking retarded + context.translate(0, canvas.height); + context.rotate(270 * Math.PI / 180); + if (sx >= 2) { + //fixes a bug where iOS devices will spit out a black image. + //This only happens when the user has scrolled all the way to + //the right, but sx is always subtracted by 2 (negative not allowed). + sx = sx - 2; + } + context.drawImage(img, sx, sy, sw, sh, 0, 0, 255, 255); + break; + default: + if (sx >= 2) { + //fixes a bug where iOS devices will spit out a black image. + //This only happens when the user has scrolled all the way to + //the right, but sx is always subtracted by 2 (negative not allowed). + sx = sx - 2; + } + context.drawImage(img, sx, sy, sw, sh, 0, 0, 255, 255); + } } + }, + prepareForPOST: function () { + //0.5 is the quality downgrade. + var dataURL = document.getElementById('patientPhotoCanvas').toDataURL("image/jpeg", 0.5); + $('#photoInputCropped').val(dataURL); + $('#photoInput').remove();//remove file upload from DOM so it's not submitted in POST } + }; var multipleChiefComplaintFeature = { @@ -162,15 +256,15 @@ var multipleChiefComplaintFeature = { var chiefComplaintText = $(evt.target).parent().text(); chiefComplaintText = chiefComplaintText.substring(1, chiefComplaintText.length); var index = multipleChiefComplaintFeature.chiefComplaintsJSON.indexOf(chiefComplaintText); - if (index > -1){ - multipleChiefComplaintFeature.chiefComplaintsJSON.splice(index,1); + if (index > -1) { + multipleChiefComplaintFeature.chiefComplaintsJSON.splice(index, 1); } $(evt.target).parent().remove(); }, JSONifyChiefComplaints: function () { var chiefComplaintBox = $('#chiefComplaint').val().trim(); //add the value in the box currently (if it exists and if it doesn't already exist in the current list) - if (chiefComplaintBox && $.inArray(chiefComplaintBox, multipleChiefComplaintFeature.chiefComplaintsJSON) === -1){ + if (chiefComplaintBox && $.inArray(chiefComplaintBox, multipleChiefComplaintFeature.chiefComplaintsJSON) === -1) { multipleChiefComplaintFeature.chiefComplaintsJSON.push(chiefComplaintBox); } $('input[name=chiefComplaintsJSON]').val(JSON.stringify(multipleChiefComplaintFeature.chiefComplaintsJSON)); @@ -405,9 +499,10 @@ $(document).ready(function () { }); $('#triageSubmitBtn').click(function () { - patientPhotoFeature.cropAndReplace(); + //get the base64 URI string from the canvas + patientPhotoFeature.prepareForPOST(); //make sure the feature is turned on before JSONifying - if (multipleChiefComplaintFeature.isActive == true){ + if (multipleChiefComplaintFeature.isActive == true) { multipleChiefComplaintFeature.JSONifyChiefComplaints(); } return validate(); //located in triageClientValidation.js From 3d5ba92be9a9e0bcdb316ae60992cdd67c58a9f3 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Mon, 1 Dec 2014 19:36:38 -0500 Subject: [PATCH 04/21] service refactor, sex null error catching in research, and interface/implementation separation --- app/femr/business/helpers/DomainMapper.java | 124 +++++- app/femr/business/helpers/LogicDoer.java | 2 +- app/femr/business/helpers/QueryHelper.java | 5 +- app/femr/business/helpers/QueryProvider.java | 12 +- app/femr/business/services/TriageService.java | 271 ------------- .../{ => core}/IConfigureService.java | 6 +- .../ICustomTabService.java} | 18 +- .../IEncounterService.java} | 80 ++-- .../{ => core}/IInventoryService.java | 4 +- .../IMedicationService.java} | 43 +-- .../services/core/IMissionTripService.java | 68 ++++ .../IPatientService.java} | 39 +- .../services/{ => core}/IPhotoService.java | 4 +- .../services/{ => core}/IResearchService.java | 12 +- .../services/{ => core}/IRoleService.java | 6 +- .../services/{ => core}/ISearchService.java | 5 +- .../services/{ => core}/ISessionService.java | 6 +- .../services/{ => core}/IUserService.java | 8 +- .../business/services/core/IVitalService.java | 55 +++ .../{ => system}/ConfigureService.java | 11 +- .../CustomTabService.java} | 115 ++++-- .../EncounterService.java} | 361 ++++++++---------- .../{ => system}/InventoryService.java | 12 +- .../MedicationService.java} | 179 ++++----- .../services/system/MissionTripService.java | 263 +++++++++++++ .../services/system/PatientService.java | 131 +++++++ .../services/{ => system}/PhotoService.java | 13 +- .../{ => system}/ResearchService.java | 45 +-- .../services/{ => system}/RoleService.java | 9 +- .../services/{ => system}/SearchService.java | 8 +- .../services/{ => system}/SessionService.java | 10 +- .../services/{ => system}/UserService.java | 13 +- .../services/system/VitalService.java | 157 ++++++++ .../common/{dto => dtos}/CurrentUser.java | 4 +- .../common/{dto => dtos}/ServiceResponse.java | 2 +- app/femr/common/models/TripItem.java | 69 ++++ .../models/{ => core}/IChiefComplaint.java | 2 +- .../data/models/{ => core}/IMedication.java | 2 +- .../{ => core}/IMedicationActiveDrug.java | 2 +- .../{ => core}/IMedicationActiveDrugName.java | 2 +- .../{ => core}/IMedicationAdministration.java | 2 +- .../models/{ => core}/IMedicationForm.java | 2 +- .../IMedicationMeasurementUnit.java | 2 +- app/femr/data/models/core/IMissionCity.java | 31 ++ .../data/models/core/IMissionCountry.java | 27 ++ app/femr/data/models/core/IMissionTeam.java | 35 ++ app/femr/data/models/core/IMissionTrip.java | 45 +++ app/femr/data/models/{ => core}/IPatient.java | 2 +- .../{ => core}/IPatientAgeClassification.java | 2 +- .../models/{ => core}/IPatientEncounter.java | 6 +- .../{ => core}/IPatientEncounterPhoto.java | 2 +- .../{ => core}/IPatientEncounterTabField.java | 3 +- .../{ => core}/IPatientEncounterVital.java | 2 +- .../{ => core}/IPatientPrescription.java | 2 +- app/femr/data/models/{ => core}/IPhoto.java | 2 +- app/femr/data/models/{ => core}/IRole.java | 2 +- .../models/{ => core}/ISystemSetting.java | 2 +- app/femr/data/models/{ => core}/ITab.java | 2 +- .../data/models/{ => core}/ITabField.java | 2 +- .../data/models/{ => core}/ITabFieldSize.java | 2 +- .../data/models/{ => core}/ITabFieldType.java | 2 +- app/femr/data/models/{ => core}/IUser.java | 2 +- app/femr/data/models/{ => core}/IVital.java | 2 +- .../models/{ => mysql}/ChiefComplaint.java | 7 +- .../data/models/{ => mysql}/Medication.java | 6 +- .../{ => mysql}/MedicationActiveDrug.java | 6 +- .../{ => mysql}/MedicationActiveDrugName.java | 6 +- .../{ => mysql}/MedicationAdministration.java | 6 +- .../models/{ => mysql}/MedicationForm.java | 6 +- .../MedicationMeasurementUnit.java | 6 +- app/femr/data/models/mysql/MissionCity.java | 62 +++ .../data/models/mysql/MissionCountry.java | 51 +++ app/femr/data/models/mysql/MissionTeam.java | 75 ++++ app/femr/data/models/mysql/MissionTrip.java | 101 +++++ app/femr/data/models/{ => mysql}/Patient.java | 5 +- .../{ => mysql}/PatientAgeClassification.java | 4 +- .../models/{ => mysql}/PatientEncounter.java | 16 +- .../{ => mysql}/PatientEncounterPhoto.java | 4 +- .../{ => mysql}/PatientEncounterTabField.java | 7 +- .../{ => mysql}/PatientEncounterVital.java | 5 +- .../{ => mysql}/PatientPrescription.java | 3 +- app/femr/data/models/{ => mysql}/Photo.java | 4 +- app/femr/data/models/{ => mysql}/Role.java | 4 +- app/femr/data/models/{ => mysql}/Roles.java | 2 +- .../models/{ => mysql}/SystemSetting.java | 4 +- app/femr/data/models/{ => mysql}/Tab.java | 3 +- .../data/models/{ => mysql}/TabField.java | 7 +- .../data/models/{ => mysql}/TabFieldSize.java | 4 +- .../data/models/{ => mysql}/TabFieldType.java | 4 +- app/femr/data/models/{ => mysql}/User.java | 4 +- app/femr/data/models/{ => mysql}/Vital.java | 4 +- .../ui/controllers/HistoryController.java | 29 +- app/femr/ui/controllers/HomeController.java | 4 +- .../ui/controllers/MedicalController.java | 56 +-- .../ui/controllers/PharmaciesController.java | 59 ++- app/femr/ui/controllers/PhotoController.java | 6 +- .../ui/controllers/ResearchController.java | 14 +- app/femr/ui/controllers/SearchController.java | 8 +- .../ui/controllers/SessionsController.java | 10 +- .../ui/controllers/SuperuserController.java | 135 +++++-- app/femr/ui/controllers/TriageController.java | 49 +-- .../ui/controllers/admin/AdminController.java | 6 +- .../admin/ConfigureController.java | 12 +- .../admin/InventoryController.java | 11 +- .../ui/controllers/admin/UsersController.java | 12 +- .../helpers/security/AllowedRolesAction.java | 4 +- .../ui/models/admin/users/EditViewModel.java | 1 - .../models/admin/users/IndexViewModelGet.java | 2 +- .../ui/models/superuser/TripViewModel.java | 124 ++++++ .../ui/views/admin/configure/index.scala.html | 2 +- app/femr/ui/views/admin/index.scala.html | 2 +- .../ui/views/admin/inventory/index.scala.html | 2 +- .../ui/views/admin/users/create.scala.html | 2 +- app/femr/ui/views/admin/users/edit.scala.html | 2 +- .../ui/views/admin/users/manage.scala.html | 2 +- .../views/history/indexEncounter.scala.html | 2 +- .../ui/views/history/indexPatient.scala.html | 2 +- app/femr/ui/views/home/index.scala.html | 2 +- app/femr/ui/views/layouts/admin.scala.html | 13 +- app/femr/ui/views/layouts/main.scala.html | 2 +- app/femr/ui/views/medical/edit.scala.html | 2 +- app/femr/ui/views/medical/index.scala.html | 2 +- .../views/partials/authenticated.scala.html | 2 +- app/femr/ui/views/partials/header.scala.html | 2 +- .../helpers/AuthenticatedPartialHelper.java | 4 +- app/femr/ui/views/pharmacies/edit.scala.html | 2 +- app/femr/ui/views/pharmacies/index.scala.html | 2 +- .../ui/views/research/generatedata.scala.html | 3 +- app/femr/ui/views/research/index.scala.html | 4 +- app/femr/ui/views/superuser/fields.scala.html | 5 +- app/femr/ui/views/superuser/index.scala.html | 5 +- .../views/superuser/superuserMenu.scala.html | 6 - app/femr/ui/views/superuser/tabs.scala.html | 5 +- app/femr/ui/views/superuser/trips.scala.html | 96 +++++ app/femr/ui/views/triage/index.scala.html | 2 +- .../modules/BusinessLayerModule.java | 16 +- .../modules/DataLayerModule.java | 11 +- .../providers/ChiefComplaintProvider.java | 4 +- .../MedicationActiveDrugNameProvider.java | 4 +- .../MedicationActiveDrugProvider.java | 2 +- .../MedicationAdministrationProvider.java | 4 +- .../providers/MedicationFormProvider.java | 2 +- .../MedicationMeasurementUnitProvider.java | 2 +- .../providers/MedicationProvider.java | 4 +- .../providers/MissionCityProvider.java | 30 ++ .../providers/MissionCountryProvider.java | 30 ++ .../providers/MissionTeamProvider.java | 30 ++ .../providers/MissionTripProvider.java | 30 ++ .../PatientAgeClassificationProvider.java | 4 +- .../PatientEncounterPhotoProvider.java | 4 +- .../providers/PatientEncounterProvider.java | 4 +- .../PatientEncounterTabFieldProvider.java | 4 +- .../PatientEncounterVitalProvider.java | 4 +- .../PatientPrescriptionProvider.java | 4 +- .../providers/PatientProvider.java | 4 +- .../providers/PhotoProvider.java | 4 +- .../providers/RoleProvider.java | 4 +- .../providers/SystemSettingProvider.java | 4 +- .../providers/TabFieldProvider.java | 4 +- .../providers/TabFieldSizeProvider.java | 4 +- .../providers/TabFieldTypeProvider.java | 4 +- .../providers/TabProvider.java | 4 +- .../providers/UserProvider.java | 4 +- .../providers/VitalProvider.java | 4 +- app/femr/util/startup/DatabaseSeeder.java | 132 ++++++- app/femr/util/startup/Global.java | 6 +- conf/application.conf | 2 +- conf/evolutions/default/71.sql | 90 ++++- conf/routes | 3 + public/css/admin/users.css | 3 - public/css/femr.css | 5 + public/css/research.css | 1 + public/css/superuser.css | 9 + public/js/pharmacy/pharmacy.js | 14 +- public/js/research/research.js | 2 +- public/js/shared/femr.js | 15 + .../services/MockSessionsService.java | 6 +- .../business/services/MockTriageService.java | 8 +- .../business/services/MockUserService.java | 6 +- test/mock/femr/data/models/MockPatient.java | 3 +- .../data/models/MockPatientEncounter.java | 2 +- .../models/MockPatientEncounterVital.java | 4 +- .../data/models/MockPatientPrescription.java | 1 + test/mock/femr/data/models/MockUser.java | 4 +- 184 files changed, 2871 insertions(+), 1170 deletions(-) delete mode 100644 app/femr/business/services/TriageService.java rename app/femr/business/services/{ => core}/IConfigureService.java (91%) rename app/femr/business/services/{ISuperuserService.java => core/ICustomTabService.java} (89%) rename app/femr/business/services/{IMedicalService.java => core/IEncounterService.java} (56%) rename app/femr/business/services/{ => core}/IInventoryService.java (95%) rename app/femr/business/services/{IPharmacyService.java => core/IMedicationService.java} (72%) create mode 100644 app/femr/business/services/core/IMissionTripService.java rename app/femr/business/services/{ITriageService.java => core/IPatientService.java} (55%) rename app/femr/business/services/{ => core}/IPhotoService.java (96%) rename app/femr/business/services/{ => core}/IResearchService.java (84%) rename app/femr/business/services/{ => core}/IRoleService.java (89%) rename app/femr/business/services/{ => core}/ISearchService.java (97%) rename app/femr/business/services/{ => core}/ISessionService.java (89%) rename app/femr/business/services/{ => core}/IUserService.java (93%) create mode 100644 app/femr/business/services/core/IVitalService.java rename app/femr/business/services/{ => system}/ConfigureService.java (91%) rename app/femr/business/services/{SuperuserService.java => system/CustomTabService.java} (80%) rename app/femr/business/services/{MedicalService.java => system/EncounterService.java} (58%) rename app/femr/business/services/{ => system}/InventoryService.java (95%) rename app/femr/business/services/{PharmacyService.java => system/MedicationService.java} (67%) create mode 100644 app/femr/business/services/system/MissionTripService.java create mode 100644 app/femr/business/services/system/PatientService.java rename app/femr/business/services/{ => system}/PhotoService.java (97%) rename app/femr/business/services/{ => system}/ResearchService.java (96%) rename app/femr/business/services/{ => system}/RoleService.java (92%) rename app/femr/business/services/{ => system}/SearchService.java (99%) rename app/femr/business/services/{ => system}/SessionService.java (92%) rename app/femr/business/services/{ => system}/UserService.java (96%) create mode 100644 app/femr/business/services/system/VitalService.java rename app/femr/common/{dto => dtos}/CurrentUser.java (96%) rename app/femr/common/{dto => dtos}/ServiceResponse.java (98%) create mode 100644 app/femr/common/models/TripItem.java rename app/femr/data/models/{ => core}/IChiefComplaint.java (97%) rename app/femr/data/models/{ => core}/IMedication.java (98%) rename app/femr/data/models/{ => core}/IMedicationActiveDrug.java (97%) rename app/femr/data/models/{ => core}/IMedicationActiveDrugName.java (97%) rename app/femr/data/models/{ => core}/IMedicationAdministration.java (97%) rename app/femr/data/models/{ => core}/IMedicationForm.java (97%) rename app/femr/data/models/{ => core}/IMedicationMeasurementUnit.java (97%) create mode 100644 app/femr/data/models/core/IMissionCity.java create mode 100644 app/femr/data/models/core/IMissionCountry.java create mode 100644 app/femr/data/models/core/IMissionTeam.java create mode 100644 app/femr/data/models/core/IMissionTrip.java rename app/femr/data/models/{ => core}/IPatient.java (97%) rename app/femr/data/models/{ => core}/IPatientAgeClassification.java (97%) rename app/femr/data/models/{ => core}/IPatientEncounter.java (93%) rename app/femr/data/models/{ => core}/IPatientEncounterPhoto.java (96%) rename app/femr/data/models/{ => core}/IPatientEncounterTabField.java (94%) rename app/femr/data/models/{ => core}/IPatientEncounterVital.java (97%) rename app/femr/data/models/{ => core}/IPatientPrescription.java (98%) rename app/femr/data/models/{ => core}/IPhoto.java (97%) rename app/femr/data/models/{ => core}/IRole.java (96%) rename app/femr/data/models/{ => core}/ISystemSetting.java (97%) rename app/femr/data/models/{ => core}/ITab.java (97%) rename app/femr/data/models/{ => core}/ITabField.java (97%) rename app/femr/data/models/{ => core}/ITabFieldSize.java (96%) rename app/femr/data/models/{ => core}/ITabFieldType.java (96%) rename app/femr/data/models/{ => core}/IUser.java (98%) rename app/femr/data/models/{ => core}/IVital.java (97%) rename app/femr/data/models/{ => mysql}/ChiefComplaint.java (89%) rename app/femr/data/models/{ => mysql}/Medication.java (95%) rename app/femr/data/models/{ => mysql}/MedicationActiveDrug.java (93%) rename app/femr/data/models/{ => mysql}/MedicationActiveDrugName.java (93%) rename app/femr/data/models/{ => mysql}/MedicationAdministration.java (93%) rename app/femr/data/models/{ => mysql}/MedicationForm.java (93%) rename app/femr/data/models/{ => mysql}/MedicationMeasurementUnit.java (94%) create mode 100644 app/femr/data/models/mysql/MissionCity.java create mode 100644 app/femr/data/models/mysql/MissionCountry.java create mode 100644 app/femr/data/models/mysql/MissionTeam.java create mode 100644 app/femr/data/models/mysql/MissionTrip.java rename app/femr/data/models/{ => mysql}/Patient.java (96%) rename app/femr/data/models/{ => mysql}/PatientAgeClassification.java (95%) rename app/femr/data/models/{ => mysql}/PatientEncounter.java (92%) rename app/femr/data/models/{ => mysql}/PatientEncounterPhoto.java (94%) rename app/femr/data/models/{ => mysql}/PatientEncounterTabField.java (94%) rename app/femr/data/models/{ => mysql}/PatientEncounterVital.java (95%) rename app/femr/data/models/{ => mysql}/PatientPrescription.java (98%) rename app/femr/data/models/{ => mysql}/Photo.java (96%) rename app/femr/data/models/{ => mysql}/Role.java (95%) rename app/femr/data/models/{ => mysql}/Roles.java (97%) rename app/femr/data/models/{ => mysql}/SystemSetting.java (95%) rename app/femr/data/models/{ => mysql}/Tab.java (97%) rename app/femr/data/models/{ => mysql}/TabField.java (94%) rename app/femr/data/models/{ => mysql}/TabFieldSize.java (95%) rename app/femr/data/models/{ => mysql}/TabFieldType.java (95%) rename app/femr/data/models/{ => mysql}/User.java (97%) rename app/femr/data/models/{ => mysql}/Vital.java (96%) create mode 100644 app/femr/ui/models/superuser/TripViewModel.java delete mode 100644 app/femr/ui/views/superuser/superuserMenu.scala.html create mode 100644 app/femr/ui/views/superuser/trips.scala.html create mode 100644 app/femr/util/dependencyinjection/providers/MissionCityProvider.java create mode 100644 app/femr/util/dependencyinjection/providers/MissionCountryProvider.java create mode 100644 app/femr/util/dependencyinjection/providers/MissionTeamProvider.java create mode 100644 app/femr/util/dependencyinjection/providers/MissionTripProvider.java diff --git a/app/femr/business/helpers/DomainMapper.java b/app/femr/business/helpers/DomainMapper.java index cf428fc60..ec5f0d59d 100644 --- a/app/femr/business/helpers/DomainMapper.java +++ b/app/femr/business/helpers/DomainMapper.java @@ -20,13 +20,18 @@ import com.avaje.ebean.Ebean; import com.google.inject.Inject; + import javax.inject.Provider; + import femr.common.models.*; import femr.data.models.*; import femr.ui.models.research.FilterViewModel; +import femr.data.models.core.*; import femr.util.calculations.dateUtils; import femr.util.stringhelpers.StringUtils; import org.joda.time.DateTime; + +import java.util.Date; import java.util.List; /** @@ -40,6 +45,10 @@ public class DomainMapper { private final Provider medicationActiveDrugProvider; private final Provider medicationMeasurementUnitProvider; private final Provider medicationFormProvider; + private final Provider missionCityProvider; + private final Provider missionCountryProvider; + private final Provider missionTeamProvider; + private final Provider missionTripProvider; private final Provider patientProvider; private final Provider patientAgeClassificationProvider; private final Provider patientEncounterPhotoProvider; @@ -47,7 +56,6 @@ public class DomainMapper { private final Provider patientEncounterTabFieldProvider; private final Provider patientEncounterVitalProvider; private final Provider patientPrescriptionProvider; - private final Provider photoProvider; private final Provider roleProvider; private final Provider tabFieldProvider; @@ -64,6 +72,10 @@ public DomainMapper(Provider chiefComplaintProvider, Provider medicationFormProvider, Provider medicationActiveDrugProvider, Provider medicationMeasurementUnitProvider, + Provider missionCityProvider, + Provider missionCountryProvider, + Provider missionTeamProvider, + Provider missionTripProvider, Provider patientProvider, Provider patientAgeClassificationProvider, Provider patientEncounterPhotoProvider, @@ -85,7 +97,11 @@ public DomainMapper(Provider chiefComplaintProvider, this.medicationActiveDrugNameProvider = medicationActiveDrugNameProvider; this.medicationFormProvider = medicationFormProvider; this.medicationActiveDrugProvider = medicationActiveDrugProvider; - this.medicationMeasurementUnitProvider = medicationMeasurementUnitProvider; + this.medicationMeasurementUnitProvider = medicationMeasurementUnitProvider; + this.missionCityProvider = missionCityProvider; + this.missionCountryProvider = missionCountryProvider; + this.missionTeamProvider = missionTeamProvider; + this.missionTripProvider = missionTripProvider; this.patientProvider = patientProvider; this.patientAgeClassificationProvider = patientAgeClassificationProvider; this.patientEncounterPhotoProvider = patientEncounterPhotoProvider; @@ -176,6 +192,81 @@ public ITabField createTabField(TabFieldItem tabFieldItem, Boolean isDeleted, IT return tabField; } + /** + * Create a trip item + * + * @param teamName name of the team + * @param cityName name of the city that the team is working in + * @param countryName name of the country that the team is working in + * @param description a brief description of the team + * @return + */ + public static TripItem createTripItem(String teamName, String teamLocation, String cityName, String countryName, String description, Date startDate, Date endDate) { + + TripItem tripItem = new TripItem(); + tripItem.setTeam(teamName); + tripItem.setTeamLocation(teamLocation); + tripItem.setCity(cityName); + tripItem.setCountry(countryName); + tripItem.setDescription(description); + tripItem.setTripStartDate(startDate); + tripItem.setTripEndDate(endDate); + return tripItem; + } + + /** + * Create a new mission trip + * + * @param startDate start date of the trip + * @param endDate end date of the trip + * @param isCurrent is this the current trip? + * @param missionCity the city where the trip is taking place + * @param missionTeam the country where the trip is taking place + * @return a brand spankin' new freakin' mission trip + */ + public IMissionTrip createMissionTrip(Date startDate, Date endDate, boolean isCurrent, IMissionCity missionCity, IMissionTeam missionTeam) { + if (startDate == null || endDate == null || missionCity == null || missionTeam == null){ + return null; + } + IMissionTrip missionTrip = missionTripProvider.get(); + missionTrip.setCurrent(isCurrent); + missionTrip.setStartDate(startDate); + missionTrip.setEndDate(endDate); + missionTrip.setMissionCity(missionCity); + missionTrip.setMissionTeam(missionTeam); + return missionTrip; + } + + public IMissionCountry createMissionCountry(String name) { + if (StringUtils.isNullOrWhiteSpace(name)){ + return null; + } + IMissionCountry missionCountry = missionCountryProvider.get(); + missionCountry.setName(name); + return missionCountry; + } + + public IMissionCity createMissionCity(String name, IMissionCountry missionCountry){ + if (StringUtils.isNullOrWhiteSpace(name) || missionCountry == null){ + return null; + } + IMissionCity missionCity = missionCityProvider.get(); + missionCity.setMissionCountry(missionCountry); + missionCity.setName(name); + return missionCity; + } + + public IMissionTeam createMissionTeam(String name, String location, String description){ + if (StringUtils.isNullOrWhiteSpace(name) || StringUtils.isNullOrWhiteSpace(location) || StringUtils.isNullOrWhiteSpace(description)){ + return null; + } + IMissionTeam missionTeam = missionTeamProvider.get(); + missionTeam.setName(name); + missionTeam.setLocation(location); + missionTeam.setDescription(description); + return missionTeam; + } + /** * Create a new TabItem (DTO) * @@ -287,7 +378,7 @@ public static MedicationItem createMedicationItem(IMedication medication) { } String fullActiveDrugName = ""; - for(IMedicationActiveDrug medicationActiveDrug : medication.getMedicationActiveDrugs()){ + for (IMedicationActiveDrug medicationActiveDrug : medication.getMedicationActiveDrugs()) { medicationItem.addActiveIngredient(medicationActiveDrug.getMedicationActiveDrugName().getName(), medicationActiveDrug.getMedicationMeasurementUnit().getName(), medicationActiveDrug.getValue(), @@ -326,7 +417,7 @@ public IMedication createMedication(MedicationItem medicationItem, List getMedicationFormQuery() { return Ebean.find(MedicationForm.class); } + public static Query getMissionTripQuery() { return Ebean.find(MissionTrip.class);} + + public static Query getMissionCityQuery() { return Ebean.find(MissionCity.class);} + + public static Query getMissionCountryQuery() { return Ebean.find(MissionCountry.class);} + + public static Query getMissionTeamQuery() { return Ebean.find(MissionTeam.class);} + public static Query getPatientQuery() { return Ebean.find(Patient.class); } diff --git a/app/femr/business/services/TriageService.java b/app/femr/business/services/TriageService.java deleted file mode 100644 index 7124faecd..000000000 --- a/app/femr/business/services/TriageService.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - fEMR - fast Electronic Medical Records - Copyright (C) 2014 Team fEMR - - fEMR is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - fEMR is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with fEMR. If not, see . If - you have any questions, contact . -*/ -package femr.business.services; - -import com.avaje.ebean.ExpressionList; -import com.avaje.ebean.Query; -import com.google.inject.Inject; -import com.google.inject.Provider; -import femr.business.helpers.DomainMapper; -import femr.business.helpers.QueryHelper; -import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; -import femr.data.daos.IRepository; -import femr.data.models.*; -import femr.common.models.PatientEncounterItem; -import femr.common.models.PatientItem; -import femr.common.models.VitalItem; -import femr.util.calculations.dateUtils; -import femr.util.stringhelpers.StringUtils; - -import java.util.*; - -public class TriageService implements ITriageService { - - private final IRepository chiefComplaintRepository; - private final IRepository patientRepository; - private final IRepository patientAgeClassificationRepository; - private final IRepository patientEncounterRepository; - private final IRepository patientEncounterVitalRepository; - private final IRepository userRepository; - private final IRepository vitalRepository; - private final Provider patientEncounterVitalProvider; - private final DomainMapper domainMapper; - - @Inject - public TriageService(IRepository chiefComplaintRepository, - IRepository patientRepository, - IRepository patientAgeClassificationRepository, - IRepository patientEncounterRepository, - IRepository patientEncounterVitaRepository, - IRepository userRepository, - IRepository vitalRepository, - Provider patientEncounterVitalProvider, - DomainMapper domainMapper) { - this.chiefComplaintRepository = chiefComplaintRepository; - this.patientRepository = patientRepository; - this.patientAgeClassificationRepository = patientAgeClassificationRepository; - this.patientEncounterRepository = patientEncounterRepository; - this.patientEncounterVitalRepository = patientEncounterVitaRepository; - this.userRepository = userRepository; - this.vitalRepository = vitalRepository; - this.patientEncounterVitalProvider = patientEncounterVitalProvider; - this.domainMapper = domainMapper; - } - - /** - * {@inheritDoc} - */ - public ServiceResponse> findPossibleAgeClassifications() { - ServiceResponse> response = new ServiceResponse<>(); - Map patientAgeClassificationStrings = new LinkedHashMap<>(); - try { - Query patientAgeClassificationExpressionList = QueryProvider.getPatientAgeClassificationQuery() - .where() - .eq("isDeleted", false) - .order() - .asc("sortOrder"); - List patientAgeClassifications = patientAgeClassificationRepository.find(patientAgeClassificationExpressionList); - for (IPatientAgeClassification pac : patientAgeClassifications) { - patientAgeClassificationStrings.put(pac.getName(), pac.getDescription()); - } - response.setResponseObject(patientAgeClassificationStrings); - } catch (Exception ex) { - response.addError("", ex.getMessage()); - } - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse findPatientAndUpdateSex(int id, String sex) { - ServiceResponse response = new ServiceResponse<>(); - if (id < 1) { - response.addError("", "patient id can not be less than 1"); - return response; - } - - ExpressionList query = QueryProvider.getPatientQuery() - .where() - .eq("id", id); - - try { - IPatient savedPatient = patientRepository.findOne(query); - //if a patient doesn't have a sex and the - //user is trying to identify the patients sex - if (StringUtils.isNullOrWhiteSpace(savedPatient.getSex()) && StringUtils.isNotNullOrWhiteSpace(sex)) { - savedPatient.setSex(sex); - savedPatient = patientRepository.update(savedPatient); - } - PatientItem patientItem = domainMapper.createPatientItem(savedPatient, null, null, null, null); - response.setResponseObject(patientItem); - - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse> findAllVitalItems() { - ServiceResponse> response = new ServiceResponse<>(); - - try { - List vitals = vitalRepository.findAll(Vital.class); - List vitalItems = new ArrayList<>(); - for (IVital v : vitals) { - vitalItems.add(domainMapper.createVitalItem(v)); - } - response.setResponseObject(vitalItems); - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse createPatient(PatientItem patient) { - ServiceResponse response = new ServiceResponse<>(); - if (patient == null) { - response.addError("", "no patient received"); - return response; - } - - try { - IPatient newPatient = domainMapper.createPatient(patient); - newPatient = patientRepository.create(newPatient); - response.setResponseObject(DomainMapper.createPatientItem(newPatient, null, null, null, null)); - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse createPatientEncounter(PatientEncounterItem patientEncounterItem) { - ServiceResponse response = new ServiceResponse<>(); - if (patientEncounterItem == null) { - response.addError("", "no patient encounter item specified"); - return response; - } - - try { - //find the nurse that checked in the patient - ExpressionList nurseQuery = QueryProvider.getUserQuery() - .where() - .eq("email", patientEncounterItem.getNurseEmailAddress()); - - IUser nurseUser = userRepository.findOne(nurseQuery); - - - //find the age classification of the patient, if it exists - ExpressionList patientAgeClassificationExpressionList = QueryProvider.getPatientAgeClassificationQuery() - .where() - .eq("name", patientEncounterItem.getAgeClassification()); - IPatientAgeClassification patientAgeClassification = patientAgeClassificationRepository.findOne(patientAgeClassificationExpressionList); - Integer patientAgeClassificationId = null; - if (patientAgeClassification != null) - patientAgeClassificationId = patientAgeClassification.getId(); - - IPatientEncounter newPatientEncounter = domainMapper.createPatientEncounter(patientEncounterItem, nurseUser.getId(), patientAgeClassificationId); - newPatientEncounter = patientEncounterRepository.create(newPatientEncounter); - - List chiefComplaints = new ArrayList<>(); - for (String cc : patientEncounterItem.getChiefComplaints()) { - chiefComplaints.add(domainMapper.createChiefComplaint(cc, newPatientEncounter.getId())); - } - if (chiefComplaints.size() > 0) { - chiefComplaintRepository.createAll(chiefComplaints); - } - - - response.setResponseObject(DomainMapper.createPatientEncounterItem(newPatientEncounter)); - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } - - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse> createPatientEncounterVitalItems(Map patientEncounterVitalMap, int userId, int encounterId) { - ServiceResponse> response = new ServiceResponse<>(); - if (patientEncounterVitalMap == null || userId < 1 || encounterId < 1) { - response.addError("", "bad parameters"); - return response; - } - - List patientEncounterVitals = new ArrayList<>(); - IPatientEncounterVital patientEncounterVital; - IVital vital; - - ExpressionList query; - String currentTime = dateUtils.getCurrentDateTimeString(); - - try { - - - for (String key : patientEncounterVitalMap.keySet()) { - if (patientEncounterVitalMap.get(key) != null) { - - query = QueryProvider.getVitalQuery().where().eq("name", key); - vital = vitalRepository.findOne(query); - patientEncounterVital = patientEncounterVitalProvider.get(); - patientEncounterVital.setPatientEncounterId(encounterId); - patientEncounterVital.setUserId(userId); - patientEncounterVital.setDateTaken(currentTime); - patientEncounterVital.setVital(vital); - patientEncounterVital.setVitalValue(patientEncounterVitalMap.get(key)); - patientEncounterVitals.add(patientEncounterVital); - } - } - - List newPatientEncounterVitals = patientEncounterVitalRepository.createAll(patientEncounterVitals); - List vitalItems = new ArrayList<>(); - for (IPatientEncounterVital pev : patientEncounterVitals) { - vitalItems.add(domainMapper.createVitalItem(pev)); - } - - response.setResponseObject(vitalItems); - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } - -} diff --git a/app/femr/business/services/IConfigureService.java b/app/femr/business/services/core/IConfigureService.java similarity index 91% rename from app/femr/business/services/IConfigureService.java rename to app/femr/business/services/core/IConfigureService.java index d6fe15671..519ee0a86 100644 --- a/app/femr/business/services/IConfigureService.java +++ b/app/femr/business/services/core/IConfigureService.java @@ -16,10 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; -import femr.data.models.ISystemSetting; +import femr.common.dtos.ServiceResponse; +import femr.data.models.core.ISystemSetting; import java.util.List; diff --git a/app/femr/business/services/ISuperuserService.java b/app/femr/business/services/core/ICustomTabService.java similarity index 89% rename from app/femr/business/services/ISuperuserService.java rename to app/femr/business/services/core/ICustomTabService.java index 8fa8893c0..dd1ee3ed4 100644 --- a/app/femr/business/services/ISuperuserService.java +++ b/app/femr/business/services/core/ICustomTabService.java @@ -16,17 +16,16 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; - -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.TabFieldItem; import femr.common.models.TabItem; import java.util.List; +import java.util.Map; -public interface ISuperuserService { - +public interface ICustomTabService { /** * Edit a tab field * @@ -96,7 +95,7 @@ public interface ISuperuserService { * @param isDeleted whether or not the fields are deleted * @return a list of fields for the tab with possible exceptions */ - ServiceResponse> getTabFields(String tabName, Boolean isDeleted); + ServiceResponse> getTabFieldsByTabName(String tabName, Boolean isDeleted); /** * Get all possible types of tab fields @@ -128,4 +127,11 @@ public interface ISuperuserService { */ ServiceResponse doesTabExist(String tabName); + /** + * Maps a list of TabFieldItems to their respective tab + * + * @param encounterId current encounter id + * @return TabFieldItems mapped to their respective tab + */ + ServiceResponse>> getTabFields(int encounterId); } diff --git a/app/femr/business/services/IMedicalService.java b/app/femr/business/services/core/IEncounterService.java similarity index 56% rename from app/femr/business/services/IMedicalService.java rename to app/femr/business/services/core/IEncounterService.java index e1d5b72cb..0f6d58285 100644 --- a/app/femr/business/services/IMedicalService.java +++ b/app/femr/business/services/core/IEncounterService.java @@ -16,63 +16,53 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; -import femr.common.models.*; -import femr.data.models.IUser; +import femr.common.dtos.ServiceResponse; +import femr.common.models.PatientEncounterItem; +import femr.common.models.ProblemItem; +import femr.common.models.TabFieldItem; +import femr.common.models.UserItem; +import femr.data.models.core.IPatientEncounter; import java.util.List; import java.util.Map; -public interface IMedicalService { +public interface IEncounterService { /** - * Creates all patient encounter vitals + * Create a patient encounter * - * @param patientEncounterVital list of vitals for saving - * @param userId id of the user saving the vitals - * @param encounterId id of the current encounter - * @return vitals that were saved + * @param patientEncounterItem the patient encounter + * @return the patient encounter with id (pk) */ - ServiceResponse> createPatientEncounterVitals(Map patientEncounterVital, int userId, int encounterId); + ServiceResponse createPatientEncounter(PatientEncounterItem patientEncounterItem); /** - * Gets the physician that saw a patient in medical. + * Checks a patient into medical (updates date_of_medical_visit and the user checking them in) * - * @param encounterId id of the encounter to check - * @return the physician or null + * @param encounterId current encounter id + * @param userId id of the physician + * @return updated patient encounter */ - ServiceResponse getPhysicianThatCheckedInPatient(int encounterId); + ServiceResponse checkPatientInToMedical(int encounterId, int userId); /** - * creates multiple prescriptions + * Checks a patient into pharmacy (updates date_of_pharmacy_visit and identifies the user) * - * @param prescriptionItems list of prescription items - * @param userId id of the user saving the prescriptions - * @param encounterId id of the current encounter - * @return updated prescription list + * @param encounterId current encounter + * @param userId id of the pharmacist + * @return updated patient encounter */ - ServiceResponse> createPatientPrescriptions(List prescriptionItems, int userId, int encounterId, boolean isDispensed); + ServiceResponse checkPatientInToPharmacy(int encounterId, int userId); /** - * Adds tab field items to the PatientEncounterTabField table - * - * @param tabFieldItems list of fields to be saved - * @param encounterId id of the current encounter - * @param userId id of the user saving the fields - * @return updated list of items - */ - ServiceResponse> createPatientEncounterTabFields(List tabFieldItems, int encounterId, int userId); - - /** - * Checks a patient into medical (updates date_of_medical_visit and the user checking them in) + * Gets the physician that saw a patient in medical. * - * @param encounterId current encounter id - * @param userId id of the physician - * @return updated patient encounter + * @param encounterId id of the encounter to check + * @return the physician or null */ - ServiceResponse checkPatientIn(int encounterId, int userId); + ServiceResponse getPhysicianThatCheckedInPatientToMedical(int encounterId); /** * Finds non-custom current field values for medical tabs @@ -83,17 +73,21 @@ public interface IMedicalService { ServiceResponse> findCurrentTabFieldsByEncounterId(int encounterId); /** - * Gets all available custom tabs for the medical page + * Adds tab field items to the PatientEncounterTabField table * - * @return list of tabs + * @param tabFieldItems list of fields to be saved + * @param encounterId id of the current encounter + * @param userId id of the user saving the fields + * @return updated list of items */ - ServiceResponse> getCustomTabs(); + ServiceResponse> createPatientEncounterTabFields(List tabFieldItems, int encounterId, int userId); /** - * Matches a list of TabFieldItems to their respective tab + * Find all problems * - * @param encounterId current encounter id - * @return TabFieldItems mapped to their respective tab + * @param encounterId id of the encounter + * @return list of problems */ - ServiceResponse>> getCustomFields(int encounterId); + ServiceResponse> findProblemItems(int encounterId); + } diff --git a/app/femr/business/services/IInventoryService.java b/app/femr/business/services/core/IInventoryService.java similarity index 95% rename from app/femr/business/services/IInventoryService.java rename to app/femr/business/services/core/IInventoryService.java index 6dc2930ab..f967d2ca3 100644 --- a/app/femr/business/services/IInventoryService.java +++ b/app/femr/business/services/core/IInventoryService.java @@ -16,9 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.MedicationItem; import java.util.List; diff --git a/app/femr/business/services/IPharmacyService.java b/app/femr/business/services/core/IMedicationService.java similarity index 72% rename from app/femr/business/services/IPharmacyService.java rename to app/femr/business/services/core/IMedicationService.java index 7dd636e0a..195602a7f 100644 --- a/app/femr/business/services/IPharmacyService.java +++ b/app/femr/business/services/core/IMedicationService.java @@ -16,51 +16,39 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; +import femr.common.dtos.ServiceResponse; import femr.common.models.PrescriptionItem; -import femr.common.dto.ServiceResponse; -import femr.common.models.ProblemItem; -import femr.common.models.VitalItem; -import femr.data.models.IPatientEncounter; import java.util.List; -public interface IPharmacyService { - +public interface IMedicationService { /** - * Checks a patient into pharmacy (updates date_of_pharmacy_visit and identifies the user) + * Get a JSON string representing medication names * - * @param encounterId current encounter - * @param userId id of the pharmacist - * @return updated patient encounter + * @return JSON object in the form of { medication# : "name" } */ - ServiceResponse checkPatientIn(int encounterId, int userId); + ServiceResponse getMedicationNames(); /** * Create a new prescription and replace an old one with it * * @param prescriptionItem new prescription to replace the old one * @param oldScriptId id of the old prescription that is being replaced - * @param isCounseled was the patient counseled on this prescription * @return updated new prescription */ ServiceResponse createAndReplacePrescription(PrescriptionItem prescriptionItem, int oldScriptId, int userId, boolean isCounseled); /** - * Find all problems - * - * @param encounterId id of the encounter - * @return list of problems - */ - ServiceResponse> findProblemItems(int encounterId); - - /** - * Retrieve all medication names for typeahead + * creates multiple prescriptions * - * @return + * @param prescriptionItems list of prescription items + * @param userId id of the user saving the prescriptions + * @param encounterId id of the current encounter + * @return updated prescription list */ - ServiceResponse> findAllMedications(); + ServiceResponse> createPatientPrescriptions(List prescriptionItems, int userId, int encounterId, boolean isDispensed, boolean isCounseled); /** * Mark prescriptions as filled @@ -77,4 +65,11 @@ public interface IPharmacyService { * @return prescription items that were marked as counseled */ ServiceResponse> markPrescriptionsAsCounseled(List prescriptionIds); + + /** + * Retrieve all medication names for typeahead + * + * @return + */ + ServiceResponse> findAllMedications(); } diff --git a/app/femr/business/services/core/IMissionTripService.java b/app/femr/business/services/core/IMissionTripService.java new file mode 100644 index 000000000..4caef31d9 --- /dev/null +++ b/app/femr/business/services/core/IMissionTripService.java @@ -0,0 +1,68 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.business.services.core; + +import femr.common.dtos.ServiceResponse; +import femr.common.models.TripItem; + +import java.util.List; + +public interface IMissionTripService { + /** + * Retrieve the current trip information + * @return the current trip or an error if one doesn't exist + */ + ServiceResponse findCurrentMissionTrip(); + + /** + * Retrieve a list of the teams that are already in the database + * @return a list of team names + */ + ServiceResponse> findAvailableTeams(); + + /** + * Retrieve a list of the cities that are already in the database + * @return a list of cities + */ + ServiceResponse> findAvailableCities(); + + /** + * Retrieve a list of the countries that are already in the database + * @return a list of countries + */ + ServiceResponse> findAvailableCountries(); + + /** + * Given a trip item, do: + *
    + *
  • Nothing if the current trip is the same as tripItem
  • + *
  • Create new teams, cities, countries as needed
  • + *
  • If something new is created (other than description), creates a new current trip
  • + *
+ * @return + */ + ServiceResponse updateTrip(TripItem tripItem); + + /** + * Get a JSON string representing trip information + * + * @return + */ + ServiceResponse getTripInformation(); +} diff --git a/app/femr/business/services/ITriageService.java b/app/femr/business/services/core/IPatientService.java similarity index 55% rename from app/femr/business/services/ITriageService.java rename to app/femr/business/services/core/IPatientService.java index 37ddb3f2e..17d028de3 100644 --- a/app/femr/business/services/ITriageService.java +++ b/app/femr/business/services/core/IPatientService.java @@ -16,21 +16,15 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; -import femr.common.models.PatientEncounterItem; +import femr.common.dtos.ServiceResponse; import femr.common.models.PatientItem; -import femr.common.models.VitalItem; -import java.util.List; import java.util.Map; -/** - * Interface for Triage Service. Contains the documentation which - * is inherited in the implementations. - */ -public interface ITriageService { +public interface IPatientService { + /** * Finds all possible age classifications for a patient */ @@ -45,35 +39,10 @@ public interface ITriageService { */ ServiceResponse findPatientAndUpdateSex(int id, String sex); - /** - * Gets vital items, but only the names - * - * @return Returns a list of all vitals without values - */ - ServiceResponse> findAllVitalItems(); - /** * Creates a new patient * @param patient patient to be created * @return patient with an assigned Id (pk) */ ServiceResponse createPatient(PatientItem patient); - - /** - * Create a patient encounter - * - * @param patientEncounterItem the patient encounter - * @return the patient encounter with id (pk) - */ - ServiceResponse createPatientEncounter(PatientEncounterItem patientEncounterItem); - - /** - * Create all vitals for an encounter - * - * @param patientEncounterVitalMap A keypair of vitals to be created - * @param userId User creating the vitals - * @param encounterId Encounter that the vitals are for - * @return List of vitals that were created - */ - ServiceResponse> createPatientEncounterVitalItems(Map patientEncounterVitalMap, int userId, int encounterId); } diff --git a/app/femr/business/services/IPhotoService.java b/app/femr/business/services/core/IPhotoService.java similarity index 96% rename from app/femr/business/services/IPhotoService.java rename to app/femr/business/services/core/IPhotoService.java index 7bf96d02c..ee6f819ee 100644 --- a/app/femr/business/services/IPhotoService.java +++ b/app/femr/business/services/core/IPhotoService.java @@ -16,11 +16,11 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; import java.util.List; import femr.common.models.PatientEncounterItem; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.PhotoItem; import femr.ui.models.medical.EditViewModelPost; import play.mvc.Http.MultipartFormData.FilePart; diff --git a/app/femr/business/services/IResearchService.java b/app/femr/business/services/core/IResearchService.java similarity index 84% rename from app/femr/business/services/IResearchService.java rename to app/femr/business/services/core/IResearchService.java index 8d62a7761..a5b3d58e5 100644 --- a/app/femr/business/services/IResearchService.java +++ b/app/femr/business/services/core/IResearchService.java @@ -16,18 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; - -import com.google.gson.JsonObject; - -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; -import femr.ui.models.research.FilterViewModel; - - -import java.util.Date; -import java.util.List; import java.util.Map; /** diff --git a/app/femr/business/services/IRoleService.java b/app/femr/business/services/core/IRoleService.java similarity index 89% rename from app/femr/business/services/IRoleService.java rename to app/femr/business/services/core/IRoleService.java index 5e876fa55..c91c345a1 100644 --- a/app/femr/business/services/IRoleService.java +++ b/app/femr/business/services/core/IRoleService.java @@ -16,10 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; -import femr.data.models.IRole; +import femr.common.dtos.ServiceResponse; +import femr.data.models.core.IRole; import java.util.List; diff --git a/app/femr/business/services/ISearchService.java b/app/femr/business/services/core/ISearchService.java similarity index 97% rename from app/femr/business/services/ISearchService.java rename to app/femr/business/services/core/ISearchService.java index a90f299f4..4af8adf63 100644 --- a/app/femr/business/services/ISearchService.java +++ b/app/femr/business/services/core/ISearchService.java @@ -16,11 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; -import femr.data.models.*; import femr.util.DataStructure.Mapping.TabFieldMultiMap; import femr.util.DataStructure.Mapping.VitalMultiMap; diff --git a/app/femr/business/services/ISessionService.java b/app/femr/business/services/core/ISessionService.java similarity index 89% rename from app/femr/business/services/ISessionService.java rename to app/femr/business/services/core/ISessionService.java index c69e75403..cf4f09f48 100644 --- a/app/femr/business/services/ISessionService.java +++ b/app/femr/business/services/core/ISessionService.java @@ -16,10 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; public interface ISessionService { ServiceResponse createSession(String email, String password); diff --git a/app/femr/business/services/IUserService.java b/app/femr/business/services/core/IUserService.java similarity index 93% rename from app/femr/business/services/IUserService.java rename to app/femr/business/services/core/IUserService.java index 0396cafc1..67f4606d9 100644 --- a/app/femr/business/services/IUserService.java +++ b/app/femr/business/services/core/IUserService.java @@ -16,12 +16,12 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.core; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.UserItem; -import femr.data.models.IRole; -import femr.data.models.IUser; +import femr.data.models.core.IRole; +import femr.data.models.core.IUser; import java.util.List; diff --git a/app/femr/business/services/core/IVitalService.java b/app/femr/business/services/core/IVitalService.java new file mode 100644 index 000000000..ae503e08d --- /dev/null +++ b/app/femr/business/services/core/IVitalService.java @@ -0,0 +1,55 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.business.services.core; + +import femr.common.dtos.ServiceResponse; +import femr.common.models.VitalItem; + +import java.util.List; +import java.util.Map; + +public interface IVitalService { + + /** + * Creates all patient encounter vitals + * + * @param patientEncounterVital list of vitals for saving + * @param userId id of the user saving the vitals + * @param encounterId id of the current encounter + * @return vitals that were saved + */ + ServiceResponse> createPatientEncounterVitals(Map patientEncounterVital, int userId, int encounterId); + + /** + * Gets vital items, but only the names + * + * @return Returns a list of all vitals without values + */ + ServiceResponse> findAllVitalItems(); + + /** + * Create all vitals for an encounter + * + * @param patientEncounterVitalMap A keypair of vitals to be created + * @param userId User creating the vitals + * @param encounterId Encounter that the vitals are for + * @return List of vitals that were created + */ + ServiceResponse> createPatientEncounterVitalItems(Map patientEncounterVitalMap, int userId, int encounterId); +} diff --git a/app/femr/business/services/ConfigureService.java b/app/femr/business/services/system/ConfigureService.java similarity index 91% rename from app/femr/business/services/ConfigureService.java rename to app/femr/business/services/system/ConfigureService.java index d216cfff0..59b60af52 100644 --- a/app/femr/business/services/ConfigureService.java +++ b/app/femr/business/services/system/ConfigureService.java @@ -16,15 +16,14 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; -import com.avaje.ebean.ExpressionList; import com.google.inject.Inject; -import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.IConfigureService; +import femr.common.dtos.ServiceResponse; import femr.data.daos.IRepository; -import femr.data.models.ISystemSetting; -import femr.data.models.SystemSetting; +import femr.data.models.core.ISystemSetting; +import femr.data.models.mysql.SystemSetting; import java.util.List; diff --git a/app/femr/business/services/SuperuserService.java b/app/femr/business/services/system/CustomTabService.java similarity index 80% rename from app/femr/business/services/SuperuserService.java rename to app/femr/business/services/system/CustomTabService.java index 6ae29bad8..0aa2bb387 100644 --- a/app/femr/business/services/SuperuserService.java +++ b/app/femr/business/services/system/CustomTabService.java @@ -16,32 +16,32 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.ExpressionList; import com.avaje.ebean.Query; import com.google.inject.Inject; import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.ICustomTabService; +import femr.common.dtos.ServiceResponse; import femr.common.models.TabFieldItem; import femr.common.models.TabItem; -import femr.data.models.ITab; -import femr.data.models.ITabField; -import femr.data.models.ITabFieldSize; -import femr.data.models.ITabFieldType; import femr.data.daos.IRepository; -import femr.data.models.Tab; -import femr.data.models.TabField; -import femr.data.models.TabFieldSize; -import femr.data.models.TabFieldType; +import femr.data.models.core.*; +import femr.data.models.mysql.*; import femr.util.stringhelpers.StringUtils; import org.joda.time.DateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class SuperuserService implements ISuperuserService { +public class CustomTabService implements ICustomTabService { + + private final IRepository chiefComplaintRepository; + private final IRepository patientEncounterTabFieldRepository; private final IRepository tabRepository; private final IRepository tabFieldRepository; private final IRepository tabFieldTypeRepository; @@ -49,11 +49,15 @@ public class SuperuserService implements ISuperuserService { private final DomainMapper domainMapper; @Inject - public SuperuserService(IRepository tabRepository, + public CustomTabService(IRepository chiefComplaintRepository, + IRepository patientEncounterTabFieldRepository, + IRepository tabRepository, IRepository tabFieldRepository, IRepository tabFieldTypeRepository, IRepository tabFieldSizeRepository, DomainMapper domainMapper) { + this.chiefComplaintRepository = chiefComplaintRepository; + this.patientEncounterTabFieldRepository = patientEncounterTabFieldRepository; this.tabRepository = tabRepository; this.tabFieldRepository = tabFieldRepository; this.tabFieldTypeRepository = tabFieldTypeRepository; @@ -90,7 +94,7 @@ public ServiceResponse toggleTab(String name) { return response; } - TabItem tabItem = domainMapper.createTabItem(tab); + TabItem tabItem = DomainMapper.createTabItem(tab); response.setResponseObject(tabItem); return response; } @@ -154,7 +158,7 @@ public ServiceResponse> getCustomTabs(Boolean isDeleted) { List tabItems = new ArrayList<>(); for (ITab t : tabs) { - tabItems.add(domainMapper.createTabItem(t)); + tabItems.add(DomainMapper.createTabItem(t)); } response.setResponseObject(tabItems); @@ -165,7 +169,7 @@ public ServiceResponse> getCustomTabs(Boolean isDeleted) { * {@inheritDoc} */ @Override - public ServiceResponse> getTabFields(String tabName, Boolean isDeleted) { + public ServiceResponse> getTabFieldsByTabName(String tabName, Boolean isDeleted) { ServiceResponse> response = new ServiceResponse<>(); if (StringUtils.isNullOrWhiteSpace(tabName)) { response.addError("", "bad parameters, wtf are you doing?"); @@ -183,7 +187,7 @@ public ServiceResponse> getTabFields(String tabName, Boolean List tabFields = tabFieldRepository.find(query); List customFieldItems = new ArrayList<>(); for (ITabField tf : tabFields) { - customFieldItems.add(domainMapper.createTabFieldItem(tf)); + customFieldItems.add(DomainMapper.createTabFieldItem(tf)); } response.setResponseObject(customFieldItems); } catch (Exception ex) { @@ -193,6 +197,64 @@ public ServiceResponse> getTabFields(String tabName, Boolean return response; } + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse>> getTabFields(int encounterId) { + ServiceResponse>> response = new ServiceResponse<>(); + if (encounterId < 1) { + response.addError("", "encounterId must be greater than 0"); + return response; + } + Map> customFieldMap = new HashMap<>(); + ExpressionList query = QueryProvider.getTabQuery() + .where() + .eq("isDeleted", false); + try { + //O(n^2) because who gives a fuck + List customTabs = tabRepository.find(query); + for (ITab ct : customTabs) { + Query query2 = QueryProvider.getTabFieldQuery() + .fetch("tab") + .where() + .eq("isDeleted", false) + .eq("tab.name", ct.getName()) + .order() + .asc("sort_order"); + + + List customFields = tabFieldRepository.find(query2); + List customFieldItems = new ArrayList<>(); + for (ITabField cf : customFields) { + Query query3 = QueryProvider.getPatientEncounterTabFieldQuery() + .where() + .eq("tabField", cf)//somethings fucky + .eq("patient_encounter_id", encounterId) + .order() + .desc("date_taken"); + + List patientEncounterCustomField = patientEncounterTabFieldRepository.find(query3); + if (patientEncounterCustomField != null && patientEncounterCustomField.size() > 0) { + customFieldItems.add(DomainMapper.createTabFieldItem(patientEncounterCustomField.get(0))); + } else { + customFieldItems.add(DomainMapper.createTabFieldItem(cf)); + } + + + } + customFieldMap.put(ct.getName(), customFieldItems); + + } + response.setResponseObject(customFieldMap); + } catch (Exception ex) { + response.addError("", "error"); + return response; + } + + return response; + } + /** * {@inheritDoc} */ @@ -418,20 +480,20 @@ public ServiceResponse> getSizes() { * {@inheritDoc} */ @Override - public ServiceResponse doesTabFieldExist(String fieldName){ + public ServiceResponse doesTabFieldExist(String fieldName) { ServiceResponse response = new ServiceResponse<>(); ExpressionList query = QueryProvider.getTabFieldQuery() .where() .eq("name", fieldName); - try{ + try { ITabField customField = tabFieldRepository.findOne(query); - if (customField == null){ + if (customField == null) { response.setResponseObject(false); - }else{ + } else { response.setResponseObject(true); } - }catch (Exception ex){ + } catch (Exception ex) { response.setResponseObject(false); } return response; @@ -441,22 +503,21 @@ public ServiceResponse doesTabFieldExist(String fieldName){ * {@inheritDoc} */ @Override - public ServiceResponse doesTabExist(String tabName){ + public ServiceResponse doesTabExist(String tabName) { ServiceResponse response = new ServiceResponse<>(); ExpressionList query = QueryProvider.getTabQuery() .where() .eq("name", tabName); - try{ + try { ITab customTab = tabRepository.findOne(query); - if (customTab == null){ + if (customTab == null) { response.setResponseObject(false); - }else{ + } else { response.setResponseObject(true); } - }catch(Exception ex){ + } catch (Exception ex) { response.setResponseObject(false); } return response; } - } diff --git a/app/femr/business/services/MedicalService.java b/app/femr/business/services/system/EncounterService.java similarity index 58% rename from app/femr/business/services/MedicalService.java rename to app/femr/business/services/system/EncounterService.java index d5c42f8f0..b0a39775f 100644 --- a/app/femr/business/services/MedicalService.java +++ b/app/femr/business/services/system/EncounterService.java @@ -16,62 +16,55 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.ExpressionList; import com.avaje.ebean.Query; import com.google.inject.Inject; -import com.google.inject.Provider; import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; -import femr.common.models.*; +import femr.business.services.core.IEncounterService; +import femr.common.dtos.ServiceResponse; +import femr.common.models.PatientEncounterItem; +import femr.common.models.ProblemItem; +import femr.common.models.TabFieldItem; +import femr.common.models.UserItem; import femr.data.daos.IRepository; -import femr.data.models.*; -import femr.util.calculations.dateUtils; +import femr.data.models.core.*; +import femr.data.models.mysql.*; import femr.util.stringhelpers.StringUtils; import org.joda.time.DateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; -public class MedicalService implements IMedicalService { +public class EncounterService implements IEncounterService { private final IRepository chiefComplaintRepository; + private final IRepository patientAgeClassificationRepository; private final IRepository patientEncounterRepository; - private final IRepository patientEncounterVitalRepository; private final IRepository patientEncounterTabFieldRepository; - private final Provider patientEncounterVitalProvider; - private final IRepository patientPrescriptionRepository; - private final IRepository customTabRepository; private final IRepository tabFieldRepository; private final IRepository userRepository; - private final IRepository vitalRepository; - private final DomainMapper domainMapper; @Inject - public MedicalService(IRepository chiefComplaintRepository, - IRepository patientEncounterRepository, - IRepository patientEncounterVitalRepository, - IRepository tabFieldRepository, - IRepository patientEncounterTabFieldRepository, - IRepository patientPrescriptionRepository, - IRepository vitalRepository, - IRepository customTabRepository, - IRepository userRepository, - Provider patientEncounterVitalProvider, - DomainMapper domainMapper) { + public EncounterService(IRepository chiefComplaintRepository, + IRepository patientAgeClassificationRepository, + IRepository patientEncounterRepository, + IRepository patientEncounterTabFieldRepository, + IRepository tabFieldRepository, + IRepository userRepository, + DomainMapper domainMapper){ this.chiefComplaintRepository = chiefComplaintRepository; + this.patientAgeClassificationRepository = patientAgeClassificationRepository; this.patientEncounterRepository = patientEncounterRepository; - this.patientEncounterVitalRepository = patientEncounterVitalRepository; - this.tabFieldRepository = tabFieldRepository; this.patientEncounterTabFieldRepository = patientEncounterTabFieldRepository; - this.patientPrescriptionRepository = patientPrescriptionRepository; - this.vitalRepository = vitalRepository; - this.customTabRepository = customTabRepository; + this.tabFieldRepository = tabFieldRepository; this.userRepository = userRepository; - this.patientEncounterVitalProvider = patientEncounterVitalProvider; this.domainMapper = domainMapper; } @@ -79,26 +72,47 @@ public MedicalService(IRepository chiefComplaintRepository, * {@inheritDoc} */ @Override - public ServiceResponse getPhysicianThatCheckedInPatient(int encounterId) { - ServiceResponse response = new ServiceResponse<>(); - if (encounterId < 1) { - response.addError("", "encounter id must be greater than 0"); + public ServiceResponse createPatientEncounter(PatientEncounterItem patientEncounterItem) { + ServiceResponse response = new ServiceResponse<>(); + if (patientEncounterItem == null) { + response.addError("", "no patient encounter item specified"); return response; } + try { - ExpressionList patientEncounterQuery = QueryProvider.getPatientEncounterQuery() + //find the nurse that checked in the patient + ExpressionList nurseQuery = QueryProvider.getUserQuery() .where() - .eq("id", encounterId); - IPatientEncounter patientEncounter = patientEncounterRepository.findOne(patientEncounterQuery); - if (patientEncounter.getDoctor() == null) { - response.setResponseObject(null); - } else { - UserItem userItem = DomainMapper.createUserItem(patientEncounter.getDoctor()); - response.setResponseObject(userItem); + .eq("email", patientEncounterItem.getNurseEmailAddress()); + + IUser nurseUser = userRepository.findOne(nurseQuery); + + //find the age classification of the patient, if it exists + ExpressionList patientAgeClassificationExpressionList = QueryProvider.getPatientAgeClassificationQuery() + .where() + .eq("name", patientEncounterItem.getAgeClassification()); + IPatientAgeClassification patientAgeClassification = patientAgeClassificationRepository.findOne(patientAgeClassificationExpressionList); + Integer patientAgeClassificationId = null; + if (patientAgeClassification != null) + patientAgeClassificationId = patientAgeClassification.getId(); + + IPatientEncounter newPatientEncounter = domainMapper.createPatientEncounter(patientEncounterItem, nurseUser.getId(), patientAgeClassificationId); + newPatientEncounter = patientEncounterRepository.create(newPatientEncounter); + + List chiefComplaints = new ArrayList<>(); + for (String cc : patientEncounterItem.getChiefComplaints()) { + chiefComplaints.add(domainMapper.createChiefComplaint(cc, newPatientEncounter.getId())); + } + if (chiefComplaints.size() > 0) { + chiefComplaintRepository.createAll(chiefComplaints); } + + + response.setResponseObject(DomainMapper.createPatientEncounterItem(newPatientEncounter)); } catch (Exception ex) { - response.addError("", "error finding encounter"); + response.addError("exception", ex.getMessage()); } + return response; } @@ -106,26 +120,59 @@ public ServiceResponse getPhysicianThatCheckedInPatient(int encounterI * {@inheritDoc} */ @Override - public ServiceResponse> createPatientPrescriptions(List prescriptionItems, int userId, int encounterId, boolean isDispensed) { - ServiceResponse> response = new ServiceResponse<>(); - if (prescriptionItems == null || userId < 1 || encounterId < 1) { - response.addError("", "invalid parameters"); + public ServiceResponse checkPatientInToMedical(int encounterId, int userId) { + ServiceResponse response = new ServiceResponse<>(); + if (encounterId < 1) { + response.addError("", "encounterId must be greater than 0"); return response; } - List patientPrescriptions = new ArrayList<>(); - for (PrescriptionItem pi : prescriptionItems) { - IMedication medication = domainMapper.createMedication(pi.getName()); - patientPrescriptions.add(domainMapper.createPatientPrescription(0, medication, userId, encounterId, null, false, false)); + ExpressionList query = QueryProvider.getPatientEncounterQuery() + .where() + .eq("id", encounterId); + + try { + IPatientEncounter patientEncounter = patientEncounterRepository.findOne(query); + patientEncounter.setDateOfMedicalVisit(DateTime.now()); + ExpressionList getUserQuery = QueryProvider.getUserQuery() + .where() + .eq("id", userId); + IUser user = userRepository.findOne(getUserQuery); + patientEncounter.setDoctor(user); + + patientEncounter = patientEncounterRepository.update(patientEncounter); + + + response.setResponseObject(DomainMapper.createPatientEncounterItem(patientEncounter)); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse checkPatientInToPharmacy(int encounterId, int userId) { + ServiceResponse response = new ServiceResponse<>(); + if (encounterId < 1) { + response.addError("", "encounterId can not be less than 1"); + return response; } try { - List newPatientPrescriptions = patientPrescriptionRepository.createAll(patientPrescriptions); - List newPrescriptionItems = new ArrayList<>(); - for (IPatientPrescription pp : newPatientPrescriptions) { - newPrescriptionItems.add(domainMapper.createPatientPrescriptionItem(pp)); - } - response.setResponseObject(newPrescriptionItems); + ExpressionList query = QueryProvider.getPatientEncounterQuery().where().eq("id", encounterId); + IPatientEncounter patientEncounter = patientEncounterRepository.findOne(query); + patientEncounter.setDateOfPharmacyVisit(DateTime.now()); + ExpressionList getUserQuery = QueryProvider.getUserQuery() + .where() + .eq("id", userId); + IUser user = userRepository.findOne(getUserQuery); + patientEncounter.setPharmacist(user); + patientEncounter = patientEncounterRepository.update(patientEncounter); + response.setResponseObject(patientEncounter); } catch (Exception ex) { response.addError("exception", ex.getMessage()); } @@ -133,6 +180,33 @@ public ServiceResponse> createPatientPrescriptions(List

getPhysicianThatCheckedInPatientToMedical(int encounterId) { + ServiceResponse response = new ServiceResponse<>(); + if (encounterId < 1) { + response.addError("", "encounter id must be greater than 0"); + return response; + } + try { + ExpressionList patientEncounterQuery = QueryProvider.getPatientEncounterQuery() + .where() + .eq("id", encounterId); + IPatientEncounter patientEncounter = patientEncounterRepository.findOne(patientEncounterQuery); + if (patientEncounter.getDoctor() == null) { + response.setResponseObject(null); + } else { + UserItem userItem = DomainMapper.createUserItem(patientEncounter.getDoctor()); + response.setResponseObject(userItem); + } + } catch (Exception ex) { + response.addError("", "error finding encounter"); + } + return response; + } + /** * {@inheritDoc} */ @@ -210,10 +284,11 @@ public ServiceResponse> findCurrentTabFieldsByEncounte response.addError("exception", ex.getMessage()); } - return response; } + + /** * {@inheritDoc} */ @@ -263,7 +338,7 @@ public ServiceResponse> createPatientEncounterTabFields(List< List tabFieldItemsToReturn = new ArrayList<>(); for (IPatientEncounterTabField petf : savedTabFields) { - tabFieldItemsToReturn.add(domainMapper.createTabFieldItem(petf)); + tabFieldItemsToReturn.add(DomainMapper.createTabFieldItem(petf)); } response.setResponseObject(tabFieldItemsToReturn); } catch (Exception ex) { @@ -277,163 +352,31 @@ public ServiceResponse> createPatientEncounterTabFields(List< * {@inheritDoc} */ @Override - public ServiceResponse>> getCustomFields(int encounterId) { - ServiceResponse>> response = new ServiceResponse<>(); - if (encounterId < 1) { - response.addError("", "encounterId must be greater than 0"); - return response; - } - Map> customFieldMap = new HashMap<>(); - ExpressionList query = QueryProvider.getTabQuery() + public ServiceResponse> findProblemItems(int encounterId) { + ServiceResponse> response = new ServiceResponse<>(); + List problemItems = new ArrayList<>(); + Query query = QueryProvider.getPatientEncounterTabFieldQuery() + .fetch("tabField") .where() - .eq("isDeleted", false); - try { - //O(n^2) because who gives a fuck - List customTabs = customTabRepository.find(query); - for (ITab ct : customTabs) { - Query query2 = QueryProvider.getTabFieldQuery() - .fetch("tab") - .where() - .eq("isDeleted", false) - .eq("tab.name", ct.getName()) - .order() - .asc("sort_order"); - - - List customFields = tabFieldRepository.find(query2); - List customFieldItems = new ArrayList<>(); - for (ITabField cf : customFields) { - Query query3 = QueryProvider.getPatientEncounterTabFieldQuery() - .where() - .eq("tabField", cf)//somethings fucky - .eq("patient_encounter_id", encounterId) - .order() - .desc("date_taken"); - - List patientEncounterCustomField = patientEncounterTabFieldRepository.find(query3); - if (patientEncounterCustomField != null && patientEncounterCustomField.size() > 0) { - customFieldItems.add(domainMapper.createTabFieldItem(patientEncounterCustomField.get(0))); - } else { - customFieldItems.add(domainMapper.createTabFieldItem(cf)); - } - - - } - customFieldMap.put(ct.getName(), customFieldItems); - - } - response.setResponseObject(customFieldMap); - } catch (Exception ex) { - response.addError("", "error"); - return response; - } - - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse> createPatientEncounterVitals(Map patientEncounterVitalMap, int userId, int encounterId) { - ServiceResponse> response = new ServiceResponse<>(); - if (patientEncounterVitalMap == null || userId < 1 || encounterId < 1) { - response.addError("", "invalid parameters"); - return response; - } - List patientEncounterVitals = new ArrayList<>(); - IPatientEncounterVital patientEncounterVital; - IVital vital; - - ExpressionList query; - String currentTime = dateUtils.getCurrentDateTimeString(); - - for (String key : patientEncounterVitalMap.keySet()) { - if (patientEncounterVitalMap.get(key) != null) { - query = QueryProvider.getVitalQuery().where().eq("name", key); - vital = vitalRepository.findOne(query); - - patientEncounterVital = patientEncounterVitalProvider.get(); - patientEncounterVital.setPatientEncounterId(encounterId); - patientEncounterVital.setUserId(userId); - patientEncounterVital.setDateTaken(currentTime); - patientEncounterVital.setVital(vital); - patientEncounterVital.setVitalValue(patientEncounterVitalMap.get(key)); - patientEncounterVitals.add(patientEncounterVital); - } - } - - try { - List vitalItems = new ArrayList<>(); - List newPatientEncounterVitals = patientEncounterVitalRepository.createAll(patientEncounterVitals); - for (IPatientEncounterVital pev : newPatientEncounterVitals) { - vitalItems.add(domainMapper.createVitalItem(pev)); - } - response.setResponseObject(vitalItems); - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } + .eq("patient_encounter_id", encounterId) + .eq("tabField.name", "problem") + .order() + .asc("date_taken"); - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse> getCustomTabs() { - ServiceResponse> response = new ServiceResponse<>(); - ExpressionList query = QueryProvider.getTabQuery() - .where() - .eq("isDeleted", false) - .eq("isCustom", true); try { - List customTabs = customTabRepository.find(query); - List customTabNames = new ArrayList<>(); - - for (ITab t : customTabs) { - customTabNames.add(domainMapper.createTabItem(t)); + List patientEncounterTreatmentFields = patientEncounterTabFieldRepository.find(query); + if (patientEncounterTreatmentFields == null) { + response.addError("", "bad query"); + } else { + for (IPatientEncounterTabField petf : patientEncounterTreatmentFields) { + problemItems.add(domainMapper.createProblemItem(petf)); + } + response.setResponseObject(problemItems); } - response.setResponseObject(customTabNames); } catch (Exception ex) { response.addError("", "error"); } return response; } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse checkPatientIn(int encounterId, int userId) { - ServiceResponse response = new ServiceResponse<>(); - if (encounterId < 1) { - response.addError("", "encounterId must be greater than 0"); - return response; - } - - ExpressionList query = QueryProvider.getPatientEncounterQuery() - .where() - .eq("id", encounterId); - - try { - IPatientEncounter patientEncounter = patientEncounterRepository.findOne(query); - patientEncounter.setDateOfMedicalVisit(DateTime.now()); - ExpressionList getUserQuery = QueryProvider.getUserQuery() - .where() - .eq("id", userId); - IUser user = userRepository.findOne(getUserQuery); - patientEncounter.setDoctor(user); - - patientEncounter = patientEncounterRepository.update(patientEncounter); - - - response.setResponseObject(DomainMapper.createPatientEncounterItem(patientEncounter)); - } catch (Exception ex) { - response.addError("exception", ex.getMessage()); - } - - return response; - } } diff --git a/app/femr/business/services/InventoryService.java b/app/femr/business/services/system/InventoryService.java similarity index 95% rename from app/femr/business/services/InventoryService.java rename to app/femr/business/services/system/InventoryService.java index 09cdac93f..35262cfd2 100644 --- a/app/femr/business/services/InventoryService.java +++ b/app/femr/business/services/system/InventoryService.java @@ -16,17 +16,21 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.ExpressionList; -import com.avaje.ebean.Query; import com.google.inject.Inject; import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; -import femr.data.models.*; +import femr.business.services.core.IInventoryService; +import femr.common.dtos.ServiceResponse; import femr.data.daos.IRepository; import femr.common.models.MedicationItem; +import femr.data.models.core.*; +import femr.data.models.mysql.Medication; +import femr.data.models.mysql.MedicationActiveDrugName; +import femr.data.models.mysql.MedicationForm; +import femr.data.models.mysql.MedicationMeasurementUnit; import java.util.ArrayList; import java.util.List; diff --git a/app/femr/business/services/PharmacyService.java b/app/femr/business/services/system/MedicationService.java similarity index 67% rename from app/femr/business/services/PharmacyService.java rename to app/femr/business/services/system/MedicationService.java index 2e40eb370..62623fd20 100644 --- a/app/femr/business/services/PharmacyService.java +++ b/app/femr/business/services/system/MedicationService.java @@ -16,49 +16,37 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.*; +import com.google.gson.JsonObject; import com.google.inject.Inject; import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryProvider; +import femr.business.services.core.IMedicationService; +import femr.common.dtos.ServiceResponse; import femr.common.models.PrescriptionItem; -import femr.common.dto.ServiceResponse; -import femr.common.models.ProblemItem; -import femr.common.models.VitalItem; -import femr.data.models.IUser; -import femr.data.models.User; import femr.data.daos.IRepository; -import femr.data.models.*; +import femr.data.models.core.IMedication; +import femr.data.models.core.IPatientPrescription; +import femr.data.models.mysql.Medication; +import femr.data.models.mysql.PatientPrescription; import femr.util.stringhelpers.StringUtils; -import org.joda.time.DateTime; - import java.util.ArrayList; import java.util.List; -public class PharmacyService implements IPharmacyService { +public class MedicationService implements IMedicationService { + private final IRepository medicationRepository; - private final IRepository patientEncounterRepository; - private final IRepository patientEncounterTabFieldRepository; - private final IRepository patientEncounterVitalRepository; private final IRepository patientPrescriptionRepository; - private final IRepository userRepository; private final DomainMapper domainMapper; @Inject - public PharmacyService(IRepository patientPrescriptionRepository, - IRepository patientEncounterTabFieldRepository, - IRepository patientEncounterVitalRepository, - IRepository medicationRepository, - IRepository patientEncounterRepository, - IRepository userRepository, - DomainMapper domainMapper) { - this.patientPrescriptionRepository = patientPrescriptionRepository; - this.patientEncounterTabFieldRepository = patientEncounterTabFieldRepository; - this.patientEncounterVitalRepository = patientEncounterVitalRepository; + public MedicationService(IRepository medicationRepository, + IRepository patientPrescriptionRepository, + DomainMapper domainMapper) { this.medicationRepository = medicationRepository; - this.patientEncounterRepository = patientEncounterRepository; - this.userRepository = userRepository; + this.patientPrescriptionRepository = patientPrescriptionRepository; this.domainMapper = domainMapper; } @@ -66,24 +54,22 @@ public PharmacyService(IRepository patientPrescriptionRepo * {@inheritDoc} */ @Override - public ServiceResponse checkPatientIn(int encounterId, int userId) { - ServiceResponse response = new ServiceResponse<>(); - if (encounterId < 1) { - response.addError("", "encounterId can not be less than 1"); - return response; - } + public ServiceResponse getMedicationNames() { + + ServiceResponse response = new ServiceResponse<>(); try { - ExpressionList query = QueryProvider.getPatientEncounterQuery().where().eq("id", encounterId); - IPatientEncounter patientEncounter = patientEncounterRepository.findOne(query); - patientEncounter.setDateOfPharmacyVisit(DateTime.now()); - ExpressionList getUserQuery = QueryProvider.getUserQuery() - .where() - .eq("id", userId); - IUser user = userRepository.findOne(getUserQuery); - patientEncounter.setPharmacist(user); - patientEncounter = patientEncounterRepository.update(patientEncounter); - response.setResponseObject(patientEncounter); + List medicationNames = new ArrayList<>(); + List medications = medicationRepository.findAll(Medication.class); + + JsonObject jsonObject = new JsonObject(); + if (medications != null) { + for (int medicationIndex = 0; medicationIndex < medications.size(); medicationIndex++) { + jsonObject.addProperty("medicine" + medicationIndex, medications.get(medicationIndex).getName()); + } + } + + response.setResponseObject(jsonObject.toString()); } catch (Exception ex) { response.addError("exception", ex.getMessage()); } @@ -116,7 +102,6 @@ public ServiceResponse createAndReplacePrescription(Prescripti //replace the old prescription oldPatientPrescription.setReplacementId(newPatientPrescription.getId()); - oldPatientPrescription.setDispensed(false); patientPrescriptionRepository.update(oldPatientPrescription); PrescriptionItem newPrescriptionItem = domainMapper.createPrescriptionItem(newPatientPrescription); @@ -132,65 +117,26 @@ public ServiceResponse createAndReplacePrescription(Prescripti * {@inheritDoc} */ @Override - public ServiceResponse> findProblemItems(int encounterId) { - ServiceResponse> response = new ServiceResponse<>(); - List problemItems = new ArrayList<>(); - Query query = QueryProvider.getPatientEncounterTabFieldQuery() - .fetch("tabField") - .where() - .eq("patient_encounter_id", encounterId) - .eq("tabField.name", "problem") - .order() - .asc("date_taken"); - - try { - List patientEncounterTreatmentFields = patientEncounterTabFieldRepository.find(query); - if (patientEncounterTreatmentFields == null) { - response.addError("", "bad query"); - } else { - for (IPatientEncounterTabField petf : patientEncounterTreatmentFields) { - problemItems.add(domainMapper.createProblemItem(petf)); - } - response.setResponseObject(problemItems); - } - } catch (Exception ex) { - response.addError("", "error"); + public ServiceResponse> createPatientPrescriptions(List prescriptionItems, int userId, int encounterId, boolean isDispensed, boolean isCounseled) { + ServiceResponse> response = new ServiceResponse<>(); + if (prescriptionItems == null || userId < 1 || encounterId < 1) { + response.addError("", "invalid parameters"); + return response; } - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public ServiceResponse> findAllMedications() { - - ServiceResponse> response = new ServiceResponse<>(); + List patientPrescriptions = new ArrayList<>(); + for (PrescriptionItem pi : prescriptionItems) { + IMedication medication = domainMapper.createMedication(pi.getName()); + patientPrescriptions.add(domainMapper.createPatientPrescription(0, medication, userId, encounterId, null, isDispensed, isCounseled)); + } try { - List medicationNames = new ArrayList<>(); - - //List medications = medicationRepository.findAll(Medication.class); - - //use raw sql to temporarily filter out the duplicate medication names - //after implementing the inventory tracking feature, this shouldn't be needed - //as duplicates will never exist. - //Also - ebeans "setDistinct" method has known bugs in it hence rawsql crap - String rawSqlString = "SELECT id, name FROM medications GROUP BY name"; - RawSql rawSql = RawSqlBuilder.parse(rawSqlString).create(); - - Query medicationQuery = Ebean.find(Medication.class); - medicationQuery.setRawSql(rawSql); - - List medications = medicationQuery.findList(); - - - - for (IMedication m : medications) { - medicationNames.add(m.getName()); + List newPatientPrescriptions = patientPrescriptionRepository.createAll(patientPrescriptions); + List newPrescriptionItems = new ArrayList<>(); + for (IPatientPrescription pp : newPatientPrescriptions) { + newPrescriptionItems.add(domainMapper.createPatientPrescriptionItem(pp)); } - response.setResponseObject(medicationNames); + response.setResponseObject(newPrescriptionItems); } catch (Exception ex) { response.addError("exception", ex.getMessage()); } @@ -232,7 +178,7 @@ public ServiceResponse> markPrescriptionsAsFilled(List> markPrescriptionsAsCounseled(List prescriptionIds){ + public ServiceResponse> markPrescriptionsAsCounseled(List prescriptionIds) { ServiceResponse> response = new ServiceResponse<>(); List updatedPrescriptions = new ArrayList<>(); @@ -257,5 +203,40 @@ public ServiceResponse> markPrescriptionsAsCounseled(List return response; } -} + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> findAllMedications() { + ServiceResponse> response = new ServiceResponse<>(); + + try { + List medicationNames = new ArrayList<>(); + + //List medications = medicationRepository.findAll(Medication.class); + + //use raw sql to temporarily filter out the duplicate medication names + //after implementing the inventory tracking feature, this shouldn't be needed + //as duplicates will never exist. + //Also - ebeans "setDistinct" method has known bugs in it hence rawsql crap + String rawSqlString = "SELECT id, name FROM medications GROUP BY name"; + RawSql rawSql = RawSqlBuilder.parse(rawSqlString).create(); + + Query medicationQuery = Ebean.find(Medication.class); + medicationQuery.setRawSql(rawSql); + + List medications = medicationQuery.findList(); + + + for (IMedication m : medications) { + medicationNames.add(m.getName()); + } + response.setResponseObject(medicationNames); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } +} diff --git a/app/femr/business/services/system/MissionTripService.java b/app/femr/business/services/system/MissionTripService.java new file mode 100644 index 000000000..8a6fc103d --- /dev/null +++ b/app/femr/business/services/system/MissionTripService.java @@ -0,0 +1,263 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.business.services.system; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.SqlUpdate; +import com.google.inject.Inject; +import femr.business.helpers.DomainMapper; +import femr.business.helpers.QueryProvider; +import femr.business.services.core.IMissionTripService; +import femr.common.dtos.ServiceResponse; +import femr.common.models.TripItem; +import femr.data.daos.IRepository; +import femr.data.models.core.IMissionCity; +import femr.data.models.core.IMissionCountry; +import femr.data.models.core.IMissionTeam; +import femr.data.models.core.IMissionTrip; +import femr.data.models.mysql.MissionCity; +import femr.data.models.mysql.MissionCountry; +import femr.data.models.mysql.MissionTeam; +import femr.data.models.mysql.MissionTrip; + +import java.util.List; + +public class MissionTripService implements IMissionTripService { + + private final IRepository missionCityRepository; + private final IRepository missionCountryRepository; + private final IRepository missionTeamRepository; + private final IRepository missionTripRepository; + private final DomainMapper domainMapper; + + @Inject + public MissionTripService(IRepository missionCityRepository, + IRepository missionCountryRepository, + IRepository missionTeamRepository, + IRepository missionTripRepository, + DomainMapper domainMapper) { + + this.missionCityRepository = missionCityRepository; + this.missionCountryRepository = missionCountryRepository; + this.missionTripRepository = missionTripRepository; + this.missionTeamRepository = missionTeamRepository; + this.domainMapper = domainMapper; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse findCurrentMissionTrip() { + + ServiceResponse response = new ServiceResponse<>(); + IMissionTrip missionTrip = getCurrentMissionTrip(); + + if (missionTrip == null) { + + response.setResponseObject(null); + // response.addError("", "there is not a current trip."); + } else { + + response.setResponseObject( + DomainMapper.createTripItem( + missionTrip.getMissionTeam().getName(), + missionTrip.getMissionTeam().getLocation(), + missionTrip.getMissionCity().getName(), + missionTrip.getMissionCity().getMissionCountry().getName(), + missionTrip.getMissionTeam().getDescription(), + missionTrip.getStartDate(), + missionTrip.getEndDate() + ) + ); + } + + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> findAvailableTeams() { + ServiceResponse> response = new ServiceResponse<>(); + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> findAvailableCities() { + + ServiceResponse> response = new ServiceResponse<>(); + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> findAvailableCountries() { + + ServiceResponse> response = new ServiceResponse<>(); + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse updateTrip(TripItem tripItem) { + //ALL COMMENTS IN THIS METHOD ARE IN CAPS AND ANGRY + ServiceResponse response = new ServiceResponse<>(); + boolean isNewTrip = false; + + //CHECK TRIP COUNTRY + IMissionCountry missionCountry = null; + ExpressionList missionCountryExpressionList = QueryProvider.getMissionCountryQuery() + .where() + .eq("name", tripItem.getCountry()); + try { + missionCountry = missionCountryRepository.findOne(missionCountryExpressionList); + if (missionCountry == null) { + missionCountry = domainMapper.createMissionCountry(tripItem.getCountry()); + missionCountry = missionCountryRepository.create(missionCountry); + isNewTrip = true; + } + } catch (Exception ex) { + response.addError("", "error searching for the country, more than one might exist"); + return response; + } + + //CHECK TRIP CITY + IMissionCity missionCity = null; + ExpressionList missionCityExpressionList = QueryProvider.getMissionCityQuery() + .where() + .eq("name", tripItem.getCity()); + try { + missionCity = missionCityRepository.findOne(missionCityExpressionList); + if (missionCity == null || isNewTrip) { + missionCity = domainMapper.createMissionCity(tripItem.getCity(), missionCountry); + missionCity = missionCityRepository.create(missionCity); + isNewTrip = true; + } + } catch (Exception ex) { + response.addError("", "error searching for the city, more than one might exist"); + return response; + } + + //CHECK TEAM NAME + IMissionTeam missionTeam = null; + ExpressionList missionTeamExpressionList = QueryProvider.getMissionTeamQuery() + .where() + .eq("name", tripItem.getTeam()); + try { + missionTeam = missionTeamRepository.findOne(missionTeamExpressionList); + if (missionTeam == null) { + missionTeam = domainMapper.createMissionTeam(tripItem.getTeam(), tripItem.getTeamLocation(), tripItem.getDescription()); + missionTeam = missionTeamRepository.create(missionTeam); + isNewTrip = true; + } else { + //the team was found, just update the teams description and location + missionTeam.setDescription(tripItem.getDescription()); + missionTeam.setLocation(tripItem.getTeamLocation()); + missionTeam = missionTeamRepository.update(missionTeam); + } + } catch (Exception ex) { + response.addError("", "error searching for the team, more than one might exist"); + return response; + } + + //CHECK TRIP START AND END DATE + ExpressionList missionTripExpressionList = QueryProvider.getMissionTripQuery() + .where() + .eq("mission_team_id", missionTeam.getId()) + .eq("mission_city_id", missionCity.getId()) + .eq("start_date", tripItem.getTripStartDate()) + .eq("end_date", tripItem.getTripEndDate()); + try { + IMissionTrip missionTrip = missionTripRepository.findOne(missionTripExpressionList); + if (missionTrip == null) { + isNewTrip = true; + } + } catch (Exception ex) { + response.addError("", "error checking start and end dates"); + return response; + } + + //CREATE THE NEW TRIP IF NECESSARY, OTHERWISE DON'T + if (isNewTrip) { + IMissionTrip missionTrip = domainMapper.createMissionTrip(tripItem.getTripStartDate(), tripItem.getTripEndDate(), true, missionCity, missionTeam); + if (missionTrip == null) { + response.addError("", "serious problems are happening now"); + return response; + } else { + String setAllTripsToNotCurrent = "UPDATE `mission_trips` SET `isCurrent` = false"; + SqlUpdate update = Ebean.createSqlUpdate(setAllTripsToNotCurrent); + try { + int modifiedCount = Ebean.execute(update); + if (modifiedCount > 0) { + //a current trip was removed + } + } catch (Exception ex) { + response.addError("", "error updating current trips"); + return response; + } + missionTrip = missionTripRepository.create(missionTrip); + } + } else { + //do nothing + } + + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse getTripInformation(){ + + ServiceResponse response = new ServiceResponse<>(); + + return response; + } + + /** + * Get the current mission trip. + * + * @return null if none or more than one exists + */ + private IMissionTrip getCurrentMissionTrip() { + ExpressionList missionTripQuery = QueryProvider.getMissionTripQuery() + .where() + .eq("isCurrent", true); + IMissionTrip missionTrip = null; + + try { + missionTrip = missionTripRepository.findOne(missionTripQuery); + } catch (Exception ex) { + + } + + return missionTrip; + } +} diff --git a/app/femr/business/services/system/PatientService.java b/app/femr/business/services/system/PatientService.java new file mode 100644 index 000000000..bcb8ed4b5 --- /dev/null +++ b/app/femr/business/services/system/PatientService.java @@ -0,0 +1,131 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.business.services.system; + +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.Query; +import com.google.inject.Inject; +import femr.business.helpers.DomainMapper; +import femr.business.helpers.QueryProvider; +import femr.business.services.core.IPatientService; +import femr.common.dtos.ServiceResponse; +import femr.common.models.PatientItem; +import femr.data.daos.IRepository; +import femr.data.models.core.*; +import femr.data.models.mysql.Patient; +import femr.data.models.mysql.PatientAgeClassification; +import femr.util.stringhelpers.StringUtils; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class PatientService implements IPatientService { + + private final IRepository patientRepository; + private final IRepository patientAgeClassificationRepository; + private final DomainMapper domainMapper; + + @Inject + public PatientService(IRepository patientRepository, + IRepository patientAgeClassificationRepository, + DomainMapper domainMapper){ + + this.patientRepository = patientRepository; + this.patientAgeClassificationRepository = patientAgeClassificationRepository; + this.domainMapper = domainMapper; + } + + /** + * {@inheritDoc} + */ + public ServiceResponse> findPossibleAgeClassifications() { + ServiceResponse> response = new ServiceResponse<>(); + Map patientAgeClassificationStrings = new LinkedHashMap<>(); + try { + Query patientAgeClassificationExpressionList = QueryProvider.getPatientAgeClassificationQuery() + .where() + .eq("isDeleted", false) + .order() + .asc("sortOrder"); + List patientAgeClassifications = patientAgeClassificationRepository.find(patientAgeClassificationExpressionList); + for (IPatientAgeClassification pac : patientAgeClassifications) { + patientAgeClassificationStrings.put(pac.getName(), pac.getDescription()); + } + response.setResponseObject(patientAgeClassificationStrings); + } catch (Exception ex) { + response.addError("", ex.getMessage()); + } + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse findPatientAndUpdateSex(int id, String sex) { + ServiceResponse response = new ServiceResponse<>(); + if (id < 1) { + response.addError("", "patient id can not be less than 1"); + return response; + } + + ExpressionList query = QueryProvider.getPatientQuery() + .where() + .eq("id", id); + + try { + IPatient savedPatient = patientRepository.findOne(query); + //if a patient doesn't have a sex and the + //user is trying to identify the patients sex + if (StringUtils.isNullOrWhiteSpace(savedPatient.getSex()) && StringUtils.isNotNullOrWhiteSpace(sex)) { + savedPatient.setSex(sex); + savedPatient = patientRepository.update(savedPatient); + } + PatientItem patientItem = DomainMapper.createPatientItem(savedPatient, null, null, null, null); + response.setResponseObject(patientItem); + + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse createPatient(PatientItem patient) { + ServiceResponse response = new ServiceResponse<>(); + if (patient == null) { + response.addError("", "no patient received"); + return response; + } + + try { + IPatient newPatient = domainMapper.createPatient(patient); + newPatient = patientRepository.create(newPatient); + response.setResponseObject(DomainMapper.createPatientItem(newPatient, null, null, null, null)); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } +} diff --git a/app/femr/business/services/PhotoService.java b/app/femr/business/services/system/PhotoService.java similarity index 97% rename from app/femr/business/services/PhotoService.java rename to app/femr/business/services/system/PhotoService.java index 6fcf8d88d..273c1da6f 100644 --- a/app/femr/business/services/PhotoService.java +++ b/app/femr/business/services/system/PhotoService.java @@ -16,10 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.ExpressionList; -import com.typesafe.config.ConfigFactory; import java.awt.image.BufferedImage; import java.io.*; @@ -31,12 +30,18 @@ import femr.business.helpers.DomainMapper; import femr.business.helpers.LogicDoer; import femr.business.helpers.QueryProvider; +import femr.business.services.core.IPhotoService; import femr.common.models.PatientEncounterItem; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import com.google.inject.Inject; import femr.common.models.PhotoItem; import femr.data.daos.IRepository; -import femr.data.models.*; +import femr.data.models.core.IPatient; +import femr.data.models.core.IPatientEncounterPhoto; +import femr.data.models.core.IPhoto; +import femr.data.models.mysql.Patient; +import femr.data.models.mysql.PatientEncounterPhoto; +import femr.data.models.mysql.Photo; import femr.ui.models.medical.EditViewModelPost; import femr.util.stringhelpers.StringUtils; import org.apache.commons.codec.binary.Base64; diff --git a/app/femr/business/services/ResearchService.java b/app/femr/business/services/system/ResearchService.java similarity index 96% rename from app/femr/business/services/ResearchService.java rename to app/femr/business/services/system/ResearchService.java index 448ccfbd7..756db39f4 100644 --- a/app/femr/business/services/ResearchService.java +++ b/app/femr/business/services/system/ResearchService.java @@ -16,70 +16,54 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; -import com.avaje.ebean.Ebean; -import com.avaje.ebean.ExpressionList; import com.avaje.ebean.Query; -import com.avaje.ebean.SqlQuery; import com.google.inject.Inject; import com.google.inject.Provider; +import femr.business.services.core.IResearchService; import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; import femr.data.daos.IRepository; -import femr.data.models.*; +import femr.data.models.core.*; +import femr.data.models.mysql.Medication; +import femr.data.models.mysql.PatientEncounter; +import femr.data.models.mysql.PatientEncounterVital; +import femr.data.models.mysql.PatientPrescription; import femr.util.calculations.dateUtils; import femr.util.stringhelpers.StringUtils; import org.apache.commons.lang3.text.WordUtils; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; public class ResearchService implements IResearchService { - - - //repositories - private final IRepository chiefComplaintRepository; - private final IRepository patientRepository; private final IRepository patientEncounterRepository; private final IRepository patientEncounterVitalRepository; - private final IRepository userRepository; - private final IRepository vitalRepository; + protected final IRepository vitalRepository; private final IRepository prescriptionRepository; private final IRepository medicationRepository; - private final Provider patientEncounterVitalProvider; private final DomainMapper domainMapper; /** * Initializes the research service and injects the dependence */ @Inject - public ResearchService(IRepository chiefComplaintRepository, - IRepository patientRepository, - IRepository patientEncounterRepository, + public ResearchService(IRepository patientEncounterRepository, IRepository patientEncounterVitaRepository, - IRepository userRepository, IRepository vitalRepository, IRepository prescriptionRepository, IRepository medicationRepository, - - Provider patientEncounterVitalProvider, - DomainMapper domainMapper) { - this.chiefComplaintRepository = chiefComplaintRepository; - this.patientRepository = patientRepository; this.patientEncounterRepository = patientEncounterRepository; this.patientEncounterVitalRepository = patientEncounterVitaRepository; - this.userRepository = userRepository; this.vitalRepository = vitalRepository; this.prescriptionRepository = prescriptionRepository; this.medicationRepository = medicationRepository; - this.patientEncounterVitalProvider = patientEncounterVitalProvider; this.domainMapper = domainMapper; } @@ -90,6 +74,7 @@ public ServiceResponse getGraphData(ResearchFilterItem fi ServiceResponse response = new ServiceResponse<>(); String primaryDatasetName = filters.getPrimaryDataset(); + //TODO: gender throws error here due to patients with no sex ResearchResult primaryItems = getDatasetItems(primaryDatasetName, filters); ResearchResult secondaryItems = new ResearchResult(); @@ -687,13 +672,19 @@ public ResearchResult getPatientAttribute(String attributeName, ResearchFilterIt resultMap.put(0.0f, "Male"); resultMap.put(1.0f, "Female"); + resultMap.put(2.0f, "N/A"); for (IPatientEncounter encounter : encounters) { IPatient patient = encounter.getPatient(); float gender = -1; // Do case in-sensitve comparison to be safe - if (patient.getSex().matches("(?i:Male)")) { + //1 = female + //0 = male + //2 = no sex + if (patient.getSex() == null){ + gender = 2; + }else if (patient.getSex().matches("(?i:Male)")) { gender = 0; } else if (patient.getSex().matches("(?i:Female)")) { gender = 1; diff --git a/app/femr/business/services/RoleService.java b/app/femr/business/services/system/RoleService.java similarity index 92% rename from app/femr/business/services/RoleService.java rename to app/femr/business/services/system/RoleService.java index ef952c665..2f358654f 100644 --- a/app/femr/business/services/RoleService.java +++ b/app/femr/business/services/system/RoleService.java @@ -16,15 +16,16 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.ExpressionList; import com.google.inject.Inject; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; -import femr.data.models.IRole; +import femr.business.services.core.IRoleService; +import femr.common.dtos.ServiceResponse; +import femr.data.models.core.IRole; import femr.data.daos.IRepository; -import femr.data.models.Role; +import femr.data.models.mysql.Role; import java.util.ArrayList; import java.util.List; diff --git a/app/femr/business/services/SearchService.java b/app/femr/business/services/system/SearchService.java similarity index 99% rename from app/femr/business/services/SearchService.java rename to app/femr/business/services/system/SearchService.java index 6ae0f03c9..aa86a883f 100644 --- a/app/femr/business/services/SearchService.java +++ b/app/femr/business/services/system/SearchService.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.Expr; import com.avaje.ebean.ExpressionList; @@ -25,10 +25,12 @@ import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryHelper; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.ISearchService; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; import femr.data.daos.IRepository; -import femr.data.models.*; +import femr.data.models.core.*; +import femr.data.models.mysql.*; import femr.util.DataStructure.Mapping.TabFieldMultiMap; import femr.util.DataStructure.Mapping.VitalMultiMap; import femr.util.stringhelpers.StringUtils; diff --git a/app/femr/business/services/SessionService.java b/app/femr/business/services/system/SessionService.java similarity index 92% rename from app/femr/business/services/SessionService.java rename to app/femr/business/services/system/SessionService.java index f9c946d17..bd40a74d4 100644 --- a/app/femr/business/services/SessionService.java +++ b/app/femr/business/services/system/SessionService.java @@ -16,13 +16,15 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.ISessionService; +import femr.business.services.core.IUserService; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; import femr.business.wrappers.sessions.ISessionHelper; -import femr.data.models.IUser; +import femr.data.models.core.IUser; import femr.data.daos.IRepository; import femr.util.encryptions.IPasswordEncryptor; diff --git a/app/femr/business/services/UserService.java b/app/femr/business/services/system/UserService.java similarity index 96% rename from app/femr/business/services/UserService.java rename to app/femr/business/services/system/UserService.java index e6f2d7b3b..c07583428 100644 --- a/app/femr/business/services/UserService.java +++ b/app/femr/business/services/system/UserService.java @@ -16,19 +16,20 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.business.services; +package femr.business.services.system; import com.avaje.ebean.ExpressionList; import com.google.inject.Inject; import femr.business.helpers.DomainMapper; import femr.business.helpers.QueryProvider; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.IUserService; +import femr.common.dtos.ServiceResponse; import femr.common.models.UserItem; -import femr.data.models.IRole; -import femr.data.models.IUser; +import femr.data.models.core.IRole; +import femr.data.models.core.IUser; import femr.data.daos.IRepository; -import femr.data.models.Role; -import femr.data.models.User; +import femr.data.models.mysql.Role; +import femr.data.models.mysql.User; import femr.util.encryptions.IPasswordEncryptor; import femr.util.stringhelpers.StringUtils; diff --git a/app/femr/business/services/system/VitalService.java b/app/femr/business/services/system/VitalService.java new file mode 100644 index 000000000..46fda14c1 --- /dev/null +++ b/app/femr/business/services/system/VitalService.java @@ -0,0 +1,157 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.business.services.system; + +import com.avaje.ebean.ExpressionList; +import com.google.inject.Inject; +import femr.business.helpers.DomainMapper; +import femr.business.helpers.QueryProvider; +import femr.business.services.core.IVitalService; +import femr.common.dtos.ServiceResponse; +import femr.common.models.VitalItem; +import femr.data.daos.IRepository; +import femr.data.models.core.IPatientEncounterVital; +import femr.data.models.core.IVital; +import femr.data.models.mysql.Vital; +import femr.util.calculations.dateUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class VitalService implements IVitalService { + + private final IRepository patientEncounterVitalRepository; + private final IRepository vitalRepository; + private final DomainMapper domainMapper; + + @Inject + public VitalService(IRepository patientEncounterVitalRepository, + IRepository vitalRepository, + DomainMapper domainMapper){ + + this.patientEncounterVitalRepository = patientEncounterVitalRepository; + this.vitalRepository = vitalRepository; + this.domainMapper = domainMapper; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> createPatientEncounterVitals(Map patientEncounterVitalMap, int userId, int encounterId) { + ServiceResponse> response = new ServiceResponse<>(); + if (patientEncounterVitalMap == null || userId < 1 || encounterId < 1) { + response.addError("", "invalid parameters"); + return response; + } + List patientEncounterVitals = new ArrayList<>(); + IPatientEncounterVital patientEncounterVital; + IVital vital; + + ExpressionList query; + String currentTime = dateUtils.getCurrentDateTimeString(); + + for (String key : patientEncounterVitalMap.keySet()) { + if (patientEncounterVitalMap.get(key) != null) { + + query = QueryProvider.getVitalQuery().where().eq("name", key); + vital = vitalRepository.findOne(query); + patientEncounterVitals.add(domainMapper.createPatientEncounterVital(encounterId, userId, currentTime, vital, patientEncounterVitalMap.get(key))); + } + } + + try { + List vitalItems = new ArrayList<>(); + List newPatientEncounterVitals = patientEncounterVitalRepository.createAll(patientEncounterVitals); + for (IPatientEncounterVital pev : newPatientEncounterVitals) { + vitalItems.add(DomainMapper.createVitalItem(pev)); + } + response.setResponseObject(vitalItems); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> findAllVitalItems() { + ServiceResponse> response = new ServiceResponse<>(); + + try { + List vitals = vitalRepository.findAll(Vital.class); + List vitalItems = new ArrayList<>(); + for (IVital v : vitals) { + vitalItems.add(DomainMapper.createVitalItem(v)); + } + response.setResponseObject(vitalItems); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceResponse> createPatientEncounterVitalItems(Map patientEncounterVitalMap, int userId, int encounterId) { + ServiceResponse> response = new ServiceResponse<>(); + if (patientEncounterVitalMap == null || userId < 1 || encounterId < 1) { + response.addError("", "bad parameters"); + return response; + } + + List patientEncounterVitals = new ArrayList<>(); + IPatientEncounterVital patientEncounterVital; + IVital vital; + + ExpressionList query; + String currentTime = dateUtils.getCurrentDateTimeString(); + + try { + + + for (String key : patientEncounterVitalMap.keySet()) { + if (patientEncounterVitalMap.get(key) != null) { + + query = QueryProvider.getVitalQuery().where().eq("name", key); + vital = vitalRepository.findOne(query); + patientEncounterVitals.add(domainMapper.createPatientEncounterVital(encounterId, userId, currentTime, vital, patientEncounterVitalMap.get(key))); + } + } + + List newPatientEncounterVitals = patientEncounterVitalRepository.createAll(patientEncounterVitals); + List vitalItems = new ArrayList<>(); + for (IPatientEncounterVital pev : patientEncounterVitals) { + vitalItems.add(DomainMapper.createVitalItem(pev)); + } + + response.setResponseObject(vitalItems); + } catch (Exception ex) { + response.addError("exception", ex.getMessage()); + } + + return response; + } +} diff --git a/app/femr/common/dto/CurrentUser.java b/app/femr/common/dtos/CurrentUser.java similarity index 96% rename from app/femr/common/dto/CurrentUser.java rename to app/femr/common/dtos/CurrentUser.java index 265d3b697..cea42bb3e 100644 --- a/app/femr/common/dto/CurrentUser.java +++ b/app/femr/common/dtos/CurrentUser.java @@ -16,9 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.common.dto; +package femr.common.dtos; -import femr.data.models.IRole; +import femr.data.models.core.IRole; import java.util.List; diff --git a/app/femr/common/dto/ServiceResponse.java b/app/femr/common/dtos/ServiceResponse.java similarity index 98% rename from app/femr/common/dto/ServiceResponse.java rename to app/femr/common/dtos/ServiceResponse.java index 3fa5ce6f3..925b8d472 100644 --- a/app/femr/common/dto/ServiceResponse.java +++ b/app/femr/common/dtos/ServiceResponse.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.common.dto; +package femr.common.dtos; import java.util.HashMap; import java.util.Map; diff --git a/app/femr/common/models/TripItem.java b/app/femr/common/models/TripItem.java new file mode 100644 index 000000000..fc7b6306f --- /dev/null +++ b/app/femr/common/models/TripItem.java @@ -0,0 +1,69 @@ +package femr.common.models; + +import java.util.Date; + +public class TripItem { + private String team; + private String teamLocation; + private String city; + private String country; + private String description; + private Date tripStartDate; + private Date tripEndDate; + + public String getTeam() { + return team; + } + + public void setTeam(String team) { + this.team = team; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getTripStartDate() { + return tripStartDate; + } + + public void setTripStartDate(Date tripStartDate) { + this.tripStartDate = tripStartDate; + } + + public Date getTripEndDate() { + return tripEndDate; + } + + public void setTripEndDate(Date tripEndDate) { + this.tripEndDate = tripEndDate; + } + + public String getTeamLocation() { + return teamLocation; + } + + public void setTeamLocation(String teamLocation) { + this.teamLocation = teamLocation; + } +} diff --git a/app/femr/data/models/IChiefComplaint.java b/app/femr/data/models/core/IChiefComplaint.java similarity index 97% rename from app/femr/data/models/IChiefComplaint.java rename to app/femr/data/models/core/IChiefComplaint.java index 8015ba092..2e9683fa2 100644 --- a/app/femr/data/models/IChiefComplaint.java +++ b/app/femr/data/models/core/IChiefComplaint.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * Tracks chief complaints. Important when trying to diff --git a/app/femr/data/models/IMedication.java b/app/femr/data/models/core/IMedication.java similarity index 98% rename from app/femr/data/models/IMedication.java rename to app/femr/data/models/core/IMedication.java index 98e0d2c2b..e0dab6df8 100644 --- a/app/femr/data/models/IMedication.java +++ b/app/femr/data/models/core/IMedication.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import java.util.List; diff --git a/app/femr/data/models/IMedicationActiveDrug.java b/app/femr/data/models/core/IMedicationActiveDrug.java similarity index 97% rename from app/femr/data/models/IMedicationActiveDrug.java rename to app/femr/data/models/core/IMedicationActiveDrug.java index 05147f6c0..29751f96d 100644 --- a/app/femr/data/models/IMedicationActiveDrug.java +++ b/app/femr/data/models/core/IMedicationActiveDrug.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * MedicationActiveDrug represents the active ingredient inside a medication, diff --git a/app/femr/data/models/IMedicationActiveDrugName.java b/app/femr/data/models/core/IMedicationActiveDrugName.java similarity index 97% rename from app/femr/data/models/IMedicationActiveDrugName.java rename to app/femr/data/models/core/IMedicationActiveDrugName.java index b7d000c16..bd425a0b4 100644 --- a/app/femr/data/models/IMedicationActiveDrugName.java +++ b/app/femr/data/models/core/IMedicationActiveDrugName.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * MedicationActiveDrugName is the name of the active drug inside diff --git a/app/femr/data/models/IMedicationAdministration.java b/app/femr/data/models/core/IMedicationAdministration.java similarity index 97% rename from app/femr/data/models/IMedicationAdministration.java rename to app/femr/data/models/core/IMedicationAdministration.java index d8159a2a6..6b5518888 100644 --- a/app/femr/data/models/IMedicationAdministration.java +++ b/app/femr/data/models/core/IMedicationAdministration.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * MedicationAdministration represents the manner in which a medication diff --git a/app/femr/data/models/IMedicationForm.java b/app/femr/data/models/core/IMedicationForm.java similarity index 97% rename from app/femr/data/models/IMedicationForm.java rename to app/femr/data/models/core/IMedicationForm.java index 306daa97c..1409e4532 100644 --- a/app/femr/data/models/IMedicationForm.java +++ b/app/femr/data/models/core/IMedicationForm.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * MedicationForm represents the form of the medication diff --git a/app/femr/data/models/IMedicationMeasurementUnit.java b/app/femr/data/models/core/IMedicationMeasurementUnit.java similarity index 97% rename from app/femr/data/models/IMedicationMeasurementUnit.java rename to app/femr/data/models/core/IMedicationMeasurementUnit.java index aa164100f..1fd80a706 100644 --- a/app/femr/data/models/IMedicationMeasurementUnit.java +++ b/app/femr/data/models/core/IMedicationMeasurementUnit.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * MedicationMeasurementUnit represents the unit of diff --git a/app/femr/data/models/core/IMissionCity.java b/app/femr/data/models/core/IMissionCity.java new file mode 100644 index 000000000..6aedb8664 --- /dev/null +++ b/app/femr/data/models/core/IMissionCity.java @@ -0,0 +1,31 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.core; + +public interface IMissionCity { + int getId(); + + String getName(); + + void setName(String name); + + IMissionCountry getMissionCountry(); + + void setMissionCountry(IMissionCountry missionCountry); +} diff --git a/app/femr/data/models/core/IMissionCountry.java b/app/femr/data/models/core/IMissionCountry.java new file mode 100644 index 000000000..0b8aa0968 --- /dev/null +++ b/app/femr/data/models/core/IMissionCountry.java @@ -0,0 +1,27 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.core; + +public interface IMissionCountry { + int getId(); + + String getName(); + + void setName(String name); +} diff --git a/app/femr/data/models/core/IMissionTeam.java b/app/femr/data/models/core/IMissionTeam.java new file mode 100644 index 000000000..f80f1d160 --- /dev/null +++ b/app/femr/data/models/core/IMissionTeam.java @@ -0,0 +1,35 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.core; + +public interface IMissionTeam { + int getId(); + + String getName(); + + void setName(String name); + + String getLocation(); + + void setLocation(String location); + + String getDescription(); + + void setDescription(String description); +} diff --git a/app/femr/data/models/core/IMissionTrip.java b/app/femr/data/models/core/IMissionTrip.java new file mode 100644 index 000000000..c3ed874c9 --- /dev/null +++ b/app/femr/data/models/core/IMissionTrip.java @@ -0,0 +1,45 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.core; + +import java.util.Date; + +public interface IMissionTrip { + int getId(); + + IMissionTeam getMissionTeam(); + + void setMissionTeam(IMissionTeam missionTeam); + + IMissionCity getMissionCity(); + + void setMissionCity(IMissionCity missionCity); + + boolean isCurrent(); + + void setCurrent(boolean isCurrent); + + Date getStartDate(); + + void setStartDate(Date startDate); + + Date getEndDate(); + + void setEndDate(Date endDate); +} diff --git a/app/femr/data/models/IPatient.java b/app/femr/data/models/core/IPatient.java similarity index 97% rename from app/femr/data/models/IPatient.java rename to app/femr/data/models/core/IPatient.java index c1c7dd934..57dce9f24 100644 --- a/app/femr/data/models/IPatient.java +++ b/app/femr/data/models/core/IPatient.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import java.util.Date; diff --git a/app/femr/data/models/IPatientAgeClassification.java b/app/femr/data/models/core/IPatientAgeClassification.java similarity index 97% rename from app/femr/data/models/IPatientAgeClassification.java rename to app/femr/data/models/core/IPatientAgeClassification.java index 769d0e8d3..335b4ec64 100644 --- a/app/femr/data/models/IPatientAgeClassification.java +++ b/app/femr/data/models/core/IPatientAgeClassification.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * Represents one of the ways of guessing a patients "length of living". diff --git a/app/femr/data/models/IPatientEncounter.java b/app/femr/data/models/core/IPatientEncounter.java similarity index 93% rename from app/femr/data/models/IPatientEncounter.java rename to app/femr/data/models/core/IPatientEncounter.java index 93a44fc82..fc0a5a36e 100644 --- a/app/femr/data/models/IPatientEncounter.java +++ b/app/femr/data/models/core/IPatientEncounter.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import org.joda.time.DateTime; import java.util.List; @@ -63,4 +63,8 @@ public interface IPatientEncounter { IPatientAgeClassification getPatientAgeClassification(); void setPatientAgeClassification(IPatientAgeClassification patientAgeClassification); + + IMissionTrip getMissionTrip(); + + void setMissionTrip(IMissionTrip missionTrip); } diff --git a/app/femr/data/models/IPatientEncounterPhoto.java b/app/femr/data/models/core/IPatientEncounterPhoto.java similarity index 96% rename from app/femr/data/models/IPatientEncounterPhoto.java rename to app/femr/data/models/core/IPatientEncounterPhoto.java index 5a0b5003a..e229559d7 100644 --- a/app/femr/data/models/IPatientEncounterPhoto.java +++ b/app/femr/data/models/core/IPatientEncounterPhoto.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; public interface IPatientEncounterPhoto { int getPatientEncounterId(); diff --git a/app/femr/data/models/IPatientEncounterTabField.java b/app/femr/data/models/core/IPatientEncounterTabField.java similarity index 94% rename from app/femr/data/models/IPatientEncounterTabField.java rename to app/femr/data/models/core/IPatientEncounterTabField.java index 2aa4053b5..35025c9c7 100644 --- a/app/femr/data/models/IPatientEncounterTabField.java +++ b/app/femr/data/models/core/IPatientEncounterTabField.java @@ -16,8 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; +import femr.data.models.mysql.ChiefComplaint; import org.joda.time.DateTime; /** diff --git a/app/femr/data/models/IPatientEncounterVital.java b/app/femr/data/models/core/IPatientEncounterVital.java similarity index 97% rename from app/femr/data/models/IPatientEncounterVital.java rename to app/femr/data/models/core/IPatientEncounterVital.java index 4954b9e85..8c2a30d56 100644 --- a/app/femr/data/models/IPatientEncounterVital.java +++ b/app/femr/data/models/core/IPatientEncounterVital.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; public interface IPatientEncounterVital { int getId(); diff --git a/app/femr/data/models/IPatientPrescription.java b/app/femr/data/models/core/IPatientPrescription.java similarity index 98% rename from app/femr/data/models/IPatientPrescription.java rename to app/femr/data/models/core/IPatientPrescription.java index 6e61149d8..8fb011291 100644 --- a/app/femr/data/models/IPatientPrescription.java +++ b/app/femr/data/models/core/IPatientPrescription.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import org.joda.time.DateTime; diff --git a/app/femr/data/models/IPhoto.java b/app/femr/data/models/core/IPhoto.java similarity index 97% rename from app/femr/data/models/IPhoto.java rename to app/femr/data/models/core/IPhoto.java index 3206ccb99..3cbc0695a 100644 --- a/app/femr/data/models/IPhoto.java +++ b/app/femr/data/models/core/IPhoto.java @@ -17,7 +17,7 @@ you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import java.util.Date; diff --git a/app/femr/data/models/IRole.java b/app/femr/data/models/core/IRole.java similarity index 96% rename from app/femr/data/models/IRole.java rename to app/femr/data/models/core/IRole.java index e5559c4cb..c5ee4c16d 100644 --- a/app/femr/data/models/IRole.java +++ b/app/femr/data/models/core/IRole.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * User roles - nurse, physician, pharmacist, researcher, etc diff --git a/app/femr/data/models/ISystemSetting.java b/app/femr/data/models/core/ISystemSetting.java similarity index 97% rename from app/femr/data/models/ISystemSetting.java rename to app/femr/data/models/core/ISystemSetting.java index 62704d1be..b8bc032a8 100644 --- a/app/femr/data/models/ISystemSetting.java +++ b/app/femr/data/models/core/ISystemSetting.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * Created by kevin on 7/28/14. diff --git a/app/femr/data/models/ITab.java b/app/femr/data/models/core/ITab.java similarity index 97% rename from app/femr/data/models/ITab.java rename to app/femr/data/models/core/ITab.java index c0ee62312..14d641b21 100644 --- a/app/femr/data/models/ITab.java +++ b/app/femr/data/models/core/ITab.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import org.joda.time.DateTime; diff --git a/app/femr/data/models/ITabField.java b/app/femr/data/models/core/ITabField.java similarity index 97% rename from app/femr/data/models/ITabField.java rename to app/femr/data/models/core/ITabField.java index acc9b8671..76bb4f5cf 100644 --- a/app/femr/data/models/ITabField.java +++ b/app/femr/data/models/core/ITabField.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; public interface ITabField { int getId(); diff --git a/app/femr/data/models/ITabFieldSize.java b/app/femr/data/models/core/ITabFieldSize.java similarity index 96% rename from app/femr/data/models/ITabFieldSize.java rename to app/femr/data/models/core/ITabFieldSize.java index c5145907b..c026b4df2 100644 --- a/app/femr/data/models/ITabFieldSize.java +++ b/app/femr/data/models/core/ITabFieldSize.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * Used to identify a dynamic tab fields size diff --git a/app/femr/data/models/ITabFieldType.java b/app/femr/data/models/core/ITabFieldType.java similarity index 96% rename from app/femr/data/models/ITabFieldType.java rename to app/femr/data/models/core/ITabFieldType.java index 51fab913c..0cd0c959d 100644 --- a/app/femr/data/models/ITabFieldType.java +++ b/app/femr/data/models/core/ITabFieldType.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; /** * Used to identify a tab fields type - number, text, etc diff --git a/app/femr/data/models/IUser.java b/app/femr/data/models/core/IUser.java similarity index 98% rename from app/femr/data/models/IUser.java rename to app/femr/data/models/core/IUser.java index 89d534958..498b8ec54 100644 --- a/app/femr/data/models/IUser.java +++ b/app/femr/data/models/core/IUser.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; import org.joda.time.DateTime; diff --git a/app/femr/data/models/IVital.java b/app/femr/data/models/core/IVital.java similarity index 97% rename from app/femr/data/models/IVital.java rename to app/femr/data/models/core/IVital.java index b3a30a839..e71fd518a 100644 --- a/app/femr/data/models/IVital.java +++ b/app/femr/data/models/core/IVital.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.core; public interface IVital { void setId(int id); diff --git a/app/femr/data/models/ChiefComplaint.java b/app/femr/data/models/mysql/ChiefComplaint.java similarity index 89% rename from app/femr/data/models/ChiefComplaint.java rename to app/femr/data/models/mysql/ChiefComplaint.java index a72b360d8..8290213da 100644 --- a/app/femr/data/models/ChiefComplaint.java +++ b/app/femr/data/models/mysql/ChiefComplaint.java @@ -16,13 +16,16 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IChiefComplaint; +import femr.data.models.core.IPatientEncounter; import javax.persistence.*; @Entity @Table(name = "chief_complaints") -public class ChiefComplaint implements IChiefComplaint{ +public class ChiefComplaint implements IChiefComplaint { @Id @Column(name = "id", unique = true, nullable = false) private int id; diff --git a/app/femr/data/models/Medication.java b/app/femr/data/models/mysql/Medication.java similarity index 95% rename from app/femr/data/models/Medication.java rename to app/femr/data/models/mysql/Medication.java index 4cd6a38f5..743dc20c9 100644 --- a/app/femr/data/models/Medication.java +++ b/app/femr/data/models/mysql/Medication.java @@ -16,7 +16,11 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IMedication; +import femr.data.models.core.IMedicationActiveDrug; +import femr.data.models.core.IMedicationForm; import javax.persistence.*; import java.util.List; diff --git a/app/femr/data/models/MedicationActiveDrug.java b/app/femr/data/models/mysql/MedicationActiveDrug.java similarity index 93% rename from app/femr/data/models/MedicationActiveDrug.java rename to app/femr/data/models/mysql/MedicationActiveDrug.java index c742d8443..d7ed9a8c6 100644 --- a/app/femr/data/models/MedicationActiveDrug.java +++ b/app/femr/data/models/mysql/MedicationActiveDrug.java @@ -16,7 +16,11 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IMedicationActiveDrug; +import femr.data.models.core.IMedicationActiveDrugName; +import femr.data.models.core.IMedicationMeasurementUnit; import javax.persistence.*; diff --git a/app/femr/data/models/MedicationActiveDrugName.java b/app/femr/data/models/mysql/MedicationActiveDrugName.java similarity index 93% rename from app/femr/data/models/MedicationActiveDrugName.java rename to app/femr/data/models/mysql/MedicationActiveDrugName.java index 4895f9322..0c30191b1 100644 --- a/app/femr/data/models/MedicationActiveDrugName.java +++ b/app/femr/data/models/mysql/MedicationActiveDrugName.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IMedicationActiveDrugName; import javax.persistence.Column; import javax.persistence.Entity; @@ -25,7 +27,7 @@ @Entity @Table(name = "medication_active_drug_names") -public class MedicationActiveDrugName implements IMedicationActiveDrugName{ +public class MedicationActiveDrugName implements IMedicationActiveDrugName { @Id @Column(name = "id", unique = true, nullable = false) private int id; diff --git a/app/femr/data/models/MedicationAdministration.java b/app/femr/data/models/mysql/MedicationAdministration.java similarity index 93% rename from app/femr/data/models/MedicationAdministration.java rename to app/femr/data/models/mysql/MedicationAdministration.java index 10701d828..ec99ee4e2 100644 --- a/app/femr/data/models/MedicationAdministration.java +++ b/app/femr/data/models/mysql/MedicationAdministration.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IMedicationAdministration; import javax.persistence.Column; import javax.persistence.Entity; @@ -25,7 +27,7 @@ @Entity @Table(name = "medication_administrations") -public class MedicationAdministration implements IMedicationAdministration{ +public class MedicationAdministration implements IMedicationAdministration { @Id @Column(name = "id", unique = true, nullable = false) private int id; diff --git a/app/femr/data/models/MedicationForm.java b/app/femr/data/models/mysql/MedicationForm.java similarity index 93% rename from app/femr/data/models/MedicationForm.java rename to app/femr/data/models/mysql/MedicationForm.java index 855cee13d..df71f4da3 100644 --- a/app/femr/data/models/MedicationForm.java +++ b/app/femr/data/models/mysql/MedicationForm.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IMedicationForm; import javax.persistence.Column; import javax.persistence.Entity; @@ -25,7 +27,7 @@ @Entity @Table(name = "medication_forms") -public class MedicationForm implements IMedicationForm{ +public class MedicationForm implements IMedicationForm { @Id @Column(name = "id", unique = true, nullable = false) private int id; diff --git a/app/femr/data/models/MedicationMeasurementUnit.java b/app/femr/data/models/mysql/MedicationMeasurementUnit.java similarity index 94% rename from app/femr/data/models/MedicationMeasurementUnit.java rename to app/femr/data/models/mysql/MedicationMeasurementUnit.java index d578fbfe1..a3b131a95 100644 --- a/app/femr/data/models/MedicationMeasurementUnit.java +++ b/app/femr/data/models/mysql/MedicationMeasurementUnit.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IMedicationMeasurementUnit; import javax.persistence.Column; import javax.persistence.Entity; @@ -25,7 +27,7 @@ @Entity @Table(name = "medication_measurement_units") -public class MedicationMeasurementUnit implements IMedicationMeasurementUnit{ +public class MedicationMeasurementUnit implements IMedicationMeasurementUnit { @Id @Column(name = "id", unique = true, nullable = false) private int id; diff --git a/app/femr/data/models/mysql/MissionCity.java b/app/femr/data/models/mysql/MissionCity.java new file mode 100644 index 000000000..af1a5a6e5 --- /dev/null +++ b/app/femr/data/models/mysql/MissionCity.java @@ -0,0 +1,62 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.mysql; + +import femr.data.models.core.IMissionCity; +import femr.data.models.core.IMissionCountry; + +import javax.persistence.*; + +@Entity +@Table(name = "mission_cities") +public class MissionCity implements IMissionCity { + @Id + @Column(name = "id", unique = true, nullable = false) + private int id; + @Column(name = "name", unique = true, nullable = false) + private String name; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "mission_country_id", nullable = false) + private MissionCountry missionCountry; + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public IMissionCountry getMissionCountry() { + return missionCountry; + } + + @Override + public void setMissionCountry(IMissionCountry missionCountry) { + this.missionCountry = (MissionCountry) missionCountry; + } +} diff --git a/app/femr/data/models/mysql/MissionCountry.java b/app/femr/data/models/mysql/MissionCountry.java new file mode 100644 index 000000000..8c3e1083b --- /dev/null +++ b/app/femr/data/models/mysql/MissionCountry.java @@ -0,0 +1,51 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.mysql; + +import femr.data.models.core.IMissionCountry; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "mission_countries") +public class MissionCountry implements IMissionCountry { + @Id + @Column(name = "id", unique = true, nullable = false) + private int id; + @Column(name = "name", unique = true, nullable = false) + private String name; + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } +} diff --git a/app/femr/data/models/mysql/MissionTeam.java b/app/femr/data/models/mysql/MissionTeam.java new file mode 100644 index 000000000..ee7bbb775 --- /dev/null +++ b/app/femr/data/models/mysql/MissionTeam.java @@ -0,0 +1,75 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.mysql; + +import femr.data.models.core.IMissionTeam; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "mission_teams") +public class MissionTeam implements IMissionTeam { + @Id + @Column(name = "id", unique = true, nullable = false) + private int id; + @Column(name = "name", unique = true, nullable = false) + private String name; + @Column(name = "location", unique = true, nullable = false) + private String location; + @Column(name = "description", unique = true, nullable = false) + private String description; + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public void setLocation(String location) { + this.location = location; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(String description) { + this.description = description; + } +} diff --git a/app/femr/data/models/mysql/MissionTrip.java b/app/femr/data/models/mysql/MissionTrip.java new file mode 100644 index 000000000..ea025bab7 --- /dev/null +++ b/app/femr/data/models/mysql/MissionTrip.java @@ -0,0 +1,101 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.data.models.mysql; + +import femr.data.models.core.IMissionCity; +import femr.data.models.core.IMissionTeam; +import femr.data.models.core.IMissionTrip; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "mission_trips") +public class MissionTrip implements IMissionTrip { + @Id + @Column(name = "id", unique = true, nullable = false) + private int id; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "mission_team_id") + private MissionTeam missionTeam; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "mission_city_id") + private MissionCity missionCity; + @Column(name = "isCurrent", nullable = false) + private boolean isCurrent; + @Column(name = "start_date") + private Date startDate; + @Column(name = "end_date") + private Date endDate; + + @Override + public int getId() { + return id; + } + + @Override + public IMissionTeam getMissionTeam() { + return missionTeam; + } + + @Override + public void setMissionTeam(IMissionTeam missionTeam) { + this.missionTeam = (MissionTeam) missionTeam; + } + + @Override + public IMissionCity getMissionCity() { + return missionCity; + } + + @Override + public void setMissionCity(IMissionCity missionCity) { + this.missionCity = (MissionCity) missionCity; + } + + @Override + public boolean isCurrent() { + return isCurrent; + } + + @Override + public void setCurrent(boolean isCurrent) { + this.isCurrent = isCurrent; + } + + @Override + public Date getStartDate() { + return startDate; + } + + @Override + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + @Override + public Date getEndDate() { + return endDate; + } + + @Override + public void setEndDate(Date endDate) { + this.endDate = endDate; + } +} diff --git a/app/femr/data/models/Patient.java b/app/femr/data/models/mysql/Patient.java similarity index 96% rename from app/femr/data/models/Patient.java rename to app/femr/data/models/mysql/Patient.java index cced180c1..648506c6e 100644 --- a/app/femr/data/models/Patient.java +++ b/app/femr/data/models/mysql/Patient.java @@ -16,7 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IPatient; +import femr.data.models.core.IPhoto; import javax.persistence.*; import java.util.Date; diff --git a/app/femr/data/models/PatientAgeClassification.java b/app/femr/data/models/mysql/PatientAgeClassification.java similarity index 95% rename from app/femr/data/models/PatientAgeClassification.java rename to app/femr/data/models/mysql/PatientAgeClassification.java index 301b42c8c..f0957a8b3 100644 --- a/app/femr/data/models/PatientAgeClassification.java +++ b/app/femr/data/models/mysql/PatientAgeClassification.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IPatientAgeClassification; import javax.persistence.*; diff --git a/app/femr/data/models/PatientEncounter.java b/app/femr/data/models/mysql/PatientEncounter.java similarity index 92% rename from app/femr/data/models/PatientEncounter.java rename to app/femr/data/models/mysql/PatientEncounter.java index 0c1a8e553..0b7b0fdee 100644 --- a/app/femr/data/models/PatientEncounter.java +++ b/app/femr/data/models/mysql/PatientEncounter.java @@ -16,8 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; +import femr.data.models.core.*; import org.joda.time.DateTime; import javax.persistence.*; import java.util.ArrayList; @@ -55,6 +56,9 @@ public class PatientEncounter implements IPatientEncounter { @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "patient_age_classification_id") private PatientAgeClassification patientAgeClassification; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_trip_id") + private MissionTrip missionTrip; @Override public int getId() { @@ -166,4 +170,14 @@ public IPatientAgeClassification getPatientAgeClassification() { public void setPatientAgeClassification(IPatientAgeClassification patientAgeClassification) { this.patientAgeClassification = (PatientAgeClassification) patientAgeClassification; } + + @Override + public IMissionTrip getMissionTrip() { + return missionTrip; + } + + @Override + public void setMissionTrip(IMissionTrip missionTrip) { + this.missionTrip = (MissionTrip) missionTrip; + } } diff --git a/app/femr/data/models/PatientEncounterPhoto.java b/app/femr/data/models/mysql/PatientEncounterPhoto.java similarity index 94% rename from app/femr/data/models/PatientEncounterPhoto.java rename to app/femr/data/models/mysql/PatientEncounterPhoto.java index f65e2d251..613f9cd49 100644 --- a/app/femr/data/models/PatientEncounterPhoto.java +++ b/app/femr/data/models/mysql/PatientEncounterPhoto.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IPatientEncounterPhoto; import javax.persistence.*; diff --git a/app/femr/data/models/PatientEncounterTabField.java b/app/femr/data/models/mysql/PatientEncounterTabField.java similarity index 94% rename from app/femr/data/models/PatientEncounterTabField.java rename to app/femr/data/models/mysql/PatientEncounterTabField.java index b374bc60c..7315cad0b 100644 --- a/app/femr/data/models/PatientEncounterTabField.java +++ b/app/femr/data/models/mysql/PatientEncounterTabField.java @@ -16,14 +16,17 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; +import femr.data.models.core.IChiefComplaint; +import femr.data.models.core.IPatientEncounterTabField; +import femr.data.models.core.ITabField; import org.joda.time.DateTime; import javax.persistence.*; @Entity @Table(name="patient_encounter_tab_fields") -public class PatientEncounterTabField implements IPatientEncounterTabField{ +public class PatientEncounterTabField implements IPatientEncounterTabField { @Id @Column(name = "id", unique = true, nullable = false) private int id; diff --git a/app/femr/data/models/PatientEncounterVital.java b/app/femr/data/models/mysql/PatientEncounterVital.java similarity index 95% rename from app/femr/data/models/PatientEncounterVital.java rename to app/femr/data/models/mysql/PatientEncounterVital.java index 34f671bb4..1ebd383e2 100644 --- a/app/femr/data/models/PatientEncounterVital.java +++ b/app/femr/data/models/mysql/PatientEncounterVital.java @@ -16,7 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IPatientEncounterVital; +import femr.data.models.core.IVital; import javax.persistence.*; diff --git a/app/femr/data/models/PatientPrescription.java b/app/femr/data/models/mysql/PatientPrescription.java similarity index 98% rename from app/femr/data/models/PatientPrescription.java rename to app/femr/data/models/mysql/PatientPrescription.java index 934f472aa..a30639f8c 100644 --- a/app/femr/data/models/PatientPrescription.java +++ b/app/femr/data/models/mysql/PatientPrescription.java @@ -16,8 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; +import femr.data.models.core.*; import org.joda.time.DateTime; import javax.persistence.*; diff --git a/app/femr/data/models/Photo.java b/app/femr/data/models/mysql/Photo.java similarity index 96% rename from app/femr/data/models/Photo.java rename to app/femr/data/models/mysql/Photo.java index 56b8c3827..c7998e21b 100644 --- a/app/femr/data/models/Photo.java +++ b/app/femr/data/models/mysql/Photo.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IPhoto; import javax.persistence.*; import java.util.Date; diff --git a/app/femr/data/models/Role.java b/app/femr/data/models/mysql/Role.java similarity index 95% rename from app/femr/data/models/Role.java rename to app/femr/data/models/mysql/Role.java index e16270602..aeb438854 100644 --- a/app/femr/data/models/Role.java +++ b/app/femr/data/models/mysql/Role.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IRole; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/app/femr/data/models/Roles.java b/app/femr/data/models/mysql/Roles.java similarity index 97% rename from app/femr/data/models/Roles.java rename to app/femr/data/models/mysql/Roles.java index 41bdfcb88..427a7a607 100644 --- a/app/femr/data/models/Roles.java +++ b/app/femr/data/models/mysql/Roles.java @@ -16,7 +16,7 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; public class Roles { public static final int ADMINISTRATOR = 1; diff --git a/app/femr/data/models/SystemSetting.java b/app/femr/data/models/mysql/SystemSetting.java similarity index 95% rename from app/femr/data/models/SystemSetting.java rename to app/femr/data/models/mysql/SystemSetting.java index 99b13bffd..c362fc0e5 100644 --- a/app/femr/data/models/SystemSetting.java +++ b/app/femr/data/models/mysql/SystemSetting.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.ISystemSetting; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/app/femr/data/models/Tab.java b/app/femr/data/models/mysql/Tab.java similarity index 97% rename from app/femr/data/models/Tab.java rename to app/femr/data/models/mysql/Tab.java index 93a71c6cf..882ba255e 100644 --- a/app/femr/data/models/Tab.java +++ b/app/femr/data/models/mysql/Tab.java @@ -16,8 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; +import femr.data.models.core.ITab; import org.joda.time.DateTime; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/app/femr/data/models/TabField.java b/app/femr/data/models/mysql/TabField.java similarity index 94% rename from app/femr/data/models/TabField.java rename to app/femr/data/models/mysql/TabField.java index 441625ebf..7b926ba22 100644 --- a/app/femr/data/models/TabField.java +++ b/app/femr/data/models/mysql/TabField.java @@ -16,7 +16,12 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.ITab; +import femr.data.models.core.ITabField; +import femr.data.models.core.ITabFieldSize; +import femr.data.models.core.ITabFieldType; import javax.persistence.*; diff --git a/app/femr/data/models/TabFieldSize.java b/app/femr/data/models/mysql/TabFieldSize.java similarity index 95% rename from app/femr/data/models/TabFieldSize.java rename to app/femr/data/models/mysql/TabFieldSize.java index 90b426705..cebf83067 100644 --- a/app/femr/data/models/TabFieldSize.java +++ b/app/femr/data/models/mysql/TabFieldSize.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.ITabFieldSize; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/app/femr/data/models/TabFieldType.java b/app/femr/data/models/mysql/TabFieldType.java similarity index 95% rename from app/femr/data/models/TabFieldType.java rename to app/femr/data/models/mysql/TabFieldType.java index 280ee6154..4beddd04c 100644 --- a/app/femr/data/models/TabFieldType.java +++ b/app/femr/data/models/mysql/TabFieldType.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.ITabFieldType; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/app/femr/data/models/User.java b/app/femr/data/models/mysql/User.java similarity index 97% rename from app/femr/data/models/User.java rename to app/femr/data/models/mysql/User.java index 5b3c10123..ffb0b9d01 100644 --- a/app/femr/data/models/User.java +++ b/app/femr/data/models/mysql/User.java @@ -16,8 +16,10 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; +import femr.data.models.core.IRole; +import femr.data.models.core.IUser; import org.joda.time.DateTime; import javax.persistence.*; diff --git a/app/femr/data/models/Vital.java b/app/femr/data/models/mysql/Vital.java similarity index 96% rename from app/femr/data/models/Vital.java rename to app/femr/data/models/mysql/Vital.java index 3499abd90..22956404f 100644 --- a/app/femr/data/models/Vital.java +++ b/app/femr/data/models/mysql/Vital.java @@ -16,7 +16,9 @@ along with fEMR. If not, see . If you have any questions, contact . */ -package femr.data.models; +package femr.data.models.mysql; + +import femr.data.models.core.IVital; import javax.persistence.*; diff --git a/app/femr/ui/controllers/HistoryController.java b/app/femr/ui/controllers/HistoryController.java index 9d12403fa..6c9e085fa 100644 --- a/app/femr/ui/controllers/HistoryController.java +++ b/app/femr/ui/controllers/HistoryController.java @@ -1,11 +1,14 @@ package femr.ui.controllers; import com.google.inject.Inject; -import femr.business.services.*; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.IEncounterService; +import femr.business.services.core.IPhotoService; +import femr.business.services.core.ISearchService; +import femr.business.services.core.ISessionService; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; -import femr.data.models.*; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.history.IndexEncounterMedicalViewModel; @@ -29,19 +32,21 @@ @Security.Authenticated(FEMRAuthenticated.class) @AllowedRoles({Roles.PHYSICIAN, Roles.PHARMACIST, Roles.NURSE}) public class HistoryController extends Controller { - private ISessionService sessionService; - private ISearchService searchService; - private IPharmacyService pharmacyService; - private IPhotoService photoService; + + private final IEncounterService encounterService; + private final ISessionService sessionService; + private final ISearchService searchService; + private final IPhotoService photoService; @Inject - public HistoryController(ISessionService sessionService, + public HistoryController(IEncounterService encounterService, + ISessionService sessionService, ISearchService searchService, - IPharmacyService pharmacyService, IPhotoService photoService) { + + this.encounterService = encounterService; this.sessionService = sessionService; this.searchService = searchService; - this.pharmacyService = pharmacyService; this.photoService = photoService; } @@ -166,7 +171,7 @@ public Result indexEncounterGet(int encounterId) { //get problems List problems = new ArrayList<>(); - ServiceResponse> problemItemServiceResponse = pharmacyService.findProblemItems(encounterId); + ServiceResponse> problemItemServiceResponse = encounterService.findProblemItems(encounterId); if (problemItemServiceResponse.hasErrors()){ throw new RuntimeException(); } diff --git a/app/femr/ui/controllers/HomeController.java b/app/femr/ui/controllers/HomeController.java index 3f3d99b4e..a8c424763 100644 --- a/app/femr/ui/controllers/HomeController.java +++ b/app/femr/ui/controllers/HomeController.java @@ -1,8 +1,8 @@ package femr.ui.controllers; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.business.services.ISessionService; +import femr.common.dtos.CurrentUser; +import femr.business.services.core.ISessionService; import femr.ui.views.html.home.index; import femr.ui.views.html.sessions.create; import play.mvc.Controller; diff --git a/app/femr/ui/controllers/MedicalController.java b/app/femr/ui/controllers/MedicalController.java index dd1351e75..19886d53b 100644 --- a/app/femr/ui/controllers/MedicalController.java +++ b/app/femr/ui/controllers/MedicalController.java @@ -3,11 +3,11 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.google.inject.Inject; -import femr.business.services.*; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.*; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; -import femr.data.models.Roles; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.medical.*; @@ -32,20 +32,29 @@ public class MedicalController extends Controller { private final Form createViewModelPostForm = Form.form(EditViewModelPost.class); private final Form updateVitalsModelForm = Form.form(UpdateVitalsModel.class); + private final ICustomTabService customTabService; + private final IEncounterService encounterService; + private final IMedicationService medicationService; + private final IPhotoService photoService; private final ISessionService sessionService; private final ISearchService searchService; - private final IMedicalService medicalService; - private final IPhotoService photoService; + private final IVitalService vitalService; @Inject - public MedicalController(ISessionService sessionService, + public MedicalController(ICustomTabService customTabService, + IEncounterService encounterService, + IMedicationService medicationService, + IPhotoService photoService, + ISessionService sessionService, ISearchService searchService, - IMedicalService medicalService, - IPhotoService photoService) { + IVitalService vitalService) { + this.customTabService = customTabService; + this.encounterService = encounterService; this.sessionService = sessionService; this.searchService = searchService; - this.medicalService = medicalService; + this.medicationService = medicationService; this.photoService = photoService; + this.vitalService = vitalService; } public Result indexGet() { @@ -77,7 +86,7 @@ public Result indexPost() { } //check if the doc has already seen the patient today - ServiceResponse userItemServiceResponse = medicalService.getPhysicianThatCheckedInPatient(patientEncounterItem.getId()); + ServiceResponse userItemServiceResponse = encounterService.getPhysicianThatCheckedInPatientToMedical(patientEncounterItem.getId()); if (userItemServiceResponse.hasErrors()) { throw new RuntimeException(); } else { @@ -133,7 +142,7 @@ public Result editGet(int patientId) { //Map // String = tab field name // TabFieldItem contains value - ServiceResponse> patientEncounterTabFieldResponse = medicalService.findCurrentTabFieldsByEncounterId(patientEncounter.getId()); + ServiceResponse> patientEncounterTabFieldResponse = encounterService.findCurrentTabFieldsByEncounterId(patientEncounter.getId()); Map tabFieldItemMap; if (patientEncounterTabFieldResponse.hasErrors()) { throw new RuntimeException(); @@ -143,13 +152,13 @@ public Result editGet(int patientId) { } //get custom tabs/fields - ServiceResponse> tabItemResponse = medicalService.getCustomTabs(); + ServiceResponse> tabItemResponse = customTabService.getCustomTabs(false); if (tabItemResponse.hasErrors()) { throw new RuntimeException(); } else { viewModelGet.setCustomTabs(tabItemResponse.getResponseObject()); } - ServiceResponse>> tabFieldResponse = medicalService.getCustomFields(patientEncounter.getId()); + ServiceResponse>> tabFieldResponse = customTabService.getTabFields(patientEncounter.getId()); if (tabFieldResponse.hasErrors()) { throw new RuntimeException(); } else { @@ -180,7 +189,7 @@ public Result editPost(int patientId) { throw new RuntimeException(); } PatientEncounterItem patientEncounterItem = patientEncounterServiceResponse.getResponseObject(); - patientEncounterItem = medicalService.checkPatientIn(patientEncounterItem.getId(), currentUserSession.getId()).getResponseObject(); + patientEncounterItem = encounterService.checkPatientInToMedical(patientEncounterItem.getId(), currentUserSession.getId()).getResponseObject(); //update patient encounter //Maps the dynamic tab name to the field list @@ -203,7 +212,7 @@ public Result editPost(int patientId) { //save the custom fields, if any if (customFieldItems.size() > 0) { ServiceResponse> customFieldItemResponse = - medicalService.createPatientEncounterTabFields(customFieldItems, patientEncounterItem.getId(), currentUserSession.getId()); + encounterService.createPatientEncounterTabFields(customFieldItems, patientEncounterItem.getId(), currentUserSession.getId()); if (customFieldItemResponse.hasErrors()) { throw new RuntimeException(); } @@ -225,7 +234,7 @@ public Result editPost(int patientId) { if (nonCustomFieldItems.size() > 0) { ServiceResponse> nonCustomFieldItemResponse = - medicalService.createPatientEncounterTabFields(nonCustomFieldItems, patientEncounterItem.getId(), currentUserSession.getId()); + encounterService.createPatientEncounterTabFields(nonCustomFieldItems, patientEncounterItem.getId(), currentUserSession.getId()); if (nonCustomFieldItemResponse.hasErrors()) { throw new RuntimeException(); } @@ -258,12 +267,9 @@ public Result editPost(int patientId) { if (viewModelPost.getPrescription5() != null && StringUtils.isNotNullOrWhiteSpace(viewModelPost.getPrescription5())) prescriptionItems.add(new PrescriptionItem(viewModelPost.getPrescription5())); if (prescriptionItems.size() > 0) { - ServiceResponse> prescriptionResponse = medicalService.createPatientPrescriptions( - prescriptionItems, - currentUserSession.getId(), - patientEncounterItem.getId(), - false - ); + + ServiceResponse> prescriptionResponse = + medicationService.createPatientPrescriptions(prescriptionItems, currentUserSession.getId(), patientEncounterItem.getId(), false, false); if (prescriptionResponse.hasErrors()) { throw new RuntimeException(); } @@ -282,7 +288,7 @@ public Result updateVitalsPost(int id) { throw new RuntimeException(); } //update date_of_medical_visit when a vital is updated - medicalService.checkPatientIn(currentEncounterByPatientId.getResponseObject().getId(), currentUser.getId()); + encounterService.checkPatientInToMedical(currentEncounterByPatientId.getResponseObject().getId(), currentUser.getId()); PatientEncounterItem patientEncounter = currentEncounterByPatientId.getResponseObject(); @@ -290,7 +296,7 @@ public Result updateVitalsPost(int id) { Map patientEncounterVitals = getPatientEncounterVitals(updateVitalsModel); ServiceResponse> patientEncounterVitalsServiceResponse = - medicalService.createPatientEncounterVitals(patientEncounterVitals, currentUser.getId(), patientEncounter.getId()); + vitalService.createPatientEncounterVitals(patientEncounterVitals, currentUser.getId(), patientEncounter.getId()); if (patientEncounterVitalsServiceResponse.hasErrors()) { throw new RuntimeException(); } diff --git a/app/femr/ui/controllers/PharmaciesController.java b/app/femr/ui/controllers/PharmaciesController.java index 44720e040..7313bfe40 100644 --- a/app/femr/ui/controllers/PharmaciesController.java +++ b/app/femr/ui/controllers/PharmaciesController.java @@ -1,12 +1,14 @@ package femr.ui.controllers; -import com.google.gson.JsonObject; import com.google.inject.Inject; -import femr.business.services.*; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.IEncounterService; +import femr.business.services.core.IMedicationService; +import femr.business.services.core.ISearchService; +import femr.business.services.core.ISessionService; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; import femr.common.models.*; -import femr.data.models.Roles; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.pharmacy.*; @@ -24,16 +26,20 @@ @Security.Authenticated(FEMRAuthenticated.class) @AllowedRoles({Roles.PHYSICIAN, Roles.PHARMACIST, Roles.NURSE}) public class PharmaciesController extends Controller { + private final Form populatedViewModelPostForm = Form.form(EditViewModelPost.class); - private ISessionService sessionService; - private ISearchService searchService; - private IPharmacyService pharmacyService; + private final IEncounterService encounterService; + private final IMedicationService medicationService; + private final ISessionService sessionService; + private final ISearchService searchService; @Inject - public PharmaciesController(IPharmacyService pharmacyService, + public PharmaciesController(IEncounterService encounterService, + IMedicationService medicationService, ISessionService sessionService, ISearchService searchService) { - this.pharmacyService = pharmacyService; + this.encounterService = encounterService; + this.medicationService = medicationService; this.sessionService = sessionService; this.searchService = searchService; } @@ -121,7 +127,7 @@ public Result editGet(int patientId) { viewModelGet.setMedications(prescriptionItemServiceResponse.getResponseObject()); //find patient problems, they do not have to exist. - ServiceResponse> problemItemServiceResponse = pharmacyService.findProblemItems(patientEncounterItem.getId()); + ServiceResponse> problemItemServiceResponse = encounterService.findProblemItems(patientEncounterItem.getId()); if (problemItemServiceResponse.hasErrors()) { throw new RuntimeException(); } else { @@ -160,7 +166,7 @@ public Result editPost(int id) { PrescriptionItem prescriptionItem = new PrescriptionItem(); if (StringUtils.isNotNullOrWhiteSpace(createViewModelPost.getReplacementMedication1())) { prescriptionItem.setName(createViewModelPost.getReplacementMedication1()); - ServiceResponse response = pharmacyService.createAndReplacePrescription( + ServiceResponse response = medicationService.createAndReplacePrescription( prescriptionItem, createViewModelPost.getId_prescription1(), currentUserSession.getId(), @@ -175,7 +181,7 @@ public Result editPost(int id) { //replace prescription 2 if (StringUtils.isNotNullOrWhiteSpace(createViewModelPost.getReplacementMedication2())) { prescriptionItem.setName(createViewModelPost.getReplacementMedication2()); - ServiceResponse response = pharmacyService.createAndReplacePrescription( + ServiceResponse response = medicationService.createAndReplacePrescription( prescriptionItem, createViewModelPost.getId_prescription2(), currentUserSession.getId(), @@ -190,7 +196,7 @@ public Result editPost(int id) { //replace prescription 3 if (StringUtils.isNotNullOrWhiteSpace(createViewModelPost.getReplacementMedication3())) { prescriptionItem.setName(createViewModelPost.getReplacementMedication3()); - ServiceResponse response = pharmacyService.createAndReplacePrescription( + ServiceResponse response = medicationService.createAndReplacePrescription( prescriptionItem, createViewModelPost.getId_prescription3(), currentUserSession.getId(), @@ -205,7 +211,7 @@ public Result editPost(int id) { //replace prescription 4 if (StringUtils.isNotNullOrWhiteSpace(createViewModelPost.getReplacementMedication4())) { prescriptionItem.setName(createViewModelPost.getReplacementMedication4()); - ServiceResponse response = pharmacyService.createAndReplacePrescription( + ServiceResponse response = medicationService.createAndReplacePrescription( prescriptionItem, createViewModelPost.getId_prescription4(), currentUserSession.getId(), @@ -220,7 +226,7 @@ public Result editPost(int id) { //replace prescription 5 if (StringUtils.isNotNullOrWhiteSpace(createViewModelPost.getReplacementMedication5())) { prescriptionItem.setName(createViewModelPost.getReplacementMedication5()); - ServiceResponse response = pharmacyService.createAndReplacePrescription( + ServiceResponse response = medicationService.createAndReplacePrescription( prescriptionItem, createViewModelPost.getId_prescription5(), currentUserSession.getId(), @@ -234,20 +240,20 @@ public Result editPost(int id) { } //update non-replaced prescriptions to dispensed - ServiceResponse> prescriptionDispensedResponse = pharmacyService.markPrescriptionsAsFilled(prescriptionToMarkAsDispensedOrCounseled); + ServiceResponse> prescriptionDispensedResponse = medicationService.markPrescriptionsAsFilled(prescriptionToMarkAsDispensedOrCounseled); if (prescriptionDispensedResponse.hasErrors()) { throw new RuntimeException(); } //update non-replaced prescriptions that the patient was counseled on if (isCounseled){ - ServiceResponse> prescriptionCounseledResponse = pharmacyService.markPrescriptionsAsCounseled(prescriptionToMarkAsDispensedOrCounseled); + ServiceResponse> prescriptionCounseledResponse = medicationService.markPrescriptionsAsCounseled(prescriptionToMarkAsDispensedOrCounseled); if (prescriptionCounseledResponse.hasErrors()){ throw new RuntimeException(); } } //check the patient in! - pharmacyService.checkPatientIn(patientEncounterItem.getId(), currentUserSession.getId()); + encounterService.checkPatientInToPharmacy(patientEncounterItem.getId(), currentUserSession.getId()); String message = "Patient information for " + patientItem.getFirstName() + " " + @@ -266,21 +272,12 @@ public Result editPost(int id) { * @return JSON object of medications that exist in the medications table */ public Result typeaheadJSONGet() { - JsonObject jsonObject = new JsonObject(); - //get a list of medications in the medication table - //these medications are added by an administrator in the admin section - ServiceResponse> medicationServiceResponse = pharmacyService.findAllMedications(); + ServiceResponse medicationServiceResponse = medicationService.getMedicationNames(); if (medicationServiceResponse.hasErrors()) { - return ok(jsonObject.toString()); + return ok(""); } - List medications = medicationServiceResponse.getResponseObject(); - - //create a JsonObject to send back via AJAX - for (int medication = 0; medication < medications.size(); medication++) { - jsonObject.addProperty("medicine" + medication, medications.get(medication)); - } - return ok(jsonObject.toString()); + return ok(medicationServiceResponse.getResponseObject()); } } diff --git a/app/femr/ui/controllers/PhotoController.java b/app/femr/ui/controllers/PhotoController.java index b4c117209..fd05331ca 100644 --- a/app/femr/ui/controllers/PhotoController.java +++ b/app/femr/ui/controllers/PhotoController.java @@ -2,9 +2,9 @@ import com.google.inject.Inject; import com.typesafe.config.ConfigFactory; -import femr.common.dto.ServiceResponse; -import femr.business.services.IPhotoService; -import femr.data.models.Roles; +import femr.common.dtos.ServiceResponse; +import femr.business.services.core.IPhotoService; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.util.stringhelpers.StringUtils; diff --git a/app/femr/ui/controllers/ResearchController.java b/app/femr/ui/controllers/ResearchController.java index 7ca1b4e42..67eb8d05d 100644 --- a/app/femr/ui/controllers/ResearchController.java +++ b/app/femr/ui/controllers/ResearchController.java @@ -3,14 +3,13 @@ import com.google.gson.Gson; import com.google.inject.Inject; import femr.business.helpers.DomainMapper; -import femr.common.dto.CurrentUser; -import femr.business.services.IResearchService; -import femr.business.services.ISessionService; - -import femr.common.dto.ServiceResponse; +import femr.common.dtos.ServiceResponse; import femr.common.models.ResearchFilterItem; import femr.common.models.ResearchGraphDataItem; -import femr.data.models.Roles; +import femr.common.dtos.CurrentUser; +import femr.business.services.core.IResearchService; +import femr.business.services.core.ISessionService; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.views.html.research.index; @@ -54,6 +53,7 @@ public ResearchController(ISessionService sessionService, IResearchService resea public Result indexGet() { // There isn't really a request here, should this be different? + //TODO: nothing to bind here FilterViewModel filterViewModel = FilterViewModelForm.bindFromRequest().get(); // Set Default Start (30 Days Ago) and End Date (Today) @@ -81,7 +81,7 @@ public Result generateData() { public Result getGraphPost(){ FilterViewModel filterViewModel = FilterViewModelForm.bindFromRequest().get(); - + //TODO: domain mapper out of scope ResearchFilterItem researchFilterItem = DomainMapper.createResearchFilterItem(filterViewModel); ServiceResponse response = researchService.getGraphData(researchFilterItem); diff --git a/app/femr/ui/controllers/SearchController.java b/app/femr/ui/controllers/SearchController.java index 723e6bd0e..4fa3b26d7 100644 --- a/app/femr/ui/controllers/SearchController.java +++ b/app/femr/ui/controllers/SearchController.java @@ -1,11 +1,11 @@ package femr.ui.controllers; import com.google.inject.Inject; -import femr.business.services.ISearchService; -import femr.business.services.ISessionService; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.ISearchService; +import femr.business.services.core.ISessionService; +import femr.common.dtos.ServiceResponse; import femr.common.models.PatientItem; -import femr.data.models.Roles; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import org.h2.util.StringUtils; diff --git a/app/femr/ui/controllers/SessionsController.java b/app/femr/ui/controllers/SessionsController.java index 488b89113..d8f5aab5e 100644 --- a/app/femr/ui/controllers/SessionsController.java +++ b/app/femr/ui/controllers/SessionsController.java @@ -1,11 +1,11 @@ package femr.ui.controllers; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; -import femr.business.services.ISessionService; -import femr.business.services.IUserService; -import femr.data.models.IUser; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; +import femr.business.services.core.ISessionService; +import femr.business.services.core.IUserService; +import femr.data.models.core.IUser; import femr.ui.models.sessions.CreateViewModel; import femr.ui.views.html.sessions.create; import femr.ui.views.html.sessions.editPassword; diff --git a/app/femr/ui/controllers/SuperuserController.java b/app/femr/ui/controllers/SuperuserController.java index 0d9ddcb63..06cbb184d 100644 --- a/app/femr/ui/controllers/SuperuserController.java +++ b/app/femr/ui/controllers/SuperuserController.java @@ -1,13 +1,33 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ package femr.ui.controllers; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; +import femr.business.services.core.IMissionTripService; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; import femr.common.models.TabFieldItem; import femr.common.models.TabItem; -import femr.business.services.ISessionService; -import femr.business.services.ISuperuserService; -import femr.data.models.Roles; +import femr.business.services.core.ISessionService; +import femr.business.services.core.ICustomTabService; +import femr.common.models.TripItem; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.superuser.*; @@ -19,6 +39,7 @@ import femr.ui.views.html.superuser.index; import femr.ui.views.html.superuser.tabs; import femr.ui.views.html.superuser.fields; +import femr.ui.views.html.superuser.trips; import java.util.List; @@ -27,14 +48,18 @@ public class SuperuserController extends Controller { private final Form TabsViewModelForm = Form.form(TabsViewModelPost.class); private final Form ContentViewModelForm = Form.form(ContentViewModelPost.class); - private ISessionService sessionService; - private ISuperuserService superuserService; + private Form tripViewModelForm = Form.form(TripViewModel.class); + private final ICustomTabService customTabService; + private final IMissionTripService missionTripService; + private final ISessionService sessionService; @Inject - public SuperuserController(ISessionService sessionService, - ISuperuserService superuserService) { + public SuperuserController(ICustomTabService customTabService, + IMissionTripService missionTripService, + ISessionService sessionService) { + this.customTabService = customTabService; + this.missionTripService = missionTripService; this.sessionService = sessionService; - this.superuserService = superuserService; } public Result indexGet() { @@ -42,11 +67,62 @@ public Result indexGet() { return ok(index.render(currentUser)); } + public Result tripsGet(){ + CurrentUser currentUser = sessionService.getCurrentUserSession(); + + ServiceResponse currentMissionTripServiceResponse = missionTripService.findCurrentMissionTrip(); + if (currentMissionTripServiceResponse.getResponseObject() == null){ + //either a trip doesnt exist or more than one exists + }else{ + //a trip exists + TripViewModel tripViewModel = new TripViewModel(); + TripItem tripItem = currentMissionTripServiceResponse.getResponseObject(); + tripViewModel.setTeam(tripItem.getTeam()); + tripViewModel.setTeamLocation(tripItem.getTeamLocation()); + tripViewModel.setCity(tripItem.getCity()); + tripViewModel.setCountry(tripItem.getCountry()); + tripViewModel.setDescription(tripItem.getDescription()); + tripViewModel.setStartDate(tripItem.getTripStartDate()); + tripViewModel.setEndDate(tripItem.getTripEndDate()); + tripViewModelForm = tripViewModelForm.fill(tripViewModel); + } + + + return ok(trips.render(currentUser, tripViewModelForm)); + } + + public Result tripsPost(){ + CurrentUser currentUser = sessionService.getCurrentUserSession(); + Form form = tripViewModelForm.bindFromRequest(); + if (form.hasErrors()){ + + return badRequest(trips.render(currentUser, form)); + }else{ + TripViewModel tripViewModel = form.bindFromRequest().get(); + + TripItem tripItem = new TripItem(); + tripItem.setCity(tripViewModel.getCity()); + tripItem.setCountry(tripViewModel.getCountry()); + tripItem.setTeam(tripViewModel.getTeam()); + tripItem.setTeamLocation(tripViewModel.getTeamLocation()); + tripItem.setDescription(tripViewModel.getDescription()); + tripItem.setTripStartDate(tripViewModel.getStartDate()); + tripItem.setTripEndDate(tripViewModel.getEndDate()); + + ServiceResponse tripItemServiceResponse = missionTripService.updateTrip(tripItem); + if (tripItemServiceResponse.hasErrors()){ + throw new RuntimeException(); + } + } + + return ok(index.render(currentUser)); + } + public Result tabsGet() { CurrentUser currentUser = sessionService.getCurrentUserSession(); ServiceResponse> response; - response = superuserService.getCustomTabs(false); + response = customTabService.getCustomTabs(false); if (response.hasErrors()) { throw new RuntimeException(); } @@ -55,7 +131,7 @@ public Result tabsGet() { viewModelGet.setCurrentTabs(response.getResponseObject()); //get deleted tabs - response = superuserService.getCustomTabs(true); + response = customTabService.getCustomTabs(true); if (response.hasErrors()) { throw new RuntimeException(); } @@ -72,11 +148,11 @@ public Result tabsPost() { if (StringUtils.isNotNullOrWhiteSpace(viewModelPost.getAddTabName())) { TabItem tabItem = new TabItem(); //new - if (!superuserService.doesTabExist(viewModelPost.getAddTabName()).getResponseObject()) { + if (!customTabService.doesTabExist(viewModelPost.getAddTabName()).getResponseObject()) { tabItem.setName(viewModelPost.getAddTabName()); if (viewModelPost.getAddTabLeft() != null) tabItem.setLeftColumnSize(viewModelPost.getAddTabLeft()); if (viewModelPost.getAddTabRight() != null) tabItem.setRightColumnSize(viewModelPost.getAddTabRight()); - ServiceResponse response = superuserService.createTab(tabItem, currentUser.getId()); + ServiceResponse response = customTabService.createTab(tabItem, currentUser.getId()); if (response.hasErrors()) { throw new RuntimeException(); } @@ -88,7 +164,7 @@ public Result tabsPost() { else tabItem.setRightColumnSize(viewModelPost.getAddTabRight()); tabItem.setName(viewModelPost.getAddTabName()); - ServiceResponse response = superuserService.editTab(tabItem, currentUser.getId()); + ServiceResponse response = customTabService.editTab(tabItem, currentUser.getId()); if (response.hasErrors()) { throw new RuntimeException(); } @@ -96,7 +172,7 @@ public Result tabsPost() { } //becomes toggle if (StringUtils.isNotNullOrWhiteSpace(viewModelPost.getDeleteTab())) { - ServiceResponse response = superuserService.toggleTab(viewModelPost.getDeleteTab()); + ServiceResponse response = customTabService.toggleTab(viewModelPost.getDeleteTab()); if (response.hasErrors()) { throw new RuntimeException(); } @@ -113,28 +189,28 @@ public Result contentGet(String name) { viewModelGet.setName(name); //get current custom fields - ServiceResponse> currentFieldItemsResponse = superuserService.getTabFields(name, false); + ServiceResponse> currentFieldItemsResponse = customTabService.getTabFieldsByTabName(name, false); if (currentFieldItemsResponse.hasErrors()) { throw new RuntimeException(); } viewModelGet.setCurrentCustomFieldItemList(currentFieldItemsResponse.getResponseObject()); //get removed custom fields - ServiceResponse> removedFieldItemsResponse = superuserService.getTabFields(name, true); + ServiceResponse> removedFieldItemsResponse = customTabService.getTabFieldsByTabName(name, true); if (currentFieldItemsResponse.hasErrors()) { throw new RuntimeException(); } viewModelGet.setRemovedCustomFieldItemList(removedFieldItemsResponse.getResponseObject()); //get available field types - ServiceResponse> fieldTypesResponse = superuserService.getTypes(); + ServiceResponse> fieldTypesResponse = customTabService.getTypes(); if (fieldTypesResponse.hasErrors()) { throw new RuntimeException(); } viewModelGet.setCustomFieldTypes(fieldTypesResponse.getResponseObject()); //get available fields sizes - ServiceResponse> fieldSizesResponse = superuserService.getSizes(); + ServiceResponse> fieldSizesResponse = customTabService.getSizes(); if (fieldSizesResponse.hasErrors()) { throw new RuntimeException(); } @@ -157,11 +233,11 @@ public Result contentPost(String name) { tabFieldItem.setOrder(viewModelPost.getAddOrder()); tabFieldItem.setPlaceholder(viewModelPost.getAddPlaceholder()); //edit - if (superuserService.doesTabFieldExist(viewModelPost.getAddName()).getResponseObject()) { - superuserService.editTabField(tabFieldItem); + if (customTabService.doesTabFieldExist(viewModelPost.getAddName()).getResponseObject()) { + customTabService.editTabField(tabFieldItem); } else { - ServiceResponse response = superuserService.createTabField(tabFieldItem, currentUser.getId(), name); + ServiceResponse response = customTabService.createTabField(tabFieldItem, currentUser.getId(), name); if (response.hasErrors()) { throw new RuntimeException(); } @@ -169,7 +245,7 @@ public Result contentPost(String name) { } //deactivating a field if (StringUtils.isNotNullOrWhiteSpace(viewModelPost.getToggleName())) { - ServiceResponse response = superuserService.toggleTabField(viewModelPost.getToggleName(), name); + ServiceResponse response = customTabService.toggleTabField(viewModelPost.getToggleName(), name); if (response.hasErrors()) { throw new RuntimeException(); } @@ -178,4 +254,15 @@ public Result contentPost(String name) { return redirect("/superuser/tabs/" + name); } + public Result typeaheadJSONGet() { + + /*ServiceResponse medicationServiceResponse = typeaheadService.getTripInformation(); + if (medicationServiceResponse.hasErrors()){ + + } + + return ok(medicationServiceResponse.getResponseObject());*/ + return ok(""); + } + } diff --git a/app/femr/ui/controllers/TriageController.java b/app/femr/ui/controllers/TriageController.java index 1d6bd513a..f0665c7e0 100644 --- a/app/femr/ui/controllers/TriageController.java +++ b/app/femr/ui/controllers/TriageController.java @@ -3,14 +3,11 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; -import femr.business.services.IPhotoService; -import femr.business.services.ISearchService; -import femr.business.services.ISessionService; -import femr.business.services.ITriageService; +import femr.business.services.core.*; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; import femr.common.models.SettingItem; -import femr.data.models.Roles; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.common.models.PatientEncounterItem; @@ -21,7 +18,6 @@ import femr.util.stringhelpers.StringUtils; import play.data.Form; import play.mvc.*; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,20 +27,27 @@ public class TriageController extends Controller { private final Form IndexViewModelForm = Form.form(IndexViewModelPost.class); - private ITriageService triageService; - private ISessionService sessionService; - private ISearchService searchService; - private IPhotoService photoService; + private final IEncounterService encounterService; + private final IPatientService patientService; + private final ISessionService sessionService; + private final ISearchService searchService; + private final IPhotoService photoService; + private final IVitalService vitalService; @Inject - public TriageController(ITriageService triageService, + public TriageController(IEncounterService encounterService, ISessionService sessionService, ISearchService searchService, - IPhotoService photoService) { - this.triageService = triageService; + IPatientService patientService, + IPhotoService photoService, + IVitalService vitalService) { + + this.encounterService = encounterService; this.sessionService = sessionService; this.searchService = searchService; + this.patientService = patientService; this.photoService = photoService; + this.vitalService = vitalService; } public Result indexGet() { @@ -52,7 +55,7 @@ public Result indexGet() { //retrieve all the vitals in the database so we can dynamically name //the vitals in the view - ServiceResponse> vitalServiceResponse = triageService.findAllVitalItems(); + ServiceResponse> vitalServiceResponse = vitalService.findAllVitalItems(); if (vitalServiceResponse.hasErrors()) { throw new RuntimeException(); } @@ -67,7 +70,7 @@ public Result indexGet() { } //get age classifications - ServiceResponse> patientAgeClassificationsResponse = triageService.findPossibleAgeClassifications(); + ServiceResponse> patientAgeClassificationsResponse = patientService.findPossibleAgeClassifications(); if (patientAgeClassificationsResponse.hasErrors()){ throw new RuntimeException(); } @@ -90,7 +93,7 @@ public Result indexPopulatedGet(int patientId) { CurrentUser currentUser = sessionService.getCurrentUserSession(); //retrieve vitals names for dynamic html element naming - ServiceResponse> vitalServiceResponse = triageService.findAllVitalItems(); + ServiceResponse> vitalServiceResponse = vitalService.findAllVitalItems(); if (vitalServiceResponse.hasErrors()) { throw new RuntimeException(); } @@ -109,7 +112,7 @@ public Result indexPopulatedGet(int patientId) { } //get age classifications - ServiceResponse> patientAgeClassificationsResponse = triageService.findPossibleAgeClassifications(); + ServiceResponse> patientAgeClassificationsResponse = patientService.findPossibleAgeClassifications(); if (patientAgeClassificationsResponse.hasErrors()){ throw new RuntimeException(); } @@ -137,9 +140,9 @@ public Result indexPost(int id) { PatientItem patientItem; if (id == 0) { patientItem = populatePatientItem(viewModel, currentUser); - patientServiceResponse = triageService.createPatient(patientItem); + patientServiceResponse = patientService.createPatient(patientItem); } else { - patientServiceResponse = triageService.findPatientAndUpdateSex(id, viewModel.getSex()); + patientServiceResponse = patientService.findPatientAndUpdateSex(id, viewModel.getSex()); } if (patientServiceResponse.hasErrors()) { throw new RuntimeException(); @@ -156,7 +159,7 @@ public Result indexPost(int id) { //create and save a new encounter PatientEncounterItem patientEncounterItem = populatePatientEncounterItem(viewModel.getChiefComplaint(), viewModel.getChiefComplaintsJSON(), viewModel.getWeeksPregnant(), currentUser, patientServiceResponse.getResponseObject().getId(), viewModel.getAgeClassification()); - ServiceResponse patientEncounterServiceResponse = triageService.createPatientEncounter(patientEncounterItem); + ServiceResponse patientEncounterServiceResponse = encounterService.createPatientEncounter(patientEncounterItem); if (patientEncounterServiceResponse.hasErrors()) { throw new RuntimeException(); } else { @@ -198,7 +201,7 @@ public Result indexPost(int id) { newVitals.put("glucose", viewModel.getGlucose().floatValue()); } - ServiceResponse> vitalServiceResponse = triageService.createPatientEncounterVitalItems(newVitals, currentUser.getId(), patientEncounterItem.getId()); + ServiceResponse> vitalServiceResponse = vitalService.createPatientEncounterVitalItems(newVitals, currentUser.getId(), patientEncounterItem.getId()); if (vitalServiceResponse.hasErrors()) { throw new RuntimeException(); } diff --git a/app/femr/ui/controllers/admin/AdminController.java b/app/femr/ui/controllers/admin/AdminController.java index 1823069e5..a12e568ea 100644 --- a/app/femr/ui/controllers/admin/AdminController.java +++ b/app/femr/ui/controllers/admin/AdminController.java @@ -19,9 +19,9 @@ package femr.ui.controllers.admin; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.business.services.ISessionService; -import femr.data.models.Roles; +import femr.common.dtos.CurrentUser; +import femr.business.services.core.ISessionService; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.views.html.admin.index; diff --git a/app/femr/ui/controllers/admin/ConfigureController.java b/app/femr/ui/controllers/admin/ConfigureController.java index 5d9d20e34..6cbb8a9ee 100644 --- a/app/femr/ui/controllers/admin/ConfigureController.java +++ b/app/femr/ui/controllers/admin/ConfigureController.java @@ -19,12 +19,12 @@ package femr.ui.controllers.admin; import com.google.inject.Inject; -import femr.business.services.IConfigureService; -import femr.business.services.ISessionService; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; -import femr.data.models.ISystemSetting; -import femr.data.models.Roles; +import femr.business.services.core.IConfigureService; +import femr.business.services.core.ISessionService; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; +import femr.data.models.core.ISystemSetting; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.admin.configure.IndexViewModelGet; diff --git a/app/femr/ui/controllers/admin/InventoryController.java b/app/femr/ui/controllers/admin/InventoryController.java index 712e73b9c..727bc40ca 100644 --- a/app/femr/ui/controllers/admin/InventoryController.java +++ b/app/femr/ui/controllers/admin/InventoryController.java @@ -19,17 +19,16 @@ package femr.ui.controllers.admin; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; -import femr.business.services.IInventoryService; -import femr.business.services.ISessionService; -import femr.data.models.Roles; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; +import femr.business.services.core.IInventoryService; +import femr.business.services.core.ISessionService; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.admin.inventory.InventoryViewModelGet; import femr.common.models.MedicationItem; import femr.ui.models.admin.inventory.InventoryViewModelPost; -import femr.ui.models.admin.users.CreateViewModel; import femr.ui.views.html.admin.inventory.index; import play.data.Form; import play.mvc.Controller; diff --git a/app/femr/ui/controllers/admin/UsersController.java b/app/femr/ui/controllers/admin/UsersController.java index d1737baa5..a349aea95 100644 --- a/app/femr/ui/controllers/admin/UsersController.java +++ b/app/femr/ui/controllers/admin/UsersController.java @@ -19,13 +19,13 @@ package femr.ui.controllers.admin; import com.google.inject.Inject; -import femr.common.dto.CurrentUser; -import femr.common.dto.ServiceResponse; -import femr.business.services.IRoleService; -import femr.business.services.ISessionService; -import femr.business.services.IUserService; +import femr.common.dtos.CurrentUser; +import femr.common.dtos.ServiceResponse; +import femr.business.services.core.IRoleService; +import femr.business.services.core.ISessionService; +import femr.business.services.core.IUserService; import femr.common.models.UserItem; -import femr.data.models.Roles; +import femr.data.models.mysql.Roles; import femr.ui.helpers.security.AllowedRoles; import femr.ui.helpers.security.FEMRAuthenticated; import femr.ui.models.admin.users.*; diff --git a/app/femr/ui/helpers/security/AllowedRolesAction.java b/app/femr/ui/helpers/security/AllowedRolesAction.java index 82440bea8..d119bf7e4 100644 --- a/app/femr/ui/helpers/security/AllowedRolesAction.java +++ b/app/femr/ui/helpers/security/AllowedRolesAction.java @@ -1,8 +1,8 @@ package femr.ui.helpers.security; import com.google.inject.Inject; -import femr.business.services.IUserService; -import femr.data.models.IRole; +import femr.business.services.core.IUserService; +import femr.data.models.core.IRole; import play.libs.F; import play.mvc.Action; import play.mvc.Http; diff --git a/app/femr/ui/models/admin/users/EditViewModel.java b/app/femr/ui/models/admin/users/EditViewModel.java index 26eff0370..41392e631 100644 --- a/app/femr/ui/models/admin/users/EditViewModel.java +++ b/app/femr/ui/models/admin/users/EditViewModel.java @@ -20,7 +20,6 @@ import femr.util.stringhelpers.StringUtils; import play.data.validation.ValidationError; - import java.util.ArrayList; import java.util.List; diff --git a/app/femr/ui/models/admin/users/IndexViewModelGet.java b/app/femr/ui/models/admin/users/IndexViewModelGet.java index 078cad869..a2b0c740d 100644 --- a/app/femr/ui/models/admin/users/IndexViewModelGet.java +++ b/app/femr/ui/models/admin/users/IndexViewModelGet.java @@ -18,7 +18,7 @@ */ package femr.ui.models.admin.users; -import femr.data.models.IUser; +import femr.data.models.core.IUser; import java.util.List; public class IndexViewModelGet { diff --git a/app/femr/ui/models/superuser/TripViewModel.java b/app/femr/ui/models/superuser/TripViewModel.java new file mode 100644 index 000000000..a7e8b9492 --- /dev/null +++ b/app/femr/ui/models/superuser/TripViewModel.java @@ -0,0 +1,124 @@ +/* + fEMR - fast Electronic Medical Records + Copyright (C) 2014 Team fEMR + + fEMR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fEMR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fEMR. If not, see . If + you have any questions, contact . +*/ +package femr.ui.models.superuser; + +import femr.util.stringhelpers.StringUtils; +import play.data.validation.ValidationError; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class TripViewModel { + //team stuff + private String team; + private String teamLocation; + private String description; + //trip stuff + private String city; + private String country; + private Date startDate; + private Date endDate; + + public List validate() { + + List errors = new ArrayList<>(); + + if (StringUtils.isNullOrWhiteSpace(team)) { + errors.add(new ValidationError("team", "team name is a required field")); + } + if (StringUtils.isNullOrWhiteSpace(teamLocation)) { + errors.add(new ValidationError("teamLocation", "team location is a required field")); + } + if (StringUtils.isNullOrWhiteSpace(description)) { + errors.add(new ValidationError("description", "team description is a required field")); + } + if (StringUtils.isNullOrWhiteSpace(city)) { + errors.add(new ValidationError("city", "trip city is a required field")); + } + if (StringUtils.isNullOrWhiteSpace(country)) { + errors.add(new ValidationError("country", "trip country is a required field")); + } + if (startDate == null) { + errors.add(new ValidationError("startDate", "trip start date is a required field")); + } + if (endDate == null) { + errors.add(new ValidationError("endDate", "trip end date is a required field")); + } + + return errors.isEmpty() ? null : errors; + } + + + public String getTeam() { + return team; + } + + public void setTeam(String team) { + this.team = team; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public String getTeamLocation() { + return teamLocation; + } + + public void setTeamLocation(String teamLocation) { + this.teamLocation = teamLocation; + } +} diff --git a/app/femr/ui/views/admin/configure/index.scala.html b/app/femr/ui/views/admin/configure/index.scala.html index 83253ac4f..12112ff41 100644 --- a/app/femr/ui/views/admin/configure/index.scala.html +++ b/app/femr/ui/views/admin/configure/index.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, viewModel: femr.ui.models.admin.configure.IndexViewModelGet) +@(currentUser: femr.common.dtos.CurrentUser, viewModel: femr.ui.models.admin.configure.IndexViewModelGet) @import femr.ui.controllers.admin.routes.ConfigureController @import femr.ui.views.html.layouts.admin diff --git a/app/femr/ui/views/admin/index.scala.html b/app/femr/ui/views/admin/index.scala.html index f8ca98c4c..bb79a12d0 100644 --- a/app/femr/ui/views/admin/index.scala.html +++ b/app/femr/ui/views/admin/index.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser) +@(currentUser: femr.common.dtos.CurrentUser) @import femr.ui.views.html.layouts.admin diff --git a/app/femr/ui/views/admin/inventory/index.scala.html b/app/femr/ui/views/admin/inventory/index.scala.html index 24f3c1348..04d2c04a3 100644 --- a/app/femr/ui/views/admin/inventory/index.scala.html +++ b/app/femr/ui/views/admin/inventory/index.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, viewModel: femr.ui.models.admin.inventory.InventoryViewModelGet) +@(currentUser: femr.common.dtos.CurrentUser, viewModel: femr.ui.models.admin.inventory.InventoryViewModelGet) @import femr.ui.controllers.admin.routes.InventoryController diff --git a/app/femr/ui/views/admin/users/create.scala.html b/app/femr/ui/views/admin/users/create.scala.html index b06bcaf0f..026e22d90 100644 --- a/app/femr/ui/views/admin/users/create.scala.html +++ b/app/femr/ui/views/admin/users/create.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, form: play.data.Form[femr.ui.models.admin.users.CreateViewModel], messages: java.util.List[_ <: java.lang.String], availableRoles: java.util.List[_ <: java.lang.String]) +@(currentUser: femr.common.dtos.CurrentUser, form: play.data.Form[femr.ui.models.admin.users.CreateViewModel], messages: java.util.List[_ <: java.lang.String], availableRoles: java.util.List[_ <: java.lang.String]) @import views.html.helper.FieldConstructor @import femr.ui.views.html.partials.admin.inputFieldConstructor diff --git a/app/femr/ui/views/admin/users/edit.scala.html b/app/femr/ui/views/admin/users/edit.scala.html index 609590bd3..1ee67fb2f 100644 --- a/app/femr/ui/views/admin/users/edit.scala.html +++ b/app/femr/ui/views/admin/users/edit.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, form: play.data.Form[femr.ui.models.admin.users.EditViewModel], availableRoles: java.util.List[_ <: java.lang.String], messages: java.util.List[_ <: java.lang.String]) +@(currentUser: femr.common.dtos.CurrentUser, form: play.data.Form[femr.ui.models.admin.users.EditViewModel], availableRoles: java.util.List[_ <: java.lang.String], messages: java.util.List[_ <: java.lang.String]) @import views.html.helper.FieldConstructor @import femr.ui.views.html.partials.admin.inputFieldConstructor diff --git a/app/femr/ui/views/admin/users/manage.scala.html b/app/femr/ui/views/admin/users/manage.scala.html index 57c0de4fa..49d1a5794 100644 --- a/app/femr/ui/views/admin/users/manage.scala.html +++ b/app/femr/ui/views/admin/users/manage.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, viewModelGet: femr.ui.models.admin.users.ManageViewModelGet) +@(currentUser: femr.common.dtos.CurrentUser, viewModelGet: femr.ui.models.admin.users.ManageViewModelGet) @import femr.ui.controllers.admin.routes.UsersController @import femr.ui.views.html.layouts.admin diff --git a/app/femr/ui/views/history/indexEncounter.scala.html b/app/femr/ui/views/history/indexEncounter.scala.html index a84cb103e..2e132be09 100644 --- a/app/femr/ui/views/history/indexEncounter.scala.html +++ b/app/femr/ui/views/history/indexEncounter.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, viewModel: femr.ui.models.history.IndexEncounterViewModel, viewModelMedical: femr.ui.models.history.IndexEncounterMedicalViewModel, viewModelPharmacy: femr.ui.models.history.IndexEncounterPharmacyViewModel) +@(currentUser: femr.common.dtos.CurrentUser, viewModel: femr.ui.models.history.IndexEncounterViewModel, viewModelMedical: femr.ui.models.history.IndexEncounterMedicalViewModel, viewModelPharmacy: femr.ui.models.history.IndexEncounterPharmacyViewModel) @import femr.ui.views.html.layouts.main @import femr.ui.views.html.partials.helpers.outputFloatOrNA diff --git a/app/femr/ui/views/history/indexPatient.scala.html b/app/femr/ui/views/history/indexPatient.scala.html index d65bdbf88..700b37f92 100644 --- a/app/femr/ui/views/history/indexPatient.scala.html +++ b/app/femr/ui/views/history/indexPatient.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, searchError: java.lang.Boolean, viewModel: femr.ui.models.history.IndexPatientViewModelGet, patientEncounters: List[_ <: femr.common.models.PatientEncounterItem]) +@(currentUser: femr.common.dtos.CurrentUser, searchError: java.lang.Boolean, viewModel: femr.ui.models.history.IndexPatientViewModelGet, patientEncounters: List[_ <: femr.common.models.PatientEncounterItem]) @import femr.ui.views.html.layouts.main @import femr.ui.views.html.partials.search diff --git a/app/femr/ui/views/home/index.scala.html b/app/femr/ui/views/home/index.scala.html index 985263a2d..1dc1e422e 100644 --- a/app/femr/ui/views/home/index.scala.html +++ b/app/femr/ui/views/home/index.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser) +@(currentUser: femr.common.dtos.CurrentUser) @import femr.ui.views.html.layouts.main diff --git a/app/femr/ui/views/layouts/admin.scala.html b/app/femr/ui/views/layouts/admin.scala.html index 08b91c0e8..b0c10d181 100644 --- a/app/femr/ui/views/layouts/admin.scala.html +++ b/app/femr/ui/views/layouts/admin.scala.html @@ -1,6 +1,5 @@ @(title: String, - currentUser: femr.common.dto.CurrentUser, - menus: Html = Html(""), + currentUser: femr.common.dtos.CurrentUser, styles: Html = Html(""), scripts: Html = Html(""), message: Html = Html(""), @@ -11,6 +10,7 @@ @import femr.ui.controllers.admin.routes.UsersController @import femr.ui.controllers.admin.routes.ConfigureController @import femr.ui.controllers.admin.routes.InventoryController +@import femr.ui.controllers.routes.SuperuserController @* Add scripts to script variable, how? *@ @adminScripts = { @@ -31,13 +31,16 @@ Manage Users Add User + + Configure @if(currentUser.getEmail == "superuser"){ Inventory Tracking + + Trip Tracking + + Manage Tabs } - - Configure - @menus

diff --git a/app/femr/ui/views/layouts/main.scala.html b/app/femr/ui/views/layouts/main.scala.html index 8cf8bb3d8..7936c1908 100644 --- a/app/femr/ui/views/layouts/main.scala.html +++ b/app/femr/ui/views/layouts/main.scala.html @@ -1,4 +1,4 @@ -@(title: String, currentUser: femr.common.dto.CurrentUser = null, +@(title: String, currentUser: femr.common.dtos.CurrentUser = null, styles: Html = Html(""), scripts: Html = Html(""), outsideContainerTop: Html = Html(""), outsideContainerBottom: Html = Html(""), search: Html = Html(""))(content: Html) diff --git a/app/femr/ui/views/medical/edit.scala.html b/app/femr/ui/views/medical/edit.scala.html index 3b56cf6b5..b55925585 100644 --- a/app/femr/ui/views/medical/edit.scala.html +++ b/app/femr/ui/views/medical/edit.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, viewModel: femr.ui.models.medical.EditViewModelGet) +@(currentUser: femr.common.dtos.CurrentUser, viewModel: femr.ui.models.medical.EditViewModelGet) @import femr.ui.controllers.routes.MedicalController diff --git a/app/femr/ui/views/medical/index.scala.html b/app/femr/ui/views/medical/index.scala.html index e4bae3174..98630046c 100644 --- a/app/femr/ui/views/medical/index.scala.html +++ b/app/femr/ui/views/medical/index.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser, message: java.lang.String, patientId: java.lang.Integer) +@(currentUser: femr.common.dtos.CurrentUser, message: java.lang.String, patientId: java.lang.Integer) @import femr.ui.controllers.routes.MedicalController @import femr.ui.views.html.layouts.main diff --git a/app/femr/ui/views/partials/authenticated.scala.html b/app/femr/ui/views/partials/authenticated.scala.html index 24dd37a13..1069288f2 100644 --- a/app/femr/ui/views/partials/authenticated.scala.html +++ b/app/femr/ui/views/partials/authenticated.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser) +@(currentUser: femr.common.dtos.CurrentUser) @import femr.ui.views.partials.helpers.AuthenticatedPartialHelper @import femr.ui.controllers.routes.HomeController diff --git a/app/femr/ui/views/partials/header.scala.html b/app/femr/ui/views/partials/header.scala.html index 01fc1c059..b159bf101 100644 --- a/app/femr/ui/views/partials/header.scala.html +++ b/app/femr/ui/views/partials/header.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dto.CurrentUser = null) +@(currentUser: femr.common.dtos.CurrentUser = null) @import femr.ui.views.html.partials
} diff --git a/app/femr/ui/views/partials/admin/toggleButton.scala.html b/app/femr/ui/views/partials/admin/toggleButton.scala.html index 44f5030ea..d9988ca69 100644 --- a/app/femr/ui/views/partials/admin/toggleButton.scala.html +++ b/app/femr/ui/views/partials/admin/toggleButton.scala.html @@ -1,10 +1,7 @@ -@(user: femr.common.models.UserItem) +@(isDeactivate: java.lang.Boolean, id: java.lang.Integer) -@*Make sure you can't remove the master admin account*@ -@if(user.getEmail != "admin") { - @if(!user.isDeleted) { - - } else { - - } -} \ No newline at end of file +@if(isDeactivate) { + +} else { + +} diff --git a/app/femr/ui/views/superuser/trips.scala.html b/app/femr/ui/views/superuser/trips.scala.html index e113dd06b..34e522978 100644 --- a/app/femr/ui/views/superuser/trips.scala.html +++ b/app/femr/ui/views/superuser/trips.scala.html @@ -1,4 +1,4 @@ -@(currentUser: femr.common.dtos.CurrentUser, form: play.data.Form[femr.ui.models.superuser.TripViewModel]) +@(currentUser: femr.common.dtos.CurrentUser, viewModel: femr.ui.models.superuser.TripViewModelGet) @import femr.ui.controllers.routes.SuperuserController @import femr.ui.views.html.layouts.admin @@ -7,90 +7,144 @@ } -@admin("SuperUser Panel", currentUser, styles = additionalStyles) { + @additionalScripts = { + + } + +@admin("SuperUser Panel", currentUser, styles = additionalStyles, scripts = additionalScripts) { @helper.form(action = SuperuserController.tripsPost()) { -

-Changing the 'Team Name' will create a new team.

-

-Changing anything related to the Trip will create a new Trip.

-
-
-

Current Team:

- @helper.inputText(form("team"), - 'class -> "fInput", - 'placeholder -> "Team Name", - '_label -> "Team Name:" - ) - @helper.inputText(form("teamLocation"), - 'class -> "fInput", - 'placeholder -> "Team Location", - '_label -> "Team Location:" - ) - @helper.inputText(form("description"), - 'class -> "fInput", - 'placeholder -> "Description", - '_label -> "Team Description:" - ) +

This is where a superuser can identify the trip that is currently underway. The trip, when defined, will be linked to + the patient's encounter. The submit button will create a new team and/or city and/or trip depending on which required fields + are filled out.

+
+

Add Team:

+
+ +
+ +
+
-
-

Current Trip:

- @helper.inputText(form("city"), - 'class -> "fInput", - 'placeholder -> "City", - '_label -> "Trip City:" - ) - @helper.inputText(form("country"), - 'class -> "fInput", - 'placeholder -> "Country", - '_label -> "Trip Country:" - ) - - @helper.inputDate(form("startDate"), - '_label -> "Trip Start Date" - ) - @helper.inputDate(form("endDate"), - '_label -> "Trip End Date" - ) + +
+ +
+ +

Add City:

+
+
+ +
+ +
+
- +
+

Add Trip:

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
} - @* helper.form(action = SuperuserController.tripsPost()) { -

Change Trip:

-
-
- - - -
-
- - - -
-
- - - -
+
+ +
'+xAxisTitle+'Number of Patients
'+mapGraphData(obj.primaryName, valueMap)+''+obj.primaryValue+'
@viewModelGet.getUser(y - 1).getLastLoginDate - @toggleButton(viewModelGet.getUser(y - 1)) + @defining(viewModelGet.getUser(y - 1)) { user => + @if(user.getEmail != "admin") { + @toggleButton(!user.isDeleted, user.getId) + } + + } +
+ + + + + + + + + + + + @for(i <- 1 to viewModel.getMissionItems.size) { + @defining(viewModel.getMissionItems.get(i - 1)) { missionItem => + @for(tripIndex <- 1 to missionItem.getMissionTrips.size) { + @defining(missionItem.getMissionTrips.get(tripIndex - 1)) { missionTrip => + + + + + + + + + } + } + } + } + +
TeamCountryCityStart DateEnd DateisCurrent
@missionItem.getTeamName@missionTrip.getTripCountry@missionTrip.getTripCity@missionTrip.getFriendlyTripStartDate@missionTrip.getFriendlyTripEndDate + @if(missionTrip.isCurrent) { + Yes + } else { + + + } +
+
- } - *@ } diff --git a/app/femr/util/calculations/dateUtils.java b/app/femr/util/calculations/dateUtils.java index d46e887b7..43bef4db0 100644 --- a/app/femr/util/calculations/dateUtils.java +++ b/app/femr/util/calculations/dateUtils.java @@ -67,13 +67,17 @@ public static float getAgeFloat(Date born) { return result/12; } - public static String getFriendlyDate(DateTime dateTime){ + if (dateTime == null) + return null; DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM d, yyyy - HH:mm:ss"); String dtStr = dateTime.toString(fmt); return dtStr; } + public static String getFriendlyDate(Date date){ + if (date == null) + return null; DateFormat df = new SimpleDateFormat("MM/dd/yyyy"); String dStr = df.format(date); return dStr; diff --git a/app/femr/util/startup/DatabaseSeeder.java b/app/femr/util/startup/DatabaseSeeder.java index 692195eb1..d79587886 100644 --- a/app/femr/util/startup/DatabaseSeeder.java +++ b/app/femr/util/startup/DatabaseSeeder.java @@ -78,23 +78,216 @@ public void seed() { private void seedMissionTripInformation() { //mission countries List missionCountries = missionCountryRepository.findAll(MissionCountry.class); + List availableCountries = new ArrayList<>(); + + availableCountries.add("Afghanistan"); + availableCountries.add("Albania"); + availableCountries.add("Algeria"); + availableCountries.add("Andorra"); + availableCountries.add("Angola"); + availableCountries.add("Antigua & Deps"); + availableCountries.add("Argentina"); + availableCountries.add("Armenia"); + availableCountries.add("Australia"); + availableCountries.add("Austria"); + availableCountries.add("Azerbaijan"); + availableCountries.add("Bahamas"); + availableCountries.add("Bahrain"); + availableCountries.add("Bangladesh"); + availableCountries.add("Barbados"); + availableCountries.add("Belarus"); + availableCountries.add("Belgium"); + availableCountries.add("Belize"); + availableCountries.add("Benin"); + availableCountries.add("Bhutan"); + availableCountries.add("Bolivia"); + availableCountries.add("Bosnia Herzegovina"); + availableCountries.add("Botswana"); + availableCountries.add("Brazil"); + availableCountries.add("Brunei"); + availableCountries.add("Bulgaria"); + availableCountries.add("Burkina"); + availableCountries.add("Burundi"); + availableCountries.add("Cambodia"); + availableCountries.add("Cameroon"); + availableCountries.add("Canada"); + availableCountries.add("Cape Verde"); + availableCountries.add("Central African Rep"); + availableCountries.add("Chad"); + availableCountries.add("Chile"); + availableCountries.add("China"); + availableCountries.add("Colombia"); + availableCountries.add("Comoros"); + availableCountries.add("Congo"); + availableCountries.add("Congo {Democratic Rep}"); + availableCountries.add("Costa Rica"); + availableCountries.add("Croatia"); + availableCountries.add("Cuba"); + availableCountries.add("Cyprus"); + availableCountries.add("Czech Republic"); + availableCountries.add("Denmark"); + availableCountries.add("Djibouti"); + availableCountries.add("Dominica"); + availableCountries.add("Dominican Republic"); + availableCountries.add("East Timor"); + availableCountries.add("Ecuador"); + availableCountries.add("Egypt"); + availableCountries.add("El Salvador"); + availableCountries.add("Equatorial Guinea"); + availableCountries.add("Eritrea"); + availableCountries.add("Estonia"); + availableCountries.add("Ethiopia"); + availableCountries.add("Fiji"); + availableCountries.add("Finland"); + availableCountries.add("France"); + availableCountries.add("Gabon"); + availableCountries.add("Gambia"); + availableCountries.add("Georgia"); + availableCountries.add("Germany"); + availableCountries.add("Ghana"); + availableCountries.add("Greece"); + availableCountries.add("Grenada"); + availableCountries.add("Guatemala"); + availableCountries.add("Guinea"); + availableCountries.add("Guinea-Bissau"); + availableCountries.add("Guyana"); + availableCountries.add("Haiti"); + availableCountries.add("Honduras"); + availableCountries.add("Hungary"); + availableCountries.add("Iceland"); + availableCountries.add("India"); + availableCountries.add("Indonesia"); + availableCountries.add("Iran"); + availableCountries.add("Iraq"); + availableCountries.add("Ireland {Republic}"); + availableCountries.add("Israel"); + availableCountries.add("Italy"); + availableCountries.add("Ivory Coast"); + availableCountries.add("Jamaica"); + availableCountries.add("Japan"); + availableCountries.add("Jordan"); + availableCountries.add("Kazakhstan"); + availableCountries.add("Kenya"); + availableCountries.add("Kiribati"); + availableCountries.add("Korea North"); + availableCountries.add("Korea South"); + availableCountries.add("Kosovo"); + availableCountries.add("Kuwait"); + availableCountries.add("Kyrgyzstan"); + availableCountries.add("Laos"); + availableCountries.add("Latvia"); + availableCountries.add("Lebanon"); + availableCountries.add("Lesotho"); + availableCountries.add("Liberia"); + availableCountries.add("Libya"); + availableCountries.add("Liechtenstein"); + availableCountries.add("Lithuania"); + availableCountries.add("Luxembourg"); + availableCountries.add("Macedonia"); + availableCountries.add("Madagascar"); + availableCountries.add("Malawi"); + availableCountries.add("Malaysia"); + availableCountries.add("Maldives"); + availableCountries.add("Mali"); + availableCountries.add("Malta"); + availableCountries.add("Marshall Islands"); + availableCountries.add("Mauritania"); + availableCountries.add("Mauritius"); + availableCountries.add("Mexico"); + availableCountries.add("Micronesia"); + availableCountries.add("Moldova"); + availableCountries.add("Monaco"); + availableCountries.add("Mongolia"); + availableCountries.add("Montenegro"); + availableCountries.add("Morocco"); + availableCountries.add("Mozambique"); + availableCountries.add("Myanmar, {Burma}"); + availableCountries.add("Namibia"); + availableCountries.add("Nauru"); + availableCountries.add("Nepal"); + availableCountries.add("Netherlands"); + availableCountries.add("New Zealand"); + availableCountries.add("Nicaragua"); + availableCountries.add("Niger"); + availableCountries.add("Nigeria"); + availableCountries.add("Norway"); + availableCountries.add("Oman"); + availableCountries.add("Pakistan"); + availableCountries.add("Palau"); + availableCountries.add("Panama"); + availableCountries.add("Papua New Guinea"); + availableCountries.add("Paraguay"); + availableCountries.add("Peru"); + availableCountries.add("Philippines"); + availableCountries.add("Poland"); + availableCountries.add("Portugal"); + availableCountries.add("Qatar"); + availableCountries.add("Romania"); + availableCountries.add("Russian Federation"); + availableCountries.add("Rwanda"); + availableCountries.add("St Kitts & Nevis"); + availableCountries.add("St Lucia"); + availableCountries.add("Saint Vincent & the Grenadines"); + availableCountries.add("Samoa"); + availableCountries.add("San Marino"); + availableCountries.add("Sao Tome & Principe"); + availableCountries.add("Saudi Arabia"); + availableCountries.add("Senegal"); + availableCountries.add("Serbia"); + availableCountries.add("Seychelles"); + availableCountries.add("Sierra Leone"); + availableCountries.add("Singapore"); + availableCountries.add("Slovakia"); + availableCountries.add("Slovenia"); + availableCountries.add("Solomon Islands"); + availableCountries.add("Somalia"); + availableCountries.add("South Africa"); + availableCountries.add("South Sudan"); + availableCountries.add("Spain"); + availableCountries.add("Sri Lanka"); + availableCountries.add("Sudan"); + availableCountries.add("Suriname"); + availableCountries.add("Swaziland"); + availableCountries.add("Sweden"); + availableCountries.add("Switzerland"); + availableCountries.add("Syria"); + availableCountries.add("Taiwan"); + availableCountries.add("Tajikistan"); + availableCountries.add("Tanzania"); + availableCountries.add("Thailand"); + availableCountries.add("Togo"); + availableCountries.add("Tonga"); + availableCountries.add("Trinidad & Tobago"); + availableCountries.add("Tunisia"); + availableCountries.add("Turkey"); + availableCountries.add("Turkmenistan"); + availableCountries.add("Tuvalu"); + availableCountries.add("Uganda"); + availableCountries.add("Ukraine"); + availableCountries.add("United Arab Emirates"); + availableCountries.add("United Kingdom"); + availableCountries.add("USA"); + availableCountries.add("Uruguay"); + availableCountries.add("Uzbekistan"); + availableCountries.add("Vanuatu"); + availableCountries.add("Vatican City"); + availableCountries.add("Venezuela"); + availableCountries.add("Vietnam"); + availableCountries.add("Yemen"); + availableCountries.add("Zambia"); + availableCountries.add("Zimbabwe"); + List newMissionCountries = new ArrayList<>(); MissionCountry missionCountry; - if (missionCountries != null && !containMissionCountry(missionCountries, "Haiti")) { - missionCountry = new MissionCountry(); - missionCountry.setName("Haiti"); - newMissionCountries.add(missionCountry); - } - if (missionCountries != null && !containMissionCountry(missionCountries, "Ecuador")) { - missionCountry = new MissionCountry(); - missionCountry.setName("Ecuador"); - newMissionCountries.add(missionCountry); - } - if (missionCountries != null && !containMissionCountry(missionCountries, "USA")) { - missionCountry = new MissionCountry(); - missionCountry.setName("USA"); - newMissionCountries.add(missionCountry); + + for (String country : availableCountries){ + if (missionCountries != null && !containMissionCountry(missionCountries, country)) { + missionCountry = new MissionCountry(); + missionCountry.setName(country); + newMissionCountries.add(missionCountry); + } } + missionCountryRepository.createAll(newMissionCountries); missionCountries = missionCountryRepository.findAll(MissionCountry.class); diff --git a/app/femr/util/startup/Global.java b/app/femr/util/startup/Global.java index 1d1547972..0a9fbb256 100644 --- a/app/femr/util/startup/Global.java +++ b/app/femr/util/startup/Global.java @@ -31,13 +31,13 @@ import static play.mvc.Results.*; import femr.ui.views.html.errors.global; - public class Global extends GlobalSettings { private static final Injector INJECTOR = createInjector(); @Override public void onStart(Application app) { + super.onStart(app); new DatabaseSeeder().seed(); } diff --git a/conf/application.conf b/conf/application.conf index ca3f9355e..9d2ca85e4 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -12,7 +12,7 @@ application.langs="en" application.global="femr.util.startup.Global" db.default.driver="com.mysql.jdbc.Driver" -db.default.url="jdbc:mysql://localhost/femr_final?characterEncoding=UTF-8" +db.default.url="jdbc:mysql://localhost/femr?characterEncoding=UTF-8" db.default.user="sa" db.default.password="" diff --git a/conf/routes b/conf/routes index 60aa984d5..8ca70a8c5 100644 --- a/conf/routes +++ b/conf/routes @@ -9,7 +9,7 @@ GET /superuser/tabs @femr.ui.controllers.SuperuserContr POST /superuser/tabs @femr.ui.controllers.SuperuserController.tabsPost() GET /superuser/trips @femr.ui.controllers.SuperuserController.tripsGet() POST /superuser/trips @femr.ui.controllers.SuperuserController.tripsPost() -GET /superuser/typeahead @femr.ui.controllers.SuperuserController.typeaheadJSONGet() +POST /superuser/trips/:id @femr.ui.controllers.SuperuserController.toggleCurrentTripPost(id: Integer) GET /superuser @femr.ui.controllers.SuperuserController.indexGet() #Admin POST /admin/users/create @femr.ui.controllers.admin.UsersController.createPost() diff --git a/public/css/superuser.css b/public/css/superuser.css index e6ee00a29..8f7f68226 100644 --- a/public/css/superuser.css +++ b/public/css/superuser.css @@ -37,11 +37,25 @@ table th,td{ /*trip tracking*/ -#teamWrap, #tripWrap{ +#addTeamWrap{ width: 50%; float: left; } -#teamTripWrap{ +#addTeamWrap div{ + width: 100%; + float: left; +} +#addTripWrap{ + width: 50%; + float: left; +} +#currentWrap{ + width: 100%; + float: left; + margin-top: 40px; +} +#submitWrap{ + margin-top: 40px; + float: left; width: 100%; - overflow: hidden; } \ No newline at end of file diff --git a/public/js/admin/users.js b/public/js/admin/users.js index 055e92f3b..b801d62e5 100644 --- a/public/js/admin/users.js +++ b/public/js/admin/users.js @@ -25,7 +25,7 @@ var manageUsers = { }, toggleUser: function (btn) { //user ID - var id = $(btn).attr('data-user_id'); + var id = $(btn).attr('data-id'); $.ajax({ url: '/admin/users/toggle/' + id, diff --git a/public/js/superuser/superuser.js b/public/js/superuser/superuser.js new file mode 100644 index 000000000..b5b8cd129 --- /dev/null +++ b/public/js/superuser/superuser.js @@ -0,0 +1,20 @@ + + +$(document).ready(function(){ + $("[name='newTripCity']").change(function(){ + + $("[name='newTripCountry']").val($(this).find(':selected').attr('country-name')); + }); + + $('.currentButton').click(function(){ + + $.ajax({ + type: "POST", + url: "/superuser/trips/" + $(this).parent().find('input').val(), + success: function(data){ + window.location.href = 'trips' + } + }); + + }); +}); \ No newline at end of file From a2a01d81d14fc014277c1f87be5ea07c79e6ae97 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Thu, 15 Jan 2015 17:46:53 -0500 Subject: [PATCH 13/21] fix black picture issue --- public/js/triage/triage.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/public/js/triage/triage.js b/public/js/triage/triage.js index 6a554e7d5..ae29a1445 100644 --- a/public/js/triage/triage.js +++ b/public/js/triage/triage.js @@ -223,9 +223,12 @@ var patientPhotoFeature = { }, prepareForPOST: function () { //0.5 is the quality downgrade. - var dataURL = document.getElementById('patientPhotoCanvas').toDataURL("image/jpeg", 0.5); - $('#photoInputCropped').val(dataURL); - $('#photoInput').remove();//remove file upload from DOM so it's not submitted in POST + var canvas = document.getElementById('patientPhotoCanvas'); + if (!isCanvasBlank(canvas)){ + var dataURL = canvas.toDataURL("image/jpeg", 0.5); + $('#photoInputCropped').val(dataURL); + $('#photoInput').remove();//remove file upload from DOM so it's not submitted in POST + } } }; @@ -521,6 +524,14 @@ $(document).ready(function () { }); +function isCanvasBlank(canvas){ + var blank = document.createElement('canvas'); + blank.width = canvas.width; + blank.height = canvas.height; + + return canvas.toDataURL() == blank.toDataURL(); +} + $(function () { Date.prototype.toYMD = Date_toYMD; From 7fa180d5742b7dabe329e2bb37cefeeee1606656 Mon Sep 17 00:00:00 2001 From: Ken Dunlap Date: Mon, 19 Jan 2015 22:31:07 -0500 Subject: [PATCH 14/21] Added diagnoses typeahead to treatment tab fields --- .../partials/medical/problemField.scala.html | 16 +++-- public/js/medical/medical.js | 60 ++++++++----------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/app/femr/ui/views/partials/medical/problemField.scala.html b/app/femr/ui/views/partials/medical/problemField.scala.html index 420e6a2da..ec8f50117 100644 --- a/app/femr/ui/views/partials/medical/problemField.scala.html +++ b/app/femr/ui/views/partials/medical/problemField.scala.html @@ -2,14 +2,22 @@ @if(number == 1) { @if(value != null) { - +
+ +
} else { - +
+ +
} } else { @if(value != null) { - +
+ +
} else { - + } } \ No newline at end of file diff --git a/public/js/medical/medical.js b/public/js/medical/medical.js index 338f2b730..af590e129 100644 --- a/public/js/medical/medical.js +++ b/public/js/medical/medical.js @@ -111,12 +111,6 @@ var multipleChiefComplaintFeature = { $(document).ready(function () { - // GET DIAGNOSES - $.getJSON("/search/typeahead/diagnoses", function (data) { - console.log(data); - }); - - //set a global variable to track browser compatibility with image previews window.isFileReader = typeof FileReader !== 'undefined'; @@ -170,8 +164,8 @@ $(document).ready(function () { } else { return; } - $("#problem" + $("body").data("prob")).removeClass("hidden"); - $("#problem" + $("body").data("prob")).focus(); + $("#problem" + $("body").data("prob") + "-container").removeClass("hidden"); + $("#problem" + $("body").data("prob") + "-container").focus(); return; }); @@ -179,9 +173,9 @@ $(document).ready(function () { if (typeof $("body").data("prob") === "undefined") { return; } else if ($("body").data("prob") > 1) { - $("#problem" + $("body").data("prob")).addClass("hidden"); - $("#problem" + ($("body").data("prob"))).val(''); - $("#problem" + ($("body").data("prob") - 1)).focus(); + $("#problem" + $("body").data("prob") + "-container").addClass("hidden"); + $("#problem" + ($("body").data("prob")) + "-container").val(''); + $("#problem" + ($("body").data("prob") - 1) + "-container").focus(); $("body").data("prob", $("body").data("prob") - 1); } return; @@ -257,13 +251,7 @@ $(document).ready(function () { return photoNameFixup() && validate(); //validate from medicalClientValidation.js }); - - $("input.problem").each(function(){ - - //if( !$(this).hasClass("hidden") ) { - registerTypeAhead(this); - //} - }); + registerTypeAhead(); }); @@ -550,28 +538,28 @@ function registerTypeAhead(obj){ }; }; - var diagnoses = []; - // Get Patients from server - //$.getJSON("/search/typeahead/patients", function (data) { - - //diagnoses = data; - diagnoses = ["test1", "test2", "test3"]; - - $(obj).typeahead({ - hint: true, - highlight: true, - minLength: 1 - }, - { - name: 'dianoses', - displayKey: 'value', - source: substringMatcher(diagnoses) - }); + // get diagnoses, register typeahead on response + $.getJSON("/search/typeahead/diagnoses", function (data) { + + diagnoses = data; + $(".problem").find('input[type="text"]').each(function(){ - //}); + $(this).typeahead({ + hint: true, + highlight: true, + minLength: 1 + }, + { + name: 'dianoses', + displayKey: 'value', + source: substringMatcher(diagnoses) + }); + }); + + }); } From 7a932c38855bdfe7d15ddfb27109155281e725a0 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Tue, 20 Jan 2015 19:08:44 -0500 Subject: [PATCH 15/21] show prescribers name under each prescription --- .../services/system/SearchService.java | 2 ++ app/femr/common/models/PrescriptionItem.java | 19 +++++++++++++++- app/femr/ui/views/pharmacies/edit.scala.html | 22 ++++++++++++++----- public/css/pharmacy.css | 4 ++++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/femr/business/services/system/SearchService.java b/app/femr/business/services/system/SearchService.java index 1269982b0..88485ab6c 100644 --- a/app/femr/business/services/system/SearchService.java +++ b/app/femr/business/services/system/SearchService.java @@ -249,6 +249,8 @@ public ServiceResponse> findUnreplacedPrescriptionItems(i PrescriptionItem prescriptionItem = new PrescriptionItem(); prescriptionItem.setName(pp.getMedication().getName()); prescriptionItem.setId(pp.getId()); + prescriptionItem.setPrescriberFirstName(pp.getPhysician().getFirstName()); + prescriptionItem.setPrescriberLastName(pp.getPhysician().getLastName()); prescriptionItems.add(prescriptionItem); } diff --git a/app/femr/common/models/PrescriptionItem.java b/app/femr/common/models/PrescriptionItem.java index 4804a6f46..3cfc8897e 100644 --- a/app/femr/common/models/PrescriptionItem.java +++ b/app/femr/common/models/PrescriptionItem.java @@ -22,7 +22,8 @@ public class PrescriptionItem { private int id; private String name; private Integer replacementId; - + private String prescriberFirstName; + private String prescriberLastName; public PrescriptionItem(String name){ this.name = name; @@ -55,4 +56,20 @@ public Integer getReplacementId() { public void setReplacementId(Integer replacementId) { this.replacementId = replacementId; } + + public String getPrescriberFirstName() { + return prescriberFirstName; + } + + public void setPrescriberFirstName(String prescriberFirstName) { + this.prescriberFirstName = prescriberFirstName; + } + + public String getPrescriberLastName() { + return prescriberLastName; + } + + public void setPrescriberLastName(String prescriberLastName) { + this.prescriberLastName = prescriberLastName; + } } diff --git a/app/femr/ui/views/pharmacies/edit.scala.html b/app/femr/ui/views/pharmacies/edit.scala.html index cb7ac3a8a..0adcf7015 100644 --- a/app/femr/ui/views/pharmacies/edit.scala.html +++ b/app/femr/ui/views/pharmacies/edit.scala.html @@ -34,6 +34,7 @@

List of Diagnoses

@for(y <- 1 to viewModel.getProblems.size) { @if(viewModel.getProblems.get(y - 1) != null) {
  • @viewModel.getProblems.get(y - 1).getName
  • + } } } @@ -47,12 +48,21 @@

    List of Diagnoses

    List of Medications

      @for(x <- 1 to viewModel.getMedications.size) { -
    1. - - @viewModel.getMedications.get(x - 1).getName - - -
    2. + @defining(viewModel.getMedications.get(x - 1)) { medication => +
    3. + +
      + @medication.getName +
      +
      + + Prescriber: @medication.getPrescriberLastName, @medication.getPrescriberFirstName +
      + + +
    4. + } + }
    diff --git a/public/css/pharmacy.css b/public/css/pharmacy.css index a61f474d5..01a8ab53a 100644 --- a/public/css/pharmacy.css +++ b/public/css/pharmacy.css @@ -182,4 +182,8 @@ input[style] { #submitWrap { width: 100%; float: right; +} + +#prescriber{ + font-size: 10px; } \ No newline at end of file From 31ab29dc2c78eb93aba6c19a77eea75fa728afe7 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Thu, 29 Jan 2015 19:47:47 -0500 Subject: [PATCH 16/21] hotfix for mysql 5.6 changes --- conf/evolutions/default/68.sql | 16 ---------------- conf/evolutions/default/72.sql | 3 ++- conf/evolutions/default/73.sql | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 conf/evolutions/default/73.sql diff --git a/conf/evolutions/default/68.sql b/conf/evolutions/default/68.sql index dcdebc3d8..7bd416ec5 100644 --- a/conf/evolutions/default/68.sql +++ b/conf/evolutions/default/68.sql @@ -10,27 +10,11 @@ CREATE TABLE `patient_age_classifications` ( UNIQUE INDEX `name_UNIQUE` (`name` ASC), UNIQUE INDEX `description_UNIQUE` (`description` ASC)); -ALTER TABLE `patient_encounters` -ADD COLUMN `patient_age_classification_id` INT(11) NULL AFTER `user_id_pharmacy`, -ADD INDEX `fk_patient_encounter_patient_age_classification_id_idx` (`patient_age_classification_id` ASC); -ALTER TABLE `patient_encounters` -ADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id` -FOREIGN KEY (`patient_age_classification_id`) -REFERENCES `patient_age_classifications` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION; - ALTER TABLE `patients` CHANGE COLUMN `age` `age` DATE NULL ; # --- !Downs -ALTER TABLE `patient_encounters` -DROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`; -ALTER TABLE `patient_encounters` -DROP COLUMN `patient_age_classification_id`, -DROP INDEX `fk_patient_encounter_patient_age_classification_id_idx` ; - DROP TABLE `patient_age_classifications`; ALTER TABLE `patients` diff --git a/conf/evolutions/default/72.sql b/conf/evolutions/default/72.sql index 6da8948bc..44962ebe1 100644 --- a/conf/evolutions/default/72.sql +++ b/conf/evolutions/default/72.sql @@ -12,4 +12,5 @@ CHANGE COLUMN `id` `id` INT(11) NOT NULL AUTO_INCREMENT ; # --- !Downs -DROP TABLE `diagnoses`; \ No newline at end of file +DROP TABLE `diagnoses`; + diff --git a/conf/evolutions/default/73.sql b/conf/evolutions/default/73.sql new file mode 100644 index 000000000..23c6c99de --- /dev/null +++ b/conf/evolutions/default/73.sql @@ -0,0 +1,19 @@ +# --- !Ups + +ALTER TABLE `patient_encounters` +ADD COLUMN `patient_age_classification_id` INT(11) NULL AFTER `user_id_pharmacy`, +ADD INDEX `fk_patient_encounter_patient_age_classification_id_idx` (`patient_age_classification_id` ASC); +ALTER TABLE `patient_encounters` +ADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id` +FOREIGN KEY (`patient_age_classification_id`) +REFERENCES `patient_age_classifications` (`id`) +ON DELETE NO ACTION +ON UPDATE NO ACTION; + +# --- !Downs + +ALTER TABLE `patient_encounters` +DROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`; +ALTER TABLE `patient_encounters` +DROP COLUMN `patient_age_classification_id`, +DROP INDEX `fk_patient_encounter_patient_age_classification_id_idx` ; \ No newline at end of file From b1e2a22ef2361ed29587ba610d2833f3d523dc29 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Sat, 31 Jan 2015 17:41:51 -0500 Subject: [PATCH 17/21] Revert "hotfix for mysql 5.6 changes" This reverts commit 31ab29dc2c78eb93aba6c19a77eea75fa728afe7. --- conf/evolutions/default/68.sql | 16 ++++++++++++++++ conf/evolutions/default/72.sql | 3 +-- conf/evolutions/default/73.sql | 19 ------------------- 3 files changed, 17 insertions(+), 21 deletions(-) delete mode 100644 conf/evolutions/default/73.sql diff --git a/conf/evolutions/default/68.sql b/conf/evolutions/default/68.sql index 7bd416ec5..dcdebc3d8 100644 --- a/conf/evolutions/default/68.sql +++ b/conf/evolutions/default/68.sql @@ -10,11 +10,27 @@ CREATE TABLE `patient_age_classifications` ( UNIQUE INDEX `name_UNIQUE` (`name` ASC), UNIQUE INDEX `description_UNIQUE` (`description` ASC)); +ALTER TABLE `patient_encounters` +ADD COLUMN `patient_age_classification_id` INT(11) NULL AFTER `user_id_pharmacy`, +ADD INDEX `fk_patient_encounter_patient_age_classification_id_idx` (`patient_age_classification_id` ASC); +ALTER TABLE `patient_encounters` +ADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id` +FOREIGN KEY (`patient_age_classification_id`) +REFERENCES `patient_age_classifications` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + ALTER TABLE `patients` CHANGE COLUMN `age` `age` DATE NULL ; # --- !Downs +ALTER TABLE `patient_encounters` +DROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`; +ALTER TABLE `patient_encounters` +DROP COLUMN `patient_age_classification_id`, +DROP INDEX `fk_patient_encounter_patient_age_classification_id_idx` ; + DROP TABLE `patient_age_classifications`; ALTER TABLE `patients` diff --git a/conf/evolutions/default/72.sql b/conf/evolutions/default/72.sql index 44962ebe1..6da8948bc 100644 --- a/conf/evolutions/default/72.sql +++ b/conf/evolutions/default/72.sql @@ -12,5 +12,4 @@ CHANGE COLUMN `id` `id` INT(11) NOT NULL AUTO_INCREMENT ; # --- !Downs -DROP TABLE `diagnoses`; - +DROP TABLE `diagnoses`; \ No newline at end of file diff --git a/conf/evolutions/default/73.sql b/conf/evolutions/default/73.sql deleted file mode 100644 index 23c6c99de..000000000 --- a/conf/evolutions/default/73.sql +++ /dev/null @@ -1,19 +0,0 @@ -# --- !Ups - -ALTER TABLE `patient_encounters` -ADD COLUMN `patient_age_classification_id` INT(11) NULL AFTER `user_id_pharmacy`, -ADD INDEX `fk_patient_encounter_patient_age_classification_id_idx` (`patient_age_classification_id` ASC); -ALTER TABLE `patient_encounters` -ADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id` -FOREIGN KEY (`patient_age_classification_id`) -REFERENCES `patient_age_classifications` (`id`) -ON DELETE NO ACTION -ON UPDATE NO ACTION; - -# --- !Downs - -ALTER TABLE `patient_encounters` -DROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`; -ALTER TABLE `patient_encounters` -DROP COLUMN `patient_age_classification_id`, -DROP INDEX `fk_patient_encounter_patient_age_classification_id_idx` ; \ No newline at end of file From ab88618dd3b68bfdbc55ada486fc8e0b2c4a6497 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Sat, 31 Jan 2015 17:17:51 -0500 Subject: [PATCH 18/21] proper evolution fix for mysql 5.6 changes --- conf/application.conf | 1 + conf/evolutions/default/69.sql | 10 ++++++++++ conf/evolutions/default/70.sql | 10 ++++++++++ conf/scripts/upgrade_213.sql | 10 ++++++++++ 4 files changed, 31 insertions(+) create mode 100644 conf/scripts/upgrade_213.sql diff --git a/conf/application.conf b/conf/application.conf index 9d2ca85e4..e9a130939 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -15,6 +15,7 @@ db.default.driver="com.mysql.jdbc.Driver" db.default.url="jdbc:mysql://localhost/femr?characterEncoding=UTF-8" db.default.user="sa" db.default.password="" +db.default.logStatements=false photos.path="./Upload/Pictures/Patients" photos.encounterPath="./Upload/Pictures/PatientEncounters" diff --git a/conf/evolutions/default/69.sql b/conf/evolutions/default/69.sql index 7a48ff1d1..5085b5688 100644 --- a/conf/evolutions/default/69.sql +++ b/conf/evolutions/default/69.sql @@ -1,10 +1,20 @@ # --- !Ups +ALTER TABLE `patient_encounters` +DROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`; + ALTER TABLE `patient_age_classifications` CHANGE COLUMN `id` `id` INT(11) NOT NULL , ADD COLUMN `sortOrder` INT NOT NULL AUTO_INCREMENT AFTER `isDeleted`, ADD UNIQUE INDEX `sortOrder_UNIQUE` (`sortOrder` ASC); +ALTER TABLE `patient_encounters` +ADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id` +FOREIGN KEY (`patient_age_classification_id`) +REFERENCES `patient_age_classifications` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + # --- !Downs ALTER TABLE `patient_age_classifications` diff --git a/conf/evolutions/default/70.sql b/conf/evolutions/default/70.sql index 798f8bc4b..38b4ad1c3 100644 --- a/conf/evolutions/default/70.sql +++ b/conf/evolutions/default/70.sql @@ -1,9 +1,19 @@ # --- !Ups +ALTER TABLE `patient_encounters` +DROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`; + ALTER TABLE `patient_age_classifications` CHANGE COLUMN `id` `id` INT(11) NOT NULL AUTO_INCREMENT , CHANGE COLUMN `sortOrder` `sortOrder` INT(11) NOT NULL ; +ALTER TABLE `patient_encounters` +ADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id` +FOREIGN KEY (`patient_age_classification_id`) +REFERENCES `patient_age_classifications` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + # --- !Downs ALTER TABLE `patient_age_classifications` diff --git a/conf/scripts/upgrade_213.sql b/conf/scripts/upgrade_213.sql new file mode 100644 index 000000000..785c1a8a2 --- /dev/null +++ b/conf/scripts/upgrade_213.sql @@ -0,0 +1,10 @@ +-- Before upgrading to fEMR 2.1.3, run these scripts and upgrade MySQL to 5.6. +-- Seriously, goodluck reverting back. + +UPDATE `play_evolutions` +SET `apply_script`='ALTER TABLE `patient_encounters`\nDROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`;\n\nALTER TABLE `patient_age_classifications`\nCHANGE COLUMN `id` `id` INT(11) NOT NULL ,\nADD COLUMN `sortOrder` INT NOT NULL AUTO_INCREMENT AFTER `isDeleted`,\nADD UNIQUE INDEX `sortOrder_UNIQUE` (`sortOrder` ASC);\n\nALTER TABLE `patient_encounters`\nADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id`\nFOREIGN KEY (`patient_age_classification_id`)\nREFERENCES `patient_age_classifications` (`id`)\nON DELETE NO ACTION\nON UPDATE NO ACTION;' +WHERE `id`='69'; + +UPDATE `play_evolutions` +SET `apply_script`='ALTER TABLE `patient_encounters`\nDROP FOREIGN KEY `fk_patient_encounter_patient_age_classification_id`;\n\nALTER TABLE `patient_age_classifications`\nCHANGE COLUMN `id` `id` INT(11) NOT NULL AUTO_INCREMENT ,\nCHANGE COLUMN `sortOrder` `sortOrder` INT(11) NOT NULL ;\n\nALTER TABLE `patient_encounters`\nADD CONSTRAINT `fk_patient_encounter_patient_age_classification_id`\nFOREIGN KEY (`patient_age_classification_id`)\nREFERENCES `patient_age_classifications` (`id`)\nON DELETE NO ACTION\nON UPDATE NO ACTION;' +WHERE `id`='70'; \ No newline at end of file From 237b50dd281d2a8eba117763e252e84c716049b8 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Sat, 31 Jan 2015 17:54:45 -0500 Subject: [PATCH 19/21] update play, mysql connector, and gson --- app/femr/ui/views/home/index.scala.html | 2 +- app/femr/ui/views/partials/footer.scala.html | 2 +- project/Build.scala | 6 +++--- project/plugins.sbt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/femr/ui/views/home/index.scala.html b/app/femr/ui/views/home/index.scala.html index 13429d348..27e13b578 100644 --- a/app/femr/ui/views/home/index.scala.html +++ b/app/femr/ui/views/home/index.scala.html @@ -11,7 +11,7 @@ @top = {
    -

    Welcome to fEMR 2.1.2, @currentUser.getFirstName!

    +

    Welcome to fEMR 2.1.3, @currentUser.getFirstName!

    @*

    This is a placeholder landing page. As more features are developed, this page will change over time.

    *@

    Please select a tab at the top to get started!

    diff --git a/app/femr/ui/views/partials/footer.scala.html b/app/femr/ui/views/partials/footer.scala.html index 8ec69ccb1..a43d81765 100644 --- a/app/femr/ui/views/partials/footer.scala.html +++ b/app/femr/ui/views/partials/footer.scala.html @@ -1,4 +1,4 @@

    -

    fEMR 2.1.2 © 2014

    +

    fEMR 2.1.3 © 2015

    \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index f7477ab85..0e68c8460 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -5,7 +5,7 @@ import play.Play.autoImport._ object ApplicationBuild extends Build { val appName = "fEMR" - val appVersion = "2.1.2"//doesn't auto update everything in the code when this is changed + val appVersion = "2.1.3"//doesn't auto update everything in the code when this is changed val currentScalaVersion = "2.11.2" val appDependencies = Seq( @@ -14,10 +14,10 @@ object ApplicationBuild extends Build { javaJdbc, javaEbean, "com.google.inject" % "guice" % "3.0", - "mysql" % "mysql-connector-java" % "5.1.18", + "mysql" % "mysql-connector-java" % "5.1.34", "org.mindrot" % "jbcrypt" % "0.3m", "commons-collections" % "commons-collections" % "3.2.1", - "com.google.code.gson" % "gson" % "2.2.4" + "com.google.code.gson" % "gson" % "2.3.1" ) diff --git a/project/plugins.sbt b/project/plugins.sbt index 08bb0a6db..b813154d7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,4 +7,4 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/release resolvers += "jBCrypt Repository" at "http://repo1.maven.org/maven2/org/" // Use the Play sbt plugin for Play projects -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.4") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.7") From 181ee353831d95f0b45b410120ef9d1758cbeaad Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Sat, 31 Jan 2015 18:22:36 -0500 Subject: [PATCH 20/21] update contributing --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9224b582..692d923d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,10 @@ #Installation and Configuration using IntelliJ IDEA ### Required downloads -1. [MySQL](http://www.mysql.com/) -2. [Play Framework](http://downloads.typesafe.com/typesafe-activator/1.2.10/typesafe-activator-1.2.10.zip) -3. [Java JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) -4. [IntelliJ IDEA](http://www.jetbrains.com/idea/) +1. [MySQL 5.6](http://www.mysql.com/) +2. [Play Framework 2.3.7](http://downloads.typesafe.com/typesafe-activator/1.2.10/typesafe-activator-1.2.10.zip) +3. [Java JDK 1.7](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) +4. [IntelliJ IDEA 13](http://www.jetbrains.com/idea/) 5. [Git](http://git-scm.com/) ### Configuration From 7fcc8f9173111122594d2f59a7d057ae20407530 Mon Sep 17 00:00:00 2001 From: Kevin Zurek Date: Sat, 31 Jan 2015 18:25:14 -0500 Subject: [PATCH 21/21] fix auto focus on diagnosis --- public/js/medical/medical.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/medical/medical.js b/public/js/medical/medical.js index af590e129..a9ba89d7b 100644 --- a/public/js/medical/medical.js +++ b/public/js/medical/medical.js @@ -165,7 +165,7 @@ $(document).ready(function () { return; } $("#problem" + $("body").data("prob") + "-container").removeClass("hidden"); - $("#problem" + $("body").data("prob") + "-container").focus(); + $("#problem" + $("body").data("prob")).focus(); return; });