diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/Obfuscator.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/Obfuscator.scala index 9450aa826..dbd275c8b 100644 --- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/Obfuscator.scala +++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/Obfuscator.scala @@ -1,77 +1,77 @@ package net.shrine.adapter import com.typesafe.config.Config import net.shrine.log.Log import net.shrine.protocol.QueryResult import scala.util.Random /** * @author Ricardo De Lima * @author Bill Simons * @author clint * @author dwalend * @since August 18, 2009 * @see http://cbmi.med.harvard.edu */ case class Obfuscator(binSize:Int,stdDev:Double,noiseClamp:Int) { //todo a problem instead? if((stdDev < 6.5) || (noiseClamp < 10) || (binSize < 5)) Log.warn(s"$this does not include enough obfuscation to prevent an unobservable reidentificaiton attack. We recommend stdDev >= 6.5, noiseClamp >= 10, and binSize >= 5") val random = new Random def obfuscateResults(doObfuscation: Boolean)(results: Seq[QueryResult]): Seq[QueryResult] = { if (doObfuscation) results.map(obfuscate) else results } def obfuscate(result: QueryResult): QueryResult = { val withObfscSetSize = result.modifySetSize(obfuscate) if (withObfscSetSize.breakdowns.isEmpty) { withObfscSetSize } else { val obfuscatedBreakdowns = result.breakdowns.mapValues(_.mapValues(obfuscate)) withObfscSetSize.withBreakdowns(obfuscatedBreakdowns) } } def obfuscate(l: Long): Long = { def roundToNearest(i: Double, n: Double): Long = { Math.round( if ((i % n) >= n / 2) i + n - (i % n) //round up else i - i % n //round down ) } def clampedGaussian(i: Long, clamp: Long): Double = { val noise = random.nextGaussian() * stdDev //clamp it if (noise > clamp) clamp else if (noise < -clamp) -clamp else noise } //bin val binned = roundToNearest(l, binSize) //add noise val noised = binned + clampedGaussian(binned, noiseClamp) //bin again val rounded = roundToNearest(noised, binSize) //finally, if rounded is clamp or smaller, report that the result is too small. - if(rounded > noiseClamp) rounded + if((rounded > noiseClamp) && (rounded > 10)) rounded //todo fix with SHRINE-1716 else Obfuscator.LESS_THAN_CLAMP //will be reported as "$clamped or fewer" //todo this happens elsewhere in the code, too. But where? } } object Obfuscator { def apply(config:Config): Obfuscator = Obfuscator(config.getInt("binSize"),config.getDouble("sigma"),config.getInt("clamp")) val LESS_THAN_CLAMP = -1L } \ No newline at end of file diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/RunQueryAdapter.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/RunQueryAdapter.scala index c1775fd82..4fec9b732 100644 --- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/RunQueryAdapter.scala +++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/RunQueryAdapter.scala @@ -1,284 +1,284 @@ package net.shrine.adapter import net.shrine.adapter.audit.AdapterAuditDb import scala.util.Failure import scala.util.Success import scala.util.Try import scala.xml.NodeSeq import net.shrine.adapter.dao.AdapterDao import net.shrine.adapter.translators.QueryDefinitionTranslator import net.shrine.protocol.{AuthenticationInfo, BroadcastMessage, Credential, ErrorFromCrcException, ErrorResponse, HiveCredentials, I2b2ResultEnvelope, MissingCrCXmlResultException, QueryResult, RawCrcRunQueryResponse, ReadResultRequest, ReadResultResponse, ResultOutputType, RunQueryRequest, RunQueryResponse, ShrineResponse} import net.shrine.client.Poster import net.shrine.problem.{AbstractProblem, LoggingProblemHandler, Problem, ProblemNotYetEncoded, ProblemSources} import scala.util.control.NonFatal import net.shrine.util.XmlDateHelper import scala.concurrent.duration.Duration import scala.xml.XML /** * @author Bill Simons * @author clint * @since 4/15/11 * @see http://cbmi.med.harvard.edu * @see http://chip.org *

* NOTICE: This software comes with NO guarantees whatsoever and is * licensed as Lgpl Open Source * @see http://www.gnu.org/licenses/lgpl.html */ final case class RunQueryAdapter( poster: Poster, dao: AdapterDao, override val hiveCredentials: HiveCredentials, conceptTranslator: QueryDefinitionTranslator, adapterLockoutAttemptsThreshold: Int, //Set to 0 to disable lockout. todo remove in SHRINE 1.24 doObfuscation: Boolean, runQueriesImmediately: Boolean, breakdownTypes: Set[ResultOutputType], collectAdapterAudit:Boolean, botCountTimeThresholds:Seq[(Long,Duration)], obfuscator: Obfuscator ) extends CrcAdapter[RunQueryRequest, RunQueryResponse](poster, hiveCredentials) { logStartup() import RunQueryAdapter._ override protected[adapter] def parseShrineResponse(xml: NodeSeq) = RawCrcRunQueryResponse.fromI2b2(breakdownTypes)(xml).get //TODO: Avoid .get call override protected[adapter] def translateNetworkToLocal(request: RunQueryRequest): RunQueryRequest = { try { request.mapQueryDefinition(conceptTranslator.translate) } catch { case NonFatal(e) => throw new AdapterMappingException(request,s"Error mapping query terms from network to local forms.", e) } } override protected[adapter] def processRequest(message: BroadcastMessage): ShrineResponse = { if (collectAdapterAudit) AdapterAuditDb.db.insertQueryReceived(message) if (isLockedOut(message.networkAuthn)) { throw new AdapterLockoutException(message.networkAuthn,poster.url) } dao.checkIfBot(message.networkAuthn,botCountTimeThresholds) val runQueryReq = message.request.asInstanceOf[RunQueryRequest] //We need to use the network identity from the BroadcastMessage, since that will have the network username //(ie, ecommons) of the querying user. Using the AuthenticationInfo from the incoming request breaks the fetching //of previous queries on deployed systems where the credentials in the identity param to this method and the authn //field of the incoming request are different, like the HMS Shrine deployment. //NB: Credential field is wiped out to preserve old behavior -Clint 14 Nov, 2013 val authnToUse = message.networkAuthn.copy(credential = Credential("", isToken = false)) if (!runQueriesImmediately) { debug(s"Queueing query from user ${message.networkAuthn.domain}:${message.networkAuthn.username}") storeQuery(authnToUse, message, runQueryReq) } else { debug(s"Performing query from user ${message.networkAuthn.domain}:${message.networkAuthn.username}") val result: ShrineResponse = runQuery(authnToUse, message.copy(request = runQueryReq.withAuthn(authnToUse)), runQueryReq.withAuthn(authnToUse)) if (collectAdapterAudit) AdapterAuditDb.db.insertResultSent(runQueryReq.networkQueryId,result) result } } private def storeQuery(authnToUse: AuthenticationInfo, message: BroadcastMessage, request: RunQueryRequest): RunQueryResponse = { //Use dummy ids for what we would have received from the CRC val masterId: Long = -1L val queryInstanceId: Long = -1L val resultId: Long = -1L //TODO: is this right?? Or maybe it's project id? val groupId = authnToUse.domain - val invalidSetSize = -1L + val invalidSetSize = -1L //This might be where the results of 10 or fewer become -1 val now = XmlDateHelper.now val queryResult = QueryResult(resultId, queryInstanceId, Some(ResultOutputType.PATIENT_COUNT_XML), invalidSetSize, Some(now), Some(now), Some("Query enqueued for later processing"), QueryResult.StatusType.Held, Some("Query enqueued for later processing")) dao.inTransaction { val insertedQueryId = dao.insertQuery(masterId.toString, request.networkQueryId, authnToUse, request.queryDefinition, isFlagged = false, hasBeenRun = false, flagMessage = None) val insertedQueryResultIds = dao.insertQueryResults(insertedQueryId, Seq(queryResult)) //NB: We need to insert dummy QueryResult and Count records so that calls to StoredQueries.retrieve() in //AbstractReadQueryResultAdapter, called when retrieving results for previously-queued-or-incomplete //queries, will work. val countQueryResultId = insertedQueryResultIds(ResultOutputType.PATIENT_COUNT_XML).head dao.insertCountResult(countQueryResultId, -1L, -1L) } RunQueryResponse(masterId, XmlDateHelper.now, authnToUse.username, groupId, request.queryDefinition, queryInstanceId, queryResult) } private def runQuery(authnToUse: AuthenticationInfo, message: BroadcastMessage, request: RunQueryRequest): ShrineResponse = { if (collectAdapterAudit) AdapterAuditDb.db.insertExecutionStarted(request) //NB: Pass through ErrorResponses received from the CRC. //See: https://open.med.harvard.edu/jira/browse/SHRINE-794 val result = super.processRequest(message) match { case e: ErrorResponse => e case rawRunQueryResponse: RawCrcRunQueryResponse => processRawCrcRunQueryResponse(authnToUse, request, rawRunQueryResponse) } if (collectAdapterAudit) AdapterAuditDb.db.insertExecutionCompletedShrineResponse(request,result) result } private[adapter] def processRawCrcRunQueryResponse(authnToUse: AuthenticationInfo, request: RunQueryRequest, rawRunQueryResponse: RawCrcRunQueryResponse): RunQueryResponse = { def isBreakdown(result: QueryResult) = result.resultType.exists(_.isBreakdown) val originalResults: Seq[QueryResult] = rawRunQueryResponse.results val (originalBreakdownResults, originalNonBreakDownResults): (Seq[QueryResult],Seq[QueryResult]) = originalResults.partition(isBreakdown) val originalBreakdownCountAttempts: Seq[(QueryResult, Try[QueryResult])] = attemptToRetrieveBreakdowns(request, originalBreakdownResults) val (successfulBreakdownCountAttempts, failedBreakdownCountAttempts) = originalBreakdownCountAttempts.partition { case (_, t) => t.isSuccess } val failedBreakdownCountAttemptsWithProblems = failedBreakdownCountAttempts.map { attempt => val originalResult: QueryResult = attempt._1 val queryResult:QueryResult = if (originalResult.problemDigest.isDefined) originalResult else { attempt._2 match { case Success(_) => originalResult case Failure(x) => //noinspection RedundantBlock { val problem:Problem = x match { case e: ErrorFromCrcException => ErrorFromCrcBreakdown(e) case e: MissingCrCXmlResultException => CannotInterpretCrcBreakdownXml(e) case NonFatal(e) => { val summary = s"Unexpected exception while interpreting breakdown response" ProblemNotYetEncoded(summary, e) } } //TODO: is this needed? LoggingProblemHandler.handleProblem(problem) originalResult.copy(problemDigest = Some(problem.toDigest)) } } } (queryResult,attempt._2) } logBreakdownFailures(rawRunQueryResponse, failedBreakdownCountAttemptsWithProblems) val originalMergedBreakdowns: Map[ResultOutputType, I2b2ResultEnvelope] = { val withBreakdownCounts = successfulBreakdownCountAttempts.collect { case (_, Success(queryResultWithBreakdowns)) => queryResultWithBreakdowns } withBreakdownCounts.map(_.breakdowns).fold(Map.empty)(_ ++ _) } val obfuscatedQueryResults = originalResults.map(obfuscator.obfuscate) val obfuscatedNonBreakdownQueryResults = obfuscatedQueryResults.filterNot(isBreakdown) val obfuscatedMergedBreakdowns = originalMergedBreakdowns.mapValues(_.mapValues(obfuscator.obfuscate)) val failedBreakdownTypes = failedBreakdownCountAttemptsWithProblems.flatMap { case (qr, _) => qr.resultType } dao.storeResults( authn = authnToUse, masterId = rawRunQueryResponse.queryId.toString, networkQueryId = request.networkQueryId, queryDefinition = request.queryDefinition, rawQueryResults = originalResults, obfuscatedQueryResults = obfuscatedQueryResults, failedBreakdownTypes = failedBreakdownTypes, mergedBreakdowns = originalMergedBreakdowns, obfuscatedBreakdowns = obfuscatedMergedBreakdowns) // at this point the queryResult could be a mix of successes and failures. // SHRINE reports only the successes. See SHRINE-1567 for details val queryResults: Seq[QueryResult] = if (doObfuscation) obfuscatedNonBreakdownQueryResults else originalNonBreakDownResults val breakdownsToReturn: Map[ResultOutputType, I2b2ResultEnvelope] = if (doObfuscation) obfuscatedMergedBreakdowns else originalMergedBreakdowns //TODO: Will fail in the case of NO non-breakdown QueryResults. Can this ever happen, and is it worth protecting against here? //can failedBreakdownCountAttempts be mixed back in here? val resultWithBreakdowns: QueryResult = queryResults.head.withBreakdowns(breakdownsToReturn) if(debugEnabled) { def justBreakdowns(breakdowns: Map[ResultOutputType, I2b2ResultEnvelope]) = breakdowns.mapValues(_.data) val obfuscationMessage = s"obfuscation is ${if(doObfuscation) "ON" else "OFF"}" debug(s"Returning QueryResult with count ${resultWithBreakdowns.setSize} (original count: ${originalNonBreakDownResults.headOption.map(_.setSize)} ; $obfuscationMessage)") debug(s"Returning QueryResult with breakdowns ${justBreakdowns(resultWithBreakdowns.breakdowns)} (original breakdowns: ${justBreakdowns(originalMergedBreakdowns)} ; $obfuscationMessage)") debug(s"Full QueryResult: $resultWithBreakdowns") } //if any results had problems, this commented out code can turn it into an error QueryResult //See SHRINE-1619 //val problem: Option[ProblemDigest] = failedBreakdownCountAttemptsWithProblems.headOption.flatMap(x => x._1.problemDigest) //val queryResult = problem.fold(resultWithBreakdowns)(pd => QueryResult.errorResult(Some(pd.description),"Error with CRC",pd)) rawRunQueryResponse.toRunQueryResponse.withResult(resultWithBreakdowns) } private def getResultFromCrc(parentRequest: RunQueryRequest, networkResultId: Long): Try[ReadResultResponse] = { def readResultRequest(runQueryReq: RunQueryRequest, networkResultId: Long) = ReadResultRequest(hiveCredentials.projectId, runQueryReq.waitTime, hiveCredentials.toAuthenticationInfo, networkResultId.toString) Try(XML.loadString(callCrc(readResultRequest(parentRequest, networkResultId)))).flatMap(ReadResultResponse.fromI2b2(breakdownTypes)) } private[adapter] def attemptToRetrieveCount(runQueryReq: RunQueryRequest, originalCountQueryResult: QueryResult): (QueryResult, Try[QueryResult]) = { originalCountQueryResult -> (for { countData <- getResultFromCrc(runQueryReq, originalCountQueryResult.resultId) } yield originalCountQueryResult.withSetSize(countData.metadata.setSize)) } private[adapter] def attemptToRetrieveBreakdowns(runQueryReq: RunQueryRequest, breakdownResults: Seq[QueryResult]): Seq[(QueryResult, Try[QueryResult])] = { breakdownResults.map { origBreakdownResult => origBreakdownResult -> (for { breakdownData <- getResultFromCrc(runQueryReq, origBreakdownResult.resultId).map(_.data) } yield origBreakdownResult.withBreakdown(breakdownData)) } } private[adapter] def logBreakdownFailures(response: RawCrcRunQueryResponse, failures: Seq[(QueryResult, Try[QueryResult])]) { for { (origQueryResult, Failure(e)) <- failures } { error(s"Couldn't load breakdown for QueryResult with masterId: ${response.queryId}, instanceId: ${origQueryResult.instanceId}, resultId: ${origQueryResult.resultId}. Asked for result type: ${origQueryResult.resultType}", e) } } private def isLockedOut(authn: AuthenticationInfo): Boolean = { adapterLockoutAttemptsThreshold match { case 0 => false case _ => dao.isUserLockedOut(authn, adapterLockoutAttemptsThreshold) } } private def logStartup(): Unit = { val message = { if (runQueriesImmediately) { s"${getClass.getSimpleName} will run queries immediately" } else { s"${getClass.getSimpleName} will queue queries for later execution" } } info(message) } } case class ErrorFromCrcBreakdown(x:ErrorFromCrcException) extends AbstractProblem(ProblemSources.Adapter) { override val throwable = Some(x) override val summary: String = "The CRC reported an error." override val description = "The CRC reported an internal error." } case class CannotInterpretCrcBreakdownXml(x:MissingCrCXmlResultException) extends AbstractProblem(ProblemSources.Adapter) { override val throwable = Some(x) override val summary: String = "SHRINE cannot interpret the CRC response." override val description = "The CRC responded, but SHRINE could not interpret that response." } \ No newline at end of file diff --git a/shrine-webclient/src/main/html/js-i2b2/cells/CRC/CRC_view_Graphs.js b/shrine-webclient/src/main/html/js-i2b2/cells/CRC/CRC_view_Graphs.js index a15b9a606..1c1325191 100644 --- a/shrine-webclient/src/main/html/js-i2b2/cells/CRC/CRC_view_Graphs.js +++ b/shrine-webclient/src/main/html/js-i2b2/cells/CRC/CRC_view_Graphs.js @@ -1,1044 +1,1044 @@ /** ----------------------------------------------------------------------------------------------------------------------- * @projectDescription View controller for the query graph window (which is a GUI-only component of the CRC module). * @inherits i2b2.CRC.view * @namespace i2b2.CRC.view.graphs * @author Shawn Murphy MD PhD, Hannah Murphy * @version 1.7 * @description This set of functions uses D3 and its derivative C3 to graph the text that results in the query-status-window * of the i2b2 web client. * The main function is "createGraphs". * Because it makes extensive use of Vector Graphic in the D3 library it will only work in Microsoft Internet * Explorer 9 and above. It assumes the STATUS window is a specific height which is 146px. In theory the * Width can vary, but right not it is set to 535px in many places. * It draws the graphs in a div (which should be the dimensions above), using a string which is essentially * screen-scraped from the text what is placed in the query_status box of the web client. To distinguish the * normal i2b2 vs. SHRINE text, a boolean flag is used. A regular i2b2 result (bIsMultiSite = false) * or a SHRINE result (bIsMultiSite = true). * Internally, everything works off an array the is produced from the test that is a six element array of * **** 0 "query name", 1 "title", 2 "site", 3 "element name", 4 quantity, 5 "sentence" **** * for example, one element would be: * **** ["Circulatory sys@20:21:19", "Age patient breakdown", "MGH" "0-9 years old", 0, "0-9 years old: 0"] **** * It also uses some jQuery, but only for the scroll bar function with *** jQuery Stuff *** * in the comments. * There are four web client javascript files in the CRC folder that have references to functions in this * javascript file, and the default.htm folder in the main web client folder, they are: * CRC_view_Status, CRC_ctlr_QryStatus, CRC_ctlr_QryTools, and cell_config_data ** -----------------------------------------------------------------------------------------------------------------------*/ console.group('Load & Execute component file: CRC > view > Graphs'); console.time('execute time'); //i2b2.PM.model.isObfuscated = true; // for testing // Constants var msSpecialBreakdownSite = ""; // this constant designates a site from which the breakdown arrays will always be obtained var msStringDefineingNumberOfPatients = "number of patients"; // this constant is what appears in the breakdown text // which is the number of patients and is lower cased and trimmed on both sides for comparison // create and save the screen objects and more constants i2b2.CRC.view.graphs = new i2b2Base_cellViewController(i2b2.CRC, 'graphs'); i2b2.CRC.view.graphs.visible = false; i2b2.CRC.view.graphs.iObfuscatedFloorNumber = 3; // this is the amount reported that the numbers are obfuscated by i2b2.CRC.view.graphs.sObfuscatedText = "<3"; // this is the text that is replaced for a small number in obfuscated mode // so that it can be cleaned up before the next display i2b2.CRC.view.graphs.sObfuscatedEnding = "±3"; //this is the text that is added to all numbers in obfuscated mode -i2b2.CRC.view.graphs.sObfuscatedSHRINEText = "10 patients or fewer"; // this is the text that is replaced for a small number in obfuscated mode +i2b2.CRC.view.graphs.sObfuscatedSHRINEText = "10 patients or fewer"; // this is the text that is replaced for a small number in obfuscated mode //todo fix with SHRINE-1716 // so that it can be cleaned up before the next display -i2b2.CRC.view.graphs.sObfuscatedSHRINEEnding = "+-3 patients"; //this is the text that is added to all numbers in obfuscated mode +i2b2.CRC.view.graphs.sObfuscatedSHRINEEnding = "+-10 patients"; //this is the text that is added to all numbers in obfuscated mode //todo fix with SHRINE-1716 //i2b2.CRC.view.graphs.bIsSHRINE = false; // this changes the way the graphs are made if the file is being run in SHRINE mode // NOTE THAT THIS IS DEMO ONLY IN THIS VERSION - IT DOES NOT REALLY WORK i2b2.CRC.view.graphs.asTitleOfShrineGroup = []; // These functions manage the graph divs, but DECISIONS are made in the CRC_view_Status code i2b2.CRC.view.graphs.show = function() { i2b2.CRC.view.graphs.visible = true; $('crcGraphsBox').show(); } i2b2.CRC.view.graphs.hide = function() { i2b2.CRC.view.graphs.visible = false; $('crcGraphsBox').hide(); } i2b2.CRC.view.graphs.showDisplay = function() { var targs = $('infoQueryStatusChart').parentNode.parentNode.select('DIV.tabBox.active'); // remove all active tabs targs.each(function(el) { el.removeClassName('active'); }); // set us as active $('infoQueryStatusChart').parentNode.parentNode.select('DIV.tabBox.tabQueryGraphs')[0].addClassName('active'); $('infoQueryStatusChart').show(); } i2b2.CRC.view.graphs.hideDisplay = function() { $('infoQueryStatusChart').hide(); } // ================================================================================================== // /********************************************************************************* FUNCTION createGraphs Takes a Div, the text from the query status view, and a multisite flag and populates the Div **********************************************************************************/ i2b2.CRC.view.graphs.createGraphs = function(sDivName, sInputString, bIsMultiSite) { try { if (sDivName === undefined || sDivName === null || sDivName === "") throw ("ERROR 201 - sDivName in function createGraphs is null"); //i2b2.CRC.view.graphs.sNameOfPreviousDiv = sDivName; i2b2.CRC.view.graphs.clearGraphs(sDivName); if (!i2b2.CRC.view.graphs.bisGTIE8) { i2b2.CRC.view.graphs.aDivForIE8(sDivName); return; } // if (bIsMultiSite) sInputString = i2b2.CRC.view.graphs.returnTestString(true); //For testing if (sInputString === undefined || sInputString === null || sInputString === "") throw ("ERROR 202 - sInputString in function createGraphs is null"); var asBreakdownArray = [[]]; var iBreakdown = 0; // make the input array var asInputArray = i2b2.CRC.view.graphs.parseInputIntoArray(sInputString, bIsMultiSite); // Pull out unique breakdown types var asBreakdownTypes = []; var iBreakdown = 0; for (var i = 0; i < asInputArray.length; i++) { asBreakdownTypes[iBreakdown] = asInputArray[i][1]; iBreakdown++; } var asUniqueBreakdownTypes = []; for (var i=0; i < asBreakdownTypes.length; i++) { if (asUniqueBreakdownTypes.indexOf(asBreakdownTypes[i]) === -1 && asBreakdownTypes[i] !== '') asUniqueBreakdownTypes.push(asBreakdownTypes[i]); } if (asUniqueBreakdownTypes.length === 0) throw ("ERROR 203 in createGraphs, there are no breakdown types in *unique* array"); // rearrange unique array so that patient number is on the top for (var i = 0; i < asUniqueBreakdownTypes.length; i++) { if (asUniqueBreakdownTypes[i].toLowerCase().trim() == msStringDefineingNumberOfPatients.toLowerCase().trim()) { var sTempVariable = asUniqueBreakdownTypes[0]; asUniqueBreakdownTypes[0] = asUniqueBreakdownTypes[i]; asUniqueBreakdownTypes[i] = sTempVariable; break; } } //Make Divs in the original div for the charts oParentDiv = document.getElementById(sDivName); for (var i=0; i0) { for(var x = 1; x < allSites.length ; x++) { var siteName = allSites[x]; siteReturningResults.set(siteName,x); //SiteName,Index pairs sitesReturningError.set(siteName,true); } } for(var x = 2; x < _2DResultsArray.length ; x++) //Skipping the site name and patient count rows { var thisSiteData = _2DResultsArray[x]; for(var y = 1 ; y < thisSiteData.length; y++) //Skip the category name column { if(thisSiteData[y] != " ") { var siteName = null; siteReturningResults.each(function(item){ if(item.value==y) { siteName = item.key; } }); if(siteName){ sitesReturningError.unset(siteName); } } } } sitesReturningError.each(function(item){ if(siteReturningResults.get(item.key)) siteReturningResults.unset(item.key); }); } var titleDiv = jQuery("#" + sDivName + ' #chartTitle0'); if(titleDiv) titleDiv.html("Patient Count"); graph_multiplesite_patient_number("#" + sDivName + " #chart0", asUniqueBreakdownTypes[0], asInputArray); for (var i=1; i=0) continue; if(title.toLowerCase().indexOf('vital')>=0) title = title + ' Status'; var chartTitle = "Patient " + title + " Count Breakdown"; var titleDiv = jQuery("#" + sDivName + ' #chartTitle' + i); if(titleDiv) titleDiv.html(chartTitle); var dataArray = i2b2.CRC.view.graphs.getGraphDataArray(_2DResultsArray,asUniqueBreakdownTypes[i],siteReturningResults); graph_multiplesite_patient_breakdown("#" + sDivName + " #" + chartDivId, siteReturningResults.keys(), dataArray); } } } catch(err) { console.error(err); } } // END of function createGraphs i2b2.CRC.view.graphs.getGraphDataArray = function(_2DResultsArray,breakdownType,siteHash) { var maxRowsNum = siteHash.size(); if(maxRowsNum>0) { var breakdownHash = $H(); for(x=2;x<_2DResultsArray.length;x++) //Skip the first row which consists of only site names and 2nd row that has total patient count { var thisRow = _2DResultsArray[x]; var category = thisRow[0]; if(category == " ") continue; if(category.toLowerCase().indexOf(breakdownType.toLowerCase())>=0) { breakdownHash.set(category,x); } } var maxColsNum = breakdownHash.size() + 1; var graphDataArray = new Array(maxRowsNum+1); for (var i = 0; i <= maxRowsNum; i++) { graphDataArray[i] = new Array(maxColsNum); } graphDataArray[0][0] = 'x'; var dataArrayColIndex = 1; breakdownHash.each(function(item){ var dataArrayRowIndex = 0; var inpArrayRowIndex = item.value; var breakDownCategory = item.key; var breakDown = breakDownCategory; var tempBrkDn = breakDownCategory.split("|"); if(tempBrkDn.length>1) breakDown = tempBrkDn[1]; graphDataArray[dataArrayRowIndex][dataArrayColIndex] = breakDown; dataArrayRowIndex++; siteHash.each(function(entry){ var siteName = entry.key; var inpArrayColIndex = entry.value; graphDataArray[dataArrayRowIndex][0] = siteName; var breakDownValue = _2DResultsArray[inpArrayRowIndex][inpArrayColIndex]; if(breakDownValue == " ") breakDownValue = "Not Provided"; graphDataArray[dataArrayRowIndex][dataArrayColIndex] = breakDownValue; dataArrayRowIndex++; }); dataArrayColIndex++; }); return graphDataArray; } else return null; } /***************************************************************************************************** @Function i2b2.CRC.view.graphs.parseInputIntoArray(sInputString, isMultiSite) @Input (String *text from query status*, Boolean false = single site, true = multiple site) @Output Create a two dimensional array out of these strings called asInputFragments Each array element is a six element array of 0 "query name", 1 "title", 2 "site", 3 "element name", 4 quantity, 5 "sentence" for example, one element would be ["Circulatory sys@20:21:19", "Age patient breakdown", "MGH" "0-9 years old", 0, "0-9 years old: 0"] *****************************************************************************************************/ i2b2.CRC.view.graphs.parseInputIntoArray = function(sInputString, isMultiSite) { var sCheckForNothing = "something"; // this gets checked to be a zero length string try { var old_demo = false; if (sInputString === undefined || sInputString === null || sInputString === "") throw ("ERROR - sInputString in function parseInputIntoArray is empty"); var asInputFragments = [[]]; if (!isMultiSite) { var asTempArray = []; var sLatestTitle, sLatestQueryName, sLatestElementName, iLatestQuantity, sLatestSite; // process input one line at a time to look for the start of a block. // you know it because it has two \"'s // begin your parsing by separating into an array of sentences that were delimited with \n var asInputSentences = sInputString.split("\n"); var iFragmentArrayCounter = 0; for(var i = 0; i < asInputSentences.length; i++) { if (asInputSentences[i].indexOf("for") > 0) { asTempArray = asInputSentences[i].split("for"); sLatestTitle = asTempArray[0]; sLatestQueryName = asTempArray[1]; sLatestSite = "."; } else if (asInputSentences[i].indexOf(":") > 0) { asTemp2Array = asInputSentences[i].split(":"); sLatestElementName = asTemp2Array[0]; iLatestQuantity = asTemp2Array[1]; asInputFragments[iFragmentArrayCounter] = new Array (6); asInputFragments[iFragmentArrayCounter][0] = sLatestQueryName; asInputFragments[iFragmentArrayCounter][1] = sLatestTitle; asInputFragments[iFragmentArrayCounter][2] = sLatestSite; asInputFragments[iFragmentArrayCounter][3] = sLatestElementName; asInputFragments[iFragmentArrayCounter][4] = i2b2.CRC.view.graphs.sValueOfi2b2Text(iLatestQuantity); asInputFragments[iFragmentArrayCounter][5] = asInputSentences[i]; for(var j = 0; j < 6; j++) { } iFragmentArrayCounter++; } else { } } } else if (old_demo == true) { // parsing for old_demo SHRINE strings var asTempArray = []; var asTemp2Array = []; var sLatestTitle, sLatestQueryName, sLatestElementName, iLatestQuantity, sLatestSite; // process input one line at a time to look for the start of a block. // you know it because it has two \"'s // begin your parsing by separating into an array of sentences that were delimited with \n var asInputSentences = sInputString.split("\n"); var iFragmentArrayCounter = 0; for (var i = 0; i < asInputSentences.length; i++) { if (asInputSentences[i].indexOf("for") > 0) { asTempArray = asInputSentences[i].split("for"); sLatestTitle = asTempArray[0]; if (asTempArray[1].indexOf("=") > 0) { asTemp2Array = asTempArray[1].split("="); sLatestQueryName = asTemp2Array[0]; sLatestSite = asTemp2Array[1]; } else { sLatestQueryName = asTempArray[1]; sLatestSite = "xxx"; } } else if (asInputSentences[i].indexOf(":") > 0) { asTemp2Array = asInputSentences[i].split(":"); sLatestElementName = asTemp2Array[0]; iLatestQuantity = asTemp2Array[1]; //document.write("
Element " + i + " = " + asInputSentences[i]); asInputFragments[iFragmentArrayCounter] = new Array (6); asInputFragments[iFragmentArrayCounter][0] = sLatestQueryName; asInputFragments[iFragmentArrayCounter][1] = sLatestTitle; asInputFragments[iFragmentArrayCounter][2] = sLatestSite; asInputFragments[iFragmentArrayCounter][3] = sLatestElementName; asInputFragments[iFragmentArrayCounter][4] = iLatestQuantity; asInputFragments[iFragmentArrayCounter][5] = asInputSentences[i]; iFragmentArrayCounter++; } else { } } } else { // parsing for SHRINE strings //Read all the main breakdown categories var asInputSentences = sInputString.split("\n"); var actualBrkDowns = $H(); for (var i = 0; i < asInputSentences.length; i++) { var thisLine = asInputSentences[i]; if(thisLine && thisLine.toLowerCase().trim().indexOf('breakdown')>0) { asTemp2Array = thisLine.split(":"); // Finding a type of breakdown mainCategory = asTemp2Array[0].trim(); actualBrkDowns.set(mainCategory,mainCategory); } } i2b2.CRC.view.graphs.asTitleOfShrineGroup = []; actualBrkDowns.each(function(category) { i2b2.CRC.view.graphs.asTitleOfShrineGroup.push(category.key); }); var asInputFragments = [[]]; try{ // parsing for SHRINE strings var asTempArray = []; var asTemp2Array = []; var asInputSentencesBySite =[]; var sLatestTitle, sLatestQueryName, sLatestElementName, iLatestQuantity, sLatestSite, sQueryName; var aSiteArray = []; var aTempSiteArray = []; // process input one line at a time to look for the start of a block. // you know it because it has two \"'s // begin your parsing by separating into an array of sentences that were delimited with \n var iQueryNameIndex = -1; // look for query title for (var i = 0; i < asInputSentences.length; i++) { if (asInputSentences[i].indexOf("Finished Query:") !== -1) { asTempArray = asInputSentences[i].split('Finished Query:'); sQueryName = asTempArray[1].trim(); iQueryNameIndex = i; break; } } if (iQueryNameIndex == -1) return; sLatestQueryName = sQueryName; // look for Hospitals (which are to the right of the query name) and put each hospital into an array var iSiteNumber = -1; for (var i = iQueryNameIndex+1; i < asInputSentences.length; i++) { if (asInputSentences[i].indexOf(sQueryName) !== -1) { if (iSiteNumber !== -1) aSiteArray[iSiteNumber] = aTempSiteArray; iSiteNumber = iSiteNumber + 1; aTempSiteArray = new Array(3); asTempArray = asInputSentences[i].split(sQueryName); aTempSiteArray[0] = asTempArray[0].trim(); aTempSiteArray[1] = i; } else { aTempSiteArray[2] = i; } } if (iSiteNumber == -1) return; aSiteArray[iSiteNumber] = aTempSiteArray; var iBeginIndex; var iEndIndex; var iFragmentArrayCounter=0; for(var ii = 0; ii <= iSiteNumber; ii++) { sLatestSite = aSiteArray[ii][0]; iBeginIndex = aSiteArray[ii][1]; iEndIndex = aSiteArray[ii][2]; for(var i = iBeginIndex; i < iEndIndex; i++) { var a = asInputSentences[i]; if (asInputSentences[i].indexOf("Patient Count") > -1) { asTemp2Array = asInputSentences[i].split(" -"); sLatestTitle = 'Patient Count'; sLatestElementName = 'Patient Count'; iLatestQuantity = asTemp2Array[1]; asInputFragments[iFragmentArrayCounter] = new Array (6); asInputFragments[iFragmentArrayCounter][0] = sLatestQueryName; asInputFragments[iFragmentArrayCounter][1] = sLatestTitle; asInputFragments[iFragmentArrayCounter][2] = sLatestSite; asInputFragments[iFragmentArrayCounter][3] = sLatestElementName; asInputFragments[iFragmentArrayCounter][4] = i2b2.CRC.view.graphs.sValueOfSHRINEText(iLatestQuantity); asInputFragments[iFragmentArrayCounter][5] = asInputSentences[i]; iFragmentArrayCounter++; } else if (i2b2.CRC.view.graphs.asTitleOfShrineGroup.indexOf(asInputSentences[i]) > -1) { asTemp2Array = asInputSentences[i].split(" "); // Finding a type of breakdown sLatestTitle = asTemp2Array[1].trim(); sCheckForNothing = asTemp2Array[1]; } else if (asInputSentences[i].indexOf(" -") > -1) { // parsing the values //Lets make sure the - is not part of query name if (asInputSentences[i].indexOf('"') < 0){ asTemp2Array = asInputSentences[i].split(" -"); sLatestElementName = asTemp2Array[0]; iLatestQuantity = asTemp2Array[1]; if (i2b2.CRC.view.graphs.asTitleOfShrineGroup.indexOf(sLatestElementName.toLowerCase().trim()) > -1) { sLatestTitle = sLatestElementName.trim(); sCheckForNothing = iLatestQuantity; } asInputFragments[iFragmentArrayCounter] = new Array (6); asInputFragments[iFragmentArrayCounter][0] = sLatestQueryName; asInputFragments[iFragmentArrayCounter][1] = sLatestTitle; asInputFragments[iFragmentArrayCounter][2] = sLatestSite; asInputFragments[iFragmentArrayCounter][3] = sLatestElementName; asInputFragments[iFragmentArrayCounter][4] = i2b2.CRC.view.graphs.sValueOfSHRINEText(iLatestQuantity); asInputFragments[iFragmentArrayCounter][5] = asInputSentences[i]; iFragmentArrayCounter++; } } else if ((asInputSentences[i].indexOf("ERROR") > -1) || (asInputSentences[i].indexOf("Still Processing Request") > -1) || (asInputSentences[i].indexOf("Results not available") > -1)) { asTemp2Array = asInputSentences[i].split(" : "); sLatestError = asTemp2Array[0]; iLatestErrorMsg = asTemp2Array[1]; asInputFragments[iFragmentArrayCounter] = new Array (6); asInputFragments[iFragmentArrayCounter][0] = sLatestQueryName; asInputFragments[iFragmentArrayCounter][1] = sLatestError; asInputFragments[iFragmentArrayCounter][2] = sLatestSite; asInputFragments[iFragmentArrayCounter][3] = iLatestErrorMsg; asInputFragments[iFragmentArrayCounter][4] = ''; asInputFragments[iFragmentArrayCounter][5] = ''; iFragmentArrayCounter++; } else { //Not required to parse } // and of fragment processing if statement //document.write("
Element " + i + " = " + sLatestTitle + " " + sLatestQueryName + " " + sLatestElementName + " " + iLatestQuantity); } // } // end of for loop that processes sites in SHRINE } catch(err) { console.error(err); } } // end of if statement that directs to single or multisite return asInputFragments } catch(err) { console.error(err); } } // END of function i2b2.CRC.view.graphs.parseInputIntoArray /********************************************************************************* FUNCTION graph_singlesite_patient_number Fills in the Div for a single patient number display **********************************************************************************/ function graph_singlesite_patient_number(sDivName, sBreakdownType, asInputFragments) { try { if (sBreakdownType === undefined || sBreakdownType === null) throw ("ERROR - sBreakdownType in function graph_patient_breakdown is null"); var asBreakdownArray = [[]]; var iBreakdown = 0; // for loop to only pull out data from the breakdown type specified in sBreakdownType variable for (var i = 0; i < asInputFragments.length; i++) { if (asInputFragments[i][1].toLowerCase().trim() === sBreakdownType.toLowerCase().trim()) { //document.write("
OK? " + i + " = " + asInputFragments[i][0]); asBreakdownArray[iBreakdown] = new Array (2); asBreakdownArray[iBreakdown][0] = asInputFragments[i][3]; asBreakdownArray[iBreakdown][1] = asInputFragments[i][4]; iBreakdown++; } else { //document.write("
ERROR? " + i + " = " + asInputFragments[i][0]); } } // establish style and draw out containing Div var sDivStyle = "font-family: Verdana, Geneva, sans-serif;" + "font-size: 12px;" + "text-align: center;" + "vertical-align: middle;" + "background-color: white;" + "width: 100%;"; document.getElementById(sDivName).setAttribute("style",sDivStyle); // establish table in Div and set up its style. var sDisplayNumber = i2b2.CRC.view.graphs.sTexti2b2Value(asBreakdownArray[0][1]); var sTableHtml = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
 
'+sBreakdownType+'
'+sDisplayNumber+'
For Query '+asInputFragments[0][0]+'
 
'; document.getElementById(sDivName).innerHTML = sTableHtml; } catch(err) { console.error(err); } } // END of function graph_single_patient_number /********************************************************************************* FUNCTION graph_singlesite_patient_breakdown function where the dataset is displayed **********************************************************************************/ function graph_singlesite_patient_breakdown(sDivName,sBreakdownType,asInputFragments) { try { if (sBreakdownType === undefined || sBreakdownType === null) throw ("ERROR 101 in graph_patient_breakdown, sBreakdownType is null"); var asBreakdownArray = [[]]; var iBreakdown = 0; // for loop to only pull out data from the breakdown type specified in sBreakdownType variable // for multiple sites make a data array for each site for (var i = 0; i < asInputFragments.length; i++) { if (asInputFragments[i][1].toLowerCase().trim() === sBreakdownType.toLowerCase().trim()) { //console.log("IN> " + i + " = " + asInputFragments[i][2]); asBreakdownArray[iBreakdown] = new Array (3); asBreakdownArray[iBreakdown][0] = asInputFragments[i][2]; // site asBreakdownArray[iBreakdown][1] = asInputFragments[i][3]; // text asBreakdownArray[iBreakdown][2] = asInputFragments[i][4]; // number iBreakdown++; } else { //console.log("OUT> " + i + " = " + asInputFragments[i][0]); // items that were left out } } // the text 'patient breakdown' is removed and remainder trimmed var sBreakdownText = ""; var iPbLocation = sBreakdownType.toLowerCase().indexOf(" patient breakdown"); if (iPbLocation != -1) { sBreakdownText = sBreakdownType.substring(0,iPbLocation); //console.log(sBreakdownText + "|"); } else { sBreakdownText = sBreakdownType; //console.log(sBreakdownText + "it is"); } // function where the dataset arrays are created: var iBreakdown = 1; var c3xaxis = new Array(); var c3values = new Array(); c3xaxis[0] = 'x'; c3values[0] = sBreakdownText; for (var i = 0; i < asInputFragments.length; i++) { if (asInputFragments[i][1].toLowerCase().trim() === sBreakdownType.toLowerCase().trim()) { //document.write("
OK? " + i + " = " + asInputFragments[i][0]); c3xaxis[iBreakdown] = asInputFragments[i][3]; c3values[iBreakdown] = asInputFragments[i][4]; iBreakdown++; } else { //document.write("
ERROR? " + i + " = " + asInputFragments[i][0]); } } // Trying out some C3 var graph_color = 'darkblue'; //var graph_color = 'hsl(' + Math.floor( 360 * Math.random() ) + ', 85%, 55%)'; // random color if(!(typeof c3 === 'undefined')){ var chart = c3.generate({ bindto: '#' + sDivName, size: { //width: 535, height: 200 }, data: { x: 'x', columns: [ c3xaxis, c3values ], type: 'bar', color: function (color, d) {return graph_color;}, labels: true }, legend: { //position: 'inset' position: 'right' }, axis: { x: { type: 'category', tick: { rotate: 25 }, height: 45 }, y: { label: { text: 'Number of Patients', //position: 'outer-middle', position: 'outer-bottom' } } }, bar: { width: { ratio: 0.75 // this makes bar width 75% of length between ticks } } }); } } catch(err) { console.error(err); } }; // end of function graph_singlesite_patient_breakdown /********************************************************************************* FUNCTION graph_multiplesite_patient_number Fills in the Div for multiple patient number display **********************************************************************************/ function graph_multiplesite_patient_number(sDivName,sBreakdownType,asInputFragments) { try { if (sBreakdownType === undefined || sBreakdownType === null) throw ("ERROR - sBreakdownType in function graph_patient_breakdown is null"); var asBreakdownArray = [[]]; var iBreakdown = 0; // for loop to only pull out data from the breakdown type specified in sBreakdownType variable // for multiple sites make a data array for each site for (var i = 0; i < asInputFragments.length; i++) { if (asInputFragments[i][1].toLowerCase().trim() === sBreakdownType.toLowerCase().trim()) { //console.log("IN> " + i + " = " + asInputFragments[i][2]); asBreakdownArray[iBreakdown] = new Array (3); asBreakdownArray[iBreakdown][0] = asInputFragments[i][2]; // site asBreakdownArray[iBreakdown][1] = asInputFragments[i][3]; // text asBreakdownArray[iBreakdown][2] = asInputFragments[i][4]; // number iBreakdown++; } else { //console.log("OUT> " + i + " = " + asInputFragments[i][0]); // items that were left out } } // function where the dataset arrays are created: var c3values = new Array(); var allCountsZero = true; for (var i = 0; i < asBreakdownArray.length; i++) { //console.log("Element " + i + " = " + asBreakdownArray[i][0] + " " + asBreakdownArray[i][2]); c3values[i] = new Array(2); c3values[i][0] = asBreakdownArray[i][0].trim() + " " + asBreakdownArray[i][1].trim(); var patCount = Number(asBreakdownArray[i][2]); c3values[i][1] = patCount; if(patCount>0) allCountsZero = false; } // C3 that makes pie chart if(allCountsZero) { jQuery(sDivName).html("Not enough patients were returned to render the graph."); } else { if(!(typeof c3 === 'undefined')){ var chart = c3.generate({ bindto: sDivName, size: { //width: 535, height: 200 }, data: { columns: c3values, type: 'pie' }, pie: { label: { format: d3.format('^g,') } }, legend: { position: 'right' }, axis: { x: { type: 'category', tick: { rotate: 25 }, height: 45 }, y: { label: { text: 'Number of Patients', position: 'outer-bottom' } } }, bar: { width: { ratio: 0.75 // this makes bar width 75% of length between ticks } } }); } } } catch(err) { console.error(err); } }; // end of function graph_multiplesite_patient_number /********************************************************************************* FUNCTION graph_multiplesite_patient_breakdown function where the dataset is displayed **********************************************************************************/ function graph_multiplesite_patient_breakdown(sDivName,asUniqueBreakdownSites,c3dataarray) { try { if(!(typeof c3 === 'undefined')){ var chart = c3.generate({ bindto: sDivName, size: { width: 1000, height: 200 }, data: { x: 'x', columns: c3dataarray, type: 'bar', groups: [asUniqueBreakdownSites] }, padding: { left: 60, bottom: 40 }, legend: { position: 'right' }, axis: { x: { type: 'category', tick: { rotate: 25 }, height: 45 }, y: { label: { text: 'Number of Patients' // position: 'outer-bottom' } } }, bar: { width: { ratio: 0.50 // this makes bar width 75% of length between ticks } } }); } } catch(err) { console.error(err); } }; // end of function graph_multiplesite_patient_breakdown /********************************************************************************* FUNCTION ie function to test for the version of internet explorer usage if ie < 9 then ... **********************************************************************************/ var ie = (function(){ var undef, v = 3, div = document.createElement('div'), all = div.getElementsByTagName('i'); while ( div.innerHTML = '', all[0] ); return v > 4 ? v : undef; }()); /********************************************************************************* FUNCTION bisGTIE8 function to specifically test for internet explorer gt 8 or any other browser which returns "true" usage if bisGTIE8 then ... **********************************************************************************/ i2b2.CRC.view.graphs.bisGTIE8 = (function(){ try { if ( document.addEventListener ) { //alert("you got IE9 or greater (or a modern browser)"); return true; } else { return false; } } catch (e) { return false; } }()); /********************************************************************************* FUNCTION sValueOfi2b2Text Return a INTEGER number that was obfuscated = MAKES IT ZERO **********************************************************************************/ i2b2.CRC.view.graphs.sValueOfi2b2Text = function(sValue) { try { if (sValue === undefined || sValue === null || sValue === "") { iValue = "undefined in"; return iValue; } if (i2b2.PM.model.isObfuscated) { var asTempArray = []; if (sValue.toLowerCase().trim() == i2b2.CRC.view.graphs.sObfuscatedText.toLowerCase().trim()) { iValue = "0"; } else { if (sValue.indexOf(i2b2.CRC.view.graphs.sObfuscatedEnding) > 0) { var asTempArray = sValue.split(i2b2.CRC.view.graphs.sObfuscatedEnding); iValue = asTempArray[0]; } else { iValue = sValue; } } } else { iValue = sValue; } function isNumber(obj) {return ! isNaN(obj-0) && obj; }; if (!isNumber(iValue)) { iValue = "undefined #"; return iValue; } return iValue; } catch(err) { console.error(err); } } // END of function sValueOfi2b2Text /********************************************************************************* FUNCTION sValueOfSHRINEText Return a INTEGER number that was obfuscated = MAKES IT ZERO **********************************************************************************/ i2b2.CRC.view.graphs.sValueOfSHRINEText = function(sValue) { try { if (sValue === undefined || sValue === null || sValue === "") { iValue = "undefined in"; return iValue; } if (true) { // for testing var asTempArray = []; if (sValue.toLowerCase().trim() == i2b2.CRC.view.graphs.sObfuscatedSHRINEText.toLowerCase().trim()) { iValue = "0"; } else { if (sValue.indexOf(i2b2.CRC.view.graphs.sObfuscatedSHRINEEnding) > 0) { //alert(i2b2.CRC.view.graphs.sObfuscatedSHRINEEnding); var asTempArray = sValue.split(i2b2.CRC.view.graphs.sObfuscatedSHRINEEnding); //alert(asTempArray); iValue = asTempArray[0].trim(); } else { iValue = sValue; } } } else { iValue = sValue; } function isNumber(obj) {return ! isNaN(obj-0) && obj; }; if (!isNumber(iValue)) { iValue = "undefined #"; return iValue; } return iValue; } catch(err) { console.error(err); } } // END of function sValueOfSHRINEText /********************************************************************************* FUNCTION sTexti2b2Value Return a TEXT number that has obfuscated blurring - FOR DISPLAY ONLY **********************************************************************************/ i2b2.CRC.view.graphs.sTexti2b2Value = function(iValue) { try { if (iValue === undefined || iValue === null || iValue === "") { sValue = "undefined"; return sValue; } function isNumber(obj) {return ! isNaN(obj-0) && obj; }; if (!isNumber(iValue)) { sValue = "undefined"; return sValue; } if (iValue >= i2b2.CRC.view.graphs.iObfuscatedFloorNumber) { if (i2b2.PM.model.isObfuscated) { sValue = iValue+i2b2.CRC.view.graphs.sObfuscatedEnding; } else { sValue = iValue; } } else { if (i2b2.PM.model.isObfuscated) { sValue = i2b2.CRC.view.graphs.sObfuscatedText; } else { sValue = iValue; } } return sValue; } catch(err) { console.error(err); } } // END of function sTexti2b2Value /********************************************************************************* FUNCTION clearGraphs Clear the previously used Div for the graphs **********************************************************************************/ i2b2.CRC.view.graphs.clearGraphs = function(sDivNameToClear) { try { if (sDivNameToClear === undefined || sDivNameToClear === null || sDivNameToClear === "") throw ("ERROR 291 - i2b2.CRC.view.graphs.sNameOfPreviousDiv in function clearGraphs is null"); //Clear Divs in the original div for the charts var oClearDiv = document.getElementById(sDivNameToClear); oClearDiv.innerHTML = ""; } catch(err) { console.error(err); } } // END of function clearGraphs /********************************************************************************* FUNCTION clearDivForIE8 Clear Div and put message in the middle that "The Graphs Cannot Display in IE8" **********************************************************************************/ i2b2.CRC.view.graphs.aDivForIE8 = function(sDivNameToClear) { try { if (sDivNameToClear === undefined || sDivNameToClear === null || sDivNameToClear === "") throw ("ERROR 291 - i2b2.CRC.view.graphs.sNameOfPreviousDiv in function clearGraphs is null"); // Clear Divs in the original div for the charts var oClearDiv = document.getElementById(sDivNameToClear); var child = document.createElement("div"); child.setAttribute("id","IE_Div"); child.setAttribute("width","auto"); oClearDiv.appendChild(child); // establish style and draw out containing Div var sDivStyle = "font-family: Verdana, Geneva, sans-serif;" + "font-size: 12px;" + "text-align: center;" + "vertical-align: middle;" + "background-color: white;" + "width: 100%;"; child.setAttribute("style",sDivStyle); // establish table in Div and set up its style. var sTableHtml = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
 
Graph Results is not supported for this version
of Internet Explorer. In order to display the graphs in
Internet Explorer you will need to use version 11 or higher.
 
'; child.innerHTML = sTableHtml; } catch(err) { console.error(err); } } // END of function clearDivForIE8 console.timeEnd('execute time'); console.groupEnd(); \ No newline at end of file