# HG changeset patch # User insilico-bob # Date 1460054947 14400 # Node ID 6f702f8af5a040ad1941f79885ecd51607814f9d # Parent 759f9223ef6c920b54d58b2c3c7a126cb991e747 Uploaded diff -r 759f9223ef6c -r 6f702f8af5a0 ._mda_heatmap_viz Binary file ._mda_heatmap_viz has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/._.DS_Store Binary file mda_heatmap_viz/._.DS_Store has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/._mda_heatmap_viz.xml Binary file mda_heatmap_viz/._mda_heatmap_viz.xml has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/._mda_heatmap_viz Binary file mda_heatmap_viz/templates/._mda_heatmap_viz has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/._.DS_Store Binary file mda_heatmap_viz/templates/mda_heatmap_viz/._.DS_Store has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/._mda_heatmap_viz.xml Binary file mda_heatmap_viz/templates/mda_heatmap_viz/._mda_heatmap_viz.xml has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/config/._.DS_Store Binary file mda_heatmap_viz/templates/mda_heatmap_viz/config/._.DS_Store has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/mda_heatmap_viz.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/mda_heatmap_viz.xml Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,16 @@ + + + + + + + $Galaxy_Dir/config/plugins/visualizations/mda_heatmap_viz + + $__tool_directory__/mda_heatmap_viz + $Galaxy_Dir/config/plugins/visualizations/mda_heatmap_viz + + + + + diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/._.DS_Store Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/._.DS_Store has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/css/NGCHM.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/css/NGCHM.css Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,291 @@ +@CHARSET "ISO-8859-1"; +/** + * */ + + +body, html { + width:100%; + height:100%; + margin:0; + padding:0; + overflow:auto; + font-family: sans-serif; + font-family: arial; + font-size: 15px; +} + +div.mdaServiceHeader +{ + float: none; + width: 100%; + height: 40px; + color: #666; + margin: 2px 0px 2px 0px; + background-color: white; + border-bottom: 4px solid #da0505; + z-index: -1; + overflow: hidden; +} + +div.mdaServiceHeaderLogo +{ + float: left; + width: auto; +} + +div.mdaServiceHeaderLogo img +{ + height: 35px; +} + + +#divider{ + height: 82%; + width: 5px; + vertical-align:top; + display:inline-block; + background: #666666; + cursor: ew-resize; +} + +#summary_chm { + height:100%; + width:48%; + vertical-align:top; + margin-left: 3px; + display:inline-block; +} + +#summary_canvas { + height:82%; + width:98%; + zIndex:1; +} + +#classBarLabels{ + height:95%; + width:95%; + pointer-events:none; +} + +#detail_chm { + height:100%; + width:48%; + vertical-align:top; + display:inline-block; +} + +#detail_canvas { + height:82%; + width:90%; + zIndex:1; +} + +#detail_buttons { + margin-top: 10px; +} + +#chmFile { + display: none +} + +#helptext { + margin: 5px !important; + padding: 10px 10px 10px 10px !important; + border: 1px solid #1a1a1a; + border-radius: 15px; + font-size: 90% !important; + font-family: Arial; + font-weight: normal; + background: #dddddd; + color: #000000; + min-width: 100px; + z-index: 10000; + text-align: center; + box-shadow: 5px 5px 5px #777777; +} + +#helpprefs { + margin: 5px !important; + padding: 8px 8px 8px 8px !important; + border: 1px solid #1a1a1a; + border-radius: 15px; + font-size: 90% !important; + font-family: Arial; + font-weight: normal; + background: #dddddd; + color: #000000; + z-index: 10000; + text-align: center; + height: auto; + width: auto; + box-shadow: 5px 5px 5px #777777; + white-space: nowrap; +} + +#prefprefs { + position: relative; + display: inline-block; +} + + +.input-color { + position: relative; +} +.input-color input { + padding-left: 0px; +} +.input-color .color-box { + width: 30px; + height: 15px; + display: inline-block; + background-color: #ccc; + position: absolute; + left: -8px; + top: -8px; +} + +table { + width: 100%; +} + +td { + height: 0px; + padding: 0px; + font-size: 80% !important; + font-family: Arial; + font-weight: normal; + color: #0843c1; + vertical-align: center; +} + +.searchItem{ + background-color: yellow; +} + +#searchError{ + border: 1px solid #1a1a1a; + border-radius: 10px; + font-size: 90% !important; + font-family: Arial; + font-weight: normal; + background: #dddddd; + color: red; + position: absolute; + width: 180px; + z-index:10; + /* height: 50px; */ + text-align: center; + box-shadow: 5px 5px 5px #777777; +} + +.labelMenu{ + border: 1px solid #1a1a1a; + border-radius: 10px; + font-size: 90% !important; + font-family: Arial; + font-weight: normal; + background: #dddddd; + color: #000000; + width: 240px; + height: 150px; + text-align: center; + box-shadow: 5px 5px 5px #777777; + position:relative; +} + +.labelMenuCaption{ + padding: .4em 1em; + background: #0843c1; + color: #ffffff; + font-size: 90% !important; + font-family: Arial; + font-weight: bold; + text-align: left; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.labelMenuClose{ + position: absolute; + right: 2px; + bottom:2px; +} + +.labelMenuHeader tr{ + padding: 4em 1em; + position:relative; + margin: 0px 0px 0px 0px; + background: #f8f3f1; + color: #0843c1; + text-align: left; + font-size: 90% !important; + font-family: Arial; + font-weight: normal; + border-radius: 10px; +} + +.labelMenuBody tr{ + height: 20px; +} +.labelMenuBody td{ + height:20px; + position: absolute; + font-size: 80% !important; + font-family: Arial; + font-weight: normal; + color: #0843c1; + cursor: pointer; +} + +.labelMenuBody td:hover{ + font-weight: bold; +} + +table.breakpointContainer { + border: none; + border-spacing: 0px; + border-collapse: collapse; +} + +td.breakpointContainer +th.breakpointContainer { + text-align: center; + margin: 0px; + padding: 0px 5px 0px 0px; + border-spacing: 0px; + border-collapse: collapse; +} + +#prefsPanel, #pdfPrefsPanel { + margin: 5px !important; + padding: 2px 2px 2px 2px !important; + border: 1px solid #1a1a1a; + border-radius: 15px; + font-size: 90% !important; + font-family: Arial; + font-weight: normal; + background: #dddddd; + color: #000000; + z-index: 10000; + text-align: center; + height: auto; + width: auto; + box-shadow: 5px 5px 5px #777777; + white-space: nowrap; +} +#prefsHeader, #pdfPrefsHeader{ + display: table; + font-size: 18px; + font-weight: bold; + color: #0843c1; + margin: auto; + width: 100%; + height: 30px; + line-height: 30px; + text-align: center; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + background: #CBD1EA; +} + diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/addButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/addButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/breakButtonOff.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/breakButtonOff.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/breakButtonOn.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/breakButtonOn.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/cancel.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/cancel.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/classButtonOff.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/classButtonOff.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/classButtonOn.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/classButtonOn.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/closeButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/closeButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/covariateBarsOff.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/covariateBarsOff.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/covariateBarsOn.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/covariateBarsOn.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/createPdf.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/createPdf.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/dataLayersOff.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/dataLayersOff.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/dataLayersOn.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/dataLayersOn.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/filterClassButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/filterClassButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/full.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/full.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/full_selected.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/full_selected.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/gear.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/gear.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/gearDis.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/gearDis.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/gear_big.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/gear_big.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/go.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/go.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/join.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/join.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/mdandersonlogo260x85.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/mdandersonlogo260x85.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/minusButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/minusButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/next.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/next.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/pdf.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/pdf.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/plusButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/plusButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefApply.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefApply.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefBack.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefBack.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefCancel.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefCancel.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefSave.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prefSave.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prev.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/prev.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/removeFilterClassButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/removeFilterClassButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonH.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonH.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonH_selected.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonH_selected.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonV.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonV.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonV_selected.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/ribbonV_selected.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/rowsColsOff.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/rowsColsOff.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/rowsColsOn.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/rowsColsOn.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/save.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/save.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/searchButton.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/searchButton.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/split.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/split.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/zoom-in.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/zoom-in.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/images/zoom-out.png Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/images/zoom-out.png has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/._UserPreferenceManager.js.sav Binary file mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/._UserPreferenceManager.js.sav has changed diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/.gitignore Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,1 @@ +/temp.js diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/ColorMapManager.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/ColorMapManager.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,246 @@ + +function ColorMap(colorMapObj){ + var type = colorMapObj["type"]; + var thresholds; + if (type == "quantile"){ + thresholds = colorMapObj["linearEquiv"]; + } else { + thresholds = colorMapObj["thresholds"]; + } + var numBreaks = thresholds.length; + + // Hex colors + var colors = colorMapObj["colors"]; + var missingColor = colorMapObj["missing"]; + + // RGBA colors + var rgbaColors = []; + var rgbaMissingColor; + + if (colorMapObj["rgbaColors"] != undefined){ + rgbaColors = colorMapObj["rgbaColors"]; + } else { + for (var i =0; i= thresholds[numBreaks-1]){ + color = rgbaColors[numBreaks-1]; // return color for highest threshold if value is above range + } else { + var bounds = findBounds(value, thresholds); + color = blendColors(value, bounds); + } + + return color; + } + + this.getClassificationColor = function(value){ + var color; + if (type == "discrete"){ + for (var i = 0; i < thresholds.length; i++){ + if (value == thresholds[i]){ + color = rgbaColors[i]; + return color; + } + } + return rgbaMissingColor; + } else { + if (isNaN(value)){ + color = rgbaMissingColor; + }else{ + color = this.getColor(value); + } + } + + return color; + } + + this.addBreakpoint = function(value,color){ + var bounds = findBounds(value, thresholds); + thresholds.splice(bounds["lower"],0,value); + colors.splice(bounds["lower"],0,color); + rgbaColors.splice(bounds["lower"],0,hexToRgba(color)); + } + + this.changeBreakpoint = function(value,newColor){ + var bounds = findBounds(value, thresholds); + thresholds.splice(bounds["lower"],1,value); + colors.splice(bounds["lower"],1,newColor); + rgbaColors.splice(bounds["lower"],1,hexToRgba(newColor)); + } + + this.removeBreakpoint = function(value){ + var bounds = findBounds(value, thresholds); + thresholds.splice(bounds["lower"],1); + colors.splice(bounds["lower"],1); + rgbaColors.splice(bounds["lower"],1); + } + + //===========================// + // internal helper functions // + //===========================// + + function findBounds(value, thresholds){ + var bounds = {}; + var i =0; + while (i 0) { + document.getElementById('detail_buttons').style.display = ''; + detCanvas.width = (detailDataViewWidth + calculateTotalClassBarHeight("row") + detailDendroWidth); + detCanvas.height = (detailDataViewHeight + calculateTotalClassBarHeight("column") + detailDendroHeight); + detSetupGl(); + detInitGl(); + createLabelMenus(); + updateSelection(); + } + + detCanvas.onmousedown = clickStart; + document.onmouseup = clickEnd; + detCanvas.onmousemove = handleMove; + detCanvas.onmouseleave = userHelpClose; + document.addEventListener("touchmove", function(e){ + e.preventDefault(); + if (e.touches){ + if (e.touches.length > 1){ + return false; + } + } + }) + detCanvas.addEventListener("touchstart", function(e){ + userHelpClose(); + clickStart(e); + }, false); + detCanvas.addEventListener("touchmove", function(e){ + e.stopPropagation(); + e.preventDefault(); + handleMove(e); + }, false); + detCanvas.addEventListener("touchend", function(e){clickEnd(e)}, false); + + detCanvas.addEventListener("gestureend",function(e){ + if (e.scale > 1){ + detailDataZoomIn(); + } else if (e.scale < 1){ + detailDataZoomOut(); + } + },false) + + + document.onkeydown = keyNavigate; +} + +function clickStart(e){ + userHelpClose(); + dragOffsetX = e.touches ? e.touches[0].pageX : e.pageX; + dragOffsetY = e.touches ? e.touches[0].pageY : e.pageY; + + mouseDown = true; +} +function clickEnd(e){ + mouseDown = false; + var dragEndX = e.touches ? e.touches[0].pageX : e.pageX; + var dragEndY = e.touches ? e.touches[0].pageY : e.pageY; + var rowElementSize = dataBoxWidth * detCanvas.clientWidth/detCanvas.width; + var colElementSize = dataBoxHeight * detCanvas.clientHeight/detCanvas.height; + if (Math.abs(dragEndX - dragOffsetX) < colElementSize/10 && Math.abs(dragEndY - dragOffsetY) < rowElementSize/10){ + userHelpOpen(e); + } +} + +function handleDrag(e) { + if(!mouseDown) return; + var rowElementSize = dataBoxWidth * detCanvas.clientWidth/detCanvas.width; + var colElementSize = dataBoxHeight * detCanvas.clientHeight/detCanvas.height; + if (e.touches){ + if (e.touches.length > 1){ + return false; + } + } + var xDrag = e.touches ? e.touches[0].pageX - dragOffsetX : e.pageX - dragOffsetX; + var yDrag = e.touches ? e.touches[0].pageY - dragOffsetY : e.pageY - dragOffsetY; + + if ((Math.abs(xDrag/rowElementSize) > 1) || + (Math.abs(yDrag/colElementSize) > 1) ) { + currentRow = Math.floor(currentRow - (yDrag/colElementSize)); + currentCol = Math.floor(currentCol - (xDrag/rowElementSize)); + + dragOffsetX = e.touches ? e.touches[0].pageX : e.pageX; + dragOffsetY = e.touches ? e.touches[0].pageY : e.pageY; + var numRows = heatMap.getNumRows(MatrixManager.DETAIL_LEVEL); + var numCols = heatMap.getNumColumns(MatrixManager.DETAIL_LEVEL); + checkRow(); + checkColumn(); + + updateSelection(); + } + return false; +} + +function handleMove(e) { + // Do not clear help if the mouse position did not change. Repeated firing of the mousemove event can happen on random + // machines in all browsers but FireFox. There are varying reasons for this so we check and exit if need be. + if(old_mouse_pos[0] != e.clientX || old_mouse_pos[1] != e.clientY) { + userHelpClose(); + old_mouse_pos = [e.clientX, e.clientY]; + } + if (mouseDown){ + handleDrag(e); + } +} + +function getColClassPixelHeight() { + var classbarHeight = calculateTotalClassBarHeight("column"); + return detCanvas.clientHeight*(classbarHeight/detCanvas.height); +} + +function getRowClassPixelWidth() { + var classbarWidth = calculateTotalClassBarHeight("row"); + return detCanvas.clientWidth*(classbarWidth/detCanvas.width); +} + +function getColDendroPixelHeight() { + return detCanvas.clientHeight*(detailDendroHeight/detCanvas.height); +} + +function getRowDendroPixelWidth() { + return detCanvas.clientWidth*(detailDendroWidth/detCanvas.width); +} + +function isOnObject(e,type) { + var rowClassWidthPx = getRowClassPixelWidth(); + var colClassHeightPx = getColClassPixelHeight(); + var rowDendroWidthPx = getRowDendroPixelWidth(); + var colDendroHeightPx = getColDendroPixelHeight(); + if (e.layerY > colClassHeightPx + colDendroHeightPx) { + if ((type == "map") && e.layerX > rowClassWidthPx + rowDendroWidthPx) { + return true; + } + if ((type == "rowClass") && e.layerX < rowClassWidthPx + rowDendroWidthPx && e.layerX > rowDendroWidthPx) { + return true; + } + } else if (e.layerY > colDendroHeightPx) { + if ((type == "colClass") && e.layerX > rowClassWidthPx + rowDendroWidthPx) { + return true; + } + } + return false; +} + +function detailDataZoomIn() { + userHelpClose(); + if (mode == 'NORMAL') { + var current = zoomBoxSizes.indexOf(dataBoxWidth); + if (current < zoomBoxSizes.length - 1) { + setDetailDataSize (zoomBoxSizes[current+1]); + updateSelection(); + } + } else if ((mode == 'RIBBONH') || (mode == 'RIBBONH_DETAIL')) { + var current = zoomBoxSizes.indexOf(dataBoxHeight); + if (current < zoomBoxSizes.length - 1) { + setDetailDataHeight (zoomBoxSizes[current+1]); + updateSelection(); + } + } else if ((mode == 'RIBBONV') || (mode == 'RIBBONV_DETAIL')) { + var current = zoomBoxSizes.indexOf(dataBoxWidth); + if (current < zoomBoxSizes.length - 1) { + setDetailDataWidth(zoomBoxSizes[current+1]); + updateSelection(); + } + } +} + +function detailDataZoomOut() { + userHelpClose(); + if (mode == 'NORMAL') { + var current = zoomBoxSizes.indexOf(dataBoxWidth); + if ((current > 0) && + (Math.floor((detailDataViewHeight-detailDataViewBoarder)/zoomBoxSizes[current-1]) <= heatMap.getNumRows(MatrixManager.DETAIL_LEVEL)) && + (Math.floor((detailDataViewWidth-detailDataViewBoarder)/zoomBoxSizes[current-1]) <= heatMap.getNumColumns(MatrixManager.DETAIL_LEVEL))){ + setDetailDataSize (zoomBoxSizes[current-1]); + updateSelection(); + } + } else if ((mode == 'RIBBONH') || (mode == 'RIBBONH_DETAIL')) { + var current = zoomBoxSizes.indexOf(dataBoxHeight); + if ((current > 0) && + (Math.floor((detailDataViewHeight-detailDataViewBoarder)/zoomBoxSizes[current-1]) <= heatMap.getNumRows(MatrixManager.DETAIL_LEVEL))) { + setDetailDataHeight (zoomBoxSizes[current-1]); + updateSelection(); + } + } else if ((mode == 'RIBBONV') || (mode == 'RIBBONV_DETAIL')){ + var current = zoomBoxSizes.indexOf(dataBoxWidth); + if ((current > 0) && + (Math.floor((detailDataViewWidth-detailDataViewBoarder)/zoomBoxSizes[current-1]) <= heatMap.getNumColumns(MatrixManager.DETAIL_LEVEL))){ + setDetailDataWidth (zoomBoxSizes[current-1]); + updateSelection(); + } + } +} + +//How big each data point should be in the detail pane. +function setDetailDataSize(size) { + setDetailDataWidth (size); + setDetailDataHeight(size); +} + +//How big each data point should be in the detail pane. +function setDetailDataWidth(size) { + var prevDataPerRow = dataPerRow; + dataBoxWidth = size; + setDataPerRowFromDet(Math.floor((detailDataViewWidth-detailDataViewBoarder)/dataBoxWidth)); + + //Adjust the current column based on zoom but don't go outside or the heat map matrix dimensions. + if (prevDataPerRow != null) { + if (prevDataPerRow > dataPerRow) + currentCol += Math.floor((prevDataPerRow - dataPerRow) / 2); + else + currentCol -= Math.floor((dataPerRow - prevDataPerRow) / 2); + checkColumn(); + } +} + +//How big each data point should be in the detail pane. +function setDetailDataHeight(size) { + var prevDataPerCol = dataPerCol; + dataBoxHeight = size; + setDataPerColFromDet(Math.floor((detailDataViewHeight-detailDataViewBoarder)/dataBoxHeight)); + + //Adjust the current row but don't go outside of the current heat map dimensions + if (prevDataPerCol != null) { + if (prevDataPerCol > dataPerCol) + currentRow += Math.floor((prevDataPerCol - dataPerCol) / 2); + else + currentRow -= Math.floor((dataPerCol - prevDataPerCol) / 2); + checkRow(); + } +} + +//How much data are we showing per row - determined by dataBoxWidth and detailDataViewWidth +function getDetailDataPerRow() { + return dataPerRow; +} + +//How much data are we showing per row - determined by dataBoxWidth and detailDataViewWidth +function getDetailDataPerCol () { + return dataPerCol; +} + +function detailHRibbonButton () { + clearDendroSelection(); + detailHRibbon(); +} + +function detailVRibbonButton () { + clearDendroSelection(); + detailVRibbon(); +} + +//Change to horizontal ribbon view. Note there is a standard full ribbon view and also a sub-selection +//ribbon view if the user clicks on the dendrogram. If a dendrogram selection is in effect, then +//selectedStart and selectedStop will be set. +function detailHRibbon () { + userHelpClose(); + var previousMode = mode; + var prevWidth = dataBoxWidth; + saveCol = currentCol; + + + mode='RIBBONH'; + setButtons(); + + // If normal (full) ribbon, set the width of the detail display to the size of the horizontal ribbon view + // and data size to 1. + if (selectedStart == null || selectedStart == 0) { + detailDataViewWidth = heatMap.getNumColumns(MatrixManager.RIBBON_HOR_LEVEL) + detailDataViewBoarder; + setDetailDataWidth(1); + currentCol = 1; + } else { + var selectionSize = selectedStop - selectedStart + 1; + if (selectionSize < 500) { + mode='RIBBONH_DETAIL' + } else { + var rvRate = heatMap.getColSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + selectionSize = Math.floor(selectionSize/rvRate); + } + var width = Math.max(1, Math.floor(500/selectionSize)); + detailDataViewWidth = (selectionSize * width) + detailDataViewBoarder; + setDetailDataWidth(width); + currentCol = selectedStart; + } + + detailDataViewHeight = DETAIL_SIZE_NORMAL_MODE; + if ((previousMode=='RIBBONV') || (previousMode == 'RIBBONV_DETAIL')) { + setDetailDataHeight(prevWidth); + currentRow=saveRow; + } + + detCanvas.width = (detailDataViewWidth + calculateTotalClassBarHeight("row") + detailDendroWidth); + detCanvas.height = (detailDataViewHeight + calculateTotalClassBarHeight("column") + detailDendroHeight); + detSetupGl(); + detInitGl(); + drawDetailHeatMap(); + updateSelection(); + highlightAllColLabels(); + document.getElementById("viewport").setAttribute("content", "height=device-height"); + document.getElementById("viewport").setAttribute("content", ""); +} + +function detailVRibbon () { + userHelpClose(); + var previousMode = mode; + var prevHeight = dataBoxHeight; + saveRow = currentRow; + + mode='RIBBONV'; + setButtons(); + + // If normal (full) ribbon, set the width of the detail display to the size of the horizontal ribbon view + // and data size to 1. + if (selectedStart == null || selectedStart == 0) { + detailDataViewHeight = heatMap.getNumRows(MatrixManager.RIBBON_VERT_LEVEL) + detailDataViewBoarder; + setDetailDataHeight(1); + currentRow = 1; + } else { + var selectionSize = selectedStop - selectedStart + 1; + if (selectionSize < 500) { + mode = 'RIBBONV_DETAIL'; + } else { + var rvRate = heatMap.getRowSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + selectionSize = Math.floor(selectionSize / rvRate); + } + var height = Math.max(1, Math.floor(500/selectionSize)); + detailDataViewHeight = (selectionSize * height) + detailDataViewBoarder; + setDetailDataHeight(height); + currentRow = selectedStart; + } + + detailDataViewWidth = DETAIL_SIZE_NORMAL_MODE; + if ((previousMode=='RIBBONH') || (previousMode=='RIBBONH_DETAIL')) { + setDetailDataWidth(prevHeight); + currentCol = saveCol; + } + + detCanvas.width = (detailDataViewWidth + calculateTotalClassBarHeight("row") + detailDendroWidth); + detCanvas.height = (detailDataViewHeight + calculateTotalClassBarHeight("column") + detailDendroHeight); + detSetupGl(); + detInitGl(); + drawDetailHeatMap(); + updateSelection(); + highlightAllRowLabels(); + document.getElementById("viewport").setAttribute("content", "height=device-height"); + document.getElementById("viewport").setAttribute("content", ""); +} + +function detailNormal () { + userHelpClose(); + var previousMode = mode; + mode = 'NORMAL'; + setButtons(); + detailDataViewHeight = DETAIL_SIZE_NORMAL_MODE; + detailDataViewWidth = DETAIL_SIZE_NORMAL_MODE; + if ((previousMode=='RIBBONV') || (previousMode=='RIBBONV_DETAIL')) { + setDetailDataSize(dataBoxWidth); + currentRow = saveRow; + } else if ((previousMode=='RIBBONH') || (previousMode=='RIBBONH_DETAIL')) { + setDetailDataSize(dataBoxHeight); + currentCol = saveCol; + } else { + + } + detCanvas.width = (detailDataViewWidth + calculateTotalClassBarHeight("row") + detailDendroWidth); + detCanvas.height = (detailDataViewHeight + calculateTotalClassBarHeight("column") + detailDendroHeight); + detSetupGl(); + detInitGl(); + clearDendroSelection(); + drawDetailHeatMap(); + updateSelection(); + document.getElementById("viewport").setAttribute("content", "height=device-height"); + document.getElementById("viewport").setAttribute("content", ""); +} + +function setButtons() { + var full = document.getElementById('full_btn'); + var ribbonH = document.getElementById('ribbonH_btn'); + var ribbonV = document.getElementById('ribbonV_btn'); + full.src= staticPath+ "images/full.png"; + ribbonH.src= staticPath + "images/ribbonH.png"; + ribbonV.src= staticPath + "images/ribbonV.png"; + if (mode=='RIBBONV') + ribbonV.src= staticPath + "images/ribbonV_selected.png"; + else if (mode == "RIBBONH") + ribbonH.src= staticPath + "images/ribbonH_selected.png"; + else + full.src= staticPath + "images/full_selected.png"; +} + + +//Called when split/join button is pressed +function detailSplit(){ + userHelpClose(); + // If the summary and detail are in a single browser window, this is a split action. + if (!isSub) { + //Write current selection settings to the local storage + hasSub=true; + updateSelection(); + + //Create a new detail browser window + detWindow = window.open(window.location.href + '&sub=true', '_blank', 'modal=yes, width=' + (window.screen.availWidth / 2) + ', height='+ window.screen.availHeight + ',top=0, left=' + (window.screen.availWidth / 2)); + detWindow.moveTo(window.screen.availWidth / 2, 0); + detWindow.onbeforeunload = function(){rejoinNotice(),detailJoin(),hasSub=false;} // when you close the subwindow, it will return to the original window + var detailDiv = document.getElementById('detail_chm'); + detailDiv.style.display = 'none'; + var dividerDiv = document.getElementById('divider'); + dividerDiv.style.display = 'none'; + //In summary window, hide the action buttons and expand the summary to 100% of the window. + var detailButtonDiv = document.getElementById('detail_buttons'); + detailButtonDiv.style.display = 'none'; + var summaryDiv = document.getElementById('summary_chm'); + summaryDiv.style.width = '100%'; + } else { + updateSelection(); + rejoinNotice(); + window.close(); + } +} + +//Called when a separate detail window is joined back into the main window. +function detailJoin() { + var detailDiv = document.getElementById('detail_chm'); + detailDiv.style.display = ''; + detailDiv.style.width = '48%'; + var detailButtonDiv = document.getElementById('detail_buttons'); + detailButtonDiv.style.display = ''; + var dividerDiv = document.getElementById('divider'); + dividerDiv.style.display = ''; + var summaryDiv = document.getElementById('summary_chm'); + summaryDiv.style.width = '48%'; + initFromLocalStorage(); +} + + +// Callback that is notified every time there is an update to the heat map +// initialize, new data, etc. This callback draws the summary heat map. +function processDetailMapUpdate (event, level) { + + if (event == MatrixManager.Event_INITIALIZED) { + detailInit(); + } else { + //Data tile update - wait a bit to see if we get another new tile quickly, then draw + if (detEventTimer != 0) { + //New tile arrived - reset timer + clearTimeout(detEventTimer); + } + detEventTimer = setTimeout(drawDetailHeatMap, 200); + } +} + +//Perform all initialization functions for Detail heat map +function detailInit() { + var dendroGram = heatMap.getDendrogram(); + if (!heatMap.showRowDendrogram("DETAIL")) { + detailDendroWidth = 15; + } else { + detailDendroWidth = parseInt(dendroGram['row_dendro_height'])+5; + } + if (!heatMap.showColDendrogram("DETAIL")) { + detailDendroHeight = 15; + } else { + detailDendroHeight = parseInt(dendroGram['col_dendro_height'])+5; + } + document.getElementById('detail_buttons').style.display = ''; + detCanvas.width = (detailDataViewWidth + calculateTotalClassBarHeight("row") + detailDendroWidth); + detCanvas.height = (detailDataViewHeight + calculateTotalClassBarHeight("column") + detailDendroHeight); + createLabelMenus(); + detSetupGl(); + detInitGl(); + if (isSub) + initFromLocalStorage(); + else + updateSelection(); +} + +function drawDetailHeatMap() { + + if ((currentRow == null) || (currentRow == 0)) { + return; + } + var colorMap = heatMap.getColorMapManager().getColorMap("dl1"); + var rowClassBarWidth = calculateTotalClassBarHeight("row"); + var searchRows = getSearchRows(); + var searchCols = getSearchCols(); + var searchGridColor = [0,0,0]; + var regularGridColor = [255,255,255]; + var detDataPerRow = getCurrentDetDataPerRow(); + var detDataPerCol = getCurrentDetDataPerCol(); + + //Build a horizontal grid line for use between data lines. Tricky because some dots will be selected color if a column is in search results. + var gridLine = new Uint8Array(new ArrayBuffer((detailDendroWidth + rowClassBarWidth + detailDataViewWidth) * BYTE_PER_RGBA)); + if (detailGrid == true) { + var linePos = (detailDendroWidth+rowClassBarWidth)*BYTE_PER_RGBA; + gridLine[linePos]=0; gridLine[linePos+1]=0;gridLine[linePos+2]=0;gridLine[linePos+3]=255;linePos+=BYTE_PER_RGBA; + for (var j = 0; j < detDataPerRow; j++) { + var gridColor = ((searchCols.indexOf(currentCol+j) > -1) || (searchCols.indexOf(currentCol+j+1) > -1)) ? searchGridColor : regularGridColor; + for (var k = 0; k < dataBoxWidth; k++) { + if (k==dataBoxWidth-1 && detailGrid == true && dataBoxWidth > labelSizeLimit ){ // should the grid line be drawn? + gridLine[linePos] = gridColor[0]; gridLine[linePos+1] = gridColor[1]; gridLine[linePos+2] = gridColor[2]; gridLine[linePos+3] = 255; + } else { + gridLine[linePos]=regularGridColor[0]; gridLine[linePos + 1]=regularGridColor[1]; gridLine[linePos + 2]=regularGridColor[2]; gridLine[linePos + 3]=255; + } + linePos += BYTE_PER_RGBA; + } + } + gridLine[linePos]=0; gridLine[linePos+1]=0;gridLine[linePos+2]=0;gridLine[linePos+3]=255;linePos+=BYTE_PER_RGBA; + } + + //Setup texture to draw on canvas. + + //Draw black boarder line + var pos = (rowClassBarWidth+detailDendroWidth)*BYTE_PER_RGBA; + for (var i = 0; i < detailDataViewWidth; i++) { + detTexPixels[pos]=0;detTexPixels[pos+1]=0;detTexPixels[pos+2]=0;detTexPixels[pos+3]=255;pos+=BYTE_PER_RGBA; + } + + //Needs to go backward because WebGL draws bottom up. + var line = new Uint8Array(new ArrayBuffer((rowClassBarWidth + detailDendroWidth + detailDataViewWidth) * BYTE_PER_RGBA)); + for (var i = detDataPerCol-1; i >= 0; i--) { + var linePos = (rowClassBarWidth + detailDendroWidth)*BYTE_PER_RGBA; + //Add black boarder + line[linePos]=0; line[linePos+1]=0;line[linePos+2]=0;line[linePos+3]=255;linePos+=BYTE_PER_RGBA; + for (var j = 0; j < detDataPerRow; j++) { // for every data point... + var val = heatMap.getValue(getLevelFromMode(MatrixManager.DETAIL_LEVEL), getCurrentDetRow()+i, getCurrentDetCol()+j); + var color = colorMap.getColor(val); + var gridColor = ((searchCols.indexOf(currentCol+j) > -1) || (searchCols.indexOf(currentCol+j+1) > -1)) ? searchGridColor : regularGridColor; + + //For each data point, write it several times to get correct data point width. + for (var k = 0; k < dataBoxWidth; k++) { + if (k==dataBoxWidth-1 && detailGrid == true && dataBoxWidth > labelSizeLimit ){ // should the grid line be drawn? + line[linePos] = gridColor[0]; line[linePos+1] = gridColor[1]; line[linePos+2] = gridColor[2]; line[linePos+3] = 255; + } else { + line[linePos] = color['r']; line[linePos + 1] = color['g']; line[linePos + 2] = color['b']; line[linePos + 3] = color['a']; + } + linePos += BYTE_PER_RGBA; + } + } + line[linePos]=0; line[linePos+1]=0;line[linePos+2]=0;line[linePos+3]=255;linePos+=BYTE_PER_RGBA; + + + //Write each line several times to get correct data point height. + for (dup = 0; dup < dataBoxHeight; dup++) { + if (dup == dataBoxHeight-1 && detailGrid == true && dataBoxHeight > labelSizeLimit){ // do we draw gridlines? + if ((searchRows.indexOf(currentRow+i) > -1) || (searchRows.indexOf(currentRow+i-1) > -1)) { + pos += (rowClassBarWidth + detailDendroWidth)*BYTE_PER_RGBA; + for (var k = 0; k < detailDataViewWidth; k++) { + detTexPixels[pos]=searchGridColor[0];detTexPixels[pos+1]=searchGridColor[1];detTexPixels[pos+2]=searchGridColor[2];detTexPixels[pos+3]=255;pos+=BYTE_PER_RGBA; + } + } else { + for (k = 0; k < line.length; k++) { + detTexPixels[pos]=gridLine[k]; + pos++; + } + } + } else { + for (k = 0; k < line.length; k++) { + detTexPixels[pos]=line[k]; + pos++; + } + } + } + } + + //Draw black boarder line + pos += (rowClassBarWidth + detailDendroWidth)*BYTE_PER_RGBA; + for (var i = 0; i < detailDataViewWidth; i++) { + detTexPixels[pos]=0;detTexPixels[pos+1]=0;detTexPixels[pos+2]=0;detTexPixels[pos+3]=255;pos+=BYTE_PER_RGBA; + } + clearDetailDendrograms(); + if (heatMap.showRowDendrogram("DETAIL")) { + rowDetailDendroMatrix = buildDetailDendroMatrix('Row', currentRow, currentRow+dataPerCol, heatMap.getNumRows(MatrixManager.DETAIL_LEVEL)/dataPerCol); + detailDrawRowDendrogram(detTexPixels); + } + if (heatMap.showColDendrogram("DETAIL")) { + colDetailDendroMatrix = buildDetailDendroMatrix('Column', currentCol, currentCol+dataPerRow, heatMap.getNumColumns(MatrixManager.DETAIL_LEVEL)/dataPerRow); + detailDrawColDendrogram(detTexPixels); + } + //Draw column classification bars. + detailDrawColClassBars(); + detailDrawRowClassBars(); + + + //WebGL code to draw the summary heat map. + det_gl.activeTexture(det_gl.TEXTURE0); + det_gl.texImage2D( + det_gl.TEXTURE_2D, + 0, + det_gl.RGBA, + detTextureParams['width'], + detTextureParams['height'], + 0, + det_gl.RGBA, + det_gl.UNSIGNED_BYTE, + detTexPixels); + det_gl.uniform2fv(detUScale, detCanvasScaleArray); + det_gl.uniform2fv(detUTranslate, detCanvasTranslateArray); + det_gl.uniform2fv(detUBoxLeftTop, detCanvasBoxLeftTopArray); + det_gl.uniform2fv(detUBoxRightBottom, detCanvasBoxRightBottomArray); + det_gl.uniform1f(detUBoxThickness, 0.002); + det_gl.uniform4fv(detUBoxColor, [1.0, 1.0, 0.0, 1.0]); + det_gl.drawArrays(det_gl.TRIANGLE_STRIP, 0, det_gl.buffer.numItems); + + clearLabels(); + drawRowLabels(); + drawColLabels(); + detailDrawColClassBarLabels(); + detailDrawRowClassBarLabels(); +} + +function detailResize() { + clearLabels(); + drawRowLabels(); + drawColLabels(); + detailDrawColClassBarLabels(); + detailDrawRowClassBarLabels(); +} + +/*********************************************************** + * Search Functions Section + ***********************************************************/ + +//Called when search string is entered. +function detailSearch() { + var searchElement = document.getElementById('search_text'); + var searchString = searchElement.value; + searchItems = []; + var tmpSearchItems = searchString.split(/[;, ]+/); + itemsFound = []; + + //Put labels into the global search item list if they match a user search string. + //Regular expression is built for partial matches if the search string contains '*'. + //toUpperCase is used to make the search case insensitive. + var labels = heatMap.getRowLabels()["labels"]; + for (var j = 0; j < tmpSearchItems.length; j++) { + var reg = null; + if (tmpSearchItems[j].indexOf("*") > -1) { + reg = new RegExp("^" + tmpSearchItems[j].toUpperCase().replace(/\*/g, ".*") + "$"); + } + for (var i = 0; i < labels.length; i++) { + if ((labels[i].toUpperCase() == tmpSearchItems[j].toUpperCase()) || + ((reg != null) && reg.test(labels[i].toUpperCase()))){ + searchItems.push({'axis' : 'Row', 'label': labels[i]}); + if (itemsFound.indexOf(tmpSearchItems[j]) == -1) + itemsFound.push(tmpSearchItems[j]); + } + } + } + + labels = heatMap.getColLabels()["labels"]; + for (var j = 0; j < tmpSearchItems.length; j++) { + var reg = null; + if (tmpSearchItems[j].indexOf("*") > -1) { + reg = new RegExp("^" + tmpSearchItems[j].toUpperCase().replace(/\*/g, ".*") + "$"); + } + for (var i = 0; i < labels.length; i++) { + if ((labels[i].toUpperCase() == tmpSearchItems[j].toUpperCase()) || + ((reg != null) && reg.test(labels[i].toUpperCase()))){ + searchItems.push({'axis' : 'Column', 'label': labels[i]}); + if (itemsFound.indexOf(tmpSearchItems[j]) == -1) + itemsFound.push(tmpSearchItems[j]); + } + } + } + + //Jump to the first match + var srchText = document.getElementById('search_text'); + if (searchItems.length > 0) { + currentSearchItem = searchItems[0]; + goToCurrentSearchItem(); + if (itemsFound.length != tmpSearchItems.length) { + srchText.style.backgroundColor = "rgba(255,255,0,0.3)"; + } + } else { + if (searchString != null && searchString.length> 0) { + srchText.style.backgroundColor = "rgba(255,0,0,0.3)"; + } + //Clear previous matches when search is empty. + updateSelection(); + } +} + +function goToCurrentSearchItem() { + var row = findRowLabel(currentSearchItem.label); + if (row > -1) { + currentRow = row; + if ((mode == 'RIBBONV') && selectedStart!= 0 && (currentRow < selectedStart-1 || selectedStop-1 < currentRow)){ + showSearchError(1); + } else if (mode == 'RIBBONV' && selectedStart == 0){ + showSearchError(2); + } + checkRow(); + } else { + currentCol = findColLabel(currentSearchItem.label); + if ((mode == 'RIBBONH') && selectedStart!= 0 && (currentCol < selectedStart-1 || selectedStop-1 < currentCol )){ + showSearchError(1) + } else if (mode == 'RIBBONH' && selectedStart == 0){ + showSearchError(2); + } + checkColumn(); + } + document.getElementById('prev_btn').style.display=''; + document.getElementById('next_btn').style.display=''; + document.getElementById('cancel_btn').style.display=''; + updateSelection(); +} + +//Search the row and column labels - return position if found or -1 if not found. +function findRowLabel(name){ + var labels = heatMap.getRowLabels()["labels"]; + for (var i = 0; i < labels.length; i++) { + if (labels[i].toUpperCase() == name.toUpperCase()) + return i; + } + return -1; +} + +function findColLabel(name) { + var labels = heatMap.getColLabels()["labels"]; + for (var i = 0; i < labels.length; i++) { + if (labels[i].toUpperCase() == name.toUpperCase()) + return i; + } + return -1; +} + +//Go to next search item +function searchNext() { + var pos = findCurrentSelection(); + if (pos == searchItems.length-1) + pos = 0; + else + pos++; + currentSearchItem = searchItems[pos]; + goToCurrentSearchItem(); +} + +//Go back to previous search item. +function searchPrev() { + var pos = findCurrentSelection(); + if (pos == 0) + pos = searchItems.length-1; + else + pos--; + currentSearchItem = searchItems[pos]; + goToCurrentSearchItem(); +} + +//Called when red 'X' is clicked. +function clearSearch(){ + var searchElement = document.getElementById('search_text'); + searchElement.value = ""; + clearSrchBtns(); + detailSearch(); +} + +function clearSrchBtns() { + if ((event != null) && (event.keyCode == 13)) + return; + + document.getElementById('prev_btn').style.display='none'; + document.getElementById('next_btn').style.display='none'; + document.getElementById('cancel_btn').style.display='none'; + var srchText = document.getElementById('search_text'); + srchText.style.backgroundColor = "white"; +} + +function findCurrentSelection() { + if (currentSearchItem === undefined){ + return 0; + } + for (var i = 0; i < searchItems.length; i++) { + if (currentSearchItem.label == searchItems[i].label && currentSearchItem.axis == searchItems[i].axis) + return i; + } + return 0; +} + +//Return the column number of any columns meeting the current user search. +function getSearchCols() { + var selected = []; + for (var i = 0; i < searchItems.length; i++) { + var col = findColLabel(searchItems[i].label); + if (col > -1) + selected.push(col+1); + } + return selected; +} + +//Return row numbers of any rows meeting current user search. +function getSearchRows() { + var selected = []; + for (var i = 0; i < searchItems.length; i++) { + var row = findRowLabel(searchItems[i].label); + if (row > -1) + selected.push(row+1); + } + return selected; +} + +/*********************************************************** + * End - Search Functions + ***********************************************************/ + +function clearLabels() { + var oldLabels = document.getElementsByClassName("DynamicLabel"); + while (oldLabels.length > 0) { + labelElement.removeChild(oldLabels[0]); + } + +} + +function drawRowLabels() { + var headerSize = 0; + var colHeight = calculateTotalClassBarHeight("column") + detailDendroHeight; + if (colHeight > 0) { + headerSize = detCanvas.clientHeight * (colHeight / (detailDataViewHeight + colHeight)); + } + var skip = (detCanvas.clientHeight - headerSize) / dataPerCol; + var fontSize = Math.min(skip - 2, 11); + var start = Math.max((skip - fontSize)/2, 0) + headerSize; + var labels = heatMap.getRowLabels()["labels"]; + + + if (skip > labelSizeLimit) { + for (var i = currentRow; i < currentRow + dataPerCol; i++) { + var xPos = detCanvas.clientWidth + 3; + var yPos = start + ((i-currentRow) * skip); + addLabelDiv(labelElement, 'detail_row' + i, 'DynamicLabel', labels[i-1], xPos, yPos, fontSize, 'F'); + } + } +} + + +function drawColLabels() { + var headerSize = 0; + var rowHeight = calculateTotalClassBarHeight("row") + detailDendroWidth; + if (rowHeight > 0) { + headerSize = detCanvas.clientWidth * (rowHeight / (detailDataViewWidth + rowHeight)); + } + var skip = (detCanvas.clientWidth - headerSize) / dataPerRow; + var fontSize = Math.min(skip - 2, 11); + var start = headerSize + fontSize + Math.max((skip - fontSize)/2, 0) + 3; + var labels = heatMap.getColLabels()["labels"]; + var labelLen = getMaxLength(labels); + + if (skip > labelSizeLimit) { + var yPos = detCanvas.clientHeight + 4; + for (var i = currentCol; i < currentCol + dataPerRow; i++) { + var xPos = start + ((i-currentCol) * skip); + addLabelDiv(labelElement, 'detail_col' + i, 'DynamicLabel', labels[i-1], xPos, yPos, fontSize, 'T'); + } + } +} + +function addLabelDiv(parent, id, className, text, left, top, fontSize, rotate) { + var div = document.createElement('div'); + div.id = id; + div.className = className; + div.innerHTML = text; + if (div.classList.contains('ClassBar')){ + div.setAttribute('axis','ColumnClass'); + } else { + div.setAttribute('axis', 'Row'); + } + if (labelIndexInSearch(text,"Row") > -1 || labelIndexInSearch(text, "Column") > -1 || labelIndexInSearch(text, "ColumnClass") > -1 || labelIndexInSearch(text, "RowClass") > -1) + div.classList.add('searchItem'); + if (text == "<") { + div.style.backgroundColor = "rgba(255,255,0,0.2)"; + } + if (rotate == 'T') { + div.style.transformOrigin = 'left top'; + div.style.transform = 'rotate(90deg)'; + div.style.webkitTransformOrigin = "left top"; + div.style.webkitTransform = "rotate(90deg)"; + if (div.classList.contains('ClassBar')){ + div.setAttribute('axis','RowClass'); + } else { + div.setAttribute('axis','Column'); + } + } + div.style.position = "absolute"; + div.style.left = left; + div.style.top = top; + div.style.fontSize = fontSize.toString() +'pt'; + div.style.fontFamily = 'times new roman'; + div.style.fontWeight = 'bold'; + div.addEventListener('click',labelClick,false); + div.addEventListener('contextmenu',labelRightClick,false); + + parent.appendChild(div); +} + + +// Get max label length +function getMaxLength(list) { + var len = 0; + for (var i = 0; i < list.length; i++){ + if (list[i].length > len) + len = list[i].length; + } + return len; +} + +function labelClick(e){ + if (e.shiftKey){ // shift + click + var selection = window.getSelection(); + var focusNode = selection.focusNode.parentElement; + var focusIndex = Number(selection.focusNode.parentElement.id.substring(10)); // id = detail_rowX + if (labelLastClicked != undefined && labelLastClicked.axis == focusNode.getAttribute('axis')){ // if label in the same axis was clicked last, highlight all + var anchorIndex = labelLastClicked.index; + var anchorNode = document.getElementById('detail_' + labelLastClicked.axis.toLowerCase().substring(0,3) + labelLastClicked.index); + var currentNode = (anchorIndex < focusIndex) ? anchorNode: focusNode; + var range = Math.abs(focusIndex-anchorIndex); + for (var i =0; i < range+1; i++){ + if (labelIndexInSearch(currentNode.innerHTML, currentNode.getAttribute('axis')) == -1){ + searchItems.push({'axis':currentNode.getAttribute('axis'), 'label':currentNode.innerHTML}); + } + currentNode = currentNode.nextSibling; + } + } else { // otherwise, treat as normal click + clearSearchItems(this.getAttribute('axis')); + var labelIndex = labelIndexInSearch(this.innerHTML, this.getAttribute('axis')); + if (labelIndex > -1){ + searchItems.splice(labelIndex, 1); + } else { + searchItems.push({'axis':this.getAttribute('axis'), 'label':this.innerHTML}); + } + } + labelLastClicked = {"axis": focusNode.getAttribute('axis'), "label" : focusNode.innerHTML, "index": Number(focusNode.id.substring(10))}; + selection.empty(); + } else if (e.ctrlKey || e.metaKey){ // ctrl or Mac key + click + var labelIndex = labelIndexInSearch(this.innerHTML, this.getAttribute('axis')); + if (labelIndex > -1){ // if already searched, remove from search items + searchItems.splice(labelIndex, 1); + } else { + searchItems.push({'axis':this.getAttribute('axis'), 'label':this.innerHTML}); + this.classList.add("searchItem"); + } + labelLastClicked = {"axis": this.getAttribute('axis'), "label" : this.innerHTML, "index": Number(this.id.substring(10))}; + } else { // standard click + clearSearchItems(this.getAttribute('axis')); + var labelIndex = labelIndexInSearch(this.innerHTML, this.getAttribute('axis')); + if (labelIndex > -1){ + searchItems.splice(labelIndex, 1); + } else { + searchItems.push({'axis':this.getAttribute('axis'), 'label':this.innerHTML}); + } + labelLastClicked = {"axis": this.getAttribute('axis'), "label" : this.innerHTML, "index": Number(this.id.substring(10))}; + } + var searchElement = document.getElementById('search_text'); + searchElement.value = ""; +// clearSrchBtns(); + document.getElementById('prev_btn').style.display=''; + document.getElementById('next_btn').style.display=''; + document.getElementById('cancel_btn').style.display=''; + clearLabels(); + clearSelectionMarks(); + detailDrawRowClassBarLabels(); + detailDrawColClassBarLabels(); + drawRowLabels(); + drawColLabels(); + updateSelection(); + if (isSub){ + localStorage.setItem('selected', JSON.stringify(searchItems)); + } + if (!isSub){ + drawRowSelectionMarks(); + drawColSelectionMarks(); + } +} + +function clearSearchItems(clickAxis){ // clears the search items on a particular axis + var tempSearchItems = searchItems; + searchItems = []; + for (var i = 0; i < tempSearchItems.length; i++){ + var tempSearch = tempSearchItems[i]; + if (tempSearch.axis !== clickAxis){ + searchItems.push(tempSearch); + } + } +// var searchItemsDiv = document.getElementsByClassName('searchItem'); +// while (searchItemsDiv.length>0){ // clear highlighted labels +// var searchItem = searchItemsDiv[0]; +// searchItem.classList.remove('searchItem'); +// } + var markLabels = document.getElementsByClassName('MarkLabel'); + while (markLabels.length>0){ // clear tick marks + markLabels[0].remove(); + } +} + +function highlightAllColLabels(){ + var selectionSize = selectedStop - selectedStart + 1; + if ((mode == "RIBBONH" || mode === "RIBBONH_DETAIL") && selectionSize > 1){ + clearSearchItems("Column"); + var labels = document.getElementsByClassName("DynamicLabel"); + for (var i = 0; i < labels.length; i++){ + var label = labels[i]; + if (label.getAttribute('axis') == 'Column' && !label.classList.contains('ClassBar')){ + searchItems.push({'axis':"Column", 'label':label.innerHTML}); + label.classList.add('searchItem'); + } + } + } + drawRowSelectionMarks(); + drawColSelectionMarks(); +} + +function highlightAllRowLabels(){ + var selectionSize = selectedStop - selectedStart + 1; + if ((mode == "RIBBONV" || mode === "RIBBONV_DETAIL") && selectionSize > 1){ + clearSearchItems("Row"); + var labels = document.getElementsByClassName("DynamicLabel"); + for (var i = 0; i < labels.length; i++){ + var label = labels[i]; + if (label.getAttribute('axis') == 'Row' && !label.classList.contains('ClassBar')){ + searchItems.push({'axis':"Row", 'label':label.innerHTML}); + label.classList.add('searchItem'); + } + } + } + drawRowSelectionMarks(); + drawColSelectionMarks(); +} + +function labelRightClick(e) { + e.preventDefault(); + labelHelpClose(); + var axis = e.target.getAttribute('axis'); + var labels = searchItems; + labelHelpOpen(axis,e); + return false; +} + +function labelIndexInSearch(label,axis){ // basically a Array.contains function, but for searchItems + for (var i=0; i < searchItems.length; i++) { + if (searchItems[i].label === label && searchItems[i].axis === axis) { + return i; + } + } + return -1; +} + + +function getSearchLabelsByAxis(axis){ + var labels = []; + for (i = 0; i < searchItems.length; i++){ + if (searchItems[i]["axis"] === axis){ + labels.push(searchItems[i]["label"]) + } + } + return labels; +} + +//draws row classification bars into the texture array ("dataBuffer"). "names"/"colorSchemes" should be array of strings. +function detailDrawColClassBars(){ + var classBars = heatMap.getClassifications(); + var colClassInfo = getClassBarsToDraw("column"); + var names = colClassInfo["bars"]; + var colorSchemes = colClassInfo["colors"]; + + var rowClassBarWidth = calculateTotalClassBarHeight("row"); + var fullWidth = detailDataViewWidth + rowClassBarWidth + detailDendroWidth; + var mapHeight = detailDataViewHeight; + var pos = fullWidth*mapHeight*BYTE_PER_RGBA; + for (var i = 0; i < names.length; i++){ //for each column class bar we draw... + var currentClassBar = classBars[names[i]]; + if (currentClassBar.show === 'Y') { + var colorMap = heatMap.getColorMapManager().getColorMap(colorSchemes[i]); // assign the proper color scheme... + var classBarLength = getCurrentDetDataPerRow() * dataBoxWidth; + pos += fullWidth*paddingHeight*BYTE_PER_RGBA; // draw padding between class bars + var line = new Uint8Array(new ArrayBuffer(classBarLength * BYTE_PER_RGBA)); // save a copy of the class bar + var loc = 0; + for (var k = currentCol; k <= currentCol + getCurrentDetDataPerRow() -1; k++) { + var val = currentClassBar.values[k-1]; + var color = colorMap.getClassificationColor(val); + for (var j = 0; j < dataBoxWidth; j++) { + line[loc] = color['r']; + line[loc + 1] = color['g']; + line[loc + 2] = color['b']; + line[loc + 3] = color['a']; + loc += BYTE_PER_RGBA; + } + } + + for (var j = 0; j < currentClassBar.height-paddingHeight; j++){ // draw the class bar into the dataBuffer + pos += (rowClassBarWidth + detailDendroWidth + 1)*BYTE_PER_RGBA; + for (var k = 0; k < line.length; k++) { + detTexPixels[pos] = line[k]; + pos++; + } + pos+=BYTE_PER_RGBA; + } + } + + } +} + +function detailDrawColClassBarLabels() { + var scale = detCanvas.clientHeight / (detailDataViewHeight + calculateTotalClassBarHeight("column")+detailDendroHeight); + var colClassInfo = getClassBarsToDraw("column"); + if (colClassInfo != null && colClassInfo.bars.length > 0) { + var names = colClassInfo["bars"]; + var classBars = heatMap.getClassifications(); + var fontSize = Math.min((classBars[names[0]].height - paddingHeight) * scale, 11); + if (fontSize > 7) { + var xPos = detCanvas.clientWidth + 3; + var yPos = detailDendroHeight*scale; + for (var i = names.length-1; i >= 0; i--){ //for each column class bar + var currentClassBar = classBars[names[i]]; + if (currentClassBar.show === 'Y') { + addLabelDiv(labelElement, 'detail_col_class' + i, 'DynamicLabel ClassBar', names[i], xPos, yPos, fontSize, 'F'); + yPos += (currentClassBar.height * scale); + } + } + } + } +} + + +//draws row classification bars into the texture array ("dataBuffer"). "names"/"colorSchemes" should be array of strings. +function detailDrawRowClassBars(){ + var rowClassInfo = getClassBarsToDraw("row"); + var names = rowClassInfo["bars"]; + var colorSchemes = rowClassInfo["colors"]; + var detailTotalWidth = detailDendroWidth + calculateTotalClassBarHeight("row") + detailDataViewWidth; + var offset = ((detailTotalWidth*detailDataViewBoarder/2)+detailDendroWidth) * BYTE_PER_RGBA; // start position of very bottom dendro + var mapWidth = detailDataViewWidth; + var mapHeight = detailDataViewHeight; + var classBars = heatMap.getClassifications(); + for (var i = 0; i < names.length; i++){ // for each class bar to draw... + var currentClassBar = classBars[names[i]]; + if (currentClassBar.show === 'Y') { + var pos = offset; // move past the dendro and the other class bars... + var colorMap = heatMap.getColorMapManager().getColorMap(colorSchemes[i]); + var classBarLength = currentClassBar.values.length; + for (var j = currentRow + getCurrentDetDataPerCol() - 1; j >= currentRow; j--){ // for each row shown in the detail panel + var val = currentClassBar.values[j-1]; + var color = colorMap.getClassificationColor(val); + for (var boxRows = 0; boxRows < dataBoxHeight; boxRows++) { // draw this color to the proper height + for (var k = 0; k < currentClassBar.height-paddingHeight; k++){ // draw this however thick it needs to be + detTexPixels[pos] = color['r']; + detTexPixels[pos + 1] = color['g']; + detTexPixels[pos + 2] = color['b']; + detTexPixels[pos + 3] = color['a']; + pos+=BYTE_PER_RGBA; // 4 bytes per color + } + + // padding between class bars + pos+=paddingHeight*BYTE_PER_RGBA; + pos+=(mapWidth + detailDendroWidth)*BYTE_PER_RGBA; + } + } + offset+= currentClassBar.height; + } + } +} + +function detailDrawRowClassBarLabels() { + var scale = detCanvas.clientWidth / (detailDataViewWidth + calculateTotalClassBarHeight("row")+detailDendroWidth); + var colClassInfo = getClassBarsToDraw("row"); + if (colClassInfo != null && colClassInfo.bars.length > 0) { + var names = colClassInfo["bars"]; + var classBars = heatMap.getClassifications(); + var fontSize = Math.min((classBars[names[0]].height - paddingHeight) * scale, 11); + if (fontSize > 7) { + var xPos = detailDendroWidth*scale+fontSize + 5; + var yPos = detCanvas.clientHeight + 4;; + for (var i = names.length-1; i >= 0; i--){ //for each column class bar + var currentClassBar = classBars[names[i]]; + if (currentClassBar.show === 'Y') { + addLabelDiv(labelElement, 'detail_row_class' + i, 'DynamicLabel ClassBar', names[i], xPos, yPos, fontSize, 'T'); + xPos += (currentClassBar.height * scale); + } + } + } + } +} + + +/****************************************************** + ***** DETAIL DENDROGRAM FUNCTIONS START HERE!!! ***** + ******************************************************/ + +//Note: stop position passed in is actually one past the last row/column to be displayed. + +function buildDetailDendroMatrix(axis, start, stop, heightRatio){ + var start3NIndex = convertMapIndexTo3NSpace(start); + var stop3NIndex = convertMapIndexTo3NSpace(stop); + var boxLength, currentIndex, matrixWidth, dendroBars; + var dendroInfo = heatMap.getDendrogram()[axis]; // dendro JSON object + if (axis =='Column'){ // assign proper axis-specific variables + boxLength = dataBoxWidth; + matrixWidth = detailDataViewWidth; + dendroBars = colDendroBars; // array of the dendro bars + } else { + boxLength = dataBoxHeight; + matrixWidth = detailDataViewHeight; + dendroBars = rowDendroBars; + } + var numNodes = dendroInfo.length; + var lastRow = dendroInfo[numNodes-1]; + var matrix = new Array(normDetailDendroMatrixHeight+1); + for (var i = 0; i < normDetailDendroMatrixHeight+1; i++){ + matrix[i] = new Array(matrixWidth-1); + } + var topLineArray = new Array(matrixWidth-1); // this array is made to keep track of which bars have vertical lines that extend outside the matrix + var maxHeight = Number(lastRow.split(",")[2])/(heightRatio); // this assumes the heightData is ordered from lowest height to highest + + // check the left and right endpoints of each bar, and see if they are within the bounds. + // then check if the bar is in the desired height. + // if it is, draw it in its entirety, otherwise, see if the bar has a vertical connection with any of the bars in view + for (var i = 0; i < numNodes; i++){ + var bar = dendroInfo[i]; + var tokes = bar.split(","); + var leftJsonIndex = Number(tokes[0]); + var rightJsonIndex = Number(tokes[1]); + var height = Number(tokes[2]); + var left3NIndex = convertJsonIndexTo3NSpace(leftJsonIndex); // location in dendroBars space + var right3NIndex = convertJsonIndexTo3NSpace(rightJsonIndex); + if (right3NIndex < start3NIndex || stop3NIndex < left3NIndex){continue} //if the bar exists outside of the viewport, skip it + + var leftLoc = convertJsonIndexToDataViewSpace(leftJsonIndex); // Loc is the location in the dendro matrix + var rightLoc = convertJsonIndexToDataViewSpace(rightJsonIndex); + var normHeight = Math.round(normDetailDendroMatrixHeight*height/maxHeight); // height in matrix + var leftEnd = Math.max(leftLoc, 0); + var rightEnd = Math.min(rightLoc, matrixWidth-1); + if (height > maxHeight){ // if this line is beyond the viewport max height + if (start3NIndex < right3NIndex && right3NIndex< stop3NIndex && topLineArray[rightLoc] != 1){ // check to see if it will be connecting vertically to a line in the matrix + var drawHeight = normDetailDendroMatrixHeight; + while (drawHeight > 0 && matrix[drawHeight][rightLoc] != 1){ + matrix[drawHeight][rightLoc] = 1; + drawHeight--; + } + } + if (start3NIndex < left3NIndex && left3NIndex< stop3NIndex && topLineArray[leftLoc] != 1){ + var drawHeight = normDetailDendroMatrixHeight; + while (drawHeight > 0 && matrix[drawHeight][leftLoc] != 1){ + matrix[drawHeight][leftLoc] = 1; + drawHeight--; + } + } + for (var loc = leftEnd; loc < rightEnd; loc++){ + topLineArray[loc] = 1; // mark that the area covered by this bar can no longer be drawn in by another, higher level bar + } + } else { + for (var j = leftEnd; j < rightEnd; j++){ // draw horizontal line + matrix[normHeight][j] = 1; + } + var drawHeight = normHeight-1; + while (drawHeight > 0 && matrix[drawHeight][leftLoc] != 1 && leftLoc > 0){ // draw left vertical line + matrix[drawHeight][leftLoc] = 1; + drawHeight--; + } + drawHeight = normHeight; + while (matrix[drawHeight][rightLoc] != 1 && drawHeight > 0 && rightLoc < matrixWidth-1){ // draw right vertical line + matrix[drawHeight][rightLoc] = 1; + drawHeight--; + } + } + } + + // fill in any missing leaves but only if the viewport is zoomed in far enough to tell. + if (stop - start < 100){ + var numLeafsDrawn = 0; + for (var j in matrix[1]){numLeafsDrawn++} + var pos = Math.round(boxLength/2); + if (numLeafsDrawn < stop-start){ // have enough lines been drawn? + for (var i = 0; i < stop-start; i++){ + var height = 1; + if (matrix[height][pos] != 1){ + while (height < normDetailDendroMatrixHeight+1){ + matrix[height][pos] = 1; + height++; + } + } + pos += boxLength; + } + } + } + + return matrix; + + // HELPER FUNCTIONS + function convertMapIndexTo3NSpace(index){ + return index*pointsPerLeaf - 2; + } + function convertJsonIndexTo3NSpace(index){ + if (index < 0){ + index = 0-index; // make index a positive number to find the leaf + return index*pointsPerLeaf - 2; + } else { + index--; // dendroBars is stored in 3N, so we convert back + return Math.round((dendroBars[index].left + dendroBars[index].right)/2); // gets the middle point of the bar + } + } + function convertJsonIndexToDataViewSpace(index){ + if (index < 0){ + index = 0-index; // make index a positive number to find the leaf + return (index - start)*boxLength+ Math.round(boxLength/2) + } else { + index--; // dendroBars is stored in 3N, so we convert back + var normDistance = (Math.round((dendroBars[index].left+ dendroBars[index].right)/2)-start3NIndex) / (stop3NIndex-start3NIndex); // gets the middle point of the bar + return Math.round(normDistance*matrixWidth); + } + } +} + +function colDendroMatrixCoordToDetailTexturePos(matrixRow,matrixCol){ // convert the matrix coord to the data buffer position (start of the RGBA block) + var mapx = matrixCol*getSamplingRatio('row'); + var mapy = Math.round(matrixRow/normDetailDendroMatrixHeight * columnDendroHeight); + var detailTotalWidth = detailDendroWidth + calculateTotalClassBarHeight("row") + detailDataViewWidth; + var pos = (detailTotalWidth*(calculateTotalClassBarHeight("column") + detailDataViewHeight))*BYTE_PER_RGBA; + pos += (detailDendroWidth + calculateTotalClassBarHeight("row")-1)*BYTE_PER_RGBA; + pos += ((mapy)*detailTotalWidth)*BYTE_PER_RGBA + matrixCol*BYTE_PER_RGBA; + return pos; +} + +function rowDendroMatrixCoordToDetailTexturePos(matrixRow,matrixCol){ // convert matrix coord to data buffer position (leftmost column of matrix corresponds to the top row of the map) + var mapx = detailDataViewHeight - matrixCol-detailDataViewBoarder/2; + var mapy = detailDendroWidth - Math.round(matrixRow/normDetailDendroMatrixHeight * detailDendroWidth); // bottom most row of matrix is at the far-right of the map dendrogram + var detailTotalWidth = detailDendroWidth + calculateTotalClassBarHeight("row") + detailDataViewWidth; + var pos = (mapx*detailTotalWidth)*BYTE_PER_RGBA + (mapy)*BYTE_PER_RGBA; // pass the empty space (if any) and the border width, to get to the height on the map + return pos; +} + +function detailDrawColDendrogram(dataBuffer){ + var detailTotalWidth = detailDendroWidth + calculateTotalClassBarHeight("row") + detailDataViewWidth; + for (var i = 0; i < colDetailDendroMatrix.length; i++){ + var line = colDetailDendroMatrix[i]; // line = each row of the col dendro matrix + for (var j in line){ + var pos = colDendroMatrixCoordToDetailTexturePos(i,Number(j)); + if (j > detailDataViewWidth){ // TO DO: find out why some rows in the dendro matrix are longer than they should be + continue; + }else { + dataBuffer[pos] = 3,dataBuffer[pos+1] = 3,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } + } + } +} + +function detailDrawRowDendrogram(dataBuffer){ + for (var i = 0; i <= rowDetailDendroMatrix.length+1; i++){ + var line = rowDetailDendroMatrix[i]; // line = each row of the col dendro matrix + for (var j in line){ + var pos = rowDendroMatrixCoordToDetailTexturePos(i,Number(j)); + if (j > detailDataViewHeight){ // TO DO: find out why some rows in the dendro matrix are longer than they should be + continue; + } else { + dataBuffer[pos] = 3,dataBuffer[pos+1] = 3,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } + } + } +} + +function clearDetailDendrograms(){ + var rowClassWidth = calculateTotalClassBarHeight('row'); + var detailFullWidth = detailDendroWidth + rowClassWidth + detailDataViewWidth; + var pos = 0; + // clear the row dendro pixels + for (var i =0; i < detailDataViewHeight*BYTE_PER_RGBA; i++){ + for (var j = 0; j < detailDendroWidth*BYTE_PER_RGBA; j++){ + detTexPixels[pos] = undefined; + pos++; + }; + pos += ( detailDataViewWidth + rowClassWidth)*BYTE_PER_RGBA; + } + //clear the column dendro pixels + pos = (detailFullWidth) * (detailDataViewHeight + calculateTotalClassBarHeight("column")) * BYTE_PER_RGBA; + for (var i =0; i < detailDendroHeight; i++){ + for (var j = 0; j < detailFullWidth*BYTE_PER_RGBA; j++){ + detTexPixels[pos] = undefined; + pos++; + } + } +} + +function getSamplingRatio(axis){ + if (axis == 'row'){ + switch (mode){ + case 'RIBBONH': return heatMap.getRowSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + case 'RIBBONV': return heatMap.getRowSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + default: return heatMap.getRowSummaryRatio(MatrixManager.DETAIL_LEVEL); + } + } else { + switch (mode){ + case 'RIBBONH': return heatMap.getColSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + case 'RIBBONV': return heatMap.getColSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + default: return heatMap.getColSummaryRatio(MatrixManager.DETAIL_LEVEL); + } + } +} + +/**************************************************** + ***** DETAIL DENDROGRAM FUNCTIONS END HERE!!! ***** + ****************************************************/ + + +//WebGL stuff + +function detSetupGl() { + det_gl = detCanvas.getContext("experimental-webgl", {preserveDrawingBuffer: true}); + det_gl.viewportWidth = detailDataViewWidth+calculateTotalClassBarHeight("row")+detailDendroWidth; + det_gl.viewportHeight = detailDataViewHeight+calculateTotalClassBarHeight("column")+detailDendroHeight; + det_gl.clearColor(1, 1, 1, 1); + + var program = det_gl.createProgram(); + var vertexShader = getDetVertexShader(det_gl); + var fragmentShader = getDetFragmentShader(det_gl); + det_gl.program = program; + det_gl.attachShader(program, vertexShader); + det_gl.attachShader(program, fragmentShader); + det_gl.linkProgram(program); + det_gl.useProgram(program); +} + + +function getDetVertexShader(theGL) { + var source = 'attribute vec2 position; ' + + 'varying vec2 v_texPosition; ' + + 'uniform vec2 u_translate; ' + + 'uniform vec2 u_scale; ' + + 'void main () { ' + + ' vec2 scaledPosition = position * u_scale; ' + + ' vec2 translatedPosition = scaledPosition + u_translate; ' + + ' gl_Position = vec4(translatedPosition, 0, 1); ' + + ' v_texPosition = position * 0.5 + 0.5; ' + + '}'; + + + var shader = theGL.createShader(theGL.VERTEX_SHADER); + theGL.shaderSource(shader, source); + theGL.compileShader(shader); + if (!theGL.getShaderParameter(shader, theGL.COMPILE_STATUS)) { + alert(theGL.getShaderInfoLog(shader)); + } + + return shader; +} + + +function getDetFragmentShader(theGL) { + var source = 'precision mediump float; ' + + 'varying vec2 v_texPosition; ' + + 'varying float v_boxFlag; ' + + 'uniform sampler2D u_texture; ' + + 'uniform vec2 u_box_left_top; ' + + 'uniform vec2 u_box_right_bottom;' + + 'uniform float u_box_thickness; ' + + 'uniform vec4 u_box_color; ' + + 'void main () { ' + + ' vec2 difLeftTop = v_texPosition - u_box_left_top; ' + + ' vec2 difRightBottom = v_texPosition - u_box_right_bottom; ' + + ' if (v_texPosition.y >= u_box_left_top.y && v_texPosition.y <= u_box_right_bottom.y) { ' + + ' if ((difLeftTop.x <= u_box_thickness && difLeftTop.x >= -u_box_thickness) || ' + + ' (difRightBottom.x <= u_box_thickness && difRightBottom.x >= -u_box_thickness)) { ' + + ' gl_FragColor = u_box_color; ' + + ' } else { ' + + ' gl_FragColor = texture2D(u_texture, v_texPosition); ' + + ' } ' + + ' } else if (v_texPosition.x >= u_box_left_top.x && v_texPosition.x <= u_box_right_bottom.x) { ' + + ' if ((difLeftTop.y <= u_box_thickness && difLeftTop.y >= -u_box_thickness) || ' + + ' (difRightBottom.y <= u_box_thickness && difRightBottom.y >= -u_box_thickness)) { ' + + ' gl_FragColor = u_box_color; ' + + ' } else { ' + + ' gl_FragColor = texture2D(u_texture, v_texPosition); ' + + ' } ' + + ' } else { ' + + ' gl_FragColor = texture2D(u_texture, v_texPosition); ' + + ' } ' + + '}'; + + + var shader = theGL.createShader(theGL.FRAGMENT_SHADER);; + theGL.shaderSource(shader, source); + theGL.compileShader(shader); + if (!theGL.getShaderParameter(shader, theGL.COMPILE_STATUS)) { + alert(theGL.getShaderInfoLog(shader)); + } + + return shader; +} + + + +function detInitGl () { + det_gl.viewport(0, 0, det_gl.viewportWidth, det_gl.viewportHeight); + det_gl.clear(det_gl.COLOR_BUFFER_BIT); + + // Vertices + var buffer = det_gl.createBuffer(); + det_gl.buffer = buffer; + det_gl.bindBuffer(det_gl.ARRAY_BUFFER, buffer); + var vertices = [ -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1 ]; + det_gl.bufferData(det_gl.ARRAY_BUFFER, new Float32Array(vertices), det_gl.STATIC_DRAW); + var byte_per_vertex = Float32Array.BYTES_PER_ELEMENT; + var component_per_vertex = 2; + buffer.numItems = vertices.length / component_per_vertex; + var stride = component_per_vertex * byte_per_vertex; + var program = det_gl.program; + var position = det_gl.getAttribLocation(program, 'position'); + detUScale = det_gl.getUniformLocation(program, 'u_scale'); + detUTranslate = det_gl.getUniformLocation(program, 'u_translate'); + detUBoxLeftTop = det_gl.getUniformLocation(program, 'u_box_left_top'); + detUBoxRightBottom = det_gl.getUniformLocation(program, 'u_box_right_bottom'); + detUBoxThickness = det_gl.getUniformLocation(program, 'u_box_thickness'); + detUBoxColor = det_gl.getUniformLocation(program, 'u_box_color'); + det_gl.enableVertexAttribArray(position); + det_gl.vertexAttribPointer(position, 2, det_gl.FLOAT, false, stride, 0); + + // Texture + var texture = det_gl.createTexture(); + det_gl.bindTexture(det_gl.TEXTURE_2D, texture); + det_gl.texParameteri( + det_gl.TEXTURE_2D, + det_gl.TEXTURE_WRAP_S, + det_gl.CLAMP_TO_EDGE); + det_gl.texParameteri( + det_gl.TEXTURE_2D, + det_gl.TEXTURE_WRAP_T, + det_gl.CLAMP_TO_EDGE); + det_gl.texParameteri( + det_gl.TEXTURE_2D, + det_gl.TEXTURE_MIN_FILTER, + det_gl.NEAREST); + det_gl.texParameteri( + det_gl.TEXTURE_2D, + det_gl.TEXTURE_MAG_FILTER, + det_gl.NEAREST); + + detTextureParams = {}; + + var texWidth = null, texHeight = null, texData; + texWidth = detailDataViewWidth + calculateTotalClassBarHeight("row")+detailDendroWidth; + texHeight = detailDataViewHeight + calculateTotalClassBarHeight("column")+detailDendroHeight; + texData = new ArrayBuffer(texWidth * texHeight * 4); + detTexPixels = new Uint8Array(texData); + detTextureParams['width'] = texWidth; + detTextureParams['height'] = texHeight; +} + +function toggleGrid(){ + detailGrid = !detailGrid; + drawDetailHeatMap(); +} + + diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/MatrixManager.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/MatrixManager.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,568 @@ +// +// MatrixManager is responsible for retrieving clustered heat map data. Heat map +// data is available at different 'zoom' levels - Summary, Ribbon Vertical, Ribbon +// Horizontal, and Full. To use this code, create MatrixManger by calling the +// MatrixManager function. The MatrixManager lets you retrieve a HeatmapData object +// given a heat map name and summary level. The HeatMapData object has various +// attributes of the map including the size an number of tiles the map is broken up +// into. getTile() is called on the HeatmapData to get each tile of the data. Tile +// retrieval is asynchronous so you need to provide a callback that is triggered when +// the tile is retrieved. +// + +//Supported map data summary levels. +MatrixManager.THUMBNAIL_LEVEL = 'tn'; +MatrixManager.SUMMARY_LEVEL = 's'; +MatrixManager.RIBBON_VERT_LEVEL = 'rv'; +MatrixManager.RIBBON_HOR_LEVEL = 'rh'; +MatrixManager.DETAIL_LEVEL = 'd'; + +MatrixManager.WEB_SOURCE = 'W'; +MatrixManager.FILE_SOURCE = 'F'; + +MatrixManager.Event_INITIALIZED = 'Init'; +MatrixManager.Event_JSON = 'Json'; +MatrixManager.Event_NEWDATA = 'NewData'; + + +//Create a MatrixManager to retrieve heat maps. +//Need to specify a mode of heat map data - +//web server or local file. +function MatrixManager(mode){ + + //Main function of the matrix manager - retrieve a heat map object. + //mapFile parameter is only used for local file based heat maps. + this.getHeatMap = function (heatMapName, updateCallback, mapFile) { + return new HeatMap(heatMapName, updateCallback, mode, mapFile); + } +}; + + +//HeatMap Object - holds heat map properties and a tile cache +//Used to get HeatMapData object. +//ToDo switch from using heat map name to blob key? +function HeatMap (heatMapName, updateCallback, mode, chmFile) { + //This holds the various zoom levels of data. + var datalayers = {}; + var tileCache = {}; + var zipFiles = {}; + var colorMaps = null; + var colorMapMgr; + var classifications = null; + var rowLabels = null; + var colLabels = null; + var dendrogram = null; + var chm = null; + var initialized = 0; + var eventListeners = []; + + //Return the number of rows for a given level + this.isSaveAllowed = function(){ + if (mode === "F") { + return false; + } else { + return true; + } + } + + //Return the number of rows for a given level + this.getNumRows = function(level){ + return datalayers[level].totalRows; + } + + //Return the number of columns for a given level + this.getNumColumns = function(level){ + return datalayers[level].totalColumns; + } + + //Return the row summary ratio for a given level + this.getRowSummaryRatio = function(level){ + return datalayers[level].rowSummaryRatio; + } + + //Return the column summary ratio for a given level + this.getColSummaryRatio = function(level){ + return datalayers[level].colSummaryRatio; + } + + //Get a data value in a given row / column + this.getValue = function(level, row, column) { + return datalayers[level].getValue(row,column); + } + + this.saveHeatMapProperties = function () { + var success = saveMapProperties("colorMap",JSON.stringify(colorMaps)); + if (success !== "false") { + saveMapProperties("classifications",JSON.stringify(classifications)); + } + if (success !== "false") { + saveMapProperties("dendrogram",JSON.stringify(dendrogram)); + } + return success; + } + + //This function is used to set a read window for high resolution data layers. + //Calling setReadWindow will cause the HeatMap object to retrieve tiles needed + //for reading this area if the tiles are not already in the cache. + this.setReadWindow = function(level, row, column, numRows, numColumns) { + //Thumb nail and summary level are always kept in the cache. Don't do fetch for them. + if (level != MatrixManager.THUMBNAIL_LEVEL && level != MatrixManager.SUMMARY_LEVEL) + datalayers[level].setReadWindow(row, column, numRows, numColumns); + } + + // Retrieve color map Manager for this heat map. + this.getColorMapManager = function() { + if (initialized != 1) + return null; + + if (colorMapMgr == null ) { + colorMapMgr = new ColorMapManager(colorMaps); + } + return colorMapMgr; + } + + //Retrieve classifications + this.getClassifications = function() { + return classifications; + } + + this.setClassificationShow = function(classname, value) { + classifications[classname].show = value ? 'Y' : 'N'; + } + + this.setClassificationPrefs = function(classname, showVal, heightVal) { + classifications[classname].show = showVal ? 'Y' : 'N'; + classifications[classname].height = parseInt(heightVal); + } + + //Get Row Labels + this.getRowLabels = function() { + return rowLabels; + } + + //Get Column Labels + this.getColLabels = function() { + return colLabels; + } + + //Get Column Labels + this.getChm = function() { + return chm; + } + + //Get Column Labels + this.getDendrogram = function() { + return dendrogram; + } + + this.setRowDendrogramShow = function(value) { + dendrogram.row_dendro_show = value; + } + + this.setColDendrogramShow = function(value) { + dendrogram.col_dendro_show = value; + } + + this.setRowDendrogramHeight = function(value) { + dendrogram.row_dendro_height = value; + } + + this.setColDendrogramHeight = function(value) { + dendrogram.col_dendro_height = value; + } + + this.showRowDendrogram = function(layer) { + var showDendro = true; + if ((dendrogram.row_dendro_show === 'NONE') || (dendrogram.row_dendro_show === 'NA')) { + showDendro = false; + } + if ((layer === 'DETAIL') && (dendrogram.row_dendro_show === 'SUMMARY')) { + showDendro = false; + } + return showDendro; + } + + this.showColDendrogram = function(layer) { + var showDendro = true; + if ((dendrogram.col_dendro_show === 'NONE') || (dendrogram.col_dendro_show === 'NA')) { + showDendro = false; + } + if ((layer === 'DETAIL') && (dendrogram.col_dendro_show === 'SUMMARY')) { + showDendro = false; + } + return showDendro; + } + + + //Method used to register another callback function for a user that wants to be notifed + //of updates to the status of heat map data. + this.addEventListener = function(callback) { + eventListeners.push(callback); + } + + //Is the heat map ready for business + this.isInitialized = function() { + return initialized; + } + + //ToDo: Add methods for getting ordering/dendrogram + + + //************************************************************************************************************ + // + // Internal Heat Map Functions. Users of the heat map object don't need to use / understand these. + // + //************************************************************************************************************ + + //Initialization - this code is run once when the map is created. + + //Add the original update call back to the event listeners list. + eventListeners.push(updateCallback); + + if (mode == MatrixManager.WEB_SOURCE){ + //mode is web so user server to initialize. + + //Retrieve the high-level information about how many data tiles there are at each level. + webFetchJson('tilestructure', addDataLayers); + + //Retrieve the color maps. + webFetchJson('colormaps', addColor); + + //Retrieve classification data. + webFetchJson('classifications', addClassification); + + //Retrieve classification data. + webFetchJson('rowLabels', addRowLabels); + + //Retrieve classification data. + webFetchJson('colLabels', addColLabels); + + //Retrieve dendrogram data. + webFetchJson('dendrogram', addDendrogram); + + //Retrieve chm data. + webFetchJson('chm', addChm); + } else { + //mode is file so get the json files from the zip file. + + //First create a dictionary of all the files in the zip. + var zipBR = new zip.BlobReader(chmFile); + zip.createReader(zipBR, function(reader) { + // get all entries from the zip + reader.getEntries(function(entries) { + for (var i = 0; i < entries.length; i++) { + zipFiles[entries[i].filename] = entries[i]; + } + zipFetchJson('tilestructure.json', addDataLayers); + zipFetchJson('colormaps.json', addColor); + zipFetchJson('classifications.json', addClassification); + zipFetchJson('rowLabels.json', addRowLabels); + zipFetchJson('colLabels.json', addColLabels); + zipFetchJson('dendrogram.json', addDendrogram); + zipFetchJson('chm.json', addChm); + }); + }, function(error) { + console.log('Zip file read error ' + error); + }); + } + + + function saveMapProperties(type, jsonData) { + var success = "false"; + var name = "SaveMapProperties?map=" + heatMapName + "&type=" + type; + var req = new XMLHttpRequest(); + req.open("POST", name, false); + req.setRequestHeader("Content-Type", "application/json"); + //req.responseType = "text"; + req.onreadystatechange = function () { + if (req.readyState == req.DONE) { + if (req.status != 200) { + console.log('Failed in call to save propeties from server: ' + req.status); + success = "false"; + } else { + success = req.response; + } + } + }; + req.send(jsonData); + return success; + } + + // Initialize the data layers once we know the tile structure. + // JSON structure object describing available data layers passed in. + function addDataLayers(tileStructure) { + //Create heat map data objects for each data level. All maps should have thumb nail and full level. + //Each data layer keeps a pointer to the next lower level data layer. + + //Thumb nail + if (tileStructure.levels.tn !== undefined) { + datalayers[MatrixManager.THUMBNAIL_LEVEL] = new HeatMapData(heatMapName, + MatrixManager.THUMBNAIL_LEVEL, + tileStructure.levels.tn, + null, + tileCache, + getTile); //special callback for thumb nail. + //Kickoff retrieve of thumb nail data tile. + datalayers[MatrixManager.THUMBNAIL_LEVEL].setReadWindow(1,1,tileStructure.levels.tn.total_rows,tileStructure.levels.tn.total_cols); + } + + + //Summary + if (tileStructure.levels.s !== undefined) { + datalayers[MatrixManager.SUMMARY_LEVEL] = new HeatMapData(heatMapName, + MatrixManager.SUMMARY_LEVEL, + tileStructure.levels.s, + datalayers[MatrixManager.THUMBNAIL_LEVEL], + tileCache, + getTile); + //Kickoff retrieve of summary data tiles. + datalayers[MatrixManager.SUMMARY_LEVEL].setReadWindow(1,1,datalayers[MatrixManager.SUMMARY_LEVEL].totalRows,datalayers[MatrixManager.SUMMARY_LEVEL].totalColumns); + } else { + //If no summary level, set the summary to be the thumb nail. + datalayers[MatrixManager.SUMMARY_LEVEL] = datalayers[MatrixManager.THUMBNAIL_LEVEL]; + } + + //Detail level + if (tileStructure.levels.d !== undefined) { + datalayers[MatrixManager.DETAIL_LEVEL] = new HeatMapData(heatMapName, + MatrixManager.DETAIL_LEVEL, + tileStructure.levels.d, + datalayers[MatrixManager.SUMMARY_LEVEL], + tileCache, + getTile); + } else { + //If no detail layer, set it to summary. + datalayers[MatrixManager.DETAIL_LEVEL] = datalayers[MatrixManager.SUMMARY_LEVEL]; + } + + + + //Ribbon Vertical + if (tileStructure.levels.rv !== undefined) { + datalayers[MatrixManager.RIBBON_VERT_LEVEL] = new HeatMapData(heatMapName, + MatrixManager.RIBBON_VERT_LEVEL, + tileStructure.levels.rv, + datalayers[MatrixManager.SUMMARY_LEVEL], + tileCache, + getTile); + } else { + datalayers[MatrixManager.RIBBON_VERT_LEVEL] = datalayers[MatrixManager.DETAIL_LEVEL]; + } + + //Ribbon Horizontal + if (tileStructure.levels.rh !== undefined) { + datalayers[MatrixManager.RIBBON_HOR_LEVEL] = new HeatMapData(heatMapName, + MatrixManager.RIBBON_HOR_LEVEL, + tileStructure.levels.rh, + datalayers[MatrixManager.SUMMARY_LEVEL], + tileCache, + getTile); + } else { + datalayers[MatrixManager.RIBBON_HOR_LEVEL] = datalayers[MatrixManager.DETAIL_LEVEL]; + } + + sendCallBack(MatrixManager.Event_INITIALIZED); + } + + function addColor(cm) { + colorMaps = cm; + sendCallBack(MatrixManager.Event_JSON); + } + + + function addClassification(cs) { + classifications = cs; + sendCallBack(MatrixManager.Event_JSON); + } + + function addRowLabels(rl) { + rowLabels = rl; + sendCallBack(MatrixManager.Event_JSON); + } + + function addColLabels(cl) { + colLabels = cl; + sendCallBack(MatrixManager.Event_JSON); + } + + function addDendrogram(d) { + dendrogram = d; + sendCallBack(MatrixManager.Event_JSON); + } + + function addChm(d) { + chm = d; + sendCallBack(MatrixManager.Event_JSON); + } + + + //Call the users call back function to let them know the chm is initialized or updated. + function sendCallBack(event, level) { + + //Initialize event + if ((event == MatrixManager.Event_INITIALIZED) || (event == MatrixManager.Event_JSON) || + ((event == MatrixManager.Event_NEWDATA) && (level == MatrixManager.THUMBNAIL_LEVEL))) { + //Only send initialized status if several conditions are met: need all summary JSON and thumb nail. + if ((colorMaps != null) && + (classifications != null) && + (rowLabels != null) && + (colLabels != null) && + (dendrogram != null) && + (chm != null) && + (Object.keys(datalayers).length > 0) && + (tileCache[MatrixManager.THUMBNAIL_LEVEL+".1.1"] != null)) { + initialized = 1; + sendAllListeners(MatrixManager.Event_INITIALIZED); + } + //Unlikely, but possible to get init finished after all the summary tiles. + //As a back stop, if we already have the top left summary tile, send a data update event too. + if (tileCache[MatrixManager.SUMMARY_LEVEL+".1.1"] != null) { + sendAllListeners(MatrixManager.Event_NEWDATA, MatrixManager.SUMMARY_LEVEL); + } + } else if ((event == MatrixManager.Event_NEWDATA) && (initialized == 1)) { + //Got a new tile, notify drawing code via callback. + sendAllListeners(event, level); + } + } + + //send to all event listeners + function sendAllListeners(event, level){ + for (var i = 0; i < eventListeners.length; i++) { + eventListeners[i](event, level); + } + } + + //Fetch a data tile if needed. + function getTile(level, tileRow, tileColumn) { + var tileName=level + "." + tileRow + "." + tileColumn; + if (tileCache.hasOwnProperty(tileName)) { + //Already have tile in cache - do nothing. + return; + } + + //ToDo: need to limit the number of tiles retrieved. + + //ToDo: need to remove items from the cache if it is maxed out. - don't get rid of thumb nail or summary. + + + if (mode == MatrixManager.WEB_SOURCE) { + var name = "GetTile?map=" + heatMapName + "&level=" + level + "&tile=" + tileName; + var req = new XMLHttpRequest(); + req.open("GET", name, true); + req.responseType = "arraybuffer"; + req.onreadystatechange = function () { + if (req.readyState == req.DONE) { + if (req.status != 200) { + console.log('Failed in call to get tile from server: ' + req.status); + } else { + var arrayData = new Float32Array(req.response); + tileCache[tileName] = arrayData; + sendCallBack(MatrixManager.Event_NEWDATA, level); + } + } + }; + req.send(); + } else { + //File mode - get tile from zip + zipFiles[heatMapName + "/" + level + "/" + tileName + '.bin'].getData(new zip.BlobWriter(), function(blob) { + var fr = new FileReader(); + + fr.onload = function(e) { + var arrayBuffer = fr.result; + var far32 = new Float32Array(arrayBuffer); + tileCache[tileName] = far32; + sendCallBack(MatrixManager.Event_NEWDATA, level); + } + + fr.readAsArrayBuffer(blob); + }, function(current, total) { + // onprogress callback + }); + } + }; + + //Helper function to fetch a json file from server. + //Specify which file to get and what funciton to call when it arrives. + function webFetchJson(jsonFile, setterFunction) { + var req = new XMLHttpRequest(); + req.open("GET", "GetDescriptor?map=" + heatMapName + "&type=" + jsonFile, true); + req.onreadystatechange = function () { + if (req.readyState == req.DONE) { + if (req.status != 200) { + console.log('Failed to get json file ' + jsonFile + ' for ' + heatMapName + ' from server: ' + req.status); + } else { + //Got the result - call appropriate setter. + setterFunction(JSON.parse(req.response)); + } + } + }; + req.send(); + } + + //Helper function to fetch a json file from zip file. + //Specify which file to get and what funciton to call when it arrives. + function zipFetchJson(jsonFile, setterFunction) { + zipFiles[heatMapName + "/" + jsonFile].getData(new zip.TextWriter(), function(text) { + // got the json, now call the appropriate setter + setterFunction(JSON.parse(text)); + }, function(current, total) { + // onprogress callback + }); + } + +}; + + +//Internal object for traversing the data at a given zoom level. +function HeatMapData(heatMapName, level, jsonData, lowerLevel, tileCache, getTile) { + this.totalRows = jsonData.total_rows; + this.totalColumns = jsonData.total_cols; + var numTileRows = jsonData.tile_rows; + var numTileColumns = jsonData.tile_cols; + var rowsPerTile = jsonData.rows_per_tile; + var colsPerTile = jsonData.cols_per_tile; + this.rowSummaryRatio = jsonData.row_summary_ratio; + this.colSummaryRatio = jsonData.col_summary_ratio; + var rowToLower = (lowerLevel === null ? null : this.totalRows/lowerLevel.totalRows); + var colToLower = (lowerLevel === null ? null : this.totalColumns/lowerLevel.totalColumns); + + //Get a value for a row / column. If the tile with that value is not available, get the down sampled value from + //the lower data level. + this.getValue = function(row, column) { + //Calculate which tile holds the row / column we are looking for. + var tileRow = Math.floor((row-1)/rowsPerTile) + 1; + var tileCol = Math.floor((column-1)/colsPerTile) + 1; + arrayData = tileCache[level+"."+tileRow+"."+tileCol]; + + //If we have the tile, use it. Otherwise, use a lower resolution tile to provide a value. + if (arrayData != undefined) { + //for end tiles, the # of columns can be less than the colsPerTile - figure out the correct num columns. + var thisTileColsPerRow = tileCol == numTileColumns ? ((this.totalColumns % colsPerTile) == 0 ? colsPerTile : this.totalColumns % colsPerTile) : colsPerTile; + //Tile data is in one long list of numbers. Calculate which position maps to the row/column we want. + return arrayData[(row-1)%rowsPerTile * thisTileColsPerRow + (column-1)%colsPerTile]; + } else if (lowerLevel != null) { + return lowerLevel.getValue(Math.floor(row/rowToLower) + 1, Math.floor(column/colToLower) + 1); + } else { + return 0; + } + }; + + // External user of the matix data lets us know where they plan to read. + // Pull tiles for that area if we don't already have them. + this.setReadWindow = function(row, column, numRows, numColumns) { + var startRowTile = Math.floor(row/rowsPerTile) + 1; + var startColTile = Math.floor(column/colsPerTile) + 1; + var endRowCalc = (row+(numRows-1))/rowsPerTile; + var endColCalc = (column+(numColumns-1))/colsPerTile; + var endRowTile = Math.floor(endRowCalc)+(endRowCalc%1 > 0 ? 1 : 0); + var endColTile = Math.floor(endColCalc)+(endColCalc%1 > 0 ? 1 : 0); + + for (var i = startRowTile; i <= endRowTile; i++) { + for (var j = startColTile; j <= endColTile; j++) { + if (tileCache[level+"."+i+"."+j] === undefined) + getTile(level, i, j); + } + } + } + +}; \ No newline at end of file diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/NGCHM_Util.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/NGCHM_Util.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,34 @@ +/** + * General purpose javascript helper funcitons + */ + +//Get a value for a parm passed in the URL. +function getURLParameter(name) { + return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||'' +} + +/********************************************************************************** + * FUNCTION - toTitleCase: The purpose of this function is to change the case of + * the first letter of the first word in each sentence passed in. + **********************************************************************************/ +function toTitleCase(string) +{ + // \u00C0-\u00ff for a happy Latin-1 + return string.toLowerCase().replace(/_/g, ' ').replace(/\b([a-z\u00C0-\u00ff])/g, function (_, initial) { + return initial.toUpperCase(); + }).replace(/(\s(?:de|a|o|e|da|do|em|ou|[\u00C0-\u00ff]))\b/ig, function (_, match) { + return match.toLowerCase(); + }); +} + +/********************************************************************************** + * FUNCTION - getStyle: The purpose of this function is to return the style + * property requested for a given screen object. + **********************************************************************************/ +function getStyle(x,styleProp){ + if (x.currentStyle) + var y = x.currentStyle[styleProp]; + else if (window.getComputedStyle) + var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp); + return y; +} \ No newline at end of file diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/PdfGenerator.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/PdfGenerator.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,389 @@ +function openPdfPrefs(e){ + maxRows = 0; + userHelpClose(); + var prefspanel = document.getElementById('pdfPrefsPanel'); + //Add prefspanel table to the main preferences DIV and set position and display + prefspanel.style.top = e.offsetTop + 15; + prefspanel.style.display="inherit"; + prefspanel.style.left = e.offsetLeft - prefspanel.clientWidth; +} + +/********************************************************************************** + * FUNCTION - getPDF: This function is called when the "create pdf" button is pressed. + * It will check the checkboxes/radio buttons to see how the PDF is to be created using + * the isChecked function. for a full list of jsPDF functions, visit here: + * https://mrrio.github.io/jsPDF/doc/symbols/jsPDF.html#setLineCap + **********************************************************************************/ +function getPDF(){ + // canvas elements need to be converted to DataUrl to be loaded into PDF + updateSelection(); // redraw the canvases because otherwise they can show up blank + var sumImgData = canvas.toDataURL('image/png'); + var detImgData = detCanvas.toDataURL('image/png'); + var mapsToShow = isChecked("pdfInputSummaryMap") ? "S" : isChecked("pdfInputDetailMap") ? "D" : "B"; + var doc = isChecked("pdfInputPortrait") ? new jsPDF("p","pt") :new jsPDF("l","pt"); // landscape or portrait? + var pageHeight = doc.internal.pageSize.height; + var pageWidth = doc.internal.pageSize.width; + + doc.setFont("times"); +// doc.setFont("helvetica"); + // convert longest label units to actual length (11 is the max font size of the labels) + // these will be the bottom and left padding space for the detail Heat Map + var allLabels = document.getElementsByClassName("DynamicLabel"); + var longestRowLabelUnits = 10, longestColLabelUnits = 5; + for (var i = 0; i < allLabels.length; i++){ // go through all the labels and find the one that takes the most space + var label = allLabels[i]; + if (label.getAttribute('axis') == "Row"){ + longestRowLabelUnits = Math.max(doc.getStringUnitWidth(label.innerHTML),longestRowLabelUnits); + } else { + longestColLabelUnits = Math.max(doc.getStringUnitWidth(label.innerHTML),longestColLabelUnits); + } + } + longestColLabelUnits *= 11; + longestRowLabelUnits *= 11; + + // header + var headerCanvas = document.createElement('CANVAS'); // load the MDAnderson logo into a canvas, since you can't load an img directly + var headerCtx = headerCanvas.getContext('2d'); + var header = document.getElementsByClassName('mdaServiceHeaderLogo')[0]; + headerCanvas.height = 85; // logo png's actual dimensions + headerCanvas.width = 260; + headerCtx.drawImage(header.children[0], 0, 0); + var headerData = headerCanvas.toDataURL('image/png'); + var headerHeight = header.clientHeight + 5; + createHeader(); + + // maps + var paddingLeft = 5, paddingTop = headerHeight+10; // these are the variables that we will be using repeatedly to place items + var sumImgW,sumImgH,detImgW,detImgH; + var detImgL = paddingLeft; + if (mapsToShow == "S"){ + sumImgW = pageWidth - 2*paddingLeft, sumImgH = pageHeight - paddingTop - 2*paddingLeft; + doc.addImage(sumImgData, 'PNG', paddingLeft, paddingTop, sumImgW,sumImgH); + } else if (mapsToShow == "D"){ + detImgW = pageWidth - 2*paddingLeft - longestRowLabelUnits, detImgH = pageHeight - paddingTop - longestColLabelUnits; + doc.addImage(detImgData, 'PNG', paddingLeft, paddingTop, detImgW,detImgH); + } else { + if (!isChecked("pdfInputPages")){ + sumImgW = (pageWidth - longestRowLabelUnits - 2*paddingLeft)/2, sumImgH = pageHeight - paddingTop - longestColLabelUnits; + detImgW = (pageWidth - longestRowLabelUnits - 2*paddingLeft)/2, detImgH = pageHeight - paddingTop - longestColLabelUnits; + detImgL = sumImgW + 2*paddingLeft; + doc.addImage(sumImgData, 'PNG', paddingLeft, paddingTop, sumImgW,sumImgH); + doc.addImage(detImgData, 'PNG', detImgL, paddingTop, detImgW,detImgH); + } else { + sumImgW = pageWidth - 2*paddingLeft, sumImgH = pageHeight - paddingTop - 2*paddingLeft; + doc.addImage(sumImgData, 'PNG', paddingLeft, paddingTop, sumImgW,sumImgH); + doc.addPage(); + createHeader(); + detImgW = pageWidth - 2*paddingLeft - longestRowLabelUnits, detImgH = pageHeight - paddingTop - longestColLabelUnits; + doc.addImage(detImgData, 'PNG', detImgL, paddingTop, detImgW,detImgH); + } + } + + // labels + var detClient2PdfWRatio = detCanvas.clientWidth/detImgW; // scale factor to place the labels in their proper locations + var detClient2PdfHRatio = detCanvas.clientHeight/detImgH; + // row labels and col class bar labels (basically stolen from DetailHeatMapDisplay.js + var headerSize = paddingTop; + var colHeight = calculateTotalClassBarHeight("column") + detailDendroHeight; + if (colHeight > 0) { + headerSize += detImgH * (colHeight / (detailDataViewHeight + colHeight)); + } + var skip = (detImgH - headerSize) / dataPerCol; + var fontSize = Math.min(skip - 2, 11); + doc.setFontSize(fontSize); + for (var i = 0; i < allLabels.length; i++){ + var label = allLabels[i]; + if (label.getAttribute("axis") == "Row"){ + doc.text(label.offsetLeft/detClient2PdfWRatio+detImgL, label.offsetTop/detClient2PdfHRatio+paddingTop+fontSize, label.innerHTML, null); + } else if (label.getAttribute("axis") == "ColumnClass"){ // change font for class bars + var scale = detImgH / (detailDataViewWidth + calculateTotalClassBarHeight("row")+detailDendroWidth); + var colClassInfo = getClassBarsToDraw("column"); + var names = colClassInfo["bars"]; + var classBars = heatMap.getClassifications(); + var tempFontSize = fontSize; + fontSize = Math.min((classBars[names[0]].height - paddingHeight) * scale, 11); + doc.setFontSize(fontSize); + doc.text(label.offsetLeft/detClient2PdfWRatio+detImgL, label.offsetTop/detClient2PdfHRatio+paddingTop+fontSize/2, label.innerHTML, null); + fontSize = tempFontSize + doc.setFontSize(fontSize); + } + } + + // col labels and row class bar labels + headerSize = 0; + var rowHeight = calculateTotalClassBarHeight("row") + detailDendroWidth; + if (rowHeight > 0) { + headerSize = detImgW * (rowHeight / (detailDataViewWidth + rowHeight)); + } + skip = (detImgW - headerSize) / dataPerRow; + fontSize = Math.min(skip - 2, 11); + doc.setFontSize(fontSize); + for (var i = 0; i < allLabels.length; i++){ + var label = allLabels[i]; + if (label.getAttribute("axis") == "Column"){ + doc.text(label.offsetLeft/detClient2PdfWRatio-fontSize+detImgL, label.offsetTop/detClient2PdfHRatio+paddingTop, label.innerHTML, null, 270); + } else if (label.getAttribute("axis") == "RowClass"){ + var scale = detImgW / (detailDataViewWidth + calculateTotalClassBarHeight("row")+detailDendroWidth); + var colClassInfo = getClassBarsToDraw("row"); + var names = colClassInfo["bars"]; + var classBars = heatMap.getClassifications(); + var tempFontSize = fontSize; + fontSize = Math.min((classBars[names[0]].height - paddingHeight) * scale, 11); + doc.setFontSize(fontSize); + doc.text(label.offsetLeft/detClient2PdfWRatio-fontSize/2+detImgL, label.offsetTop/detClient2PdfHRatio+paddingTop, label.innerHTML, null, 270); + fontSize = tempFontSize + doc.setFontSize(fontSize); + } + } + + // class bar legends + var classBars = heatMap.getClassifications(); + var classBarHeaderSize = 20; // these are font sizes + var classBarTitleSize = 15; + var classBarLegendTextSize = 10; + var classBarFigureW = 150; // figure dimensions, unless discrete with 15+ categories + var classBarFigureH = 150; + var condenseClassBars = isChecked('pdfInputCondensed'); + paddingLeft = 5, paddingTop = headerHeight+classBarHeaderSize + 5; // reset the top and left coordinates + + // row + if (isChecked('pdfInputRow')){ + doc.addPage(); + createHeader(); + doc.setFontSize(classBarHeaderSize); + doc.text(10, paddingTop, "Row Covariate Bar Legends:" , null); + var rowClassInfo = getClassBarsToDraw("row"); + var names = rowClassInfo["bars"]; + var colorSchemes = rowClassInfo["colors"]; + var leftOff=10, topOff = paddingTop + classBarTitleSize; + + for (var i = 0; i < names.length; i++){ // for each class bar to draw... + doc.setFontSize(classBarTitleSize); + var currentClassBar = classBars[names[i]]; + var colorMap = heatMap.getColorMapManager().getColorMap(colorSchemes[i]); + if (currentClassBar.show === 'Y') { // place the figure if it's shown + if (colorMap.getType() == "discrete"){ + getBarGraphForDiscreteClassBar(currentClassBar,colorMap,names[i]); + } else { + getBarGraphForContinuousClassBar(currentClassBar,colorMap,names[i]); + } + } + } + } + + // column + if (isChecked('pdfInputColumn')){ + doc.addPage(); + createHeader(); + doc.setFontSize(classBarHeaderSize); + doc.text(10, paddingTop, "Column Covariate Bar Legends:" , null); + var colClassInfo = getClassBarsToDraw("column"); + var names = colClassInfo["bars"]; + var colorSchemes = colClassInfo["colors"]; + var leftOff=10, topOff = paddingTop + classBarTitleSize; + + for (var i = 0; i < names.length; i++){ // for each class bar to draw... + doc.setFontSize(classBarTitleSize); + var currentClassBar = classBars[names[i]]; + var colorMap = heatMap.getColorMapManager().getColorMap(colorSchemes[i]); + if (currentClassBar.show === 'Y') { + if (colorMap.getType() == "discrete"){ + getBarGraphForDiscreteClassBar(currentClassBar,colorMap,names[i]); + } else { + getBarGraphForContinuousClassBar(currentClassBar,colorMap,names[i]); + } + } + } + } + + // TODO: in case there is an empty page after the class bar legends, delete it + + + doc.save( heatMap.getChm().name + '.pdf'); + + + //==================// + // HELPER FUNCTIONS // + //==================// + + // makes the MDAnderson logo, the HM name, and the red divider line at the top of each page + function createHeader() { + doc.addImage(headerData, 'PNG',5,5,header.clientWidth,header.clientHeight); + doc.setFontSize(20); + doc.text(pageWidth/2 - doc.getStringUnitWidth(heatMap.getChm().name)*20/2, headerHeight, heatMap.getChm().name, null); + doc.setFillColor(255,0,0); + doc.setDrawColor(255,0,0); + doc.rect(5, header.clientHeight+10, pageWidth-10, 2, "FD"); + } + + /********************************************************************************** + * FUNCTION - getBarGraphForContinousClassBar: places the classBar legend using the + * variables leftOff and topOff, which are updated after every classBar legend. + * inputs: classBar object, colorMap object, and string for name + **********************************************************************************/ + function getBarGraphForContinuousClassBar(classBar, colorMap,name){ + doc.text(leftOff, topOff , name, null); + var thresholds = colorMap.getContinuousThresholdKeys(); + var numThresholds = thresholds.length-1; // the last threshold repeats for some reason :\ + var barHeight = !condenseClassBars ? classBarFigureH/(thresholds.length) : 10; + // get the number N in each threshold + var counts = {}, maxCount = 0, maxLabelLength = doc.getStringUnitWidth("Missing Value")*classBarLegendTextSize; + // get the continuous thresholds and find the counts for each bucket + for(var i = 0; i < classBar.values.length; i++) { + var num = classBar.values[i]; + for (var k = 0; k < thresholds.length; k++){ + var thresh = thresholds[k]; + if (k == 0 && num thresholds[thresholds.length-1]){ + counts[thresh] = counts[thresh] ? counts[thresh]+1 : 1; + } else if (num <= thresh){ + counts[thresh] = counts[thresh] ? counts[thresh]+1 : 1; + break; + } + } + } + // find the longest label length + for (var val in counts){ + maxCount = Math.max(maxCount, counts[val]); + maxLabelLength = Math.max(maxLabelLength, doc.getStringUnitWidth(val.length)*classBarLegendTextSize); + } + + var bartop = topOff+5; // top location of first bar + var missingCount = classBar.values.length; // start at total number of labels and work down + for (var j = 0; j < thresholds.length-1; j++){ + var rgb = colorMap.getClassificationColor(thresholds[j]); + doc.setFillColor(rgb.r,rgb.g,rgb.b); + doc.setDrawColor(0,0,0); + if (condenseClassBars){ // square + var barW = 10; + doc.rect(leftOff, bartop, barW, barHeight, "FD"); // make the square + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff +barW + 5, bartop + classBarLegendTextSize, thresholds[j].toString() + " " + "n = " + counts[thresholds[j]] , null); + } else { // histogram + var barW = counts[thresholds[j]]/maxCount*classBarFigureW; + doc.rect(leftOff + maxLabelLength, bartop, barW, barHeight, "FD"); // make the histo bar + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff + maxLabelLength - doc.getStringUnitWidth(thresholds[j].toString())*classBarLegendTextSize - 4, bartop + classBarLegendTextSize, thresholds[j].toString() , null); + doc.text(leftOff + maxLabelLength +barW + 5, bartop + classBarLegendTextSize, "n = " + counts[thresholds[j]] , null); + } + missingCount -= counts[thresholds[j]]; + bartop+=barHeight; // adjust top position for the next bar + } + var rgb = colorMap.getClassificationColor("Missing Value"); + doc.setFillColor(rgb.r,rgb.g,rgb.b); + doc.setDrawColor(0,0,0); + if (condenseClassBars){ + var barW = 10; + doc.rect(leftOff, bartop, barW, barHeight, "FD"); + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff +barW + 5, bartop + classBarLegendTextSize, "Missing Value n = " + missingCount , null); + } else { + var barW = missingCount/maxCount*classBarFigureW; + doc.rect(leftOff + maxLabelLength, bartop, barW, barHeight, "FD"); + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff + maxLabelLength - doc.getStringUnitWidth("Missing Value")*classBarLegendTextSize - 4, bartop + classBarLegendTextSize, "Missing Value" , null); + doc.text(leftOff + maxLabelLength +barW + 5, bartop + classBarLegendTextSize, "n = " + missingCount , null); + } + // adjust the location for the next class bar figure + leftOff+= classBarFigureW + maxLabelLength + 50; + if (leftOff + classBarFigureW > pageWidth){ // if we'll go off the width of the page... + leftOff = 10; // ...reinitialize the left side + topOff += classBarFigureH + classBarHeaderSize; + classBarFigureH = 150; // return figure height to original value in case it got changed in the current row + if (topOff + classBarFigureH > pageHeight){ // if we'll go off the bottom of the page... + doc.addPage(); + createHeader(); // ...create a new page and reinitialize the top + topOff = paddingTop + 5; + } + } + } + + + /********************************************************************************** + * FUNCTION - getBarGraphForDiscreteClassBar: places the classBar legend using the + * variables leftOff and topOff, which are updated after every classBar legend. + * inputs: classBar object, colorMap object, and string for name + **********************************************************************************/ + function getBarGraphForDiscreteClassBar(classBar, colorMap,name){ + doc.text(leftOff, topOff , name, null); + var thresholds = colorMap.getThresholds(); + var barHeight = !condenseClassBars ? classBarFigureH/(thresholds.length+1) : 10; + var counts = {}, maxCount = 0, maxLabelLength = doc.getStringUnitWidth("Missing Value")*classBarLegendTextSize; + // get the number N in each threshold + for(var i = 0; i< classBar.values.length; i++) { + var num = classBar.values[i]; + counts[num] = counts[num] ? counts[num]+1 : 1; + } + for (var val in counts){ + maxCount = Math.max(maxCount, counts[val]); + maxLabelLength = Math.max(maxLabelLength, doc.getStringUnitWidth(val.length)*classBarLegendTextSize); + } + + var bartop = topOff+5; + // NOTE: missingCount will contain all elements that are not accounted for in the thresholds + // ie: thresholds = [type1, type2, type3], typeX will get included in the missingCount + var missingCount = classBar.values.length; + // draw the bars + for (var j = 0; j < thresholds.length; j++){ // make a gradient stop (and also a bucket for continuous) + var rgb = colorMap.getClassificationColor(thresholds[j]); + doc.setFillColor(rgb.r,rgb.g,rgb.b); + doc.setDrawColor(0,0,0); + if (condenseClassBars){ + var barW = 10; + doc.rect(leftOff, bartop, barW, barHeight, "FD"); + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff +barW + 5, bartop + classBarLegendTextSize, thresholds[j].toString() + " " + "n = " + counts[thresholds[j]] , null); + } else { + var barW = counts[thresholds[j]]/maxCount*classBarFigureW; + doc.rect(leftOff + maxLabelLength, bartop, barW, barHeight, "FD"); + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff + maxLabelLength - doc.getStringUnitWidth(thresholds[j].toString())*classBarLegendTextSize - 4, bartop + barHeight/2, thresholds[j].toString() , null); + doc.text(leftOff + maxLabelLength +barW + 5, bartop + barHeight/2, "n = " + counts[thresholds[j]] , null); + } + + missingCount -= counts[thresholds[j]]; + bartop+=barHeight; + } + + var rgb = colorMap.getClassificationColor("Missing Value"); + doc.setFillColor(rgb.r,rgb.g,rgb.b); + doc.setDrawColor(0,0,0); + if (condenseClassBars){ + var barW = 10; + doc.rect(leftOff, bartop, barW, barHeight, "FD"); + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff +barW + 5, bartop + classBarLegendTextSize, "Missing Value n = " + missingCount , null); + } else { + var barW = missingCount/maxCount*classBarFigureW; + doc.rect(leftOff + maxLabelLength, bartop, barW, barHeight, "FD"); + doc.setFontSize(classBarLegendTextSize); + doc.text(leftOff + maxLabelLength - doc.getStringUnitWidth("Missing Value")*classBarLegendTextSize - 4, bartop + barHeight/2, "Missing Value" , null); + doc.text(leftOff + maxLabelLength +barW + 5, bartop + barHeight/2, "n = " + missingCount , null); + } + + if (thresholds.length > 15){ // in case a discrete classbar has over 15 categories, make the topOff increment bigger + classBarFigureH = (1+thresholds.length)*10; + } + leftOff+= classBarFigureW + maxLabelLength + 50; + if (leftOff + classBarFigureW > pageWidth){ // if the next class bar figure will go beyond the width of the page... + leftOff = 10; // ...reset leftOff... + topOff += classBarFigureH+classBarHeaderSize; // ... and move the next figure to the line below + classBarFigureH = 150; // return class bar height to original value in case it got changed in this row + if (topOff + classBarFigureH > pageHeight){ // if the next class bar goes off the page vertically... + doc.addPage(); // ... make a new page and reset topOff + createHeader(); + topOff = paddingTop + 10; + } + } + } + function isChecked(el){ + if(document.getElementById(el)) + return document.getElementById(el).checked; + } +} + +function pdfCancelButton(){ + var prefspanel = document.getElementById('pdfPrefsPanel'); + prefspanel.style.display = "none"; +} \ No newline at end of file diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/SelectionManager.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/SelectionManager.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,449 @@ +/** + * This code is responsible for handling changes in position of selected heat map region. + * It handles mouse, keyboard, and button events that change the position of the selected + * region. It also tracks whether the display is in a single window or split into two + * separate windows. If in separate windows, local storage events are used to communicate + * changes between the two windows. + */ + +//Globals that provide information about heat map position selection. + +mode = null; // Set to normal or ribbon vertical or ribbon horizontal +currentRow=null; // Top row of current selected position +currentCol=null; // Left column of the current selected position +dataPerRow=null; // How many rows are included in the current selection +dataPerCol=null; // How many columns in the current selection +selectedStart=0; // If dendrogram selection is used to limit ribbon view - which position to start selection. +selectedStop=0; // If dendrogram selection is used to limit ribbon view - which position is last of selection. +var searchItems=[]; // Valid labels found from a user search + + //isSub will be set to true if windows are split and this is the child. +isSub = getURLParameter('sub') == 'true'; +hasSub = false; //hasSub set to true if windows are split and this is the parent. + + +/* This routine is called when the selected row / column is changed. + * It is assumed that the caller modified currentRow, currentCol, dataPerRow, + * and dataPerCol as desired. This method does redrawing and notification as necessary. + */ +function updateSelection() { + if (!isSub) { + //We have the summary heat map so redraw the yellow selection box. + drawLeftCanvasBox(); + } + if (!hasSub) { + // Redraw based on mode type and selection. + heatMap.setReadWindow(getLevelFromMode(MatrixManager.DETAIL_LEVEL),getCurrentDetRow(),getCurrentDetCol(),getCurrentDetDataPerCol(),getCurrentDetDataPerRow()); + drawDetailHeatMap(); + } + + //If summary and detail as split into two browsers. Communicate the selection change + //to the other browser. + if (isSub || hasSub) { + localStorage.removeItem('event'); + localStorage.setItem('currentRow', '' + currentRow); + localStorage.setItem('currentCol', '' + currentCol); + localStorage.setItem('dataPerRow', '' + dataPerRow); + localStorage.setItem('dataPerCol', '' + dataPerCol); + localStorage.setItem('selectedStart', '' + selectedStart); + localStorage.setItem('selectedStop', '' + selectedStop); + localStorage.setItem('mode', mode); + localStorage.setItem('selected', JSON.stringify(searchItems)); + localStorage.setItem('event', 'changePosition'); + } +} + +function changeMode(newMode) { + + if (!hasSub) { + if (newMode == 'RIBBONH') + detailHRibbon(); + if (newMode == 'RIBBONV') + detailVRibbon(); + if (newMode == 'NORMAL') + detailNormal(); + } else { + localStorage.removeItem('event'); + localStorage.setItem('selectedStart', '' + selectedStart); + localStorage.setItem('selectedStop', '' + selectedStop); + localStorage.setItem('mode', newMode); + localStorage.setItem('event', 'changeMode'); + } +} + +/* Handle mouse scroll wheel events to zoom in / out. + */ +function handleScroll(evt) { + evt.preventDefault(); + if (evt.wheelDelta < 0 || evt.deltaY > 0 || evt.scale < 1) { //Zoom out + if (!hasSub) + detailDataZoomOut(); + else { + localStorage.removeItem('event'); + localStorage.setItem('event', 'zoomOut' ) + } + } else { // Zoom in + if (!hasSub) + detailDataZoomIn(); + else { + localStorage.removeItem('event'); + localStorage.setItem('event', 'zoomIn' ) + } + } + return false; +} + + +function keyNavigate(e){ + userHelpClose(); + clearTimeout(detailPoint); + switch(e.keyCode){ // prevent default added redundantly to each case so that other key inputs won't get ignored + case 37: // left key + if (document.activeElement.id !== "search_text"){ + e.preventDefault(); + if (e.shiftKey){currentCol -= dataPerRow;} + else {currentCol--;} + } + break; + case 38: // up key + if (document.activeElement.id !== "search_text"){ + e.preventDefault(); + if (e.shiftKey){currentRow -= dataPerCol;} + else {currentRow--;} + } + break; + case 39: // right key + if (document.activeElement.id !== "search_text"){ + e.preventDefault(); + if (e.shiftKey){currentCol += dataPerRow;} + else {currentCol++;} + } + break; + case 40: // down key + if (document.activeElement.id !== "search_text"){ + e.preventDefault(); + if (e.shiftKey){currentRow += dataPerCol;} + else {currentRow++;} + } + break; + case 33: // page up + e.preventDefault(); + if (e.shiftKey){ + var newMode; + clearDendroSelection(); + switch(mode){ + case "RIBBONV": newMode = 'RIBBONH'; break; + case "RIBBONH": newMode = 'NORMAL'; break; + default: newMode = mode;break; + } + changeMode(newMode); + } else { + detailDataZoomIn();; + } + break; + case 34: // page down + e.preventDefault(); + if (e.shiftKey){ + var newMode; + clearDendroSelection(); + switch(mode){ + case "NORMAL": newMode = 'RIBBONH'; break; + case "RIBBONH": newMode = 'RIBBONV'; break; + default: newMode = mode;break; + } + changeMode(newMode); + } else { + detailDataZoomOut(); + } + break; + case 191: // "divide key" / + detailSplit(); + break; + default: + return; + } + + checkRow(); + checkColumn(); + + updateSelection(); +} + +/* Local storage is used to communicate between two browser windows when the display is split. Set + * up an event to be notified when contents of local storage are modified. + */ +function setupLocalStorage () { + window.addEventListener('storage', function (evt) { + console.log('localstorage event ' + evt.key); + if (evt.key == 'event') { + handleLocalStorageEvent(evt); + } + }, false); +} + +//When the detail pane is in a separate window, local storage is used to send it updates from +//clicks in the summary view. +function handleLocalStorageEvent(evt) { + if (evt.newValue == null) + return; + + var type = localStorage.getItem('event'); + + if (type == 'changePosition') { + currentRow = Number(localStorage.getItem('currentRow')); + currentCol = Number(localStorage.getItem('currentCol')); + dataPerRow = Number(localStorage.getItem('dataPerRow')); + dataPerCol = Number(localStorage.getItem('dataPerCol')); + selectedStart = Number(localStorage.getItem('selectedStart')); + selectedStop = Number(localStorage.getItem('selectedStop')); + if (mode != localStorage.getItem('mode') && selectedStart == 0 && selectedStop == 0){ + clearDendroSelection(); + } + mode = localStorage.getItem('mode'); + if (hasSub) { + searchItems = JSON.parse(localStorage.getItem('selected')); + // Redraw the yellow selection box. + drawLeftCanvasBox (); + } + if (isSub) { + // Redraw detail view based on selection. + heatMap.setReadWindow(getLevelFromMode(MatrixManager.DETAIL_LEVEL),getCurrentDetRow(),getCurrentDetCol(),getCurrentDetDataPerCol(),getCurrentDetDataPerRow()); + drawDetailHeatMap(); + } + } else if ((type == 'zoomIn') && (isSub)) { + detailDataZoomIn(); + } else if ((type == 'zoomOut') && (isSub)) { + detailDataZoomOut(); + } else if ((type == 'changeMode') && (isSub)) { + clearDendroSelection(); + var newMode = localStorage.getItem('mode'); + selectedStart = Number(localStorage.getItem('selectedStart')); + selectedStop = Number(localStorage.getItem('selectedStop')); + if (newMode == 'RIBBONH') + detailHRibbon(); + if (newMode == 'RIBBONV') + detailVRibbon(); + if (newMode == 'NORMAL') + detailNormal(); + } else if ((type == 'join') && hasSub) { + hasSub=false; + detailJoin(); + } +} + +//If a second detail browser window is launched, use local storage when first setting +//up the detail chm to get current mode and selection settings. +function initFromLocalStorage() { + currentRow = Number(localStorage.getItem('currentRow')); + currentCol = Number(localStorage.getItem('currentCol')); + dataPerRow = Number(localStorage.getItem('dataPerRow')); + dataPerCol = Number(localStorage.getItem('dataPerCol')); + selectedStart = Number(localStorage.getItem('selectedStart')); + selectedStop = Number(localStorage.getItem('selectedStop')); + searchItems = JSON.parse(localStorage.getItem('selected')); + mode = localStorage.getItem('mode'); + buildDendroMatrix(heatMap.getDendrogram(),'Column'); + buildDendroMatrix(heatMap.getDendrogram(),'Row'); + + dataBoxHeight = (DETAIL_SIZE_NORMAL_MODE-detailDataViewBoarder)/dataPerCol; + dataBoxWidth = (DETAIL_SIZE_NORMAL_MODE-detailDataViewBoarder)/dataPerRow; + + if (mode == 'RIBBONH') + detailHRibbon(); + if (mode == 'RIBBONV') + detailVRibbon(); + if (mode == 'NORMAL') + detailNormal(); +} + + + +//Called when a separate detail map window is joined back into the main chm browser window. +function rejoinNotice() { + localStorage.removeItem('event'); + localStorage.setItem('event', 'join'); +} + +/********************************************************************************** + * FUNCTION - getLevelFromMode: This function returns the level that is associated + * with a given mode. A level is passed in from either the summary or detail display + * as a default value and returned if the mode is not one of the Ribbon modes. + **********************************************************************************/ +function getLevelFromMode(lvl) { + if (mode == 'RIBBONV') { + return MatrixManager.RIBBON_VERT_LEVEL; + } else if (mode == 'RIBBONH') { + return MatrixManager.RIBBON_HOR_LEVEL; + } else { + return lvl; + } +} + +/********************************************************************************** + * FUNCTIONS - checkRow(and Col): This function makes sure the currentRow/Col setting + * is valid and adjusts that value into the viewing pane if it is not. It is called + * just prior to calling UpdateSelection(). + **********************************************************************************/ +function checkRow() { + //Set column to one if off the row boundary when in ribbon vert view + if ((currentRow < 1) || ((mode == 'RIBBONV') && (selectedStart==0))) currentRow = 1; + if (((mode == 'RIBBONV') || (mode == 'RIBBONV_DETAIL')) && (selectedStart != 0)) currentRow = selectedStart; + //Check row against detail boundaries + var numRows = heatMap.getNumRows(MatrixManager.DETAIL_LEVEL); + if (currentRow > ((numRows + 1) - dataPerCol)) currentRow = (numRows + 1) - dataPerCol; +} + +function checkColumn() { + //Set column to one if off the column boundary when in ribbon horiz view + if ((currentCol < 1) || ((mode == 'RIBBONH') && selectedStart==0)) currentCol = 1; + if (((mode == 'RIBBONH') || (mode=='RIBBONH_DETAIL')) && selectedStart!= 0) currentCol = selectedStart; + //Check column against detail boundaries + var numCols = heatMap.getNumColumns(MatrixManager.DETAIL_LEVEL); + if (currentCol > ((numCols + 1) - dataPerRow)) currentCol = (numCols + 1) - dataPerRow; +} + +/********************************************************************************** + * FUNCTIONS - setCurrentRow(Col)FromSum: These function perform the conversion + * of currentRow and currentCol coordinates from summary to detail. This is done + * so that the proper row/col location is set on the detail pane when a user clicks + * in the summary pane. The heatmap row/col summary ratios (ratio of detail to summary) + * are used to calculate the proper detail coordinates. + **********************************************************************************/ +function setCurrentRowFromSum(sumRow) { + // Up scale current summary row to detail equivalent + var rowSummaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + if (rowSummaryRatio > 1) { + currentRow = (sumRow*rowSummaryRatio); + } else { + currentRow = sumRow; + } +} +function setCurrentColFromSum(sumCol) { + var colSummaryRatio = heatMap.getColSummaryRatio(MatrixManager.SUMMARY_LEVEL); + if (colSummaryRatio > 1) { + currentCol = (sumCol*colSummaryRatio); + } else { + currentCol = sumCol; + } +} + +/********************************************************************************** + * FUNCTIONS - getCurrentSumRow(): These functions perform the conversion of + * currentRow and currentCol coordinates from detail to summary. This is done + * so that the proper row/col location is set on the summary pane when a user clicks + * in the detail pane. This is used when the leftCanvasBox is drawn. The heat map + * row/col summary ratios (ratio of detail to summary) are used to calculate the + * proper detail coordinates. + **********************************************************************************/ +function getCurrentSumRow() { + var currRow = currentRow; + // Convert selected current row value to Summary level + var rowSummaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + return Math.round(currRow/rowSummaryRatio); +} +//Follow similar methodology for Column as is used in above row based function +function getCurrentSumCol() { + var currCol = currentCol; + var colSummaryRatio = heatMap.getColSummaryRatio(MatrixManager.SUMMARY_LEVEL); + return Math.round(currCol/colSummaryRatio); +} + +/********************************************************************************** + * FUNCTIONS - getCurrentSumDataPerRow(): These functions perform the conversion of + * dataPerRow and dataPerCol from detail to summary. This is done so that the + * proper view pane can be calculated on the summary heat map when drawing the + * leftCanvasBox on that side of the screen. + **********************************************************************************/ +function getCurrentSumDataPerRow() { + var rowSummaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + // Summary data per row for using the summary ration for that level + var sumDataPerRow = Math.floor(dataPerRow/rowSummaryRatio); + return sumDataPerRow; +} +// Follow similar methodology for Column as is used in above row based function +function getCurrentSumDataPerCol() { + var colSummaryRatio = heatMap.getColSummaryRatio(MatrixManager.SUMMARY_LEVEL); + var sumDataPerCol = Math.floor(dataPerCol/colSummaryRatio); + return sumDataPerCol; +} + + +/********************************************************************************** + * FUNCTIONS - getCurrentDetRow(): These functions perform the conversion of + * currentRow and currentCol coordinates from full matrix position to detail view + * position. This is usually the same but when in ribbon view on a large matrix, + * the positions are scaled. + **********************************************************************************/ +function getCurrentDetRow() { + var detRow = currentRow; + if ((mode == 'RIBBONV') && (selectedStart >= 1)) { + var rvRatio = heatMap.getRowSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + detRow = Math.round(selectedStart/rvRatio); + } + return detRow; +} +//Follow similar methodology for Column as is used in above row based function +function getCurrentDetCol() { + var detCol = currentCol; + if ((mode == 'RIBBONH') && (selectedStart >= 1)) { + var rhRatio = heatMap.getColSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + detCol = Math.round(selectedStart/rhRatio); + } + return detCol; +} + +/********************************************************************************** + * FUNCTIONS - getCurrentDetDataPerRow(): DataPerRow/Col is in full matrix coordinates + * and usually the detail view uses this value directly unless we are in ribbon + * view where the value needs to be scaled in one dimension. + **********************************************************************************/ +function getCurrentDetDataPerRow() { + var detDataPerRow = dataPerRow; + if (mode == 'RIBBONH') { + var rate = heatMap.getColSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + detDataPerRow = Math.round(detDataPerRow/rate); + } + return detDataPerRow; +} +// Follow similar methodology for Column as is used in above row based function +function getCurrentDetDataPerCol() { + var detDataPerCol = dataPerCol; + if (mode == 'RIBBONV') { + var rate = heatMap.getRowSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + detDataPerCol = Math.round(detDataPerCol/rate); + } + return detDataPerCol; +} + +/********************************************************************************** + * FUNCTIONS - setDataPerRowFromDet(): DataPerRow/Col is in full matrix coordinates + * so sometimes in ribbon view this needs to be translated to full coordinates. + **********************************************************************************/ +function setDataPerRowFromDet(detDataPerRow) { + dataPerRow = detDataPerRow; + if (mode == 'RIBBONH') { + if (selectedStart==0) { + dataPerRow = heatMap.getNumColumns(MatrixManager.DETAIL_LEVEL); + } else { + var rate = heatMap.getColSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + dataPerRow = detDataPerRow * rate; + } + } +} +// Follow similar methodology for Column as is used in above row based function +function setDataPerColFromDet(detDataPerCol) { + dataPerCol = detDataPerCol; + if (mode == 'RIBBONV') { + if (selectedStart==0) { + dataPerCol = heatMap.getNumRows(MatrixManager.DETAIL_LEVEL); + } else { + var rate = heatMap.getRowSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + dataPerCol = detDataPerCol * rate; + } + } +} + + + + + diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/SummaryHeatMapDisplay.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/SummaryHeatMapDisplay.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,1026 @@ +var BYTE_PER_RGBA = 4; + +var canvas; +var gl; // WebGL contexts +var textureParams; + +//Size of heat map components +var dendroPaddingHeight = 1; +var rowDendroHeight = 102; // this is the height of the row dendro in canvas coords (this value may be adjusted eventually by the user) +var columnDendroHeight = 102; // this is the height of the col dendro in canvas coords (this value may be adjusted eventually by the user) +var normDendroMatrixHeight = 500; // this is the height of the dendro matrices created in buildDendroMatrix +var paddingHeight = 2; // space between classification bars +var rowClassBarWidth; +var colClassBarHeight; +var summaryViewBorderWidth = 2; // black edge around map +var summaryMatrixWidth; +var summaryMatrixHeight; +var colEmptySpace = 0; // padding for asymmetric maps +var rowEmptySpace = 0; +var summaryTotalHeight; +var summaryTotalWidth; + +var rowDendroBars; +var colDendroBars; +var colDendroMatrix; +var rowDendroMatrix; +var chosenBar = {axis: null, index: null}; + +var leftCanvasScaleArray = new Float32Array([1.0, 1.0]); +var leftCanvasBoxLeftTopArray = new Float32Array([0, 0]); +var leftCanvasBoxRightBottomArray = new Float32Array([0, 0]); +var leftCanvasTranslateArray = new Float32Array([0, 0]); +var dendroBoxLeftTopArray = new Float32Array([0, 0]); +var dendroBoxRightBottomArray = new Float32Array([0, 0]); +var leftCanvasBoxVertThick; +var leftCanvasBoxHorThick; + +var TexPixels; + +var uScale; +var uTranslate; +var uBoxLeftTop; +var uBoxRightBottom; +var uBoxVertThickness; +var uBoxHorThickness; +var uBoxColor; +var dendroBoxLeftTop; +var dendroBoxRightBottom; +var dendroBoxColor; +var chmInitialized = 0; + +var eventTimer = 0; // Used to delay draw updates + +//Main function that draws the summary heat map. chmFile is only used in file mode. +function initSummaryDisplay() { + canvas = document.getElementById('summary_canvas'); + canvas.addEventListener('click', onClickLeftCanvas); + canvas.onmousemove = handleMove; + // set the position to (1,1) so that the detail pane loads at the top left corner of the summary. + currentRow = 1; + currentCol = 1; +}; + +// Callback that is notified every time there is an update to the heat map +// initialize, new data, etc. This callback draws the summary heat map. +function processSummaryMapUpdate (event, level) { + + if (event == MatrixManager.Event_INITIALIZED) { + summaryInit(); + } else if (event == MatrixManager.Event_NEWDATA && level == MatrixManager.SUMMARY_LEVEL){ + //Summary tile - wait a bit to see if we get another tile quickly, then draw + if (eventTimer != 0) { + //New tile arrived - reset timer + clearTimeout(eventTimer); + } + eventTimer = setTimeout(buildSummaryTexture, 200); + } + //Ignore updates to other tile types. +} + +// Perform all initialization functions for Summary heat map +function summaryInit() { + var dendroGram = heatMap.getDendrogram(); + rowDendroHeight = parseInt(dendroGram['row_dendro_height'])+2; + columnDendroHeight = parseInt(dendroGram['col_dendro_height'])+2; + if (heatMap.showRowDendrogram("SUMMARY")) { + rowDendroMatrix = buildDendroMatrix(dendroGram,'Row'); // create array with the bars + } + if (heatMap.showColDendrogram("SUMMARY")) { + colDendroMatrix = buildDendroMatrix(dendroGram,'Column'); // create array with the bars + } + rowClassBarWidth = calculateTotalClassBarHeight("row"); + colClassBarHeight = calculateTotalClassBarHeight("column"); + summaryMatrixWidth = heatMap.getNumColumns(MatrixManager.SUMMARY_LEVEL); + summaryMatrixHeight = heatMap.getNumRows(MatrixManager.SUMMARY_LEVEL); + + //If the matrix is skewed (height vs. width) by more than a 2:1 ratio, add padding to keep the summary from stretching too much. + if (summaryMatrixWidth > summaryMatrixHeight && summaryMatrixWidth/summaryMatrixHeight > 2) + rowEmptySpace = summaryMatrixWidth/2 - summaryMatrixHeight; + else if (summaryMatrixHeight > summaryMatrixWidth && summaryMatrixHeight/summaryMatrixWidth > 2) + colEmptySpace = summaryMatrixHeight/2 - summaryMatrixWidth; + + calcTotalSize(); + + canvas.width = summaryTotalWidth; + canvas.height = summaryTotalHeight; + setupGl(); + initGl(); + buildSummaryTexture(); + leftCanvasBoxVertThick = .002;//(1+Math.floor(summaryMatrixWidth/250))/1000; + leftCanvasBoxHorThick = .002;//(1+Math.floor(summaryMatrixHeight/250))/1000; +} + +//Set the variables for the total size of the summary heat map - used to set canvas, WebGL texture, and viewport size. +function calcTotalSize() { + summaryTotalHeight = summaryMatrixHeight + rowEmptySpace + summaryViewBorderWidth + colClassBarHeight + columnDendroHeight + dendroPaddingHeight; + summaryTotalWidth = summaryMatrixWidth + colEmptySpace + summaryViewBorderWidth + rowClassBarWidth + rowDendroHeight + dendroPaddingHeight; +} + +function buildSummaryTexture() { + eventTimer = 0; + + var colorMap = heatMap.getColorMapManager().getColorMap("dl1"); + var colors = colorMap.getColors(); + var missing = colorMap.getMissingColor(); + + var pos = 0; + //If the matrix is skewed, need to pad with space + if (rowEmptySpace > 0) + pos = rowEmptySpace * summaryTotalWidth * BYTE_PER_RGBA; + + //Setup texture to draw on canvas. + //Needs to go backward because WebGL draws bottom up. + pos += (rowDendroHeight+rowClassBarWidth+ dendroPaddingHeight)*BYTE_PER_RGBA; + for (var i = 0; i < heatMap.getNumColumns(MatrixManager.SUMMARY_LEVEL)+summaryViewBorderWidth; i++){ + TexPixels[pos] = 1; // bottom border + TexPixels[pos + 1] = 1; + TexPixels[pos + 2] = 1; + TexPixels[pos + 3] = 255; + pos+=BYTE_PER_RGBA; + } + pos+=(colEmptySpace*BYTE_PER_RGBA); + for (var i = heatMap.getNumRows(MatrixManager.SUMMARY_LEVEL); i > 0; i--) { + pos += (rowDendroHeight+rowClassBarWidth+ dendroPaddingHeight)*BYTE_PER_RGBA; // SKIP SPACE RESERVED FOR ROW CLASSBARS + ROW DENDRO + TexPixels[pos] = 1; // left border + TexPixels[pos + 1] = 1; + TexPixels[pos + 2] = 1; + TexPixels[pos + 3] = 255; + pos+=BYTE_PER_RGBA; + for (var j = 1; j <= heatMap.getNumColumns(MatrixManager.SUMMARY_LEVEL); j++) { // draw the heatmap + var val = heatMap.getValue(MatrixManager.SUMMARY_LEVEL, i, j); + var color = colorMap.getColor(val); + + TexPixels[pos] = color['r']; + TexPixels[pos + 1] = color['g']; + TexPixels[pos + 2] = color['b']; + TexPixels[pos + 3] = color['a']; + pos+=BYTE_PER_RGBA; + } + TexPixels[pos] = 1; // right border + TexPixels[pos + 1] = 1; + TexPixels[pos + 2] = 1; + TexPixels[pos + 3] = 255; + pos+=BYTE_PER_RGBA; + pos+=(colEmptySpace*BYTE_PER_RGBA); + } + pos += (rowDendroHeight+rowClassBarWidth + dendroPaddingHeight)*BYTE_PER_RGBA; + for (var i = 0; i < heatMap.getNumColumns(MatrixManager.SUMMARY_LEVEL)+summaryViewBorderWidth; i++){ + TexPixels[pos] = 1; // top border + TexPixels[pos + 1] = 1; + TexPixels[pos + 2] = 1; + TexPixels[pos + 3] = 255; + pos+=BYTE_PER_RGBA; + } + + // draw column classifications after the map + var colClassInfo = getClassBarsToDraw("column"); + var colClassToDraw = colClassInfo["bars"]; + var colClassColors = colClassInfo["colors"]; + drawColClassBars(colClassToDraw,colClassColors,TexPixels); + + // draw row classifications after that + var rowClassInfo = getClassBarsToDraw("row"); + var rowClassToDraw = rowClassInfo["bars"]; + var rowClassColors = rowClassInfo["colors"]; + drawRowClassBars(rowClassToDraw, rowClassColors, TexPixels); + + + // draw the dendrograms at the end of it all + if (heatMap.showRowDendrogram("SUMMARY")) { + drawRowDendrogram(TexPixels); + } + if (heatMap.showColDendrogram("SUMMARY")) { + drawColumnDendrogram(TexPixels); + } + drawSummaryHeatMap(); +} + +//WebGL code to draw the summary heat map. +function drawSummaryHeatMap() { + gl.activeTexture(gl.TEXTURE0); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + textureParams['width'], + textureParams['height'], + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + TexPixels); + gl.uniform2fv(uScale, leftCanvasScaleArray); + gl.uniform2fv(uTranslate, leftCanvasTranslateArray); + gl.uniform2fv(uBoxLeftTop, leftCanvasBoxLeftTopArray); + gl.uniform2fv(uBoxRightBottom, leftCanvasBoxRightBottomArray); + gl.uniform1f(uBoxHorThickness, leftCanvasBoxHorThick); + gl.uniform1f(uBoxVertThickness, leftCanvasBoxVertThick); + gl.uniform4fv(uBoxColor, [1.0, 1.0, 0.0, 1.0]); + + gl.uniform2fv(dendroBoxLeftTop, dendroBoxLeftTopArray); + gl.uniform2fv(dendroBoxRightBottom, dendroBoxRightBottomArray); + gl.uniform4fv(dendroBoxColor, [0.0, 1.0, 0.0, 1.0]); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, gl.buffer.numItems); +} + + +//Translate click into row column position and then draw select box. +function onClickLeftCanvas (evt) { + var clickSection = 'Matrix'; + var xPos = getCanvasX(evt.offsetX); + var yPos = getCanvasY(evt.offsetY); + var summaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + var sumDataPerRow = Math.floor(dataPerRow/summaryRatio); + var sumDataPerCol = Math.floor(dataPerCol/summaryRatio); + var sumRow = canvasToMatrixRow(yPos) - Math.floor(sumDataPerCol/2); + var sumCol = canvasToMatrixCol(xPos) - Math.floor(sumDataPerRow/2); + setCurrentRowFromSum(sumRow); + setCurrentColFromSum(sumCol); + var col = xPos + var colDendroAndClassBars = columnDendroHeight + colClassBarHeight; + var row = yPos + var rowDendroAndClassBars = rowDendroHeight + rowClassBarWidth; + if ((yPos > rowDendroAndClassBars && xPos < columnDendroHeight) && (heatMap.showRowDendrogram("SUMMARY"))) { // row dendro selection + clickSection = 'RowDendro'; + yPos -= colDendroAndClassBars; // yPos = clicked row on canvas + + var matrixX = (yPos)*pointsPerLeaf*summaryRatio; // matrixX = clicked col of dendro matrix + var matrixY = Math.round((rowDendroHeight-xPos)/rowDendroHeight * normDendroMatrixHeight); // matrixY = height of click posiiton on dendro matrix + + var clickedBar = getTopBar(matrixY,matrixX,'row'); + var sameBarClicked =true; + for (var key in clickedBar){ + if (clickedBar[key] != chosenBar[key]){ + sameBarClicked = false; + } + } + clearDendroSelection(); + if (!sameBarClicked){ + highlightRowDendrogramMatrix(matrixY,matrixX); + drawRowDendrogram(TexPixels); + chosenBar = clickedBar; + } + } else if ((xPos > rowDendroAndClassBars && yPos < columnDendroHeight) && (heatMap.showColDendrogram("SUMMARY"))) { // column dendro selection + clickSection = 'ColDendro'; + xPos-= rowDendroAndClassBars; + + var matrixX = (xPos)*pointsPerLeaf*summaryRatio; // matrixX = clicked col of dendro matrix + var matrixY = Math.round((columnDendroHeight-yPos)/columnDendroHeight * normDendroMatrixHeight) // matrixY = height of click posiiton on dendro matrix + + var clickedBar = getTopBar(matrixY,matrixX,'column'); + var sameBarClicked =true; + for (var key in clickedBar){ + if (clickedBar[key] != chosenBar[key]){ + sameBarClicked = false; + } + } + clearDendroSelection(); + if (!sameBarClicked){ + highlightColumnDendrogramMatrix(matrixY,matrixX); + drawColumnDendrogram(TexPixels); + chosenBar = clickedBar; + } + } + + //Make sure the selected row/column are within the bounds of the matrix. + checkRow(); + checkColumn(); + + if (clickSection=='RowDendro') + changeMode('RIBBONV'); + else if (clickSection == 'ColDendro') + changeMode('RIBBONH'); + else + updateSelection(); +} + +//Browsers resizes the canvas. This function translates from a click position +//back to the original (non-scaled) canvas position. +function getCanvasX(offsetX) { + return (Math.floor((offsetX/canvas.clientWidth) * canvas.width)); +} + +function getCanvasY(offsetY) { + return (Math.floor((offsetY/canvas.clientHeight) * canvas.height)); +} + +//Return the summary row given an y position on the canvas +function canvasToMatrixRow(y) { + return (y - colClassBarHeight - columnDendroHeight - summaryViewBorderWidth/2); +} + +function canvasToMatrixCol(x) { + return (x - rowClassBarWidth - rowDendroHeight - summaryViewBorderWidth/2); +} + + +//Given a matrix row, return the canvas position +function getCanvasYFromRow(row){ + return (row + colClassBarHeight + columnDendroHeight); +} + +function getCanvasXFromCol(col){ + return (col + rowClassBarWidth + rowDendroHeight); +} + +/********************************************************************************** + * FUNCTION - drawLeftCanvasBox: This function draws the yellow box on the summary + * pane whenever the position in the detail pane has changed. (e.g. on load, on click, + * on drag, etc...). A conversion is done from detail to summary coordinates, the + * new box position is calculated, and the summary pane is re-drawn. + **********************************************************************************/ +function drawLeftCanvasBox() { + var sumRow = getCurrentSumRow(); + var sumCol = getCurrentSumCol(); + var sumDataPerRow = getCurrentSumDataPerRow(); + var sumDataPerCol = getCurrentSumDataPerCol(); + var textureX = getCanvasXFromCol(sumCol) / canvas.width; + var textureY = 1.0 - (getCanvasYFromRow(sumRow) / canvas.height); + var boxWidth = sumDataPerRow / canvas.width; + var boxHeight = sumDataPerCol / canvas.height; + leftCanvasBoxLeftTopArray = new Float32Array([textureX, textureY-boxHeight]); + leftCanvasBoxRightBottomArray = new Float32Array([textureX + boxWidth, textureY]); + + drawSummaryHeatMap(); + + //Add selection marks + clearSelectionMarks(); + drawRowSelectionMarks(); + drawColSelectionMarks(); +} + +//WebGL stuff + +function setupGl() { + gl = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true}); + // If standard webgl context cannot be found use experimental-webgl + if (!gl) { + gl = canvas.getContext('experimental-webgl'); + } + + gl.viewportWidth = summaryTotalWidth; + gl.viewportHeight = summaryTotalHeight; + gl.clearColor(1, 1, 1, 1); + + var program = gl.createProgram(); + var vertexShader = getVertexShader(gl); + var fragmentShader = getFragmentShader(gl); + gl.program = program; + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + gl.useProgram(program); +} + + +function getVertexShader(gl) { + var source = 'attribute vec2 position; ' + + 'varying vec2 v_texPosition; ' + + 'uniform vec2 u_translate; ' + + 'uniform vec2 u_scale; ' + + 'void main () { ' + + ' vec2 scaledPosition = position * u_scale; ' + + ' vec2 translatedPosition = scaledPosition + u_translate; ' + + ' gl_Position = vec4(translatedPosition, 0, 1); ' + + ' v_texPosition = position * 0.5 + 0.5; ' + + '}'; + + + var shader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(shader, source); + gl.compileShader(shader); + return shader; +} + + +function getFragmentShader(gl) { + var source = 'precision mediump float; ' + + 'varying vec2 v_texPosition; ' + + 'varying float v_boxFlag; ' + + 'uniform sampler2D u_texture; ' + + 'uniform vec2 u_box_left_top; ' + + 'uniform vec2 u_box_right_bottom;' + + 'uniform float u_box_hor_thickness; ' + + 'uniform float u_box_vert_thickness; ' + + 'uniform vec4 u_box_color; ' + + 'uniform vec2 dendro_box_left_top; ' + + 'uniform vec2 dendro_box_right_bottom;' + + 'uniform vec4 dendro_box_color; ' + + 'void main () { ' + + ' vec2 difLeftTop = v_texPosition - u_box_left_top; ' + + ' vec2 difRightBottom = v_texPosition - u_box_right_bottom; ' + + ' vec2 difDendroLeftTop = v_texPosition - dendro_box_left_top; ' + + ' vec2 difDendroRightBottom = v_texPosition - dendro_box_right_bottom; ' + + ' if (((v_texPosition.y >= (u_box_left_top.y - u_box_hor_thickness) && v_texPosition.y <= (u_box_right_bottom.y + u_box_hor_thickness)) && ' + + ' ((difLeftTop.x <= u_box_vert_thickness && difLeftTop.x >= -u_box_vert_thickness) || ' + + ' (difRightBottom.x <= u_box_vert_thickness && difRightBottom.x >= -u_box_vert_thickness))) || ' + + ' ((v_texPosition.x >= u_box_left_top.x && v_texPosition.x <= u_box_right_bottom.x) && ' + + ' ((difLeftTop.y <= u_box_hor_thickness && difLeftTop.y >= -u_box_hor_thickness) || ' + + ' (difRightBottom.y <= u_box_hor_thickness && difRightBottom.y >= -u_box_hor_thickness)))) { ' + + ' gl_FragColor = u_box_color; ' + + ' } else if (((v_texPosition.y >= (dendro_box_left_top.y - u_box_hor_thickness) && v_texPosition.y <= (dendro_box_right_bottom.y + u_box_hor_thickness)) && ' + + ' ((difDendroLeftTop.x <= u_box_vert_thickness && difDendroLeftTop.x >= -u_box_vert_thickness) || ' + + ' (difDendroRightBottom.x <= u_box_vert_thickness && difDendroRightBottom.x >= -u_box_vert_thickness))) || ' + + ' ((v_texPosition.x >= dendro_box_left_top.x && v_texPosition.x <= dendro_box_right_bottom.x) && ' + + ' ((difDendroLeftTop.y <= u_box_hor_thickness && difDendroLeftTop.y >= -u_box_hor_thickness) || ' + + ' (difDendroRightBottom.y <= u_box_hor_thickness && difDendroRightBottom.y >= -u_box_hor_thickness)))) { ' + + ' gl_FragColor = dendro_box_color; ' + + ' } else { ' + + ' gl_FragColor = texture2D(u_texture, v_texPosition); ' + + ' } ' + + '}'; + + + var shader = gl.createShader(gl.FRAGMENT_SHADER);; + gl.shaderSource(shader, source); + gl.compileShader(shader); + return shader; +} + + + +function initGl () { + gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); + gl.clear(gl.COLOR_BUFFER_BIT); + + // Vertices + var buffer = gl.createBuffer(); + gl.buffer = buffer; + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + var vertices = [ -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1 ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + var byte_per_vertex = Float32Array.BYTES_PER_ELEMENT; + var component_per_vertex = 2; + buffer.numItems = vertices.length / component_per_vertex; + var stride = component_per_vertex * byte_per_vertex; + var program = gl.program; + var position = gl.getAttribLocation(program, 'position'); + uScale = gl.getUniformLocation(program, 'u_scale'); + uTranslate = gl.getUniformLocation(program, 'u_translate'); + uBoxLeftTop = gl.getUniformLocation(program, 'u_box_left_top'); + uBoxRightBottom = gl.getUniformLocation(program, 'u_box_right_bottom'); + uBoxHorThickness = gl.getUniformLocation(program, 'u_box_hor_thickness'); + uBoxVertThickness = gl.getUniformLocation(program, 'u_box_vert_thickness'); + uBoxColor = gl.getUniformLocation(program, 'u_box_color'); + + dendroBoxLeftTop = gl.getUniformLocation(program, 'dendro_box_left_top'); + dendroBoxRightBottom = gl.getUniformLocation(program, 'dendro_box_right_bottom'); + dendroBoxColor = gl.getUniformLocation(program, 'dendro_box_color'); + + gl.enableVertexAttribArray(position); + gl.vertexAttribPointer(position, 2, gl.FLOAT, false, stride, 0); + + // Texture + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_WRAP_S, + gl.CLAMP_TO_EDGE); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_WRAP_T, + gl.CLAMP_TO_EDGE); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + gl.NEAREST); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MAG_FILTER, + gl.NEAREST); + + textureParams = {}; + var texWidth = null, texHeight = null, texData; + texWidth = summaryTotalWidth; + texHeight = summaryTotalHeight; + texData = new ArrayBuffer(texWidth * texHeight * BYTE_PER_RGBA); + TexPixels = new Uint8Array(texData); + textureParams['width'] = texWidth; + textureParams['height'] = texHeight; +} + +//=====================// +// CLASSBAR FUNCTIONS // +//=====================// + +// returns all the classifications bars for a given axis and their corresponding color schemes in an array. +function getClassBarsToDraw(axis){ + var classBars = heatMap.getClassifications(); + var barsAndColors = {"bars":[], "colors":[]}; + for (var key in classBars){ + if (classBars[key].position == axis){ + barsAndColors["bars"].push(key); + barsAndColors["colors"].push(classBars[key].colorScheme); + } + } + return barsAndColors; +} + +// draws row classification bars into the texture array ("dataBuffer"). "names"/"colorSchemes" should be array of strings. +function drawColClassBars(names,colorSchemes,dataBuffer){ + var classBars = heatMap.getClassifications(); + var colorMapMgr = heatMap.getColorMapManager(); + var pos = (summaryTotalWidth)*(rowEmptySpace+summaryMatrixHeight+summaryViewBorderWidth)*BYTE_PER_RGBA; + for (var i = 0; i < names.length; i++){ //for each column class bar we draw... + var currentClassBar = classBars[names[i]]; + if (currentClassBar.show === 'Y') { + var colorMap = colorMapMgr.getColorMap(colorSchemes[i]); // assign the proper color scheme... + var classBarValues = currentClassBar.values; + var classBarLength = currentClassBar.values.length; + if (typeof currentClassBar.svalues != 'undefined') { + classBarValues = currentClassBar.svalues; + classBarLength = currentClassBar.svalues.length; + } + pos += (summaryTotalWidth)*paddingHeight*BYTE_PER_RGBA; // draw padding between class bars + var line = new Uint8Array(new ArrayBuffer(classBarLength * BYTE_PER_RGBA)); // save a copy of the class bar + var loc = 0; + for (var k = 0; k < classBarLength; k++) { + var val = classBarValues[k]; + var color = colorMap.getClassificationColor(val); + if (val == "null") { + color = colorMap.getHexToRgba(colorMap.getMissingColor()); + } + line[loc] = color['r']; + line[loc + 1] = color['g']; + line[loc + 2] = color['b']; + line[loc + 3] = color['a']; + loc += BYTE_PER_RGBA; + } + loc = 0; + for (var j = 0; j < currentClassBar.height-paddingHeight; j++){ // draw the class bar into the dataBuffer + pos += (rowDendroHeight+dendroPaddingHeight+rowClassBarWidth+summaryViewBorderWidth/2)*BYTE_PER_RGBA; + for (var k = 0; k < line.length; k++) { + dataBuffer[pos] = line[k]; + pos++; + } + pos += (summaryViewBorderWidth/2)*BYTE_PER_RGBA; + pos += (colEmptySpace*BYTE_PER_RGBA); + } + } + } +} + +// draws row classification bars into the texture array ("dataBuffer"). "names"/"colorSchemes" should be array of strings. +function drawRowClassBars(names,colorSchemes,dataBuffer){ + var offset = ((rowEmptySpace*summaryTotalWidth)+(summaryTotalWidth+rowDendroHeight))*BYTE_PER_RGBA; + var colorMapMgr = heatMap.getColorMapManager(); + var classBars = heatMap.getClassifications(); + for (var i = 0; i < names.length; i++){ + var pos = 0 + offset; + var colorMap = colorMapMgr.getColorMap(colorSchemes[i]); + var currentClassBar = classBars[names[i]]; + if (currentClassBar.show === 'Y') { + var classBarValues = currentClassBar.values; + var classBarLength = currentClassBar.values.length; + if (typeof currentClassBar.svalues != 'undefined') { + classBarValues = currentClassBar.svalues; + classBarLength = currentClassBar.svalues.length; + } + for (var j = classBarLength; j > 0; j--){ + var val = classBarValues[j-1]; + var color = colorMap.getClassificationColor(val); + if (val == "null") { + color = colorMap.getHexToRgba(colorMap.getMissingColor()); + } + for (var k = 0; k < currentClassBar.height-paddingHeight; k++){ + dataBuffer[pos] = color['r']; + dataBuffer[pos + 1] = color['g']; + dataBuffer[pos + 2] = color['b']; + dataBuffer[pos + 3] = color['a']; + pos+=BYTE_PER_RGBA; // 4 bytes per color + } + // padding between class bars + pos+=paddingHeight*BYTE_PER_RGBA; + pos+=(summaryTotalWidth - rowClassBarWidth)*BYTE_PER_RGBA; + } + offset+= currentClassBar.height; + } + } +} + + +// increase the height/width of a classbar and resize the map texture as well. redraws when done. +function increaseClassBarHeight(name){ + var classBars = heatMap.getClassifications(); + if (classBars[name].height < paddingHeight){ + classBars[name].height = paddingHeight +1; // if class bar isn't visible, then make it 1 px taller than the padding height + } else { + classBars[name].height += 2; + } + classBarHeight = calculateTotalClassBarHeight("column"); + classBarWidth = calculateTotalClassBarHeight("row"); + calcTotalSize(); + var texWidth = null, texHeight = null, texData; + texWidth = summaryTotalWidth; + texHeight = summaryTotalHeight; + texData = new ArrayBuffer(texWidth * texHeight * BYTE_PER_RGBA); + TexPixels = new Uint8Array(texData); + textureParams['width'] = texWidth; + textureParams['height'] = texHeight; + drawSummaryHeatMap(); +} + +// decrease the height/width of a classbar and resize the map texture as well. redraws when done. +function decreaseClassBarHeight(name){ + var classBars = heatMap.getClassifications(); + classBars[name].height -= 2; + if (classBars[name].height < paddingHeight){ + classBars[name].height = 0; // if the class bar is going to be shorter than the padding height, make it invisible + } + classBarHeight = calculateTotalClassBarHeight("column"); + classBarWidth = calculateTotalClassBarHeight("row"); + calcTotalSize(); + var texWidth = null, texHeight = null, texData; + texWidth = summaryTotalWidth; + texHeight = summaryTotalHeight; + texData = new ArrayBuffer(texWidth * texHeight * BYTE_PER_RGBA); + TexPixels = new Uint8Array(texData); + textureParams['width'] = texWidth; + textureParams['height'] = texHeight; + drawSummaryHeatMap(); +} + + +function calculateTotalClassBarHeight(axis){ + var totalHeight = 0; + var classBars = heatMap.getClassifications(); + for (var key in classBars){ + if (classBars[key].position == axis){ + if (classBars[key].show === 'Y') { + totalHeight += classBars[key].height; + } + } + } + return totalHeight; +} + + +//=======================// +// DENDROGRAM FUNCTIONS // +//=======================// + +// +var pointsPerLeaf = 3; // each leaf will get 3 points in the dendrogram array. This is to avoid lines being right next to each other + +function colDendroMatrixCoordToTexturePos(matrixRow,matrixCol){ // convert the matrix coord to the data buffer position (start of the RGBA block) + var summaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + var mapx = Math.round(matrixCol/pointsPerLeaf/summaryRatio); + var mapy = Math.round(matrixRow/normDendroMatrixHeight * columnDendroHeight); + var pos = (summaryTotalWidth) *(mapy+rowEmptySpace+summaryViewBorderWidth+summaryMatrixHeight+colClassBarHeight)*BYTE_PER_RGBA; // go to proper height + pos += (rowDendroHeight + dendroPaddingHeight+ rowClassBarWidth + summaryViewBorderWidth/2 + mapx)*BYTE_PER_RGBA; + return pos; +} + +function rowDendroMatrixCoordToTexturePos(matrixRow,matrixCol){ // convert matrix coord to data buffer position (leftmost column of matrix corresponds to the top row of the map) + var summaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + var mapx = rowDendroHeight - Math.round(matrixRow/normDendroMatrixHeight * rowDendroHeight); // bottom most row of matrix is at the far-right of the map dendrogram + var mapy = summaryMatrixHeight - Math.round(matrixCol/pointsPerLeaf/summaryRatio); // matrix column 1 is the top row of the map + var pos = (summaryTotalWidth)*(mapy+rowEmptySpace)*BYTE_PER_RGBA; // pass the empty space (if any) and the border width, to get to the height on the map + pos += mapx*BYTE_PER_RGBA; + return pos; +} + + +function drawColumnDendrogram(dataBuffer){ + var mod,firstSkip,skipInterval; + if (columnDendroHeight > normDendroMatrixHeight){ + mod = rowDendroHeight % normDendroMatrixHeight; // a row may have to be drawn twice in case there is a rounding error from matrix to texture + firstSkip = Math.round(normDendroMatrixHeight/mod/2); + skipInterval = Math.round(normDendroMatrixHeight/mod); + } + + for (var i = 0; i <= colDendroMatrix.length+1; i++){ + var line = colDendroMatrix[i]; // line = each row of the col dendro matrix + for (var j in line){ + var pos = colDendroMatrixCoordToTexturePos(i,j); + if (colDendroMatrix[i][j] == 1){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 3,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } else if (colDendroMatrix[i][j] == 2){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 255,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } + } + if (i !=0 && (i % skipInterval == 0 || i % skipInterval == skipInterval/2)){ // if there was a rounding error made, redraw the dendro line on previous line + for (var j in line){ + var pos = colDendroMatrixCoordToTexturePos(i,j) - summaryTotalWidth*BYTE_PER_RGBA; + if (colDendroMatrix[i][j] == 1){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 3,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } else if (colDendroMatrix[i][j] == 2){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 255,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } + } + } + } +} + + +function drawRowDendrogram(dataBuffer){ + var mod,firstSkip,skipInterval; + if (rowDendroHeight > normDendroMatrixHeight){ + mod = rowDendroHeight % normDendroMatrixHeight; // a row may have to be drawn twice in case there is a rounding error from matrix to texture + firstSkip = Math.round(normDendroMatrixHeight/mod/2); + skipInterval = Math.round(normDendroMatrixHeight/mod); + } + + for (var i = 0; i <= rowDendroMatrix.length+1; i++){ + var line = rowDendroMatrix[i]; // line = each row of the col dendro matrix + for (var j in line){ + var pos = rowDendroMatrixCoordToTexturePos(i,j); + if (rowDendroMatrix[i][j] == 1){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 3,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } else if (rowDendroMatrix[i][j] == 2){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 255,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } + } + if (i !=0 && (i % skipInterval == 0 || i % skipInterval == skipInterval/2)){ // if there was a rounding error made, redraw the dendro line on previous line + for (var j in line){ + var pos = rowDendroMatrixCoordToTexturePos(i,j) + BYTE_PER_RGBA; + if (rowDendroMatrix[i][j] == 1){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 3,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } else if (rowDendroMatrix[i][j] == 2){ + dataBuffer[pos] = 3,dataBuffer[pos+1] = 255,dataBuffer[pos+2] = 3,dataBuffer[pos+3] = 255; + } + } + } + } +} + +function getTranslatedLocation(location){ + var summaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + return Math.round((location/summaryRatio)/pointsPerLeaf); +} + +//creates an array of bar objects from the dendrogram info +function buildDendroMatrix(dendroData,axis){ + var numNodes = dendroData[axis].length; + var bars = []; + var lastRow = dendroData[axis][numNodes-1]; + var maxHeight = Number(lastRow.split(",")[2]); // this assumes the heightData is ordered from lowest height to highest + var matrix = new Array(normDendroMatrixHeight+1); + for (var i = 0; i < normDendroMatrixHeight+1; i++){ // 500rows * (3xWidth)cols matrix + matrix[i] = new Array(pointsPerLeaf*heatMap.getNumColumns('d')); + } + for (var i = 0; i < numNodes; i++){ + var tokes = dendroData[axis][i].split(","); + var leftIndex = Number(tokes[0]); // index is the location of the bar in the clustered data + var rightIndex = Number(tokes[1]); + var height = Number(tokes[2]); + var leftLoc = findLocationFromIndex(leftIndex); // this is the position it occupies in the dendroMatrix space + var rightLoc = findLocationFromIndex(rightIndex); + var normHeight = Math.round(normDendroMatrixHeight*height/maxHeight); + bars.push({"left":leftLoc, "right":rightLoc, "height":normHeight}); + for (var j = leftLoc; j < rightLoc; j++){ + matrix[normHeight][j] = 1; + } + var drawHeight = normHeight-1; + while (drawHeight > 0 && matrix[drawHeight][leftLoc] != 1){ + matrix[drawHeight][leftLoc] = 1; + drawHeight--; + } + drawHeight = normHeight; + while (matrix[drawHeight][rightLoc] != 1 && drawHeight > 0){ + matrix[drawHeight][rightLoc] = 1; + drawHeight--; + } + } + + if (axis == 'Column'){ + colDendroBars = bars; + } else { + rowDendroBars = bars; + } + return matrix; + + // returns the position in terms of the 3N space + function findLocationFromIndex(index){ + if (index < 0){ + index = 0-index; // make index a positive number to find the leaf + return pointsPerLeaf*index-2; // all leafs should occupy the middle space of the 3 points available + } else { + index--; + return Math.round((bars[index].left + bars[index].right)/2); // gets the middle point of the bar + } + } +} + +function getTopBar(i,j,axis){ + var dendroMatrix; + if (axis == "row")dendroMatrix = rowDendroMatrix; + else dendroMatrix = colDendroMatrix; + while (dendroMatrix[i][j]==undefined){ i--;}// find the first line that is below the clicked coord + var leftAndRightExtremes = exploreToEndOfBar(i,j,dendroMatrix); // find the endpoints of the highest level node + return {axis: axis, leftEnd: leftAndRightExtremes[0], rightEnd: leftAndRightExtremes[1], height: i} +} + + + +function highlightRowDendrogramMatrix(i, j){ // i-th row, j-th column of dendro matrix + var leftExtreme, rightExtreme; + while (rowDendroMatrix[i][j]==undefined){i--;} // find the first line that is below the clicked coord + var leftAndRightExtremes = exploreToEndOfBar(i,j,rowDendroMatrix); // find the endpoints of the highest level node + leftExtreme = leftAndRightExtremes[0], rightExtreme = leftAndRightExtremes[1]; + leftExtreme = findLeftEnd(i,leftExtreme,rowDendroMatrix); + rightExtreme = findRightEnd(i,rightExtreme,rowDendroMatrix); // L and R extreme values are in dendro matrix coords right now + highlightAllBranchesInRange(i,leftExtreme,rightExtreme,rowDendroMatrix); + + leftExtreme = getTranslatedLocation(leftExtreme); // L and R extreme values gets converted to heatmap locations + rightExtreme = getTranslatedLocation(rightExtreme); + + // Draw green dendrogram box over summary heatmap + var matrixBottom = rowEmptySpace / canvas.height + leftCanvasBoxHorThick; + var matrixRight = colEmptySpace / canvas.width + leftCanvasBoxVertThick; + var leftMin = leftCanvasBoxVertThick + ((rowClassBarWidth+rowDendroHeight)/canvas.width); + var topMin = leftCanvasBoxHorThick + ((colClassBarHeight+columnDendroHeight)/canvas.height); + dendroBoxLeftTopArray = new Float32Array([leftMin, 1-rightExtreme/canvas.height-topMin]); + dendroBoxRightBottomArray = new Float32Array([1-matrixRight, 1-leftExtreme/canvas.height-topMin]); + // Set start and stop coordinates + var rvRatio = heatMap.getRowSummaryRatio(MatrixManager.RIBBON_VERT_LEVEL); + var summaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + selectedStart = Math.round(leftExtreme*summaryRatio) +1; + selectedStop = Math.round(rightExtreme*summaryRatio) +1; +} + +function highlightColumnDendrogramMatrix(i,j){ + var leftExtreme, rightExtreme; + while (colDendroMatrix[i][j]==undefined){ i--;} + var leftAndRightExtremes = exploreToEndOfBar(i,j,colDendroMatrix); // find the endpoints of the highest level node + leftExtreme = leftAndRightExtremes[0], rightExtreme = leftAndRightExtremes[1]; + + leftExtreme = findLeftEnd(i,leftExtreme,colDendroMatrix); + rightExtreme = findRightEnd(i,rightExtreme,colDendroMatrix); // L and R extreme values are in dendro matrix coords right now + highlightAllBranchesInRange(i,leftExtreme,rightExtreme,colDendroMatrix); + + leftExtreme = getTranslatedLocation(leftExtreme); // L and R extreme values gets converted to heatmap locations + rightExtreme = getTranslatedLocation(rightExtreme); + + // Draw green dendrogram box over summary heatmap + var matrixBottom = rowEmptySpace / canvas.height + leftCanvasBoxHorThick; + var matrixRight = colEmptySpace / canvas.width + leftCanvasBoxVertThick; + var leftMin = leftCanvasBoxVertThick + ((rowClassBarWidth+rowDendroHeight)/canvas.width); + var topMin = leftCanvasBoxHorThick + ((colClassBarHeight+columnDendroHeight)/canvas.height); + dendroBoxLeftTopArray = new Float32Array([leftExtreme/canvas.width+leftMin, matrixBottom]); + dendroBoxRightBottomArray = new Float32Array([rightExtreme/canvas.width+leftMin, 1-topMin]); + // Set start and stop coordinates + var rhRatio = heatMap.getColSummaryRatio(MatrixManager.RIBBON_HOR_LEVEL); + var summaryRatio = heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL); + selectedStart = Math.round(leftExtreme*summaryRatio) +1; + selectedStop = Math.round(rightExtreme*summaryRatio) +1; +} + +function exploreToEndOfBar(i,j, dendroMatrix){ + var leftExtreme = j, rightExtreme = j; + dendroMatrix[i][j] = 2; + while (dendroMatrix[i][rightExtreme+1]==1 || dendroMatrix[i][rightExtreme+1]==2){ // now find the right and left end points of the line in the matrix and highlight as we go + rightExtreme++; + dendroMatrix[i][rightExtreme] = 2; + } + while (dendroMatrix[i][leftExtreme-1]==1 || dendroMatrix[i][leftExtreme-1]==2){ + leftExtreme--; + dendroMatrix[i][leftExtreme] = 2; + } + return [leftExtreme,rightExtreme]; +} + + +function findLeftEnd(i,j,dendroMatrix){ + dendroMatrix[i][j] = 2; + while (i != 0 && j != 0){ // as long as we aren't at the far left or the very bottom, keep moving + if (dendroMatrix[i][j-1] == 1 ||dendroMatrix[i][j-1] == 2){ // can we keep moving left? + j--; + dendroMatrix[i][j] = 2; + } else {//if (dendroMatrix[i-1][j] == 1 ||dendroMatrix[i-1][j] == 2){ // can we move towards the bottom? + i--; + dendroMatrix[i][j] = 2; + } + } + return j; +} + +function findRightEnd(i,j,dendroMatrix){ + dendroMatrix[i][j] = 2; + while (i != 0 && j <= dendroMatrix[1].length){ + if (dendroMatrix[i][j+1] == 1 ||dendroMatrix[i][j+1] == 2){ + j++; + dendroMatrix[i][j] = 2; + } else {//if (dendroMatrix[i-1][j] == 1 ||dendroMatrix[i-1][j] == 2){ + i--; + dendroMatrix[i][j] = 2; + } + } + return j; +} + +function highlightAllBranchesInRange(height,leftExtreme,rightExtreme,dendroMatrix){ + for (var i = height; i >= 0; i--){ + for (var loc in dendroMatrix[i]){ + if (leftExtreme < loc && loc < rightExtreme){ + dendroMatrix[i][loc] = 2; + } + } + } +} + +function clearDendroSelection(){ + chosenBar = {axis: null, index: null}; + selectedStart = 0; + selectedStop = 0; + if (!isSub) { + dendroBoxLeftTopArray = new Float32Array([0, 0]); + dendroBoxRightBottomArray = new Float32Array([0, 0]); + if (heatMap.showColDendrogram("summary")) { + colDendroMatrix = buildDendroMatrix(heatMap.getDendrogram(),'Column'); + drawColumnDendrogram(TexPixels); + } + if (heatMap.showRowDendrogram("summary")) { + rowDendroMatrix = buildDendroMatrix(heatMap.getDendrogram(),"Row"); + drawRowDendrogram(TexPixels); + } + drawSummaryHeatMap(); + } +} + + +//***************************// +//Selection Label Functions *// +//***************************// +function summaryResize() { + clearSelectionMarks(); + drawRowSelectionMarks(); + drawColSelectionMarks(); +} + + +function drawRowSelectionMarks() { + var markElement = document.getElementById('sumlabelDiv'); + var headerSize = summaryTotalHeight - summaryMatrixHeight; + + var fontSize = 10; + var selectedRows = getSearchRows(); + + + for (var i = 0; i < selectedRows.length; i++) { + var xPos = (1-colEmptySpace/canvas.width)*canvas.clientWidth + 3; + var position = headerSize + (selectedRows[i]/heatMap.getRowSummaryRatio(MatrixManager.SUMMARY_LEVEL)); + var yPos = ((position /summaryTotalHeight-(rowEmptySpace/canvas.height))* canvas.clientHeight) - fontSize; + addLabelDiv(markElement, 'sum_row' + i, 'MarkLabel', '<', xPos, yPos, fontSize, 'F'); + } +} + +function drawColSelectionMarks() { + var markElement = document.getElementById('sumlabelDiv'); + var headerSize = summaryTotalWidth - summaryMatrixWidth; + + var fontSize = 10; + var selectedCols = getSearchCols(); + + + for (var i = 0; i < selectedCols.length; i++) { + var position = headerSize + (selectedCols[i]/heatMap.getColSummaryRatio(MatrixManager.SUMMARY_LEVEL)); + var xPos = ((position / summaryTotalWidth-(colEmptySpace/canvas.width))*(canvas.clientWidth)) + fontSize/2; + var yPos = (1-rowEmptySpace/canvas.height)*canvas.clientHeight + 4; + addLabelDiv(markElement, 'sum_row' + i, 'MarkLabel', '<', xPos, yPos, fontSize, 'T'); + } +} + +function clearSelectionMarks() { + var markElement = document.getElementById('sumlabelDiv'); + var oldMarks = document.getElementsByClassName("MarkLabel"); + while (oldMarks.length > 0) { + markElement.removeChild(oldMarks[0]); + } + +} + + +function dividerStart(){ + userHelpClose(); + document.addEventListener('mousemove', dividerMove); + document.addEventListener('touchmove', dividerMove); + document.addEventListener('mouseup', dividerEnd); + document.addEventListener('touchend',dividerEnd); +} +function dividerMove(e){ + var divider = document.getElementById('divider'); + if (e.touches){ + if (e.touches.length > 1){ + return false; + } + } + var Xmove = e.touches ? divider.offsetLeft - e.touches[0].pageX : divider.offsetLeft - e.pageX; + var summary = document.getElementById('summary_chm'); + var summaryX = summary.offsetWidth - Xmove; + summary.setAttribute("style","position: relative; width:" + summaryX + "px"); + var detail = document.getElementById('detail_chm'); + var detailX = detail.offsetWidth + Xmove; + detail.setAttribute("style","position: relative; width:" + detailX + "px"); + clearLabels(); + clearSelectionMarks(); +} +function dividerEnd(){ + document.removeEventListener('mousemove', dividerMove); + document.removeEventListener('mouseup', dividerEnd); + document.removeEventListener('touchmove',dividerMove); + document.removeEventListener('touchend',dividerEnd); + detailResize(); + summaryResize(); +} diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/UserHelpManager.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/UserHelpManager.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,527 @@ +/********************************************************************************** + * USER HELP FUNCTIONS: The following functions handle the processing + * for user help popup windows for the detail canvas and the detail canvas buttons. + **********************************************************************************/ + +/********************************************************************************** + * FUNCTION - userHelpOpen: This function handles all of the tasks necessary to + * generate help pop-up panels for the detail heat map and the detail heat map + * classification bars. + **********************************************************************************/ +function userHelpOpen(e){ + userHelpClose(); + clearTimeout(detailPoint); + var orgW = window.innerWidth+window.pageXOffset; + var orgH = window.innerHeight+window.pageYOffset; + var helptext = getDivElement("helptext"); + helptext.style.position = "absolute"; + document.getElementsByTagName('body')[0].appendChild(helptext); + var rowElementSize = dataBoxWidth * detCanvas.clientWidth/detCanvas.width; // px/Glpoint + var colElementSize = dataBoxHeight * detCanvas.clientHeight/detCanvas.height; + + // pixels + var rowClassWidthPx = getRowClassPixelWidth(); + var colClassHeightPx = getColClassPixelHeight(); + var rowDendroWidthPx = getRowDendroPixelWidth(); + var colDendroHeightPx = getColDendroPixelHeight(); + var mapLocY = e.layerY - colClassHeightPx - colDendroHeightPx; + var mapLocX = e.layerX - rowClassWidthPx - rowDendroWidthPx; + + if (isOnObject(e,"map")) { + var row = Math.floor(currentRow + (mapLocY/colElementSize)*getSamplingRatio('row')); + var col = Math.floor(currentCol + (mapLocX/rowElementSize)*getSamplingRatio('col')); + var rowLabels = heatMap.getRowLabels().labels; + var colLabels = heatMap.getColLabels().labels; + var classBars = heatMap.getClassifications(); + var helpContents = document.createElement("TABLE"); + setTableRow(helpContents, [""+"Data Details"+"", " "], 2); + setTableRow(helpContents,[" Value:", heatMap.getValue(MatrixManager.DETAIL_LEVEL,row,col).toFixed(5)]); + setTableRow(helpContents,[ " Row:", rowLabels[row-1]]); + setTableRow(helpContents,[" Column:", colLabels[col-1]]); + helpContents.insertRow().innerHTML = formatBlankRow(); + var rowCtr = 8; + var writeFirstCol = true; + var pos = col; + var classLen = Object.keys(classBars).length; + if (classLen > 0) { + setTableRow(helpContents, [" "+"Row Classifications"+"", " "], 2); + for (var key in classBars){ + if (classBars[key].position == "column") { + if (writeFirstCol) { + helpContents.insertRow().innerHTML = formatBlankRow(); + setTableRow(helpContents, [" "+"Column Classifications"+"", " "], 2); + writeFirstCol = false; + } + pos = row; + rowCtr = rowCtr++; + } + setTableRow(helpContents,["   "+key+":"+"", classBars[key].values[pos-1]]); + rowCtr++; + } + } + helptext.style.display="inherit"; + helptext.appendChild(helpContents); + locateHelpBox(e, helptext); + } else if (isOnObject(e,"rowClass") || isOnObject(e,"colClass")) { + var pos, classInfo, names, colorSchemes, value; + var classBars = heatMap.getClassifications(); + var hoveredBar, hoveredBarColorScheme; //coveredWidth = 0, coveredHeight = 0; + if (isOnObject(e,"colClass")) { + var coveredHeight = detCanvas.clientHeight*detailDendroHeight/detCanvas.height + pos = Math.floor(currentCol + (mapLocX/rowElementSize)); + classInfo = getClassBarsToDraw("column"); + names = classInfo["bars"]; + colorSchemes = classInfo["colors"]; + for (var i = names.length-1; i >= 0; i--) { // find which class bar the mouse is over + var currentBar = names[i]; + var bar = classBars[currentBar]; + if ((bar.show === 'Y') && (bar.position === 'column')) { + coveredHeight += detCanvas.clientHeight*classBars[currentBar].height/detCanvas.height; + if (coveredHeight >= e.layerY) { + hoveredBar = currentBar; + hoveredBarColorScheme = colorSchemes[i]; + break; + } + } + } + } else { + var coveredWidth = detCanvas.clientHeight*detailDendroWidth/detCanvas.height + pos = Math.floor(currentRow + (mapLocY/colElementSize)); + classInfo = getClassBarsToDraw("row"); + names = classInfo["bars"]; + colorSchemes = classInfo["colors"]; + for (var i = names.length-1; i >= 0; i--){ // find which class bar the mouse is over + var currentBar = names[i]; + var bar = classBars[currentBar]; + if ((bar.show === 'Y') && (bar.position === 'row')) { + coveredWidth += detCanvas.clientWidth*classBars[currentBar].height/detCanvas.width; + if (coveredWidth >= e.layerX){ + hoveredBar = currentBar; + hoveredBarColorScheme = colorSchemes[i]; + break; + } + } + } + } + var colorScheme = heatMap.getColorMapManager().getColorMap(hoveredBarColorScheme); + var value = classBars[hoveredBar].values[pos-1]; + var colors = colorScheme.getColors(); + var classType = colorScheme.getType(); + if (value == 'null') { + value = "Missing Value"; + } + var thresholds = colorScheme.getThresholds(); + var thresholdSize = 0; + // For Continuous Classifications: + // 1. Retrieve continuous threshold array from colorMapManager + // 2. Retrieve threshold range size divided by 2 (1/2 range size) + // 3. If remainder of half range > .75 set threshold value up to next value, Else use floor value. + if (classType == 'continuous') { + thresholds = colorScheme.getContinuousThresholdKeys(); + var threshSize = colorScheme.getContinuousThresholdKeySize()/2; + if ((threshSize%1) > .5) { + // Used to calculate modified threshold size for all but first and last threshold + // This modified value will be used for color and display later. + thresholdSize = Math.floor(threshSize)+1; + } else { + thresholdSize = Math.floor(threshSize); + } + } + + // Build TABLE HTML for contents of help box + var helpContents = document.createElement("TABLE"); + setTableRow(helpContents, ["Class: ", " "+hoveredBar]); + setTableRow(helpContents, ["Value: ", " "+value]); + helpContents.insertRow().innerHTML = formatBlankRow(); + var rowCtr = 3 + thresholds.length; + var prevThresh = currThresh; + for (var i = 0; i < thresholds.length; i++){ // generate the color scheme diagram + var color = colors[i]; + var valSelected = 0; + var valTotal = classBars[hoveredBar].values.length; + var currThresh = thresholds[i]; + var modThresh = currThresh; + if (classType == 'continuous') { + // IF threshold not first or last, the modified threshold is set to the threshold value + // less 1/2 of the threshold range ELSE the modified threshold is set to the threshold value. + if ((i != 0) && (i != thresholds.length - 1)) { + modThresh = currThresh - thresholdSize; + } + color = colorScheme.getRgbToHex(colorScheme.getClassificationColor(modThresh)); + } + + //Count classification value occurrences within each breakpoint. + for (var j = 0; j < valTotal; j++) { + classBarVal = classBars[hoveredBar].values[j]; + if (classType == 'continuous') { + // Count based upon location in threshold array + // 1. For first threshhold, count those values <= threshold. + // 2. For second threshold, count those values >= threshold. + // 3. For penultimate threshhold, count those values > previous threshold AND values < final threshold. + // 3. For all others, count those values > previous threshold AND values <= final threshold. + if (i == 0) { + if (classBarVal <= currThresh) { + valSelected++; + } + } else if (i == thresholds.length - 1) { + if (classBarVal >= currThresh) { + valSelected++; + } + } else if (i == thresholds.length - 2) { + if ((classBarVal > prevThresh) && (classBarVal < currThresh)) { + valSelected++; + } + } else { + if ((classBarVal > prevThresh) && (classBarVal <= currThresh)) { + valSelected++; + } + } + } else { + var value = thresholds[i]; + if (classBarVal == value) { + valSelected++; + } + } + } + var selPct = Math.round(((valSelected / valTotal) * 100) * 100) / 100; //new line + setTableRow(helpContents, ["
", modThresh + " (n = " + valSelected + ", " + selPct+ "%)"]); + prevThresh = currThresh; + } + var valSelected = 0; + var valTotal = classBars[hoveredBar].values.length; + for (var j = 0; j < valTotal; j++) { + if (classBars[hoveredBar].values[j] == "null") { + valSelected++; + } + } + var selPct = Math.round(((valSelected / valTotal) * 100) * 100) / 100; //new line + setTableRow(helpContents, ["
", "Missing Color (n = " + valSelected + ", " + selPct+ "%)"]); + helptext.style.display="inherit"; + helptext.appendChild(helpContents); + locateHelpBox(e, helptext); + } else { // on the blank area in the top left corner + } + +} + +/********************************************************************************** + * FUNCTION - locateHelpBox: The purpose of this function is to set the location + * for the display of a pop-up help panel based upon the cursor location and the + * size of the panel. + **********************************************************************************/ +function locateHelpBox(e, helptext) { + var rowClassWidthPx = getRowClassPixelWidth(); + var colClassHeightPx = getColClassPixelHeight(); + var mapLocY = e.layerY - colClassHeightPx; + var mapLocX = e.layerX - rowClassWidthPx; + var mapH = e.target.clientHeight - colClassHeightPx; + var mapW = e.target.clientWidth - rowClassWidthPx; + var boxLeft = e.pageX; + if (mapLocX > (mapW / 2)) { + boxLeft = e.pageX - helptext.clientWidth - 10; + } + helptext.style.left = boxLeft; + var boxTop = e.pageY; + if ((boxTop+helptext.clientHeight) > e.target.clientHeight + 90) { + boxTop = e.pageY - helptext.clientHeight; + } + helptext.style.top = boxTop; +} + +/********************************************************************************** + * FUNCTION - detailDataToolHelp: The purpose of this function is to generate a + * pop-up help panel for the tool buttons at the top of the detail pane. It receives + * text from chm.html. If the screen has been split, it changes the test for the + * split screen button + **********************************************************************************/ +function detailDataToolHelp(e,text,width) { + userHelpClose(); + detailPoint = setTimeout(function(){ + if (typeof width === "undefined") { + width=50; + } + if ((isSub) && (text == "Split Into Two Windows")) { + text = "Join Screens"; + } + var helptext = getDivElement("helptext"); + helptext.style.position = "absolute"; + document.getElementsByTagName('body')[0].appendChild(helptext); + if (text === "Modify Map Preferences") { + helptext.style.left = e.offsetLeft - 125; + + } else { + helptext.style.left = e.offsetLeft + 15; + } + helptext.style.top = e.offsetTop + 15; + helptext.style.width = width; + var htmlclose = ""; + helptext.innerHTML = ""+text+""; + helptext.style.display="inherit"; + },1000); +} + +/********************************************************************************** + * FUNCTION - getDivElement: The purpose of this function is to create and + * return a DIV html element that is configured for a help pop-up panel. + **********************************************************************************/ +function getDivElement(elemName) { + var divElem = document.createElement('div'); + divElem.id = elemName; + divElem.style.backgroundColor = 'CBDBF6'; + divElem.style.display="none"; + return divElem; +} + +/********************************************************************************** + * FUNCTION - setTableRow: The purpose of this function is to set a row into a help + * or configuration html TABLE item for a given help pop-up panel. It receives text for + * the header column, detail column, and the number of columns to span as inputs. + **********************************************************************************/ +function setTableRow(tableObj, tdArray, colSpan, align) { + var tr = tableObj.insertRow(); + for (var i = 0; i < tdArray.length; i++) { + var td = tr.insertCell(i); + if (typeof colSpan != 'undefined') { + td.colSpan = colSpan; + } + if (i === 0) { + td.style.fontWeight="bold"; + } + td.innerHTML = tdArray[i]; + if (typeof align != 'undefined') { + td.align = align; + } + } +} + +/********************************************************************************** + * FUNCTION - setTableRow: The purpose of this function is to set a row into a help + * or configuration html TABLE item for a given help pop-up panel. It receives text for + * the header column, detail column, and the number of columns to span as inputs. + **********************************************************************************/ +function setErrorRow(tableObj, errorMsg) { + var tr = tableObj.insertRow(); + var td = tr.insertCell(0); + td.colSpan = 2; + td.align="center"; + td.style.fontWeight="bold"; + td.style.color="red"; + td.innerHTML = errorMsg; +} + + + +/********************************************************************************** + * FUNCTION - formatBlankRow: The purpose of this function is to return the html + * text for a blank row. + **********************************************************************************/ +function formatBlankRow() { + return " "; +} + +/********************************************************************************** + * FUNCTION - userHelpClose: The purpose of this function is to close any open + * user help pop-ups and any active timeouts associated with those pop-up panels. + **********************************************************************************/ +function userHelpClose(){ + clearTimeout(detailPoint); + var helptext = document.getElementById('helptext'); + if (helptext){ + helptext.remove(); + } +} + + +//============================ +// LABEL MENU FUNCTIONS START +//============================ + +var linkouts = {}; + +function createLabelMenus(){ + createLabelMenu('Column'); // create the menu divs + createLabelMenu('ColumnClass'); + createLabelMenu('Row'); + createLabelMenu('RowClass'); + getDefaultLinkouts(); + populateLabelMenus(); // fill the divs with the appropriate linkouts +} + +function labelHelpClose(axis){ + var labelMenu = document.getElementById(axis + 'LabelMenu'); + if (labelMenu){ + labelMenu.style.display = 'none'; + } +} + +function labelHelpOpen(axis, e){ + var labelMenu = document.getElementById(axis + 'LabelMenu'); + var labelMenuTable = document.getElementById(axis + 'LabelMenuTable'); + if (labelMenu){ + labelMenu.style.display = 'inherit'; + labelMenu.style.left = e.x + labelMenu.offsetWidth > window.innerWidth ? window.innerWidth-labelMenu.offsetWidth : e.x; + labelMenu.style.top = e.y + labelMenu.offsetHeight > window.innerHeight ? window.innerHeight-labelMenu.offsetHeight : e.y; + } + var axisLabelsLength = getSearchLabelsByAxis(axis).length; + var header = labelMenu.getElementsByClassName('labelMenuHeader')[0]; + var row = header.getElementsByTagName('TR')[0]; + if (axisLabelsLength > 0){ + row.innerHTML = "Selected labels : " + axisLabelsLength; + labelMenuTable.getElementsByTagName("TBODY")[0].style.display = 'inherit'; + } else { + row.innerHTML = "Please select a label"; + labelMenuTable.getElementsByTagName("TBODY")[0].style.display = 'none'; + } + +} + +function createLabelMenu(axis){ // creates the divs for the label menu + var labelMenu = getDivElement(axis + 'LabelMenu'); + document.body.appendChild(labelMenu); + labelMenu.style.position = 'absolute'; + labelMenu.classList.add('labelMenu'); + var topDiv = document.createElement("DIV"); + topDiv.classList.add("labelMenuCaption"); + topDiv.innerHTML = axis + ' Label Menu:'; + var closeMenu = document.createElement("IMG"); + closeMenu.src = staticPath + "images/closeButton.png"; + closeMenu.classList.add('labelMenuClose') + closeMenu.addEventListener('click', function(){labelHelpClose(axis)},false); + var table = document.createElement("TABLE"); + table.id = axis + 'LabelMenuTable'; + var tableHead = table.createTHead(); + tableHead.classList.add('labelMenuHeader'); + var row = tableHead.insertRow(); + labelMenu.appendChild(topDiv); + labelMenu.appendChild(table); + labelMenu.appendChild(closeMenu); + var tableBody = table.createTBody(); + tableBody.classList.add('labelMenuBody'); + var labelHelpCloseAxis = function(){ labelHelpClose(axis)}; + document.addEventListener('click', labelHelpCloseAxis); +} + + +function populateLabelMenus(){ // adds the row linkouts and the column linkouts to the menus + var table = document.getElementById('RowLabelMenuTable'); + var labelType = heatMap.getRowLabels()["label_type"]; + for (i = 0; i < linkouts[labelType].length; i++) + addMenuItemToTable("Row", table, linkouts[labelType][i]); + + table = document.getElementById('ColumnLabelMenuTable'); + labelType = heatMap.getColLabels()["label_type"]; + for (i = 0; i < linkouts[labelType].length; i++) + addMenuItemToTable("Column", table, linkouts[labelType][i]); + + table = document.getElementById('ColumnClassLabelMenuTable'); + labelType = 'ColumnClass'; + for (i = 0; i < linkouts[labelType].length; i++) + addMenuItemToTable("ColumnClass", table, linkouts[labelType][i]); + + table = document.getElementById('RowClassLabelMenuTable'); + labelType = 'RowClass'; + for (i = 0; i < linkouts[labelType].length; i++) + addMenuItemToTable("RowClass", table, linkouts[labelType][i]); +} + +function addMenuItemToTable(axis, table, linkout){ + var body = table.getElementsByClassName('labelMenuBody')[0]; + var row = body.insertRow(); + var cell = row.insertCell(); + cell.innerHTML = linkout.title; + + function functionWithParams(){ // this is the function that gets called when the linkout is clicked + var input; + switch (linkout.inputType){ // TO DO: make the function input types (ie: labels, index, etc) global constants. Possibly add more input types? + case "labels": input = getSearchLabelsByAxis(axis); break; +// case "index": input = axis.toUpperCase() == "ROW" ? getSearchRows() : getSearchCols();break; + default: input = axis.toUpperCase() == "ROW" ? getSearchRows() : getSearchCols();break; + } + linkout.callback(input,axis); // all linkout functions will have these inputs! + }; + cell.addEventListener('click', functionWithParams); +} + +function getDefaultLinkouts(){ + addLinkout("Copy " + heatMap.getColLabels()["label_type"] +" to Clipboard", heatMap.getColLabels()["label_type"], "labels", copyToClipBoard,0); + addLinkout("Copy " +heatMap.getRowLabels()["label_type"] + " to Clipboard", heatMap.getRowLabels()["label_type"], "labels", copyToClipBoard,0); + addLinkout("Copy bar data for all labels to Clipboard", "ColumnClass", "labels",copyEntireClassBarToClipBoard,0); + addLinkout("Copy bar data for selected labels to Clipboard", "ColumnClass", "labels",copyPartialClassBarToClipBoard,1); + addLinkout("Copy bar data for all labels to Clipboard", "RowClass", "labels",copyEntireClassBarToClipBoard,0); + addLinkout("Copy bar data for selected labels to Clipboard", "RowClass", "labels",copyPartialClassBarToClipBoard,1); +} + +function linkout (title, inputType, callback){ // the linkout object + this.title = title; + this.inputType = inputType; // input type of the callback function + this.callback = callback; +} + +function addLinkout(name, labelType, inputType, callback, index){ // adds linkout objects to the linkouts global variable + if (!linkouts[labelType]){ + linkouts[labelType] = [new linkout(name, inputType,callback)]; + } else { + if (index !== undefined){ + linkouts[labelType].splice(index, 0, new linkout(name,inputType,callback)); + }else { + linkouts[labelType].push(new linkout(name,inputType,callback)); + } + } +} + +function copyToClipBoard(labels,axis){ + window.open("","",'width=335,height=330,resizable=1').document.write(labels.join(", ")); +} + +function copyEntireClassBarToClipBoard(labels,axis){ + var newWindow = window.open("","",'width=335,height=330,resizable=1'); + var newDoc = newWindow.document; + var axisLabels = axis == "ColumnClass" ? heatMap.getColLabels()["labels"] : heatMap.getRowLabels()["labels"]; + var classifications = heatMap.getClassifications(); + newDoc.write("Sample " + labels.join(" ") + ":
"); + for (var i = 0; i < axisLabels.length; i++){ + newDoc.write(axisLabels[i] + " "); + for (var j = 0; j < labels.length; j++){ + newDoc.write(classifications[labels[j]].values[i] + " "); + } + newDoc.write("
"); + } +} + +function copyPartialClassBarToClipBoard(labels,axis){ + var newWindow = window.open("","",'width=335,height=330,resizable=1'); + var newDoc = newWindow.document; + var axisLabels = axis == "ColumnClass" ? getSearchLabelsByAxis("Column") : getSearchLabelsByAxis("Row"); + var labelIndex = axis == "ColumnClass" ? getSearchCols() : getSearchRows(); + var classifications = heatMap.getClassifications(); + newDoc.write("Sample " + labels.join(" ") + ":
"); + for (var i = 0; i < axisLabels.length; i++){ + newDoc.write(axisLabels[i] + " "); + for (var j = 0; j < labels.length; j++){ + newDoc.write(classifications[labels[j]].values[labelIndex[i]-1] + " "); + } + newDoc.write("
"); + } +} + +//=========================== +// LABEL MENU FUNCTIONS END +//=========================== + +function showSearchError(type){ + var searchError = getDivElement('searchError'); + searchError.style.display = 'inherit'; + var searchBar = document.getElementById('search_text'); + searchError.style.top = searchBar.offsetTop + searchBar.offsetHeight; + searchError.style.left = searchBar.offsetLeft + searchBar.offsetWidth; + switch (type){ + case 0: searchError.innerHTML = "No matching labels found"; break; + case 1: searchError.innerHTML = "Exit dendrogram selection to go to " + currentSearchItem.label;break; + case 2: searchError.innerHTML = "All " + currentSearchItem.axis + " items are visible. Change the view mode to see " + currentSearchItem.label;break; + } + document.body.appendChild(searchError); + setTimeout(function(){ + searchError.remove(); + }, 2000); + +} \ No newline at end of file diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/UserPreferenceManager.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/UserPreferenceManager.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,1159 @@ +/********************************************************************************** + * USER PREFERENCE FUNCTIONS: The following functions handle the processing + * for user preference editing. + **********************************************************************************/ + +//Global variables for preference processing +var maxRows = 0; +var helpRowSize = 0; +var bkpColorMap = null; +var filterVal; +var searchPerformed = false; + +/*=================================================================================== + * COMMON PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present the entire heat map preferences + * dialog and, therefore, sit above those functions designed specifically for processing + * individual data layer and covariate classification bar preferences: + * - editPreferences + * - setPrefsDivSizing + * - showLayerPrefs + * - showClassPrefs + * - showRowsColsPrefs + * - prefsCancel + * - prefsApply + * - prefsValidate + * - prefsValidateBreakPoints + * - prefsValidateBreakColors + * - prefsApplyBreaks + * - getNewBreakColors + * - getNewBreakThresholds + * - prefsSave + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - editPreferences: This is the MAIN driver function for edit + * preferences processing. It is called under two conditions (1) The Edit + * preferences "gear" button is pressed on the main application screen + * (2) User preferences have been applied BUT errors have occurred. + * + * Processing for this function is documented in detail in the body of the function. + **********************************************************************************/ +function editPreferences(e,errorMsg){ + maxRows = 0; + userHelpClose(); + + // If helpPrefs element already exists, the user is pressing the gear button + // when preferences are already open. Disregard. + var helpExists = document.getElementById('prefsPanel'); + if ((isSub) || (helpExists !== null)) { + return; + } + + //If first time thru, save the dataLayer colorMap + //This is done because the colorMap must be edited to add/delete breakpoints while retaining their state + var colorMap = heatMap.getColorMapManager().getColorMap("dl1"); //TODO - Modify when multiple data layers (flick) are added + if (bkpColorMap === null) { + bkpColorMap = colorMap; + } + + //Create a "master" DIV for displaying edit preferences + var prefspanel = getDivElement("prefsPanel"); + document.getElementsByTagName('body')[0].appendChild(prefspanel); + + //Create a one cell table and populate the header and tab elements in the first 3 table rows + var prefContents = document.createElement("TABLE"); + var headDiv = document.createElement('div'); + headDiv.className = 'prefsHeader'; + headDiv.id = 'prefsHeader'; + prefspanel.appendChild(headDiv); + headDiv.textContent = 'Heat Map Display Properties'; + + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = " "; + prefContents.insertRow().innerHTML = "
Edit Rows & Columns Edit Data Layers Edit Classifications
"; + //Initialize rowCtr variable + var rowCtr = 3; + + //Create a parent DIV as a container for breakpoint and classification edit display + var prefprefs = getDivElement("prefprefs"); + + //Create and populate row & col preferences DIV and add to parent DIV + var rowcolprefs = setupRowColPrefs(e, prefprefs); + rowcolprefs.style.display="none"; + prefprefs.appendChild(rowcolprefs); + + //Create and populate classifications preferences DIV and add to parent DIV + var classprefs = setupClassPrefs(e, prefprefs); + classprefs.style.display="none"; + prefprefs.appendChild(classprefs); + + //Create and populate breakpoint preferences DIV and add to parent DIV + var layerprefs = setupLayerPrefs(e, prefprefs); + layerprefs.style.display="none"; + prefprefs.appendChild(layerprefs); + + // Set DIV containing both class and break DIVs to visible and append to prefspanel table + prefprefs.style.display="block"; + var row1 = prefContents.insertRow(); + var row1Cell = row1.insertCell(0); + row1Cell.appendChild(prefprefs); + + //If error message exists add table row to prefspanel table containing error message + if (errorMsg != null) { + setErrorRow(prefContents, errorMsg[2]); + } + + var prefButtons = document.createElement("TABLE"); + //Add Cancel, Apply, and Save buttons to bottom of prefspanel table + var buttons = "Cancel changes Apply changes"; + if (heatMap.isSaveAllowed()) { + buttons = buttons + " Save changes"; + } + setTableRow(prefButtons,["
"+buttons+"
"]); + rowCtr++; + prefprefs.appendChild(prefButtons); + + //Add prefspanel table to the main preferences DIV and set position and display + prefspanel.appendChild(prefContents); + prefspanel.style.position = "absolute"; + prefspanel.style.top = e.offsetTop + 15; + prefspanel.style.display="inherit"; + + //maxRows has been loaded with a count of the datalayer/class panel with the most rows + //add to this the number of rows added during construction of prefspanel table. + maxRows = maxRows+rowCtr; + //Retrieve maximum row height size used in various preferences DIVs + helpRowSize = parseFloat(getStyle(prefspanel, 'font-size' ), 10)*1.45; + //Use the two above numbers to apply sizing to all of the preferences DIVs. + setPrefsDivSizing(); + prefspanel.style.left = e.offsetLeft - parseInt(layerprefs.style.width,10); + + //If errors exist and they are NOT on the currently visible DIV (dataLayer1), + //hide the dataLayers DIV, set the tab to "Covariates", and open the appropriate + //covariate bar DIV. + showDendroSelections(); + setShowAll(); + if ((errorMsg != null) && (errorMsg[1] === "classPrefs")) { + showClassBreak(errorMsg[0]); + showClassPrefs(); + } else if ((errorMsg != null) && (errorMsg[1] === "layerPrefs")){ + showLayerPrefs(); + } else if (searchPerformed){ + searchPerformed = false; + showClassPrefs(); + } else { + showRowsColsPrefs(); + } + +} + +/********************************************************************************** + * FUNCTION - setPrefsDivSizing: The purpose of this function is to resize the + * various DIVs inside the preferences dialog. It is called when the dialog is + * first opened and whenever data layer breakpoints are added (if necessary). + **********************************************************************************/ +//TODO - This can be improved as the current sizing is not exact. +function setPrefsDivSizing() { + var rowsColsprefs = document.getElementById("rowsColsPrefs"); + var layerprefs = document.getElementById("layerPrefs"); + var classprefs = document.getElementById("classPrefs"); + var helprefs = document.getElementById("prefsPanel"); + var prefHeight = maxRows*helpRowSize; + var prefWidth = 380; + rowsColsprefs.style.width = prefWidth; + rowsColsprefs.style.height = prefHeight; + layerprefs.style.width = prefWidth; + layerprefs.style.height = prefHeight; + classprefs.style.width = prefWidth; + classprefs.style.height = prefHeight; +} + +/********************************************************************************** + * FUNCTION - showLayerPrefs: The purpose of this function is to perform the + * processing for the preferences tab when the user selects the "Data Layers" tab. + **********************************************************************************/ +function showRowsColsPrefs() { + //Turn off all tabs + hideAllPrefs(); + //Turn on layer prefs tab + var rowsColsBtn = document.getElementById("prefRowsCols_btn"); + rowsColsBtn.setAttribute('src', staticPath + 'images/rowsColsOn.png'); + var rowsColsDiv = document.getElementById("rowsColsPrefs"); + rowsColsDiv.style.display="block"; +} + + +/********************************************************************************** + * FUNCTION - showLayerPrefs: The purpose of this function is to perform the + * processing for the preferences tab when the user selects the "Data Layers" tab. + **********************************************************************************/ +function showLayerPrefs() { + //Turn off all tabs + hideAllPrefs(); + //Turn on layer prefs tab + var layerBtn = document.getElementById("prefLayer_btn"); + layerBtn.setAttribute('src', staticPath + 'images/dataLayersOn.png'); + var layerDiv = document.getElementById("layerPrefs"); + layerDiv.style.display="block"; +} + +/********************************************************************************** + * FUNCTION - showClassPrefs: The purpose of this function is to perform the + * processing for the preferences tab when the user selects the "Covariates" tab. + **********************************************************************************/ +function showClassPrefs() { + //Turn off all tabs + hideAllPrefs(); + //Turn on classification prefs tab + var classBtn = document.getElementById("prefClass_btn"); + classBtn.setAttribute('src', staticPath + 'images/covariateBarsOn.png'); + var classDiv = document.getElementById("classPrefs"); + classDiv.style.display="block"; +} + +/********************************************************************************** + * FUNCTION - hideAllPrefs: The purpose of this function is to set all tabs off. It + * is called whenever a tab is clicked. All tabs are set to hidden with their + * image set to the "off" image. + **********************************************************************************/ +function hideAllPrefs() { + var classBtn = document.getElementById("prefClass_btn"); + classBtn.setAttribute('src', staticPath + 'images/covariateBarsOff.png'); + var classDiv = document.getElementById("classPrefs"); + classDiv.style.display="none"; + var layerBtn = document.getElementById("prefLayer_btn"); + layerBtn.setAttribute('src', staticPath + 'images/dataLayersOff.png'); + var layerDiv = document.getElementById("layerPrefs"); + layerDiv.style.display="none"; + var rowsColsBtn = document.getElementById("prefRowsCols_btn"); + rowsColsBtn.setAttribute('src', staticPath + 'images/rowsColsOff.png'); + var rowsColsDiv = document.getElementById("rowsColsPrefs"); + rowsColsDiv.style.display="none"; +} + +/********************************************************************************** + * FUNCTION - prefsCancelButton: The purpose of this function is to perform all processing + * necessary to exit the user preferences dialog WITHOUT applying or saving any + * changes made by the user when the Cancel button is pressed on the ColorMap + * preferences dialog. Since the dataLayer colormap must be edited to add/delete + * breakpoints, the backup colormap (saved when preferences are first opened) is re- + * applied to the colorMapManager. Then the preferences DIV is retrieved and removed. + **********************************************************************************/ +function prefsCancelButton() { + if (bkpColorMap !== null) { + var colorMapMgr = heatMap.getColorMapManager(); + colorMapMgr.setColorMap("dl1", bkpColorMap); + } + var prefspanel = document.getElementById('prefsPanel'); + if (prefspanel){ + prefspanel.remove(); + } +} + +/********************************************************************************** + * FUNCTION - prefsApplyButton: The purpose of this function is to perform all processing + * necessary to reconfigure the "current" presentation of the heat map in the + * viewer when the Apply button is pressed on the ColorMap Preferences Dialog. + * First validations are performed. If errors are found, preference + * changes are NOT applied and the user is re-presented with the preferences dialog + * and the error found. If no errors are found, all changes are applied to the heatmap + * and the summary panel, detail panel, and covariate bars are redrawn. However, + * these changes are not yet permanently saved to the JSON files that are used to + * configure heat map presentation. + **********************************************************************************/ +function prefsApplyButton() { + //Perform validations of all user-entered data layer and covariate bar + //preference changes. + var errorMsg = prefsValidate(); + prefsApply(); + if (errorMsg !== null) { + prefsError(errorMsg); + } else { + prefsSuccess(); + } +} + +/********************************************************************************** + * FUNCTION - prefsSaveButton: The purpose of this function is to perform all processing + * necessary to permanently save user preference changes. This will result in + * changes to the JSON files that are used to configure heat map presentation. + **********************************************************************************/ +function prefsSaveButton() { + var mode = heatMap.mode; + var errorMsg = prefsValidate(); + prefsApply(); + if (errorMsg !== null) { + prefsError(errorMsg); + } else { + var success = heatMap.saveHeatMapProperties(); + if (success === "false") { + prefsError(["dl1", "layerPrefs", "ERROR: Preferences failed to save. Use Apply or Cancel to continue."]); + } else { + prefsSuccess(); + } + } +} + +/********************************************************************************** + * FUNCTION - prefsSuccess: The purpose of this function perform the functions + * necessary when preferences are determined to be valid. It is shared by the + * Apply and Save buttons. + **********************************************************************************/ +function prefsSuccess() { + filterVal = null; + //Remove the backup color map (used to reinstate colors if user cancels) + //and formally apply all changes to the heat map, re-draw, and exit preferences. + bkpColorMap = null; + summaryInit(); + detailInit(); + changeMode('NORMAL'); + prefsCancelButton(); +} + +/********************************************************************************** + * FUNCTION - prefsError: The purpose of this function perform the functions + * necessary when preferences are determined to be invalid. It is shared by the + * Apply and Save buttons. + **********************************************************************************/ +function prefsError(errorMsg) { + //If a validation error exists, re-present the user preferences + //dialog with the error message displayed in red. + var prefspanel = document.getElementById('prefsPanel'); + if (prefspanel){ + prefspanel.remove(); + } + editPreferences(document.getElementById('gear_btn'),errorMsg); +} + +/********************************************************************************** + * FUNCTION - prefsSuccess: The purpose of this function is to apply all user + * ColorMap preferences settings. It is shared by the Apply and Save buttons. + **********************************************************************************/ +function prefsApply() { + // Apply Row & Column Preferences + var dendrogram = heatMap.getDendrogram(); + var rowLabels = heatMap.getRowLabels(); + var rowOrder = rowLabels['order_method']; + if (rowOrder === "Hierarchical") { + var rowDendroShowVal = document.getElementById("rowDendroShowPref").value; + dendrogram['row_dendro_show'] = rowDendroShowVal; + dendrogram['row_dendro_height'] = document.getElementById("rowDendroHeightPref").value; + } + var colLabels = heatMap.getColLabels(); + var colOrder = colLabels['order_method']; + if (colOrder === "Hierarchical") { + var colDendroShowVal = document.getElementById("colDendroShowPref").value; + dendrogram['col_dendro_show'] = colDendroShowVal; + dendrogram['col_dendro_height'] = document.getElementById("colDendroHeightPref").value; + } + // Apply Covariate Bar Preferences + var classBars = heatMap.getClassifications(); + for (var key in classBars){ + var showElement = document.getElementById(key+"_showPref"); + var heightElement = document.getElementById(key+"_heightPref"); + if (filterShow(key)) { + heatMap.setClassificationPrefs(key,showElement.checked,heightElement.value); + } else { + heatMap.setClassificationPrefs(key,false,15); + } + prefsApplyBreaks(classBars[key].colorScheme,"covariate",filterShow(key)); + } + // Apply Data Layer Preferences + //TODO - Future loop for data layers + prefsApplyBreaks("dl1","datalayer",true); +} + +/********************************************************************************** + * FUNCTION - prefsValidate: The purpose of this function is to validate all user + * changes to the heatmap properties. When the very first error is found, an error + * message (string array containing error information) is created and returned to + * the prefsApply function. + **********************************************************************************/ +function prefsValidate() { + var classBars = heatMap.getClassifications(); + var errorMsg = null; + //Loop thru all covariate classfication bars validating all break colors + for (var key in classBars){ + if (filterShow(key)) { + var showElement = document.getElementById(key+"_showPref"); + var heightElement = document.getElementById(key+"_heightPref"); + errorMsg = prefsValidateBreakColors(classBars[key].colorScheme,"classPrefs"); + if (errorMsg !== null) break; + } + } + //Validate all breakpoints and colors for the main data layer + if (errorMsg === null) { + //TODO: currently only processing for data layer 1. This will require modification + // when new data layers (e.g. flicks) are added to the heatmap. + errorMsg = prefsValidateBreakPoints("dl1","layerPrefs"); + if (errorMsg === null) { + errorMsg = prefsValidateBreakColors("dl1","layerPrefs"); + } + } + return errorMsg; +} + +/********************************************************************************** + * FUNCTION - prefsValidateBreakPoints: The purpose of this function is to validate + * all user breakpoint and color changes to heatmap data layer properties. When the + * first error is found, an error message (string array containing error information) + * is created and returned to the prefsApply function. + **********************************************************************************/ +function prefsValidateBreakPoints(colorMapName,prefPanel) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var dupeBreak = false; + var breakOrder = false; + var prevBreakValue = -99999; + var errorMsg = null; + //Loop thru colormap thresholds and validate for order and duplicates + for (var i = 0; i < thresholds.length; i++) { + var breakElement = document.getElementById(colorMapName+"_breakPt"+i+"_breakPref"); + //If current breakpoint is not greater than previous, throw order error + if (parseInt(breakElement.value) < prevBreakValue) { + breakOrder = true; + break; + } + //Loop thru thresholds, skipping current element, searching for a match to the + //current selection. If found, throw duplicate error + for (var j = 0; j < thresholds.length; j++) { + var be = document.getElementById(colorMapName+"_breakPt"+j+"_breakPref"); + if (i != j) { + if (breakElement.value === be.value) { + dupeBreak = true; + break; + } + } + } + } + if (breakOrder) { + errorMsg = [colorMapName, prefPanel, "ERROR: Data layer breakpoints must be in order"]; + } + if (dupeBreak) { + errorMsg = [colorMapName, prefPanel, "ERROR: Duplicate data layer breakpoint found above"]; + } + return errorMsg; +} + +/********************************************************************************** + * FUNCTION - prefsValidateBreakColors: The purpose of this function is to validate + * all user color changes to heatmap classification and data layer properties. When the + * first error is found, an error message (string array containing error information) + * is created and returned to the prefsApply function. + **********************************************************************************/ +function prefsValidateBreakColors(colorMapName,prefPanel) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var dupeColor = false; + for (var i = 0; i < colors.length; i++) { + var colorElement = document.getElementById(colorMapName+"_color"+i+"_colorPref"); + for (var j = 0; j < thresholds.length; j++) { + var ce = document.getElementById(colorMapName+"_color"+j+"_colorPref"); + if (i != j) { + if (colorElement.value === ce.value) { + dupeColor = true; + break; + } + } + } + } + if (dupeColor) { + return [colorMapName, prefPanel, "ERROR: Duplicate color setting found above"]; + } + return null; +} + +/********************************************************************************** + * FUNCTION - prefsApplyBreaks: The purpose of this function is to apply all + * user entered changes to colors and breakpoints. + **********************************************************************************/ +function prefsApplyBreaks(colorMapName, colorMapType, show) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + if (show) { + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var newColors = getNewBreakColors(colorMapName); + colorMap.setColors(newColors); + if (colorMapType === "datalayer") { + var newThresholds = getNewBreakThresholds(colorMapName); + colorMap.setThresholds(newThresholds); + } + var missingElement = document.getElementById(colorMapName+"_missing_colorPref"); + colorMap.setMissingColor(missingElement.value); + var colorMapMgr = heatMap.getColorMapManager(); + colorMapMgr.setColorMap(colorMapName, colorMap); + } +} + +/********************************************************************************** + * FUNCTION - getNewBreakColors: The purpose of this function is to grab all user + * color entries for a given colormap and place them on a string array. It will + * iterate thru the screen elements, pulling the current color entry for each + * element, placing it in a new array, and returning that array. This function is + * called by the prefsApplyBreaks function. It is ALSO called from the data layer + * addLayerBreak and deleteLayerBreak functions with parameters passed in for + * the position to add/delete and the action to be performed (add/delete). + **********************************************************************************/ +function getNewBreakColors(colorMapName, pos, action) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var newColors = []; + for (var j = 0; j < thresholds.length; j++) { + var colorElement = document.getElementById(colorMapName+"_color"+j+"_colorPref"); + //If being called from addLayerBreak or deleteLayerBreak + if (typeof pos !== 'undefined') { + if (action === "add") { + newColors.push(colorElement.value); + if (j === pos) { + newColors.push(colorElement.value); + } + } else { + if (j !== pos) { + newColors.push(colorElement.value); + } + } + } else { + newColors.push(colorElement.value); + } + } + return newColors; +} + +/********************************************************************************** + * FUNCTION - getNewBreakThresholds: The purpose of this function is to grab all user + * data layer breakpoint entries for a given colormap and place them on a string array. + * It will iterate thru the screen elements, pulling the current breakpoint entry for each + * element, placing it in a new array, and returning that array. This function is + * called by the prefsApplyBreaks function (only for data layers). It is ALSO called + * from the data layer addLayerBreak and deleteLayerBreak functions with parameters + * passed in for the position to add/delete and the action to be performed (add/delete). + **********************************************************************************/ +function getNewBreakThresholds(colorMapName, pos, action) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var newThresholds = []; + for (var j = 0; j < thresholds.length; j++) { + var breakElement = document.getElementById(colorMapName+"_breakPt"+j+"_breakPref"); + if (typeof pos !== 'undefined') { + if (action === "add") { + newThresholds.push(breakElement.value); + if (j === pos) { + newThresholds.push(breakElement.value); + } + } else { + if (j !== pos) { + newThresholds.push(breakElement.value); + } + } + } else { + newThresholds.push(breakElement.value); + } + } + return newThresholds; +} + +/*=================================================================================== + * DATA LAYER PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present heat map data layer + * configuration options: + * - setupLayerPrefs + * - setupLayerBreaks + * - setupLayerPrefs + * - addLayerBreak + * - deleteLayerBreak + * - reloadLayerBreaksColorMap + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - setupLayerPrefs: The purpose of this function is to construct a DIV + * panel containing all data layer preferences. A dropdown list containing all + * data layers is presented and individual DIVs for each data layer, containing + * breakpoints/colors, are added. + **********************************************************************************/ +function setupLayerPrefs(e, prefprefs){ + var layerprefs = getDivElement("layerPrefs"); + var prefContents = document.createElement("TABLE"); + var colorMapName = "dl1"; + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + prefContents.insertRow().innerHTML = formatBlankRow(); + // TODO Future: primary and flick data layers in dropdown + var dlSelect = "" + setTableRow(prefContents,["Data Layer: ", dlSelect]); + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + layerprefs.appendChild(prefContents); + var breakprefs = setupLayerBreaks(e, colorMapName, colorMapName); + breakprefs.style.display="block"; + breakprefs.style.width = 300; + layerprefs.appendChild(breakprefs); + maxRows = maxRows+3; + // TODO Future: loop for primary and flick data layers + return layerprefs; +} + +/********************************************************************************** + * FUNCTION - setupLayerBreaks: The purpose of this function is to construct a DIV + * containing a list of breakpoints/colors for a given matrix data layer. + **********************************************************************************/ +function setupLayerBreaks(e, mapName, barName, barType){ + var classBars = heatMap.getClassifications(); + var colorMap = heatMap.getColorMapManager().getColorMap(mapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var helpprefs = getDivElement("breakPrefs_"+mapName); + var prefContents = document.createElement("TABLE"); + var rowCtr = 0; + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Breakpoint", ""+"Color"+""," "]); + rowCtr++; + for (var j = 0; j < thresholds.length; j++) { + var threshold = thresholds[j]; + var color = colors[j]; + var threshId = mapName+"_breakPt"+j; + var colorId = mapName+"_color"+j; + var breakPtInput = ""; + var colorInput = ""; + var addButton = "Add Breakpoint" + var delButton = "Remove Breakpoint" + if (j === 0) { + setTableRow(prefContents, [breakPtInput, colorInput, addButton]); + } else { + setTableRow(prefContents, [breakPtInput, colorInput, addButton, delButton]); + } + rowCtr++; + } + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Missing Color:", ""]); + rowCtr++; + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + helpprefs.style.height = rowCtr; + helpprefs.style.width = 30; + helpprefs.appendChild(prefContents); + return helpprefs; +} + +/********************************************************************************** + * FUNCTION - addLayerBreak: The purpose of this function is to add a breakpoint + * row to a data layer colormap. A new row is created using the preceding row as a + * template (i.e. breakpt value and color same as row clicked on). + **********************************************************************************/ +function addLayerBreak(pos,colorMapName) { + //Retrieve colormap for data layer + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var newThresholds = getNewBreakThresholds(colorMapName, pos,"add"); + var newColors = getNewBreakColors(colorMapName, pos,"add"); + //Calculate new size of data layers panel and reset size of the + // entire preferences dialog (if necessary) + var layerRows = newThresholds.length+helpRowSize; + maxRows = Math.max(maxRows,layerRows); + setPrefsDivSizing(); + //Apply new arrays for thresholds and colors to the datalayer + //and reload the colormap. + colorMap.setThresholds(newThresholds); + colorMap.setColors(newColors); + reloadLayerBreaksColorMap(colorMapName, colorMap); +} + +/********************************************************************************** + * FUNCTION - deleteLayerBreak: The purpose of this function is to remove a breakpoint + * row from a data layer colormap. + **********************************************************************************/ +function deleteLayerBreak(pos,colorMapName) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var newThresholds = getNewBreakThresholds(colorMapName, pos,"delete"); + var newColors = getNewBreakColors(colorMapName, pos,"delete"); + //Apply new arrays for thresholds and colors to the datalayer + //and reload the colormap. + colorMap.setThresholds(newThresholds); + colorMap.setColors(newColors); + reloadLayerBreaksColorMap(colorMapName, colorMap); +} + +/********************************************************************************** + * FUNCTION - reloadLayerBreaksColorMap: The purpose of this function is to reload + * the colormap for a given data layer. The add/deleteLayerBreak methods call + * this common function. The layerPrefs DIV is retrieved and the setupLayerBreaks + * method is called, passing in the newly edited colormap. + **********************************************************************************/ +function reloadLayerBreaksColorMap(colorMapName, colorMap) { + var e = document.getElementById('gear_btn') + var colorMapMgr = heatMap.getColorMapManager(); + colorMapMgr.setColorMap(colorMapName, colorMap); + var breakPrefs = document.getElementById('breakPrefs_'+colorMapName); + if (breakPrefs){ + breakPrefs.remove(); + } + var layerprefs = getDivElement("layerPrefs"); + var breakPrefs = setupLayerBreaks(e, colorMapName, colorMapName); + breakPrefs.style.display="block"; + breakPrefs.style.width = 300; + layerPrefs.appendChild(breakPrefs); +} + +/*=================================================================================== + * COVARIATE CLASSIFICATION PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present heat map covariate classfication + * bar configuration options: + * - setupClassPrefs + * - setupClassBreaks + * - setupAllClassesPrefs + * - showAllBars + * - setShowAll + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - setupClassPrefs: The purpose of this function is to construct a DIV + * panel containing all covariate bar preferences. A dropdown list containing all + * covariate classification bars is presented and individual DIVs for each data layer, + * containing breakpoints/colors, are added. Additionally, a "front panel" DIV is + * created for "ALL" classification bars that contains preferences that are global + * to all of the individual bars. + **********************************************************************************/ +function setupClassPrefs(e, prefprefs){ + var classBars = heatMap.getClassifications(); + var classprefs = getDivElement("classPrefs"); + var prefContents = document.createElement("TABLE"); + prefContents.insertRow().innerHTML = formatBlankRow(); + var filterInput = ""; + var filterButton = "Search Covariates"; + if (filterVal != null) { + var filterInput = ""; + var filterButton = "Search Covariates"; + } + var searchClasses = filterInput+"  "+filterButton; + setTableRow(prefContents,[searchClasses], 4, 'right'); + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + var classSelect = "" + setTableRow(prefContents,["Covariate Bar: ", classSelect]); + prefContents.insertRow().innerHTML = formatBlankRow(); + classprefs.appendChild(prefContents); + var i = 0; + for (var key in classBars){ + if (filterShow(key)) { + var breakprefs = setupClassBreaks(e, classBars[key].colorScheme, key); + breakprefs.style.display="none"; + breakprefs.style.width = 300; + classprefs.appendChild(breakprefs); + } + i++; + } + // Append a DIV panel for all of the covariate class bars + var allPrefs = setupAllClassesPrefs(e); + allPrefs.style.display="block"; + classprefs.appendChild(allPrefs); + return classprefs; +} + +/********************************************************************************** + * FUNCTION - setupClassBreaks: The purpose of this function is to construct a DIV + * containing a list of all covariate bars with informational data and user preferences + * that are common to all bars (show/hide and size). + **********************************************************************************/ +function setupAllClassesPrefs(e){ + var allprefs = getDivElement("breakPrefs_ALL"); + var prefContents = document.createElement("TABLE"); + var rowCtr = 0; + prefContents.insertRow().innerHTML = formatBlankRow(); + var colShowAll = " "; + setTableRow(prefContents,[""+"Classification"+"", ""+"Position"+"", colShowAll+""+"Show"+"", ""+"Height"+""]); + rowCtr=2; + var classBars = heatMap.getClassifications(); + var checkState = true; + for (var key in classBars){ + if (filterShow(key)) { + var colShow = "        "; + var colHeight = ""; + setTableRow(prefContents,[key,toTitleCase(classBars[key].position),colShow,colHeight]); + rowCtr++; + } + } + allprefs.appendChild(prefContents); + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + return allprefs; +} + +/********************************************************************************** + * FUNCTION - setupClassBreaks: The purpose of this function is to construct a DIV + * containing a set informational data and a list of categories/colors for a given + * covariate classfication bar. + **********************************************************************************/ +function setupClassBreaks(e, mapName, barName){ + var classBars = heatMap.getClassifications(); + var colorMap = heatMap.getColorMapManager().getColorMap(mapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var helpprefs = getDivElement("breakPrefs_"+mapName); + var prefContents = document.createElement("TABLE"); + var rowCtr = 0; + prefContents.insertRow().innerHTML = formatBlankRow(); + var colShow = ""; + var colHeight = ""; + var pos = toTitleCase(classBars[barName].position); + var typ = toTitleCase(colorMap.getType()); + if (classBars[barName].position == "row") { + pos = "Row"; + } + setTableRow(prefContents,["Bar Position: ",""+pos+""]); + setTableRow(prefContents,["Bar Type: ",""+typ+""]); + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr = rowCtr+4; + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Category", ""+"Color"+""]); + rowCtr++; + for (var j = 0; j < thresholds.length; j++) { + var threshold = thresholds[j]; + var color = colors[j]; + var threshId = mapName+"_breakPt"+j; + var colorId = mapName+"_color"+j; + var colorInput = ""; + setTableRow(prefContents, [threshold, colorInput]); + rowCtr++; + } + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Missing Color:", ""]); + rowCtr++; + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + helpprefs.style.height = rowCtr; + helpprefs.style.width = 30; + helpprefs.appendChild(prefContents); + return helpprefs; +} + +/********************************************************************************** + * FUNCTION - showAllBars: The purpose of this function is to set the condition of + * the "show" checkbox for all covariate bars on the covariate bars tab of the user + * preferences dialog. These checkboxes are located on the DIV that is visible when + * the ALL entry of the covariate dropdown is selected. Whenever a user checks the + * show all box, all other boxes are checked. + **********************************************************************************/ +function showAllBars(){ + var classBars = heatMap.getClassifications(); + var showAllBox = document.getElementById('all_showPref'); + var checkState = false; + if (showAllBox.checked) { + checkState = true; + } + for (var key in classBars){ + if (filterShow(key)) { + var colShow = document.getElementById(key+'_showPref'); + colShow.checked = checkState; + } + } + return; +} + +/********************************************************************************** + * FUNCTION - setShowAll: The purpose of this function is to set the condition of + * the "show all" checkbox on the covariate bars tab of the user preferences dialog. + * This checkbox is located on the DIV that is visible when the ALL entry of the + * covariate dropdown is selected. If a user un-checks a single box in the list of + * covariate bars, the show all box is un-checked. Conversely, if a user checks a box + * resulting in all of the boxes being selected, the show all box will be checked. + **********************************************************************************/ +function setShowAll(){ + var classBars = heatMap.getClassifications(); + var checkState = true; + for (var key in classBars){ + var colShow = document.getElementById(key+'_showPref'); + if (filterShow(key)) { + if (!colShow.checked) { + checkState = false; + break; + } + } + } + var showAllBox = document.getElementById('all_showPref'); + showAllBox.checked = checkState; + return; +} + + +/********************************************************************************** + * FUNCTION - showClassBreak: The purpose of this function is to show the + * appropriate classification bar panel based upon the user selection of the + * covariate dropdown on the covariates tab of the preferences screen. This + * function is also called when an error is trappped, opening the covariate DIV + * that contains the erroneous data entry. + **********************************************************************************/ +function showClassBreak(selClass) { + var classBtn = document.getElementById("classPref_list"); + if (typeof selClass != 'undefined') { + classBtn.value = selClass; + } + for (var i=0; i= 0) { + filterShow = true; + } + } else { + filterShow = true; + } + return filterShow; + +} + +/*=================================================================================== + * ROW COLUMN PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present heat map covariate classfication + * bar configuration options: + * - setupRowColPrefs + * - showDendroSelections + * - dendroRowShowChange + * - dendroColShowChange + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - setupRowColPrefs: The purpose of this function is to construct a DIV + * panel containing all row & col preferences. Two sections are presented, one for + * rows and the other for cols. Informational data begins each section and + * properties for modifying the appearance of row/col dendograms appear at the end. + **********************************************************************************/ +function setupRowColPrefs(e, prefprefs){ + var rowcolprefs = getDivElement("rowsColsPrefs"); + var prefContents = document.createElement("TABLE"); + prefContents.insertRow().innerHTML = formatBlankRow(); + setTableRow(prefContents,["ROW INFORMATION:"], 2); + prefContents.insertRow().innerHTML = formatBlankRow(); + var rowLabels = heatMap.getRowLabels(); + var dendrogram = heatMap.getDendrogram(); + var rowOrder = rowLabels['order_method']; + setTableRow(prefContents,["  Labels Type:",rowLabels['label_type']]); + setTableRow(prefContents,["  Ordering Method:",rowOrder]); + var rowCtr = 5; + var dendroShowOptions = ""; + var dendroHeightOptions = ""; + if (rowOrder === "Hierarchical") { + setTableRow(prefContents,["  Agglomeration Method:",rowLabels['agglomeration_method']]); + rowCtr++; + setTableRow(prefContents,["  Distance Metric:",rowLabels['distance_metric']]); + rowCtr++; + var rowDendroSelect = "" + rowDendroHeightSelect = rowDendroHeightSelect+dendroHeightOptions; + setTableRow(prefContents,["  Dendrogram Height:",rowDendroHeightSelect]); + rowCtr++; + } + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents,["COLUMN INFORMATION:"], 2); + rowCtr++; + prefContents.insertRow().innerHTML = formatBlankRow(); + + var colLabels = heatMap.getColLabels(); + var colOrder = colLabels['order_method']; + setTableRow(prefContents,["  Labels Type:",colLabels['label_type']]); + rowCtr++; + setTableRow(prefContents,["  Ordering Method:",colOrder]); + rowCtr++; + if (colOrder === "Hierarchical") { + setTableRow(prefContents,["  Agglomeration Method:",colLabels['agglomeration_method']]); + rowCtr++; + setTableRow(prefContents,["  Distance Metric:",colLabels['distance_metric']]); + rowCtr++; + var colDendroShowSelect = "" + colDendroHeightSelect = colDendroHeightSelect+dendroHeightOptions; + setTableRow(prefContents,["  Show Dendrogram:",colDendroShowSelect]); + rowCtr++; + setTableRow(prefContents,["  Dendrogram Height:",colDendroHeightSelect]); + rowCtr++; + } + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + rowcolprefs.appendChild(prefContents); + return rowcolprefs; +} + +/********************************************************************************** + * FUNCTION - showDendroSelections: The purpose of this function is to set the + * states of the row/column dendrogram show and height preferences. + **********************************************************************************/ +function showDendroSelections() { + var dendrogram = heatMap.getDendrogram(); + var rowLabels = heatMap.getRowLabels(); + var rowOrder = rowLabels['order_method']; + if (rowOrder === "Hierarchical") { + var dendroShowVal = dendrogram['row_dendro_show']; + document.getElementById("rowDendroShowPref").value = dendroShowVal; + var rowHeightPref = document.getElementById("rowDendroHeightPref"); + if (dendroShowVal === 'NONE') { + var opt = rowHeightPref.options[6]; + if (typeof opt != 'undefined') { + rowHeightPref.options[6].remove(); + } + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + rowHeightPref.add(option); + rowHeightPref.disabled = true; + } + rowHeightPref.value = dendrogram['row_dendro_height']; + } + var colLabels = heatMap.getColLabels(); + var colOrder = colLabels['order_method']; + if (colOrder === "Hierarchical") { + var dendroShowVal = dendrogram['col_dendro_show']; + document.getElementById("colDendroShowPref").value = dendroShowVal; + var colHeightPref = document.getElementById("colDendroHeightPref"); + if (dendroShowVal === 'NONE') { + var opt = colHeightPref.options[6]; + if (typeof opt != 'undefined') { + colHeightPref.options[6].remove(); + } + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + colHeightPref.add(option); + colHeightPref.disabled = true; + } + colHeightPref.value = dendrogram['col_dendro_height']; + } +} + +/********************************************************************************** + * FUNCTION - dendroRowShowChange: The purpose of this function is to respond to + * a change event on the show row dendrogram dropdown. If the change is to Hide, + * the row dendro height is set to 10 and the dropdown disabled. If the change is to + * one of the 2 Show options AND was previously Hide, set height to the default + * value of 100 and enable the dropdown. + **********************************************************************************/ +function dendroRowShowChange() { + var newValue = document.getElementById("rowDendroShowPref").value; + var rowHeightPref = document.getElementById("rowDendroHeightPref"); + if (newValue === 'NONE') { + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + rowHeightPref.add(option); + rowHeightPref.value = '10'; + rowHeightPref.disabled = true; + } else if (rowHeightPref.disabled) { + var opt = rowHeightPref.options[6]; + if (typeof opt != 'undefined') { + rowHeightPref.options[6].remove(); + } + rowHeightPref.value = '100'; + rowHeightPref.disabled = false; + } +} + +/********************************************************************************** + * FUNCTION - dendroRowShowChange: The purpose of this function is to respond to + * a change event on the show row dendrogram dropdown. If the change is to Hide, + * the row dendro height is set to 10 and the dropdown disabled. If the change is to + * one of the 2 Show options AND was previously Hide, set height to the default + * value of 100 and enable the dropdown. + **********************************************************************************/ +function dendroColShowChange() { + var newValue = document.getElementById("colDendroShowPref").value; + var colHeightPref = document.getElementById("colDendroHeightPref"); + if (newValue === 'NONE') { + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + colHeightPref.add(option); + colHeightPref.value = '10'; + colHeightPref.disabled = true; + } else if (colHeightPref.disabled) { + var opt = colHeightPref.options[6]; + if (typeof opt != 'undefined') { + colHeightPref.options[6].remove(); + } + colHeightPref.value = '100'; + colHeightPref.disabled = false; + } +} + + + diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/UserPreferenceManager.js.sav --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/UserPreferenceManager.js.sav Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,1159 @@ +/********************************************************************************** + * USER PREFERENCE FUNCTIONS: The following functions handle the processing + * for user preference editing. + **********************************************************************************/ + +//Global variables for preference processing +var maxRows = 0; +var helpRowSize = 0; +var bkpColorMap = null; +var filterVal; +var searchPerformed = false; + +/*=================================================================================== + * COMMON PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present the entire heat map preferences + * dialog and, therefore, sit above those functions designed specifically for processing + * individual data layer and covariate classification bar preferences: + * - editPreferences + * - setPrefsDivSizing + * - showLayerPrefs + * - showClassPrefs + * - showRowsColsPrefs + * - prefsCancel + * - prefsApply + * - prefsValidate + * - prefsValidateBreakPoints + * - prefsValidateBreakColors + * - prefsApplyBreaks + * - getNewBreakColors + * - getNewBreakThresholds + * - prefsSave + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - editPreferences: This is the MAIN driver function for edit + * preferences processing. It is called under two conditions (1) The Edit + * preferences "gear" button is pressed on the main application screen + * (2) User preferences have been applied BUT errors have occurred. + * + * Processing for this function is documented in detail in the body of the function. + **********************************************************************************/ +function editPreferences(e,errorMsg){ + maxRows = 0; + userHelpClose(); + + // If helpPrefs element already exists, the user is pressing the gear button + // when preferences are already open. Disregard. + var helpExists = document.getElementById('prefsPanel'); + if ((isSub) || (helpExists !== null)) { + return; + } + + //If first time thru, save the dataLayer colorMap + //This is done because the colorMap must be edited to add/delete breakpoints while retaining their state + var colorMap = heatMap.getColorMapManager().getColorMap("dl1"); //TODO - Modify when multiple data layers (flick) are added + if (bkpColorMap === null) { + bkpColorMap = colorMap; + } + + //Create a "master" DIV for displaying edit preferences + var prefspanel = getDivElement("prefsPanel"); + document.getElementsByTagName('body')[0].appendChild(prefspanel); + + //Create a one cell table and populate the header and tab elements in the first 3 table rows + var prefContents = document.createElement("TABLE"); + var headDiv = document.createElement('div'); + headDiv.className = 'prefsHeader'; + headDiv.id = 'prefsHeader'; + prefspanel.appendChild(headDiv); + headDiv.textContent = 'Heat Map Display Properties'; + + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = " "; + prefContents.insertRow().innerHTML = "
Edit Rows & Columns Edit Data Layers Edit Classifications
"; + //Initialize rowCtr variable + var rowCtr = 3; + + //Create a parent DIV as a container for breakpoint and classification edit display + var prefprefs = getDivElement("prefprefs"); + + //Create and populate row & col preferences DIV and add to parent DIV + var rowcolprefs = setupRowColPrefs(e, prefprefs); + rowcolprefs.style.display="none"; + prefprefs.appendChild(rowcolprefs); + + //Create and populate classifications preferences DIV and add to parent DIV + var classprefs = setupClassPrefs(e, prefprefs); + classprefs.style.display="none"; + prefprefs.appendChild(classprefs); + + //Create and populate breakpoint preferences DIV and add to parent DIV + var layerprefs = setupLayerPrefs(e, prefprefs); + layerprefs.style.display="none"; + prefprefs.appendChild(layerprefs); + + // Set DIV containing both class and break DIVs to visible and append to prefspanel table + prefprefs.style.display="block"; + var row1 = prefContents.insertRow(); + var row1Cell = row1.insertCell(0); + row1Cell.appendChild(prefprefs); + + //If error message exists add table row to prefspanel table containing error message + if (errorMsg != null) { + setErrorRow(prefContents, errorMsg[2]); + } + + var prefButtons = document.createElement("TABLE"); + //Add Cancel, Apply, and Save buttons to bottom of prefspanel table + var buttons = "Cancel changes Apply changes"; + if (heatMap.isSaveAllowed()) { + buttons = buttons + " Save changes"; + } + setTableRow(prefButtons,["
"+buttons+"
"]); + rowCtr++; + prefprefs.appendChild(prefButtons); + + //Add prefspanel table to the main preferences DIV and set position and display + prefspanel.appendChild(prefContents); + prefspanel.style.position = "absolute"; + prefspanel.style.top = e.offsetTop + 15; + prefspanel.style.display="inherit"; + + //maxRows has been loaded with a count of the datalayer/class panel with the most rows + //add to this the number of rows added during construction of prefspanel table. + maxRows = maxRows+rowCtr; + //Retrieve maximum row height size used in various preferences DIVs + helpRowSize = parseFloat(getStyle(prefspanel, 'font-size' ), 10)*1.45; + //Use the two above numbers to apply sizing to all of the preferences DIVs. + setPrefsDivSizing(); + prefspanel.style.left = e.offsetLeft - parseInt(layerprefs.style.width,10); + + //If errors exist and they are NOT on the currently visible DIV (dataLayer1), + //hide the dataLayers DIV, set the tab to "Covariates", and open the appropriate + //covariate bar DIV. + showDendroSelections(); + setShowAll(); + if ((errorMsg != null) && (errorMsg[1] === "classPrefs")) { + showClassBreak(errorMsg[0]); + showClassPrefs(); + } else if ((errorMsg != null) && (errorMsg[1] === "layerPrefs")){ + showLayerPrefs(); + } else if (searchPerformed){ + searchPerformed = false; + showClassPrefs(); + } else { + showRowsColsPrefs(); + } + +} + +/********************************************************************************** + * FUNCTION - setPrefsDivSizing: The purpose of this function is to resize the + * various DIVs inside the preferences dialog. It is called when the dialog is + * first opened and whenever data layer breakpoints are added (if necessary). + **********************************************************************************/ +//TODO - This can be improved as the current sizing is not exact. +function setPrefsDivSizing() { + var rowsColsprefs = document.getElementById("rowsColsPrefs"); + var layerprefs = document.getElementById("layerPrefs"); + var classprefs = document.getElementById("classPrefs"); + var helprefs = document.getElementById("prefsPanel"); + var prefHeight = maxRows*helpRowSize; + var prefWidth = 380; + rowsColsprefs.style.width = prefWidth; + rowsColsprefs.style.height = prefHeight; + layerprefs.style.width = prefWidth; + layerprefs.style.height = prefHeight; + classprefs.style.width = prefWidth; + classprefs.style.height = prefHeight; +} + +/********************************************************************************** + * FUNCTION - showLayerPrefs: The purpose of this function is to perform the + * processing for the preferences tab when the user selects the "Data Layers" tab. + **********************************************************************************/ +function showRowsColsPrefs() { + //Turn off all tabs + hideAllPrefs(); + //Turn on layer prefs tab + var rowsColsBtn = document.getElementById("prefRowsCols_btn"); + rowsColsBtn.setAttribute('src', '/plugins/visualizations/mda_heatmap_viz/static/images/rowsColsOn.png'); + var rowsColsDiv = document.getElementById("rowsColsPrefs"); + rowsColsDiv.style.display="block"; +} + + +/********************************************************************************** + * FUNCTION - showLayerPrefs: The purpose of this function is to perform the + * processing for the preferences tab when the user selects the "Data Layers" tab. + **********************************************************************************/ +function showLayerPrefs() { + //Turn off all tabs + hideAllPrefs(); + //Turn on layer prefs tab + var layerBtn = document.getElementById("prefLayer_btn"); + layerBtn.setAttribute('src', '/plugins/visualizations/mda_heatmap_viz/static/images/dataLayersOn.png'); + var layerDiv = document.getElementById("layerPrefs"); + layerDiv.style.display="block"; +} + +/********************************************************************************** + * FUNCTION - showClassPrefs: The purpose of this function is to perform the + * processing for the preferences tab when the user selects the "Covariates" tab. + **********************************************************************************/ +function showClassPrefs() { + //Turn off all tabs + hideAllPrefs(); + //Turn on classification prefs tab + var classBtn = document.getElementById("prefClass_btn"); + classBtn.setAttribute('src', '/plugins/visualizations/mda_heatmap_viz/static/images/covariateBarsOn.png'); + var classDiv = document.getElementById("classPrefs"); + classDiv.style.display="block"; +} + +/********************************************************************************** + * FUNCTION - hideAllPrefs: The purpose of this function is to set all tabs off. It + * is called whenever a tab is clicked. All tabs are set to hidden with their + * image set to the "off" image. + **********************************************************************************/ +function hideAllPrefs() { + var classBtn = document.getElementById("prefClass_btn"); + classBtn.setAttribute('src', '/plugins/visualizations/mda_heatmap_viz/static/images/covariateBarsOff.png'); + var classDiv = document.getElementById("classPrefs"); + classDiv.style.display="none"; + var layerBtn = document.getElementById("prefLayer_btn"); + layerBtn.setAttribute('src', '/plugins/visualizations/mda_heatmap_viz/static/images/dataLayersOff.png'); + var layerDiv = document.getElementById("layerPrefs"); + layerDiv.style.display="none"; + var rowsColsBtn = document.getElementById("prefRowsCols_btn"); + rowsColsBtn.setAttribute('src', '/plugins/visualizations/mda_heatmap_viz/static/images/rowsColsOff.png'); + var rowsColsDiv = document.getElementById("rowsColsPrefs"); + rowsColsDiv.style.display="none"; +} + +/********************************************************************************** + * FUNCTION - prefsCancelButton: The purpose of this function is to perform all processing + * necessary to exit the user preferences dialog WITHOUT applying or saving any + * changes made by the user when the Cancel button is pressed on the ColorMap + * preferences dialog. Since the dataLayer colormap must be edited to add/delete + * breakpoints, the backup colormap (saved when preferences are first opened) is re- + * applied to the colorMapManager. Then the preferences DIV is retrieved and removed. + **********************************************************************************/ +function prefsCancelButton() { + if (bkpColorMap !== null) { + var colorMapMgr = heatMap.getColorMapManager(); + colorMapMgr.setColorMap("dl1", bkpColorMap); + } + var prefspanel = document.getElementById('prefsPanel'); + if (prefspanel){ + prefspanel.remove(); + } +} + +/********************************************************************************** + * FUNCTION - prefsApplyButton: The purpose of this function is to perform all processing + * necessary to reconfigure the "current" presentation of the heat map in the + * viewer when the Apply button is pressed on the ColorMap Preferences Dialog. + * First validations are performed. If errors are found, preference + * changes are NOT applied and the user is re-presented with the preferences dialog + * and the error found. If no errors are found, all changes are applied to the heatmap + * and the summary panel, detail panel, and covariate bars are redrawn. However, + * these changes are not yet permanently saved to the JSON files that are used to + * configure heat map presentation. + **********************************************************************************/ +function prefsApplyButton() { + //Perform validations of all user-entered data layer and covariate bar + //preference changes. + var errorMsg = prefsValidate(); + prefsApply(); + if (errorMsg !== null) { + prefsError(errorMsg); + } else { + prefsSuccess(); + } +} + +/********************************************************************************** + * FUNCTION - prefsSaveButton: The purpose of this function is to perform all processing + * necessary to permanently save user preference changes. This will result in + * changes to the JSON files that are used to configure heat map presentation. + **********************************************************************************/ +function prefsSaveButton() { + var mode = heatMap.mode; + var errorMsg = prefsValidate(); + prefsApply(); + if (errorMsg !== null) { + prefsError(errorMsg); + } else { + var success = heatMap.saveHeatMapProperties(); + if (success === "false") { + prefsError(["dl1", "layerPrefs", "ERROR: Preferences failed to save. Use Apply or Cancel to continue."]); + } else { + prefsSuccess(); + } + } +} + +/********************************************************************************** + * FUNCTION - prefsSuccess: The purpose of this function perform the functions + * necessary when preferences are determined to be valid. It is shared by the + * Apply and Save buttons. + **********************************************************************************/ +function prefsSuccess() { + filterVal = null; + //Remove the backup color map (used to reinstate colors if user cancels) + //and formally apply all changes to the heat map, re-draw, and exit preferences. + bkpColorMap = null; + summaryInit(); + detailInit(); + changeMode('NORMAL'); + prefsCancelButton(); +} + +/********************************************************************************** + * FUNCTION - prefsError: The purpose of this function perform the functions + * necessary when preferences are determined to be invalid. It is shared by the + * Apply and Save buttons. + **********************************************************************************/ +function prefsError(errorMsg) { + //If a validation error exists, re-present the user preferences + //dialog with the error message displayed in red. + var prefspanel = document.getElementById('prefsPanel'); + if (prefspanel){ + prefspanel.remove(); + } + editPreferences(document.getElementById('gear_btn'),errorMsg); +} + +/********************************************************************************** + * FUNCTION - prefsSuccess: The purpose of this function is to apply all user + * ColorMap preferences settings. It is shared by the Apply and Save buttons. + **********************************************************************************/ +function prefsApply() { + // Apply Row & Column Preferences + var dendrogram = heatMap.getDendrogram(); + var rowLabels = heatMap.getRowLabels(); + var rowOrder = rowLabels['order_method']; + if (rowOrder === "Hierarchical") { + var rowDendroShowVal = document.getElementById("rowDendroShowPref").value; + dendrogram['row_dendro_show'] = rowDendroShowVal; + dendrogram['row_dendro_height'] = document.getElementById("rowDendroHeightPref").value; + } + var colLabels = heatMap.getColLabels(); + var colOrder = colLabels['order_method']; + if (colOrder === "Hierarchical") { + var colDendroShowVal = document.getElementById("colDendroShowPref").value; + dendrogram['col_dendro_show'] = colDendroShowVal; + dendrogram['col_dendro_height'] = document.getElementById("colDendroHeightPref").value; + } + // Apply Covariate Bar Preferences + var classBars = heatMap.getClassifications(); + for (var key in classBars){ + var showElement = document.getElementById(key+"_showPref"); + var heightElement = document.getElementById(key+"_heightPref"); + if (filterShow(key)) { + heatMap.setClassificationPrefs(key,showElement.checked,heightElement.value); + } else { + heatMap.setClassificationPrefs(key,false,15); + } + prefsApplyBreaks(classBars[key].colorScheme,"covariate",filterShow(key)); + } + // Apply Data Layer Preferences + //TODO - Future loop for data layers + prefsApplyBreaks("dl1","datalayer",true); +} + +/********************************************************************************** + * FUNCTION - prefsValidate: The purpose of this function is to validate all user + * changes to the heatmap properties. When the very first error is found, an error + * message (string array containing error information) is created and returned to + * the prefsApply function. + **********************************************************************************/ +function prefsValidate() { + var classBars = heatMap.getClassifications(); + var errorMsg = null; + //Loop thru all covariate classfication bars validating all break colors + for (var key in classBars){ + if (filterShow(key)) { + var showElement = document.getElementById(key+"_showPref"); + var heightElement = document.getElementById(key+"_heightPref"); + errorMsg = prefsValidateBreakColors(classBars[key].colorScheme,"classPrefs"); + if (errorMsg !== null) break; + } + } + //Validate all breakpoints and colors for the main data layer + if (errorMsg === null) { + //TODO: currently only processing for data layer 1. This will require modification + // when new data layers (e.g. flicks) are added to the heatmap. + errorMsg = prefsValidateBreakPoints("dl1","layerPrefs"); + if (errorMsg === null) { + errorMsg = prefsValidateBreakColors("dl1","layerPrefs"); + } + } + return errorMsg; +} + +/********************************************************************************** + * FUNCTION - prefsValidateBreakPoints: The purpose of this function is to validate + * all user breakpoint and color changes to heatmap data layer properties. When the + * first error is found, an error message (string array containing error information) + * is created and returned to the prefsApply function. + **********************************************************************************/ +function prefsValidateBreakPoints(colorMapName,prefPanel) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var dupeBreak = false; + var breakOrder = false; + var prevBreakValue = -99999; + var errorMsg = null; + //Loop thru colormap thresholds and validate for order and duplicates + for (var i = 0; i < thresholds.length; i++) { + var breakElement = document.getElementById(colorMapName+"_breakPt"+i+"_breakPref"); + //If current breakpoint is not greater than previous, throw order error + if (parseInt(breakElement.value) < prevBreakValue) { + breakOrder = true; + break; + } + //Loop thru thresholds, skipping current element, searching for a match to the + //current selection. If found, throw duplicate error + for (var j = 0; j < thresholds.length; j++) { + var be = document.getElementById(colorMapName+"_breakPt"+j+"_breakPref"); + if (i != j) { + if (breakElement.value === be.value) { + dupeBreak = true; + break; + } + } + } + } + if (breakOrder) { + errorMsg = [colorMapName, prefPanel, "ERROR: Data layer breakpoints must be in order"]; + } + if (dupeBreak) { + errorMsg = [colorMapName, prefPanel, "ERROR: Duplicate data layer breakpoint found above"]; + } + return errorMsg; +} + +/********************************************************************************** + * FUNCTION - prefsValidateBreakColors: The purpose of this function is to validate + * all user color changes to heatmap classification and data layer properties. When the + * first error is found, an error message (string array containing error information) + * is created and returned to the prefsApply function. + **********************************************************************************/ +function prefsValidateBreakColors(colorMapName,prefPanel) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var dupeColor = false; + for (var i = 0; i < colors.length; i++) { + var colorElement = document.getElementById(colorMapName+"_color"+i+"_colorPref"); + for (var j = 0; j < thresholds.length; j++) { + var ce = document.getElementById(colorMapName+"_color"+j+"_colorPref"); + if (i != j) { + if (colorElement.value === ce.value) { + dupeColor = true; + break; + } + } + } + } + if (dupeColor) { + return [colorMapName, prefPanel, "ERROR: Duplicate color setting found above"]; + } + return null; +} + +/********************************************************************************** + * FUNCTION - prefsApplyBreaks: The purpose of this function is to apply all + * user entered changes to colors and breakpoints. + **********************************************************************************/ +function prefsApplyBreaks(colorMapName, colorMapType, show) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + if (show) { + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var newColors = getNewBreakColors(colorMapName); + colorMap.setColors(newColors); + if (colorMapType === "datalayer") { + var newThresholds = getNewBreakThresholds(colorMapName); + colorMap.setThresholds(newThresholds); + } + var missingElement = document.getElementById(colorMapName+"_missing_colorPref"); + colorMap.setMissingColor(missingElement.value); + var colorMapMgr = heatMap.getColorMapManager(); + colorMapMgr.setColorMap(colorMapName, colorMap); + } +} + +/********************************************************************************** + * FUNCTION - getNewBreakColors: The purpose of this function is to grab all user + * color entries for a given colormap and place them on a string array. It will + * iterate thru the screen elements, pulling the current color entry for each + * element, placing it in a new array, and returning that array. This function is + * called by the prefsApplyBreaks function. It is ALSO called from the data layer + * addLayerBreak and deleteLayerBreak functions with parameters passed in for + * the position to add/delete and the action to be performed (add/delete). + **********************************************************************************/ +function getNewBreakColors(colorMapName, pos, action) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var newColors = []; + for (var j = 0; j < thresholds.length; j++) { + var colorElement = document.getElementById(colorMapName+"_color"+j+"_colorPref"); + //If being called from addLayerBreak or deleteLayerBreak + if (typeof pos !== 'undefined') { + if (action === "add") { + newColors.push(colorElement.value); + if (j === pos) { + newColors.push(colorElement.value); + } + } else { + if (j !== pos) { + newColors.push(colorElement.value); + } + } + } else { + newColors.push(colorElement.value); + } + } + return newColors; +} + +/********************************************************************************** + * FUNCTION - getNewBreakThresholds: The purpose of this function is to grab all user + * data layer breakpoint entries for a given colormap and place them on a string array. + * It will iterate thru the screen elements, pulling the current breakpoint entry for each + * element, placing it in a new array, and returning that array. This function is + * called by the prefsApplyBreaks function (only for data layers). It is ALSO called + * from the data layer addLayerBreak and deleteLayerBreak functions with parameters + * passed in for the position to add/delete and the action to be performed (add/delete). + **********************************************************************************/ +function getNewBreakThresholds(colorMapName, pos, action) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var newThresholds = []; + for (var j = 0; j < thresholds.length; j++) { + var breakElement = document.getElementById(colorMapName+"_breakPt"+j+"_breakPref"); + if (typeof pos !== 'undefined') { + if (action === "add") { + newThresholds.push(breakElement.value); + if (j === pos) { + newThresholds.push(breakElement.value); + } + } else { + if (j !== pos) { + newThresholds.push(breakElement.value); + } + } + } else { + newThresholds.push(breakElement.value); + } + } + return newThresholds; +} + +/*=================================================================================== + * DATA LAYER PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present heat map data layer + * configuration options: + * - setupLayerPrefs + * - setupLayerBreaks + * - setupLayerPrefs + * - addLayerBreak + * - deleteLayerBreak + * - reloadLayerBreaksColorMap + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - setupLayerPrefs: The purpose of this function is to construct a DIV + * panel containing all data layer preferences. A dropdown list containing all + * data layers is presented and individual DIVs for each data layer, containing + * breakpoints/colors, are added. + **********************************************************************************/ +function setupLayerPrefs(e, prefprefs){ + var layerprefs = getDivElement("layerPrefs"); + var prefContents = document.createElement("TABLE"); + var colorMapName = "dl1"; + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + prefContents.insertRow().innerHTML = formatBlankRow(); + // TODO Future: primary and flick data layers in dropdown + var dlSelect = "" + setTableRow(prefContents,["Data Layer: ", dlSelect]); + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + layerprefs.appendChild(prefContents); + var breakprefs = setupLayerBreaks(e, colorMapName, colorMapName); + breakprefs.style.display="block"; + breakprefs.style.width = 300; + layerprefs.appendChild(breakprefs); + maxRows = maxRows+3; + // TODO Future: loop for primary and flick data layers + return layerprefs; +} + +/********************************************************************************** + * FUNCTION - setupLayerBreaks: The purpose of this function is to construct a DIV + * containing a list of breakpoints/colors for a given matrix data layer. + **********************************************************************************/ +function setupLayerBreaks(e, mapName, barName, barType){ + var classBars = heatMap.getClassifications(); + var colorMap = heatMap.getColorMapManager().getColorMap(mapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var helpprefs = getDivElement("breakPrefs_"+mapName); + var prefContents = document.createElement("TABLE"); + var rowCtr = 0; + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Breakpoint", ""+"Color"+""," "]); + rowCtr++; + for (var j = 0; j < thresholds.length; j++) { + var threshold = thresholds[j]; + var color = colors[j]; + var threshId = mapName+"_breakPt"+j; + var colorId = mapName+"_color"+j; + var breakPtInput = ""; + var colorInput = ""; + var addButton = "Add Breakpoint" + var delButton = "Remove Breakpoint" + if (j === 0) { + setTableRow(prefContents, [breakPtInput, colorInput, addButton]); + } else { + setTableRow(prefContents, [breakPtInput, colorInput, addButton, delButton]); + } + rowCtr++; + } + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Missing Color:", ""]); + rowCtr++; + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + helpprefs.style.height = rowCtr; + helpprefs.style.width = 30; + helpprefs.appendChild(prefContents); + return helpprefs; +} + +/********************************************************************************** + * FUNCTION - addLayerBreak: The purpose of this function is to add a breakpoint + * row to a data layer colormap. A new row is created using the preceding row as a + * template (i.e. breakpt value and color same as row clicked on). + **********************************************************************************/ +function addLayerBreak(pos,colorMapName) { + //Retrieve colormap for data layer + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var newThresholds = getNewBreakThresholds(colorMapName, pos,"add"); + var newColors = getNewBreakColors(colorMapName, pos,"add"); + //Calculate new size of data layers panel and reset size of the + // entire preferences dialog (if necessary) + var layerRows = newThresholds.length+helpRowSize; + maxRows = Math.max(maxRows,layerRows); + setPrefsDivSizing(); + //Apply new arrays for thresholds and colors to the datalayer + //and reload the colormap. + colorMap.setThresholds(newThresholds); + colorMap.setColors(newColors); + reloadLayerBreaksColorMap(colorMapName, colorMap); +} + +/********************************************************************************** + * FUNCTION - deleteLayerBreak: The purpose of this function is to remove a breakpoint + * row from a data layer colormap. + **********************************************************************************/ +function deleteLayerBreak(pos,colorMapName) { + var colorMap = heatMap.getColorMapManager().getColorMap(colorMapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var newThresholds = getNewBreakThresholds(colorMapName, pos,"delete"); + var newColors = getNewBreakColors(colorMapName, pos,"delete"); + //Apply new arrays for thresholds and colors to the datalayer + //and reload the colormap. + colorMap.setThresholds(newThresholds); + colorMap.setColors(newColors); + reloadLayerBreaksColorMap(colorMapName, colorMap); +} + +/********************************************************************************** + * FUNCTION - reloadLayerBreaksColorMap: The purpose of this function is to reload + * the colormap for a given data layer. The add/deleteLayerBreak methods call + * this common function. The layerPrefs DIV is retrieved and the setupLayerBreaks + * method is called, passing in the newly edited colormap. + **********************************************************************************/ +function reloadLayerBreaksColorMap(colorMapName, colorMap) { + var e = document.getElementById('gear_btn') + var colorMapMgr = heatMap.getColorMapManager(); + colorMapMgr.setColorMap(colorMapName, colorMap); + var breakPrefs = document.getElementById('breakPrefs_'+colorMapName); + if (breakPrefs){ + breakPrefs.remove(); + } + var layerprefs = getDivElement("layerPrefs"); + var breakPrefs = setupLayerBreaks(e, colorMapName, colorMapName); + breakPrefs.style.display="block"; + breakPrefs.style.width = 300; + layerPrefs.appendChild(breakPrefs); +} + +/*=================================================================================== + * COVARIATE CLASSIFICATION PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present heat map covariate classfication + * bar configuration options: + * - setupClassPrefs + * - setupClassBreaks + * - setupAllClassesPrefs + * - showAllBars + * - setShowAll + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - setupClassPrefs: The purpose of this function is to construct a DIV + * panel containing all covariate bar preferences. A dropdown list containing all + * covariate classification bars is presented and individual DIVs for each data layer, + * containing breakpoints/colors, are added. Additionally, a "front panel" DIV is + * created for "ALL" classification bars that contains preferences that are global + * to all of the individual bars. + **********************************************************************************/ +function setupClassPrefs(e, prefprefs){ + var classBars = heatMap.getClassifications(); + var classprefs = getDivElement("classPrefs"); + var prefContents = document.createElement("TABLE"); + prefContents.insertRow().innerHTML = formatBlankRow(); + var filterInput = ""; + var filterButton = "Search Covariates"; + if (filterVal != null) { + var filterInput = ""; + var filterButton = "Search Covariates"; + } + var searchClasses = filterInput+"  "+filterButton; + setTableRow(prefContents,[searchClasses], 4, 'right'); + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + var classSelect = "" + setTableRow(prefContents,["Covariate Bar: ", classSelect]); + prefContents.insertRow().innerHTML = formatBlankRow(); + classprefs.appendChild(prefContents); + var i = 0; + for (var key in classBars){ + if (filterShow(key)) { + var breakprefs = setupClassBreaks(e, classBars[key].colorScheme, key); + breakprefs.style.display="none"; + breakprefs.style.width = 300; + classprefs.appendChild(breakprefs); + } + i++; + } + // Append a DIV panel for all of the covariate class bars + var allPrefs = setupAllClassesPrefs(e); + allPrefs.style.display="block"; + classprefs.appendChild(allPrefs); + return classprefs; +} + +/********************************************************************************** + * FUNCTION - setupClassBreaks: The purpose of this function is to construct a DIV + * containing a list of all covariate bars with informational data and user preferences + * that are common to all bars (show/hide and size). + **********************************************************************************/ +function setupAllClassesPrefs(e){ + var allprefs = getDivElement("breakPrefs_ALL"); + var prefContents = document.createElement("TABLE"); + var rowCtr = 0; + prefContents.insertRow().innerHTML = formatBlankRow(); + var colShowAll = " "; + setTableRow(prefContents,[""+"Classification"+"", ""+"Position"+"", colShowAll+""+"Show"+"", ""+"Height"+""]); + rowCtr=2; + var classBars = heatMap.getClassifications(); + var checkState = true; + for (var key in classBars){ + if (filterShow(key)) { + var colShow = "        "; + var colHeight = ""; + setTableRow(prefContents,[key,toTitleCase(classBars[key].position),colShow,colHeight]); + rowCtr++; + } + } + allprefs.appendChild(prefContents); + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + return allprefs; +} + +/********************************************************************************** + * FUNCTION - setupClassBreaks: The purpose of this function is to construct a DIV + * containing a set informational data and a list of categories/colors for a given + * covariate classfication bar. + **********************************************************************************/ +function setupClassBreaks(e, mapName, barName){ + var classBars = heatMap.getClassifications(); + var colorMap = heatMap.getColorMapManager().getColorMap(mapName); + var thresholds = colorMap.getThresholds(); + var colors = colorMap.getColors(); + var helpprefs = getDivElement("breakPrefs_"+mapName); + var prefContents = document.createElement("TABLE"); + var rowCtr = 0; + prefContents.insertRow().innerHTML = formatBlankRow(); + var colShow = ""; + var colHeight = ""; + var pos = toTitleCase(classBars[barName].position); + var typ = toTitleCase(colorMap.getType()); + if (classBars[barName].position == "row") { + pos = "Row"; + } + setTableRow(prefContents,["Bar Position: ",""+pos+""]); + setTableRow(prefContents,["Bar Type: ",""+typ+""]); + prefContents.insertRow().innerHTML = formatBlankRow(); + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr = rowCtr+4; + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Category", ""+"Color"+""]); + rowCtr++; + for (var j = 0; j < thresholds.length; j++) { + var threshold = thresholds[j]; + var color = colors[j]; + var threshId = mapName+"_breakPt"+j; + var colorId = mapName+"_color"+j; + var colorInput = ""; + setTableRow(prefContents, [threshold, colorInput]); + rowCtr++; + } + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents, ["Missing Color:", ""]); + rowCtr++; + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + helpprefs.style.height = rowCtr; + helpprefs.style.width = 30; + helpprefs.appendChild(prefContents); + return helpprefs; +} + +/********************************************************************************** + * FUNCTION - showAllBars: The purpose of this function is to set the condition of + * the "show" checkbox for all covariate bars on the covariate bars tab of the user + * preferences dialog. These checkboxes are located on the DIV that is visible when + * the ALL entry of the covariate dropdown is selected. Whenever a user checks the + * show all box, all other boxes are checked. + **********************************************************************************/ +function showAllBars(){ + var classBars = heatMap.getClassifications(); + var showAllBox = document.getElementById('all_showPref'); + var checkState = false; + if (showAllBox.checked) { + checkState = true; + } + for (var key in classBars){ + if (filterShow(key)) { + var colShow = document.getElementById(key+'_showPref'); + colShow.checked = checkState; + } + } + return; +} + +/********************************************************************************** + * FUNCTION - setShowAll: The purpose of this function is to set the condition of + * the "show all" checkbox on the covariate bars tab of the user preferences dialog. + * This checkbox is located on the DIV that is visible when the ALL entry of the + * covariate dropdown is selected. If a user un-checks a single box in the list of + * covariate bars, the show all box is un-checked. Conversely, if a user checks a box + * resulting in all of the boxes being selected, the show all box will be checked. + **********************************************************************************/ +function setShowAll(){ + var classBars = heatMap.getClassifications(); + var checkState = true; + for (var key in classBars){ + var colShow = document.getElementById(key+'_showPref'); + if (filterShow(key)) { + if (!colShow.checked) { + checkState = false; + break; + } + } + } + var showAllBox = document.getElementById('all_showPref'); + showAllBox.checked = checkState; + return; +} + + +/********************************************************************************** + * FUNCTION - showClassBreak: The purpose of this function is to show the + * appropriate classification bar panel based upon the user selection of the + * covariate dropdown on the covariates tab of the preferences screen. This + * function is also called when an error is trappped, opening the covariate DIV + * that contains the erroneous data entry. + **********************************************************************************/ +function showClassBreak(selClass) { + var classBtn = document.getElementById("classPref_list"); + if (typeof selClass != 'undefined') { + classBtn.value = selClass; + } + for (var i=0; i= 0) { + filterShow = true; + } + } else { + filterShow = true; + } + return filterShow; + +} + +/*=================================================================================== + * ROW COLUMN PREFERENCE PROCESSING FUNCTIONS + * + * The following functions are utilized to present heat map covariate classfication + * bar configuration options: + * - setupRowColPrefs + * - showDendroSelections + * - dendroRowShowChange + * - dendroColShowChange + =================================================================================*/ + +/********************************************************************************** + * FUNCTION - setupRowColPrefs: The purpose of this function is to construct a DIV + * panel containing all row & col preferences. Two sections are presented, one for + * rows and the other for cols. Informational data begins each section and + * properties for modifying the appearance of row/col dendograms appear at the end. + **********************************************************************************/ +function setupRowColPrefs(e, prefprefs){ + var rowcolprefs = getDivElement("rowsColsPrefs"); + var prefContents = document.createElement("TABLE"); + prefContents.insertRow().innerHTML = formatBlankRow(); + setTableRow(prefContents,["ROW INFORMATION:"], 2); + prefContents.insertRow().innerHTML = formatBlankRow(); + var rowLabels = heatMap.getRowLabels(); + var dendrogram = heatMap.getDendrogram(); + var rowOrder = rowLabels['order_method']; + setTableRow(prefContents,["  Labels Type:",rowLabels['label_type']]); + setTableRow(prefContents,["  Ordering Method:",rowOrder]); + var rowCtr = 5; + var dendroShowOptions = ""; + var dendroHeightOptions = ""; + if (rowOrder === "Hierarchical") { + setTableRow(prefContents,["  Agglomeration Method:",rowLabels['agglomeration_method']]); + rowCtr++; + setTableRow(prefContents,["  Distance Metric:",rowLabels['distance_metric']]); + rowCtr++; + var rowDendroSelect = "" + rowDendroHeightSelect = rowDendroHeightSelect+dendroHeightOptions; + setTableRow(prefContents,["  Dendrogram Height:",rowDendroHeightSelect]); + rowCtr++; + } + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + prefContents.insertRow().innerHTML = formatBlankRow(); + rowCtr++; + setTableRow(prefContents,["COLUMN INFORMATION:"], 2); + rowCtr++; + prefContents.insertRow().innerHTML = formatBlankRow(); + + var colLabels = heatMap.getColLabels(); + var colOrder = colLabels['order_method']; + setTableRow(prefContents,["  Labels Type:",colLabels['label_type']]); + rowCtr++; + setTableRow(prefContents,["  Ordering Method:",colOrder]); + rowCtr++; + if (colOrder === "Hierarchical") { + setTableRow(prefContents,["  Agglomeration Method:",colLabels['agglomeration_method']]); + rowCtr++; + setTableRow(prefContents,["  Distance Metric:",colLabels['distance_metric']]); + rowCtr++; + var colDendroShowSelect = "" + colDendroHeightSelect = colDendroHeightSelect+dendroHeightOptions; + setTableRow(prefContents,["  Show Dendrogram:",colDendroShowSelect]); + rowCtr++; + setTableRow(prefContents,["  Dendrogram Height:",colDendroHeightSelect]); + rowCtr++; + } + if (rowCtr > maxRows) { + maxRows = rowCtr; + } + rowcolprefs.appendChild(prefContents); + return rowcolprefs; +} + +/********************************************************************************** + * FUNCTION - showDendroSelections: The purpose of this function is to set the + * states of the row/column dendrogram show and height preferences. + **********************************************************************************/ +function showDendroSelections() { + var dendrogram = heatMap.getDendrogram(); + var rowLabels = heatMap.getRowLabels(); + var rowOrder = rowLabels['order_method']; + if (rowOrder === "Hierarchical") { + var dendroShowVal = dendrogram['row_dendro_show']; + document.getElementById("rowDendroShowPref").value = dendroShowVal; + var rowHeightPref = document.getElementById("rowDendroHeightPref"); + if (dendroShowVal === 'NONE') { + var opt = rowHeightPref.options[6]; + if (typeof opt != 'undefined') { + rowHeightPref.options[6].remove(); + } + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + rowHeightPref.add(option); + rowHeightPref.disabled = true; + } + rowHeightPref.value = dendrogram['row_dendro_height']; + } + var colLabels = heatMap.getColLabels(); + var colOrder = colLabels['order_method']; + if (colOrder === "Hierarchical") { + var dendroShowVal = dendrogram['col_dendro_show']; + document.getElementById("colDendroShowPref").value = dendroShowVal; + var colHeightPref = document.getElementById("colDendroHeightPref"); + if (dendroShowVal === 'NONE') { + var opt = colHeightPref.options[6]; + if (typeof opt != 'undefined') { + colHeightPref.options[6].remove(); + } + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + colHeightPref.add(option); + colHeightPref.disabled = true; + } + colHeightPref.value = dendrogram['col_dendro_height']; + } +} + +/********************************************************************************** + * FUNCTION - dendroRowShowChange: The purpose of this function is to respond to + * a change event on the show row dendrogram dropdown. If the change is to Hide, + * the row dendro height is set to 10 and the dropdown disabled. If the change is to + * one of the 2 Show options AND was previously Hide, set height to the default + * value of 100 and enable the dropdown. + **********************************************************************************/ +function dendroRowShowChange() { + var newValue = document.getElementById("rowDendroShowPref").value; + var rowHeightPref = document.getElementById("rowDendroHeightPref"); + if (newValue === 'NONE') { + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + rowHeightPref.add(option); + rowHeightPref.value = '10'; + rowHeightPref.disabled = true; + } else if (rowHeightPref.disabled) { + var opt = rowHeightPref.options[6]; + if (typeof opt != 'undefined') { + rowHeightPref.options[6].remove(); + } + rowHeightPref.value = '100'; + rowHeightPref.disabled = false; + } +} + +/********************************************************************************** + * FUNCTION - dendroRowShowChange: The purpose of this function is to respond to + * a change event on the show row dendrogram dropdown. If the change is to Hide, + * the row dendro height is set to 10 and the dropdown disabled. If the change is to + * one of the 2 Show options AND was previously Hide, set height to the default + * value of 100 and enable the dropdown. + **********************************************************************************/ +function dendroColShowChange() { + var newValue = document.getElementById("colDendroShowPref").value; + var colHeightPref = document.getElementById("colDendroHeightPref"); + if (newValue === 'NONE') { + var option = document.createElement("option"); + option.text = "NA"; + option.value = '10'; + colHeightPref.add(option); + colHeightPref.value = '10'; + colHeightPref.disabled = true; + } else if (colHeightPref.disabled) { + var opt = colHeightPref.options[6]; + if (typeof opt != 'undefined') { + colHeightPref.options[6].remove(); + } + colHeightPref.value = '100'; + colHeightPref.disabled = false; + } +} + + + diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/custom.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/custom.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,42 @@ +/* + * TO DO: give custom.js its own namespace so it can't modify variables used outside + * Only outside function/variable it should be able to access right now is addLinkout and the inputs of each custom function (label text or index) + */ + +addLinkout("Search Google", "Samples", "labels", searchGoogle); + +addLinkout("Search Google", "Genes", "labels", searchGoogle); +addLinkout("Search GeneCards", "Genes", "labels", searchGeneCards); +addLinkout("Search PubMed for All", "Genes", "labels", searchPubMedForAll); +addLinkout("Search PubMed for Any", "Genes", "labels", searchPubMedForAny); + +function searchGoogle(selection, axis){ + window.open('https://www.google.com/#q=' + selection.join("+")); +} + +function searchGeneCards(labels){ + var searchTerm = ''; + for (var i = 0; i < labels.length; i++){ + searchTerm += "+" + labels[i].split("|")[0]; + } + searchTerm = searchTerm.substring(1); + window.open('http://www.genecards.org/Search/Keyword?queryString=' + searchTerm); +} + +function searchPubMedForAll(labels){ + var searchTerm = ''; + for (var i = 0; i < labels.length; i++){ + searchTerm += "+AND+" + labels[i].split("|")[0]; + } + searchTerm = searchTerm.substring(5); + window.open("http://www.ncbi.nlm.nih.gov/pubmed/?term=" + searchTerm) +} + +function searchPubMedForAny(labels){ + var searchTerm = ''; + for (var i = 0; i < labels.length; i++){ + searchTerm += "+OR+" + labels[i].split("|")[0]; + } + searchTerm = searchTerm.substring(4); + window.open("http://www.ncbi.nlm.nih.gov/pubmed/?term=" + searchTerm) +} \ No newline at end of file diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/lib/deflate.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/lib/deflate.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,2060 @@ +/* + Copyright (c) 2013 Gildas Lormeau. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program is based on JZlib 1.0.2 ymnk, JCraft,Inc. + * JZlib is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +(function(global) { + "use strict"; + + // Global + + var MAX_BITS = 15; + var D_CODES = 30; + var BL_CODES = 19; + + var LENGTH_CODES = 29; + var LITERALS = 256; + var L_CODES = (LITERALS + 1 + LENGTH_CODES); + var HEAP_SIZE = (2 * L_CODES + 1); + + var END_BLOCK = 256; + + // Bit length codes must not exceed MAX_BL_BITS bits + var MAX_BL_BITS = 7; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + var REP_3_6 = 16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + var REPZ_3_10 = 17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + var REPZ_11_138 = 18; + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit + // length codes. + + var Buf_size = 8 * 2; + + // JZlib version : "1.0.2" + var Z_DEFAULT_COMPRESSION = -1; + + // compression strategy + var Z_FILTERED = 1; + var Z_HUFFMAN_ONLY = 2; + var Z_DEFAULT_STRATEGY = 0; + + var Z_NO_FLUSH = 0; + var Z_PARTIAL_FLUSH = 1; + var Z_FULL_FLUSH = 3; + var Z_FINISH = 4; + + var Z_OK = 0; + var Z_STREAM_END = 1; + var Z_NEED_DICT = 2; + var Z_STREAM_ERROR = -2; + var Z_DATA_ERROR = -3; + var Z_BUF_ERROR = -5; + + // Tree + + // see definition of array dist_code below + var _dist_code = [ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, 18, 18, 19, 19, + 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 ]; + + function Tree() { + var that = this; + + // dyn_tree; // the dynamic tree + // max_code; // largest code with non zero frequency + // stat_desc; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit + // length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + function gen_bitlen(s) { + var tree = that.dyn_tree; + var stree = that.stat_desc.static_tree; + var extra = that.stat_desc.extra_bits; + var base = that.stat_desc.extra_base; + var max_length = that.stat_desc.max_length; + var h; // heap index + var n, m; // iterate over the tree elements + var bits; // bit length + var xbits; // extra bits + var f; // frequency + var overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= MAX_BITS; bits++) + s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap + + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1] * 2 + 1] + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n * 2 + 1] = bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > that.max_code) + continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) + xbits = extra[n - base]; + f = tree[n * 2]; + s.opt_len += f * (bits + xbits); + if (stree) + s.static_len += f * (stree[n * 2 + 1] + xbits); + } + if (overflow === 0) + return; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { + bits = max_length - 1; + while (s.bl_count[bits] === 0) + bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits + 1] += 2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } while (overflow > 0); + + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > that.max_code) + continue; + if (tree[m * 2 + 1] != bits) { + s.opt_len += (bits - tree[m * 2 + 1]) * tree[m * 2]; + tree[m * 2 + 1] = bits; + } + n--; + } + } + } + + // Reverse the first len bits of a code, using straightforward code (a + // faster + // method would use a table) + // IN assertion: 1 <= len <= 15 + function bi_reverse(code, // the value to invert + len // its bit length + ) { + var res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + function gen_codes(tree, // the tree to decorate + max_code, // largest code with non zero frequency + bl_count // number of codes at each bit length + ) { + var next_code = []; // next code value for each + // bit length + var code = 0; // running code value + var bits; // bit index + var n; // code index + var len; + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = ((code + bl_count[bits - 1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + // Assert (code + bl_count[MAX_BITS]-1 == (1<= 1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node = elems; // next internal node of the tree + do { + // n = node of least frequency + n = s.heap[1]; + s.heap[1] = s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m = s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node * 2] = (tree[n * 2] + tree[m * 2]); + s.depth[node] = Math.max(s.depth[n], s.depth[m]) + 1; + tree[n * 2 + 1] = tree[m * 2 + 1] = node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, that.max_code, s.bl_count); + }; + + } + + Tree._length_code = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 ]; + + Tree.base_length = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0 ]; + + Tree.base_dist = [ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, + 24576 ]; + + // Mapping from a distance to a distance code. dist is the distance - 1 and + // must not have side effects. _dist_code[256] and _dist_code[257] are never + // used. + Tree.d_code = function(dist) { + return ((dist) < 256 ? _dist_code[dist] : _dist_code[256 + ((dist) >>> 7)]); + }; + + // extra bits for each length code + Tree.extra_lbits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 ]; + + // extra bits for each distance code + Tree.extra_dbits = [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 ]; + + // extra bits for each bit length code + Tree.extra_blbits = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 ]; + + Tree.bl_order = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; + + // StaticTree + + function StaticTree(static_tree, extra_bits, extra_base, elems, max_length) { + var that = this; + that.static_tree = static_tree; + that.extra_bits = extra_bits; + that.extra_base = extra_base; + that.elems = elems; + that.max_length = max_length; + } + + StaticTree.static_ltree = [ 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8, 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8, 2, 8, + 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8, 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8, 10, 8, 138, 8, 74, 8, 202, 8, 42, + 8, 170, 8, 106, 8, 234, 8, 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8, 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8, + 22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8, 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8, 30, 8, 158, 8, 94, 8, + 222, 8, 62, 8, 190, 8, 126, 8, 254, 8, 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8, 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113, + 8, 241, 8, 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8, 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8, 5, 8, 133, 8, + 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8, 13, 8, 141, 8, 77, 8, 205, 8, 45, 8, + 173, 8, 109, 8, 237, 8, 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8, 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9, + 51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9, 43, 9, 299, 9, 171, 9, + 427, 9, 107, 9, 363, 9, 235, 9, 491, 9, 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9, 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379, + 9, 251, 9, 507, 9, 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9, 23, + 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9, 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9, 15, 9, 271, 9, 143, 9, + 399, 9, 79, 9, 335, 9, 207, 9, 463, 9, 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9, + 223, 9, 479, 9, 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9, 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7, 8, 7, 72, 7, + 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7, 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8, + 99, 8, 227, 8 ]; + + StaticTree.static_dtree = [ 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5, 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5, 1, 5, 17, 5, 9, 5, + 25, 5, 5, 5, 21, 5, 13, 5, 29, 5, 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 ]; + + StaticTree.static_l_desc = new StaticTree(StaticTree.static_ltree, Tree.extra_lbits, LITERALS + 1, L_CODES, MAX_BITS); + + StaticTree.static_d_desc = new StaticTree(StaticTree.static_dtree, Tree.extra_dbits, 0, D_CODES, MAX_BITS); + + StaticTree.static_bl_desc = new StaticTree(null, Tree.extra_blbits, 0, BL_CODES, MAX_BL_BITS); + + // Deflate + + var MAX_MEM_LEVEL = 9; + var DEF_MEM_LEVEL = 8; + + function Config(good_length, max_lazy, nice_length, max_chain, func) { + var that = this; + that.good_length = good_length; + that.max_lazy = max_lazy; + that.nice_length = nice_length; + that.max_chain = max_chain; + that.func = func; + } + + var STORED = 0; + var FAST = 1; + var SLOW = 2; + var config_table = [ new Config(0, 0, 0, 0, STORED), new Config(4, 4, 8, 4, FAST), new Config(4, 5, 16, 8, FAST), new Config(4, 6, 32, 32, FAST), + new Config(4, 4, 16, 16, SLOW), new Config(8, 16, 32, 32, SLOW), new Config(8, 16, 128, 128, SLOW), new Config(8, 32, 128, 256, SLOW), + new Config(32, 128, 258, 1024, SLOW), new Config(32, 258, 258, 4096, SLOW) ]; + + var z_errmsg = [ "need dictionary", // Z_NEED_DICT + // 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "",// Z_VERSION_ERROR (-6) + "" ]; + + // block not completed, need more input or more output + var NeedMore = 0; + + // block flush performed + var BlockDone = 1; + + // finish started, need only more output at next deflate + var FinishStarted = 2; + + // finish done, accept no more input or output + var FinishDone = 3; + + // preset dictionary flag in zlib header + var PRESET_DICT = 0x20; + + var INIT_STATE = 42; + var BUSY_STATE = 113; + var FINISH_STATE = 666; + + // The deflate compression method + var Z_DEFLATED = 8; + + var STORED_BLOCK = 0; + var STATIC_TREES = 1; + var DYN_TREES = 2; + + var MIN_MATCH = 3; + var MAX_MATCH = 258; + var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + + function smaller(tree, n, m, depth) { + var tn2 = tree[n * 2]; + var tm2 = tree[m * 2]; + return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m])); + } + + function Deflate() { + + var that = this; + var strm; // pointer back to this zlib stream + var status; // as the name implies + // pending_buf; // output still pending + var pending_buf_size; // size of pending_buf + // pending_out; // next pending byte to output to the stream + // pending; // nb of bytes in the pending buffer + var method; // STORED (for zip only) or DEFLATED + var last_flush; // value of flush param for previous deflate call + + var w_size; // LZ77 window size (32K by default) + var w_bits; // log2(w_size) (8..16) + var w_mask; // w_size - 1 + + var window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + var window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + var prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + var head; // Heads of the hash chains or NIL. + + var ins_h; // hash index of string to be inserted + var hash_size; // number of elements in hash table + var hash_bits; // log2(hash_size) + var hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + var hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + var block_start; + + var match_length; // length of best match + var prev_match; // previous match + var match_available; // set if previous match exists + var strstart; // start of string to insert + var match_start; // start of matching string + var lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + var prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + var max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + var max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + var level; // compression level (1..9) + var strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + var good_match; + + // Stop searching when current match exceeds this + var nice_match; + + var dyn_ltree; // literal and length tree + var dyn_dtree; // distance tree + var bl_tree; // Huffman tree for bit lengths + + var l_desc = new Tree(); // desc for literal tree + var d_desc = new Tree(); // desc for distance tree + var bl_desc = new Tree(); // desc for bit length tree + + // that.heap_len; // number of elements in the heap + // that.heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + that.depth = []; + + var l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + var lit_bufsize; + + var last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + var d_buf; // index of pendig_buf + + // that.opt_len; // bit length of current block with optimal trees + // that.static_len; // bit length of current block with static trees + var matches; // number of string matches in current block + var last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + var bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + var bi_valid; + + // number of codes at each bit length for an optimal tree + that.bl_count = []; + + // heap used to build the Huffman trees + that.heap = []; + + dyn_ltree = []; + dyn_dtree = []; + bl_tree = []; + + function lm_init() { + var i; + window_size = 2 * w_size; + + head[hash_size - 1] = 0; + for (i = 0; i < hash_size - 1; i++) { + head[i] = 0; + } + + // Set the default configuration parameters: + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + + strstart = 0; + block_start = 0; + lookahead = 0; + match_length = prev_length = MIN_MATCH - 1; + match_available = 0; + ins_h = 0; + } + + function init_block() { + var i; + // Initialize the trees. + for (i = 0; i < L_CODES; i++) + dyn_ltree[i * 2] = 0; + for (i = 0; i < D_CODES; i++) + dyn_dtree[i * 2] = 0; + for (i = 0; i < BL_CODES; i++) + bl_tree[i * 2] = 0; + + dyn_ltree[END_BLOCK * 2] = 1; + that.opt_len = that.static_len = 0; + last_lit = matches = 0; + } + + // Initialize the tree data structures for a new zlib stream. + function tr_init() { + + l_desc.dyn_tree = dyn_ltree; + l_desc.stat_desc = StaticTree.static_l_desc; + + d_desc.dyn_tree = dyn_dtree; + d_desc.stat_desc = StaticTree.static_d_desc; + + bl_desc.dyn_tree = bl_tree; + bl_desc.stat_desc = StaticTree.static_bl_desc; + + bi_buf = 0; + bi_valid = 0; + last_eob_len = 8; // enough lookahead for inflate + + // Initialize the first block of the first file: + init_block(); + } + + // Restore the heap property by moving down the tree starting at node k, + // exchanging a node with the smallest of its two sons if necessary, + // stopping + // when the heap property is re-established (each father smaller than its + // two sons). + that.pqdownheap = function(tree, // the tree to restore + k // node to move down + ) { + var heap = that.heap; + var v = heap[k]; + var j = k << 1; // left son of k + while (j <= that.heap_len) { + // Set j to the smallest of the two sons: + if (j < that.heap_len && smaller(tree, heap[j + 1], heap[j], that.depth)) { + j++; + } + // Exit if v is smaller than both sons + if (smaller(tree, v, heap[j], that.depth)) + break; + + // Exchange v with the smallest son + heap[k] = heap[j]; + k = j; + // And continue down the tree, setting j to the left son of k + j <<= 1; + } + heap[k] = v; + }; + + // Scan a literal or distance tree to determine the frequencies of the codes + // in the bit length tree. + function scan_tree(tree,// the tree to be scanned + max_code // and its largest code of non zero frequency + ) { + var n; // iterates over all tree elements + var prevlen = -1; // last emitted length + var curlen; // length of current code + var nextlen = tree[0 * 2 + 1]; // length of next code + var count = 0; // repeat count of the current code + var max_count = 7; // max repeat count + var min_count = 4; // min repeat count + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code + 1) * 2 + 1] = 0xffff; // guard + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + bl_tree[curlen * 2] += count; + } else if (curlen !== 0) { + if (curlen != prevlen) + bl_tree[curlen * 2]++; + bl_tree[REP_3_6 * 2]++; + } else if (count <= 10) { + bl_tree[REPZ_3_10 * 2]++; + } else { + bl_tree[REPZ_11_138 * 2]++; + } + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } else if (curlen == nextlen) { + max_count = 6; + min_count = 3; + } else { + max_count = 7; + min_count = 4; + } + } + } + + // Construct the Huffman tree for the bit lengths and return the index in + // bl_order of the last bit length code to send. + function build_bl_tree() { + var max_blindex; // index of last bit length code of non zero freq + + // Determine the bit length frequencies for literal and distance trees + scan_tree(dyn_ltree, l_desc.max_code); + scan_tree(dyn_dtree, d_desc.max_code); + + // Build the bit length tree: + bl_desc.build_tree(that); + // opt_len now includes the length of the tree representations, except + // the lengths of the bit lengths codes and the 5+5+4 bits for the + // counts. + + // Determine the number of bit length codes to send. The pkzip format + // requires that at least 4 bit length codes be sent. (appnote.txt says + // 3 but the actual value used is 4.) + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex] * 2 + 1] !== 0) + break; + } + // Update opt_len to include the bit length tree and counts + that.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + + return max_blindex; + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + function put_byte(p) { + that.pending_buf[that.pending++] = p; + } + + function put_short(w) { + put_byte(w & 0xff); + put_byte((w >>> 8) & 0xff); + } + + function putShortMSB(b) { + put_byte((b >> 8) & 0xff); + put_byte((b & 0xff) & 0xff); + } + + function send_bits(value, length) { + var val, len = length; + if (bi_valid > Buf_size - len) { + val = value; + // bi_buf |= (val << bi_valid); + bi_buf |= ((val << bi_valid) & 0xffff); + put_short(bi_buf); + bi_buf = val >>> (Buf_size - bi_valid); + bi_valid += len - Buf_size; + } else { + // bi_buf |= (value) << bi_valid; + bi_buf |= (((value) << bi_valid) & 0xffff); + bi_valid += len; + } + } + + function send_code(c, tree) { + var c2 = c * 2; + send_bits(tree[c2] & 0xffff, tree[c2 + 1] & 0xffff); + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + function send_tree(tree,// the tree to be sent + max_code // and its largest code of non zero frequency + ) { + var n; // iterates over all tree elements + var prevlen = -1; // last emitted length + var curlen; // length of current code + var nextlen = tree[0 * 2 + 1]; // length of next code + var count = 0; // repeat count of the current code + var max_count = 7; // max repeat count + var min_count = 4; // min repeat count + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { + send_code(curlen, bl_tree); + } while (--count !== 0); + } else if (curlen !== 0) { + if (curlen != prevlen) { + send_code(curlen, bl_tree); + count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count - 3, 2); + } else if (count <= 10) { + send_code(REPZ_3_10, bl_tree); + send_bits(count - 3, 3); + } else { + send_code(REPZ_11_138, bl_tree); + send_bits(count - 11, 7); + } + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } else if (curlen == nextlen) { + max_count = 6; + min_count = 3; + } else { + max_count = 7; + min_count = 4; + } + } + } + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + function send_all_trees(lcodes, dcodes, blcodes) { + var rank; // index in bl_order + + send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes - 1, 5); + send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank] * 2 + 1], 3); + } + send_tree(dyn_ltree, lcodes - 1); // literal tree + send_tree(dyn_dtree, dcodes - 1); // distance tree + } + + // Flush the bit buffer, keeping at most 7 bits in it. + function bi_flush() { + if (bi_valid == 16) { + put_short(bi_buf); + bi_buf = 0; + bi_valid = 0; + } else if (bi_valid >= 8) { + put_byte(bi_buf & 0xff); + bi_buf >>>= 8; + bi_valid -= 8; + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + function _tr_align() { + send_bits(STATIC_TREES << 1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES << 1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + function _tr_tally(dist, // distance of matched string + lc // match length-MIN_MATCH or unmatched char (if dist==0) + ) { + var out_length, in_length, dcode; + that.pending_buf[d_buf + last_lit * 2] = (dist >>> 8) & 0xff; + that.pending_buf[d_buf + last_lit * 2 + 1] = dist & 0xff; + + that.pending_buf[l_buf + last_lit] = lc & 0xff; + last_lit++; + + if (dist === 0) { + // lc is the unmatched char + dyn_ltree[lc * 2]++; + } else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc] + LITERALS + 1) * 2]++; + dyn_dtree[Tree.d_code(dist) * 2]++; + } + + if ((last_lit & 0x1fff) === 0 && level > 2) { + // Compute an upper bound for the compressed length + out_length = last_lit * 8; + in_length = strstart - block_start; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += dyn_dtree[dcode * 2] * (5 + Tree.extra_dbits[dcode]); + } + out_length >>>= 3; + if ((matches < Math.floor(last_lit / 2)) && out_length < Math.floor(in_length / 2)) + return true; + } + + return (last_lit == lit_bufsize - 1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + function compress_block(ltree, dtree) { + var dist; // distance of matched string + var lc; // match length or unmatched char (if dist === 0) + var lx = 0; // running index in l_buf + var code; // the code to send + var extra; // number of extra bits to send + + if (last_lit !== 0) { + do { + dist = ((that.pending_buf[d_buf + lx * 2] << 8) & 0xff00) | (that.pending_buf[d_buf + lx * 2 + 1] & 0xff); + lc = (that.pending_buf[l_buf + lx]) & 0xff; + lx++; + + if (dist === 0) { + send_code(lc, ltree); // send a literal byte + } else { + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code + LITERALS + 1, ltree); // send the length + // code + extra = Tree.extra_lbits[code]; + if (extra !== 0) { + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra !== 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is + // ok: + } while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK * 2 + 1]; + } + + // Flush the bit buffer and align the output on a byte boundary + function bi_windup() { + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte(bi_buf & 0xff); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + function copy_block(buf, // the input data + len, // its length + header // true if block header must be written + ) { + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short(len); + put_short(~len); + } + + that.pending_buf.set(window.subarray(buf, buf + len), that.pending); + that.pending += len; + } + + // Send a stored block + function _tr_stored_block(buf, // input block + stored_len, // length of input block + eof // true if this is the last block for a file + ) { + send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + function _tr_flush_block(buf, // input block, or NULL if too old + stored_len, // length of input block + eof // true if this is the last block for a file + ) { + var opt_lenb, static_lenb;// opt_len and static_len in bytes + var max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if (level > 0) { + // Construct the literal and distance trees + l_desc.build_tree(that); + + d_desc.build_tree(that); + + // At this point, opt_len and static_len are the total bit lengths + // of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the + // index + // in bl_order of the last bit length code to send. + max_blindex = build_bl_tree(); + + // Determine the best encoding. Compute first the block length in + // bytes + opt_lenb = (that.opt_len + 3 + 7) >>> 3; + static_lenb = (that.static_len + 3 + 7) >>> 3; + + if (static_lenb <= opt_lenb) + opt_lenb = static_lenb; + } else { + opt_lenb = static_lenb = stored_len + 5; // force a stored block + } + + if ((stored_len + 4 <= opt_lenb) && buf != -1) { + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes + // since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } else if (static_lenb == opt_lenb) { + send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } else { + send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3); + send_all_trees(l_desc.max_code + 1, d_desc.max_code + 1, max_blindex + 1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if (eof) { + bi_windup(); + } + } + + function flush_block_only(eof) { + _tr_flush_block(block_start >= 0 ? block_start : -1, strstart - block_start, eof); + block_start = strstart; + strm.flush_pending(); + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in === 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + function fill_window() { + var n, m; + var p; + var more; // Amount of free space at the end of the window. + + do { + more = (window_size - lookahead - strstart); + + // Deal with !@#$% 64K limit: + if (more === 0 && strstart === 0 && lookahead === 0) { + more = w_size; + } else if (more == -1) { + // Very unlikely, but possible on 16 bit machine if strstart == + // 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient + // lookahead, + // move the upper half to the lower one to make room in the + // upper half. + } else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) { + window.set(window.subarray(w_size, w_size + w_size), 0); + + match_start -= w_size; + strstart -= w_size; // we now have strstart >= MAX_DIST + block_start -= w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == + // 0 + // to keep the hash table consistent if we switch back to level + // > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p = n; + do { + m = (head[--p] & 0xffff); + head[p] = (m >= w_size ? m - w_size : 0); + } while (--n !== 0); + + n = w_size; + p = n; + do { + m = (prev[--p] & 0xffff); + prev[p] = (m >= w_size ? m - w_size : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } while (--n !== 0); + more += w_size; + } + + if (strm.avail_in === 0) + return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if (lookahead >= MIN_MATCH) { + ins_h = window[strstart] & 0xff; + ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; + } + // If the whole input has less than MIN_MATCH bytes, ins_h is + // garbage, + // but this is not important since only literal bytes will be + // emitted. + } while (lookahead < MIN_LOOKAHEAD && strm.avail_in !== 0); + } + + // Copy without compression as much as possible from the input stream, + // return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + function deflate_stored(flush) { + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + var max_block_size = 0xffff; + var max_start; + + if (max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while (true) { + // Fill the window as much as possible: + if (lookahead <= 1) { + fill_window(); + if (lookahead === 0 && flush == Z_NO_FLUSH) + return NeedMore; + if (lookahead === 0) + break; // flush the current block + } + + strstart += lookahead; + lookahead = 0; + + // Emit a stored block if pending_buf will be full: + max_start = block_start + max_block_size; + if (strstart === 0 || strstart >= max_start) { + // strstart === 0 is possible when wraparound on 16-bit machine + lookahead = (strstart - max_start); + strstart = max_start; + + flush_block_only(false); + if (strm.avail_out === 0) + return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if (strstart - block_start >= w_size - MIN_LOOKAHEAD) { + flush_block_only(false); + if (strm.avail_out === 0) + return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if (strm.avail_out === 0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + function longest_match(cur_match) { + var chain_length = max_chain_length; // max hash chain length + var scan = strstart; // current string + var match; // matched string + var len; // length of current match + var best_len = prev_length; // best match length so far + var limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0; + var _nice_match = nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + var wmask = w_mask; + + var strend = strstart + MAX_MATCH; + var scan_end1 = window[scan + best_len - 1]; + var scan_end = window[scan + best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of + // 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is + // necessary + // to make deflate deterministic. + if (_nice_match > lookahead) + _nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match + best_len] != scan_end || window[match + best_len - 1] != scan_end1 || window[match] != window[scan] + || window[++match] != window[scan + 1]) + continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; + match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] + && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] + && window[++scan] == window[++match] && window[++scan] == window[++match] && scan < strend); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + match_start = cur_match; + best_len = len; + if (len >= _nice_match) + break; + scan_end1 = window[scan + best_len - 1]; + scan_end = window[scan + best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length !== 0); + + if (best_len <= lookahead) + return best_len; + return lookahead; + } + + // Compress as much as possible from the input stream, return the current + // block state. + // This function does not perform lazy evaluation of matches and inserts + // new strings in the dictionary only for unmatched strings or for short + // matches. It is used only for the fast compression options. + function deflate_fast(flush) { + // short hash_head = 0; // head of the hash chain + var hash_head = 0; // head of the hash chain + var bflush; // set if current block must be flushed + + while (true) { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if (lookahead < MIN_LOOKAHEAD) { + fill_window(); + if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return NeedMore; + } + if (lookahead === 0) + break; // flush the current block + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + if (lookahead >= MIN_MATCH) { + ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = strstart; + } + + // Find the longest match, discarding those <= prev_length. + // At this point we have always match_length < MIN_MATCH + + if (hash_head !== 0 && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + if (strategy != Z_HUFFMAN_ONLY) { + match_length = longest_match(hash_head); + } + // longest_match() sets match_start + } + if (match_length >= MIN_MATCH) { + // check_match(strstart, match_start, match_length); + + bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if (match_length <= max_lazy_match && lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do { + strstart++; + + ins_h = ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = strstart; + + // strstart never exceeds WSIZE-MAX_MATCH, so there are + // always MIN_MATCH bytes ahead. + } while (--match_length !== 0); + strstart++; + } else { + strstart += match_length; + match_length = 0; + ins_h = window[strstart] & 0xff; + + ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; + // If lookahead < MIN_MATCH, ins_h is garbage, but it does + // not + // matter since it will be recomputed at next deflate call. + } + } else { + // No match, output a literal byte + + bflush = _tr_tally(0, window[strstart] & 0xff); + lookahead--; + strstart++; + } + if (bflush) { + + flush_block_only(false); + if (strm.avail_out === 0) + return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if (strm.avail_out === 0) { + if (flush == Z_FINISH) + return FinishStarted; + else + return NeedMore; + } + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Same as above, but achieves better compression. We use a lazy + // evaluation for matches: a match is finally adopted only if there is + // no better match at the next window position. + function deflate_slow(flush) { + // short hash_head = 0; // head of hash chain + var hash_head = 0; // head of hash chain + var bflush; // set if current block must be flushed + var max_insert; + + // Process the input block. + while (true) { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + + if (lookahead < MIN_LOOKAHEAD) { + fill_window(); + if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return NeedMore; + } + if (lookahead === 0) + break; // flush the current block + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + + if (lookahead >= MIN_MATCH) { + ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = strstart; + } + + // Find the longest match, discarding those <= prev_length. + prev_length = match_length; + prev_match = match_start; + match_length = MIN_MATCH - 1; + + if (hash_head !== 0 && prev_length < max_lazy_match && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + + if (strategy != Z_HUFFMAN_ONLY) { + match_length = longest_match(hash_head); + } + // longest_match() sets match_start + + if (match_length <= 5 && (strategy == Z_FILTERED || (match_length == MIN_MATCH && strstart - match_start > 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH - 1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if (prev_length >= MIN_MATCH && match_length <= prev_length) { + max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length - 1; + prev_length -= 2; + do { + if (++strstart <= max_insert) { + ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = strstart; + } + } while (--prev_length !== 0); + match_available = 0; + match_length = MIN_MATCH - 1; + strstart++; + + if (bflush) { + flush_block_only(false); + if (strm.avail_out === 0) + return NeedMore; + } + } else if (match_available !== 0) { + + // If there was no match at the previous position, output a + // single literal. If there was a match but the current match + // is longer, truncate the previous match to a single literal. + + bflush = _tr_tally(0, window[strstart - 1] & 0xff); + + if (bflush) { + flush_block_only(false); + } + strstart++; + lookahead--; + if (strm.avail_out === 0) + return NeedMore; + } else { + // There is no previous match to compare with, wait for + // the next step to decide. + + match_available = 1; + strstart++; + lookahead--; + } + } + + if (match_available !== 0) { + bflush = _tr_tally(0, window[strstart - 1] & 0xff); + match_available = 0; + } + flush_block_only(flush == Z_FINISH); + + if (strm.avail_out === 0) { + if (flush == Z_FINISH) + return FinishStarted; + else + return NeedMore; + } + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + function deflateReset(strm) { + strm.total_in = strm.total_out = 0; + strm.msg = null; // + + that.pending = 0; + that.pending_out = 0; + + status = BUSY_STATE; + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + that.deflateInit = function(strm, _level, bits, _method, memLevel, _strategy) { + if (!_method) + _method = Z_DEFLATED; + if (!memLevel) + memLevel = DEF_MEM_LEVEL; + if (!_strategy) + _strategy = Z_DEFAULT_STRATEGY; + + // byte[] my_version=ZLIB_VERSION; + + // + // if (!version || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (_level == Z_DEFAULT_COMPRESSION) + _level = 6; + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || _method != Z_DEFLATED || bits < 9 || bits > 15 || _level < 0 || _level > 9 || _strategy < 0 + || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = that; + + w_bits = bits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = Math.floor((hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + window = new Uint8Array(w_size * 2); + prev = []; + head = []; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + that.pending_buf = new Uint8Array(lit_bufsize * 4); + pending_buf_size = lit_bufsize * 4; + + d_buf = Math.floor(lit_bufsize / 2); + l_buf = (1 + 2) * lit_bufsize; + + level = _level; + + strategy = _strategy; + method = _method & 0xff; + + return deflateReset(strm); + }; + + that.deflateEnd = function() { + if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) { + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + that.pending_buf = null; + head = null; + prev = null; + window = null; + // free + that.dstate = null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + }; + + that.deflateParams = function(strm, _level, _strategy) { + var err = Z_OK; + + if (_level == Z_DEFAULT_COMPRESSION) { + _level = 6; + } + if (_level < 0 || _level > 9 || _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if (config_table[level].func != config_table[_level].func && strm.total_in !== 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if (level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + }; + + that.deflateSetDictionary = function(strm, dictionary, dictLength) { + var length = dictLength; + var n, index = 0; + + if (!dictionary || status != INIT_STATE) + return Z_STREAM_ERROR; + + if (length < MIN_MATCH) + return Z_OK; + if (length > w_size - MIN_LOOKAHEAD) { + length = w_size - MIN_LOOKAHEAD; + index = dictLength - length; // use the tail of the dictionary + } + window.set(dictionary.subarray(index, index + length), 0); + + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0] & 0xff; + ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask; + + for (n = 0; n <= length - MIN_MATCH; n++) { + ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + prev[n & w_mask] = head[ins_h]; + head[ins_h] = n; + } + return Z_OK; + }; + + that.deflate = function(_strm, flush) { + var i, header, level_flags, old_flush, bstate; + + if (flush > Z_FINISH || flush < 0) { + return Z_STREAM_ERROR; + } + + if (!_strm.next_out || (!_strm.next_in && _strm.avail_in !== 0) || (status == FINISH_STATE && flush != Z_FINISH)) { + _strm.msg = z_errmsg[Z_NEED_DICT - (Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if (_strm.avail_out === 0) { + _strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + strm = _strm; // just in case + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if (status == INIT_STATE) { + header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8; + level_flags = ((level - 1) & 0xff) >> 1; + + if (level_flags > 3) + level_flags = 3; + header |= (level_flags << 6); + if (strstart !== 0) + header |= PRESET_DICT; + header += 31 - (header % 31); + + status = BUSY_STATE; + putShortMSB(header); + } + + // Flush as much pending output as possible + if (that.pending !== 0) { + strm.flush_pending(); + if (strm.avail_out === 0) { + // console.log(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate + // consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } else if (strm.avail_in === 0 && flush <= old_flush && flush != Z_FINISH) { + strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if (status == FINISH_STATE && strm.avail_in !== 0) { + _strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if (strm.avail_in !== 0 || lookahead !== 0 || (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + bstate = -1; + switch (config_table[level].func) { + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + } + + if (bstate == FinishStarted || bstate == FinishDone) { + status = FINISH_STATE; + } + if (bstate == NeedMore || bstate == FinishStarted) { + if (strm.avail_out === 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out === 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate == BlockDone) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if (flush == Z_FULL_FLUSH) { + // state.head[s.hash_size-1]=0; + for (i = 0; i < hash_size/*-1*/; i++) + // forget history + head[i] = 0; + } + } + strm.flush_pending(); + if (strm.avail_out === 0) { + last_flush = -1; // avoid BUF_ERROR at next call, see above + return Z_OK; + } + } + } + + if (flush != Z_FINISH) + return Z_OK; + return Z_STREAM_END; + }; + } + + // ZStream + + function ZStream() { + var that = this; + that.next_in_index = 0; + that.next_out_index = 0; + // that.next_in; // next input byte + that.avail_in = 0; // number of bytes available at next_in + that.total_in = 0; // total nb of input bytes read so far + // that.next_out; // next output byte should be put there + that.avail_out = 0; // remaining free space at next_out + that.total_out = 0; // total nb of bytes output so far + // that.msg; + // that.dstate; + } + + ZStream.prototype = { + deflateInit : function(level, bits) { + var that = this; + that.dstate = new Deflate(); + if (!bits) + bits = MAX_BITS; + return that.dstate.deflateInit(that, level, bits); + }, + + deflate : function(flush) { + var that = this; + if (!that.dstate) { + return Z_STREAM_ERROR; + } + return that.dstate.deflate(that, flush); + }, + + deflateEnd : function() { + var that = this; + if (!that.dstate) + return Z_STREAM_ERROR; + var ret = that.dstate.deflateEnd(); + that.dstate = null; + return ret; + }, + + deflateParams : function(level, strategy) { + var that = this; + if (!that.dstate) + return Z_STREAM_ERROR; + return that.dstate.deflateParams(that, level, strategy); + }, + + deflateSetDictionary : function(dictionary, dictLength) { + var that = this; + if (!that.dstate) + return Z_STREAM_ERROR; + return that.dstate.deflateSetDictionary(that, dictionary, dictLength); + }, + + // Read a new buffer from the current input stream, update the + // total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + read_buf : function(buf, start, size) { + var that = this; + var len = that.avail_in; + if (len > size) + len = size; + if (len === 0) + return 0; + that.avail_in -= len; + buf.set(that.next_in.subarray(that.next_in_index, that.next_in_index + len), start); + that.next_in_index += len; + that.total_in += len; + return len; + }, + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + flush_pending : function() { + var that = this; + var len = that.dstate.pending; + + if (len > that.avail_out) + len = that.avail_out; + if (len === 0) + return; + + // if (that.dstate.pending_buf.length <= that.dstate.pending_out || that.next_out.length <= that.next_out_index + // || that.dstate.pending_buf.length < (that.dstate.pending_out + len) || that.next_out.length < (that.next_out_index + + // len)) { + // console.log(that.dstate.pending_buf.length + ", " + that.dstate.pending_out + ", " + that.next_out.length + ", " + + // that.next_out_index + ", " + len); + // console.log("avail_out=" + that.avail_out); + // } + + that.next_out.set(that.dstate.pending_buf.subarray(that.dstate.pending_out, that.dstate.pending_out + len), that.next_out_index); + + that.next_out_index += len; + that.dstate.pending_out += len; + that.total_out += len; + that.avail_out -= len; + that.dstate.pending -= len; + if (that.dstate.pending === 0) { + that.dstate.pending_out = 0; + } + } + }; + + // Deflater + + function Deflater(options) { + var that = this; + var z = new ZStream(); + var bufsize = 512; + var flush = Z_NO_FLUSH; + var buf = new Uint8Array(bufsize); + var level = options ? options.level : Z_DEFAULT_COMPRESSION; + if (typeof level == "undefined") + level = Z_DEFAULT_COMPRESSION; + z.deflateInit(level); + z.next_out = buf; + + that.append = function(data, onprogress) { + var err, buffers = [], lastIndex = 0, bufferIndex = 0, bufferSize = 0, array; + if (!data.length) + return; + z.next_in_index = 0; + z.next_in = data; + z.avail_in = data.length; + do { + z.next_out_index = 0; + z.avail_out = bufsize; + err = z.deflate(flush); + if (err != Z_OK) + throw new Error("deflating: " + z.msg); + if (z.next_out_index) + if (z.next_out_index == bufsize) + buffers.push(new Uint8Array(buf)); + else + buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index))); + bufferSize += z.next_out_index; + if (onprogress && z.next_in_index > 0 && z.next_in_index != lastIndex) { + onprogress(z.next_in_index); + lastIndex = z.next_in_index; + } + } while (z.avail_in > 0 || z.avail_out === 0); + array = new Uint8Array(bufferSize); + buffers.forEach(function(chunk) { + array.set(chunk, bufferIndex); + bufferIndex += chunk.length; + }); + return array; + }; + that.flush = function() { + var err, buffers = [], bufferIndex = 0, bufferSize = 0, array; + do { + z.next_out_index = 0; + z.avail_out = bufsize; + err = z.deflate(Z_FINISH); + if (err != Z_STREAM_END && err != Z_OK) + throw new Error("deflating: " + z.msg); + if (bufsize - z.avail_out > 0) + buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index))); + bufferSize += z.next_out_index; + } while (z.avail_in > 0 || z.avail_out === 0); + z.deflateEnd(); + array = new Uint8Array(bufferSize); + buffers.forEach(function(chunk) { + array.set(chunk, bufferIndex); + bufferIndex += chunk.length; + }); + return array; + }; + } + + // 'zip' may not be defined in z-worker and some tests + var env = global.zip || global; + env.Deflater = env._jzlib_Deflater = Deflater; +})(this); diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/lib/inflate.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/lib/inflate.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,2155 @@ +/* + Copyright (c) 2013 Gildas Lormeau. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program is based on JZlib 1.0.2 ymnk, JCraft,Inc. + * JZlib is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +(function(global) { + "use strict"; + + // Global + var MAX_BITS = 15; + + var Z_OK = 0; + var Z_STREAM_END = 1; + var Z_NEED_DICT = 2; + var Z_STREAM_ERROR = -2; + var Z_DATA_ERROR = -3; + var Z_MEM_ERROR = -4; + var Z_BUF_ERROR = -5; + + var inflate_mask = [ 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff, + 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff ]; + + var MANY = 1440; + + // JZlib version : "1.0.2" + var Z_NO_FLUSH = 0; + var Z_FINISH = 4; + + // InfTree + var fixed_bl = 9; + var fixed_bd = 5; + + var fixed_tl = [ 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160, 0, 8, 0, + 0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208, 81, 7, 17, 0, 8, 104, 0, 8, 40, + 0, 9, 176, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13, + 0, 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, 8, 124, 0, 8, 60, + 0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, + 35, 0, 8, 114, 0, 8, 50, 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8, + 26, 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244, 80, + 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0, + 8, 70, 0, 9, 236, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0, + 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194, 80, 7, 10, 0, 8, 97, + 0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210, + 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, + 0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154, + 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186, 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83, + 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230, + 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139, + 0, 8, 75, 0, 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174, + 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222, 82, 7, 27, 0, 8, 111, + 0, 8, 47, 0, 9, 190, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, + 193, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, 7, 59, 0, 8, + 120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, + 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8, + 92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, + 249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8, + 130, 0, 8, 66, 0, 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, + 181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205, 81, 7, 15, 0, 8, + 102, 0, 8, 38, 0, 9, 173, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, + 221, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, + 8, 113, 0, 8, 49, 0, 9, 195, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, + 147, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243, 80, 7, 4, 0, 8, + 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, + 235, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0, 8, + 141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, + 167, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8, + 107, 0, 8, 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, + 207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159, 84, 7, 99, 0, 8, + 127, 0, 8, 63, 0, 9, 223, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255 ]; + var fixed_td = [ 80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5, + 8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, 80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5, + 24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577 ]; + + // Tables for deflate from PKZIP's appnote.txt. + var cplens = [ // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 ]; + + // see note #13 above about 258 + var cplext = [ // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid + ]; + + var cpdist = [ // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 ]; + + var cpdext = [ // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 ]; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + var BMAX = 15; // maximum bit length of any code + + function InfTree() { + var that = this; + + var hn; // hufts used in space + var v; // work area for huft_build + var c; // bit length count table + var r; // table entry for structure assignment + var u; // table stack + var x; // bit offsets, then code stack + + function huft_build(b, // code lengths in bits (all assumed <= + // BMAX) + bindex, n, // number of codes (assumed <= 288) + s, // number of simple-valued codes (0..s-1) + d, // list of base values for non-simple codes + e, // list of extra bits for non-simple codes + t, // result: starting table + m, // maximum lookup bits, returns actual + hp,// space for trees + hn,// hufts used in space + v // working area: values in order of bit length + ) { + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, + // Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in + // this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set + // of + // lengths), or Z_MEM_ERROR if not enough memory. + + var a; // counter for codes of length k + var f; // i repeats in table every f entries + var g; // maximum code length + var h; // table level + var i; // counter, current code + var j; // counter + var k; // number of bits in current code + var l; // bits per table (returned in m) + var mask; // (1 << w) - 1, to avoid cc -O bug on HP + var p; // pointer into c[], b[], or v[] + var q; // points to current table + var w; // bits before this table == (l * h) + var xp; // pointer into x + var y; // number of dummy codes added + var z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; + i = n; + do { + c[b[bindex + p]]++; + p++; + i--; // assume all entries <= BMAX + } while (i !== 0); + + if (c[0] == n) { // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if (c[j] !== 0) + break; + k = j; // minimum code length + if (l < j) { + l = j; + } + for (i = BMAX; i !== 0; i--) { + if (c[i] !== 0) + break; + } + g = i; // maximum code length + if (l > i) { + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1) { + if ((y -= c[j]) < 0) { + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0) { + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; + xp = 2; + while (--i !== 0) { // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; + p = 0; + do { + if ((j = b[bindex + p]) !== 0) { + v[x[j]++] = i; + } + p++; + } while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++) { + a = c[k]; + while (a-- !== 0) { + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l) { + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if ((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table + // too few codes for + // k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + if (j < z) { + while (++j < z) { // try smaller tables up to z bits + if ((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY) { // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = /* hp+ */hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if (h !== 0) { + x[h] = i; // save pattern for backing up + r[0] = /* (byte) */j; // bits in this table + r[1] = /* (byte) */l; // bits to dump before this table + j = i >>> (w - l); + r[2] = /* (int) */(q - u[h - 1] - j); // offset to this table + hp.set(r, (u[h - 1] + j) * 3); + // to + // last + // table + } else { + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = /* (byte) */(k - w); + if (p >= n) { + r[0] = 128 + 64; // out of values--invalid code + } else if (v[p] < s) { + r[0] = /* (byte) */(v[p] < 256 ? 0 : 32 + 64); // 256 is + // end-of-block + r[2] = v[p++]; // simple code is just the value + } else { + r[0] = /* (byte) */(e[v[p] - s] + 16 + 64); // non-simple--look + // up in lists + r[2] = d[v[p++] - s]; + } + + // fill code-like entries with r + f = 1 << (k - w); + for (j = i >>> w; j < z; j += f) { + hp.set(r, (q + j) * 3); + } + + // backwards increment the k-bit code i + for (j = 1 << (k - 1); (i & j) !== 0; j >>>= 1) { + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]) { + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y !== 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + + function initWorkArea(vsize) { + var i; + if (!hn) { + hn = []; // []; //new Array(1); + v = []; // new Array(vsize); + c = new Int32Array(BMAX + 1); // new Array(BMAX + 1); + r = []; // new Array(3); + u = new Int32Array(BMAX); // new Array(BMAX); + x = new Int32Array(BMAX + 1); // new Array(BMAX + 1); + } + if (v.length < vsize) { + v = []; // new Array(vsize); + } + for (i = 0; i < vsize; i++) { + v[i] = 0; + } + for (i = 0; i < BMAX + 1; i++) { + c[i] = 0; + } + for (i = 0; i < 3; i++) { + r[i] = 0; + } + // for(int i=0; i 257)) { + if (result == Z_DATA_ERROR) { + z.msg = "oversubscribed distance tree"; + } else if (result == Z_BUF_ERROR) { + z.msg = "incomplete distance tree"; + result = Z_DATA_ERROR; + } else if (result != Z_MEM_ERROR) { + z.msg = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + }; + + } + + InfTree.inflate_trees_fixed = function(bl, // literal desired/actual bit depth + bd, // distance desired/actual bit depth + tl,// literal/length tree result + td// distance tree result + ) { + bl[0] = fixed_bl; + bd[0] = fixed_bd; + tl[0] = fixed_tl; + td[0] = fixed_td; + return Z_OK; + }; + + // InfCodes + + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + var START = 0; // x: set up for LEN + var LEN = 1; // i: get length/literal/eob next + var LENEXT = 2; // i: getting length extra (have base) + var DIST = 3; // i: get distance next + var DISTEXT = 4;// i: getting distance extra + var COPY = 5; // o: copying bytes in window, waiting + // for space + var LIT = 6; // o: got literal, waiting for output + // space + var WASH = 7; // o: got eob, possibly still output + // waiting + var END = 8; // x: got eob and all data flushed + var BADCODE = 9;// x: got error + + function InfCodes() { + var that = this; + + var mode; // current inflate_codes mode + + // mode dependent information + var len = 0; + + var tree; // pointer into tree + var tree_index = 0; + var need = 0; // bits needed + + var lit = 0; + + // if EXT or COPY, where and how much + var get = 0; // bits to get for extra + var dist = 0; // distance back to copy from + + var lbits = 0; // ltree bits decoded per branch + var dbits = 0; // dtree bits decoder per branch + var ltree; // literal/length/eob tree + var ltree_index = 0; // literal/length/eob tree + var dtree; // distance tree + var dtree_index = 0; // distance tree + + // Called with number of bytes left to write in window at least 258 + // (the maximum string length) and number of input bytes available + // at least ten. The ten bytes are six bytes for the longest length/ + // distance pair plus four bytes for overloading the bit buffer. + + function inflate_fast(bl, bd, tl, tl_index, td, td_index, s, z) { + var t; // temporary pointer + var tp; // temporary pointer + var tp_index; // temporary pointer + var e; // extra bits or operation + var b; // bit buffer + var k; // bits in bit buffer + var p; // input data pointer + var n; // bytes available there + var q; // output window write pointer + var m; // bytes to end of window or read pointer + var ml; // mask for literal/length tree + var md; // mask for distance tree + var c; // bytes to copy + var d; // distance back to copy from + var r; // copy source pointer + + var tp_index_t_3; // (tp_index+t)*3 + + // load input, output, bit values + p = z.next_in_index; + n = z.avail_in; + b = s.bitb; + k = s.bitk; + q = s.write; + m = q < s.read ? s.read - q - 1 : s.end - q; + + // initialize masks + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + // do until not enough input or output space for fast loop + do { // assume called with m >= 258 && n >= 10 + // get literal/length code + while (k < (20)) { // max bits for literal/length code + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + t = b & ml; + tp = tl; + tp_index = tl_index; + tp_index_t_3 = (tp_index + t) * 3; + if ((e = tp[tp_index_t_3]) === 0) { + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + s.window[q++] = /* (byte) */tp[tp_index_t_3 + 2]; + m--; + continue; + } + do { + + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + if ((e & 16) !== 0) { + e &= 15; + c = tp[tp_index_t_3 + 2] + (/* (int) */b & inflate_mask[e]); + + b >>= e; + k -= e; + + // decode distance base of block to copy + while (k < (15)) { // max bits for distance code + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + t = b & md; + tp = td; + tp_index = td_index; + tp_index_t_3 = (tp_index + t) * 3; + e = tp[tp_index_t_3]; + + do { + + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + if ((e & 16) !== 0) { + // get extra bits to add to distance base + e &= 15; + while (k < (e)) { // get extra bits (up to 13) + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + d = tp[tp_index_t_3 + 2] + (b & inflate_mask[e]); + + b >>= (e); + k -= (e); + + // do the copy + m -= c; + if (q >= d) { // offset before dest + // just copy + r = q - d; + if (q - r > 0 && 2 > (q - r)) { + s.window[q++] = s.window[r++]; // minimum + // count is + // three, + s.window[q++] = s.window[r++]; // so unroll + // loop a + // little + c -= 2; + } else { + s.window.set(s.window.subarray(r, r + 2), q); + q += 2; + r += 2; + c -= 2; + } + } else { // else offset after destination + r = q - d; + do { + r += s.end; // force pointer in window + } while (r < 0); // covers invalid distances + e = s.end - r; + if (c > e) { // if source crosses, + c -= e; // wrapped copy + if (q - r > 0 && e > (q - r)) { + do { + s.window[q++] = s.window[r++]; + } while (--e !== 0); + } else { + s.window.set(s.window.subarray(r, r + e), q); + q += e; + r += e; + e = 0; + } + r = 0; // copy rest from start of window + } + + } + + // copy all or what's left + if (q - r > 0 && c > (q - r)) { + do { + s.window[q++] = s.window[r++]; + } while (--c !== 0); + } else { + s.window.set(s.window.subarray(r, r + c), q); + q += c; + r += c; + c = 0; + } + break; + } else if ((e & 64) === 0) { + t += tp[tp_index_t_3 + 2]; + t += (b & inflate_mask[e]); + tp_index_t_3 = (tp_index + t) * 3; + e = tp[tp_index_t_3]; + } else { + z.msg = "invalid distance code"; + + c = z.avail_in - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= c << 3; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + + return Z_DATA_ERROR; + } + } while (true); + break; + } + + if ((e & 64) === 0) { + t += tp[tp_index_t_3 + 2]; + t += (b & inflate_mask[e]); + tp_index_t_3 = (tp_index + t) * 3; + if ((e = tp[tp_index_t_3]) === 0) { + + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + s.window[q++] = /* (byte) */tp[tp_index_t_3 + 2]; + m--; + break; + } + } else if ((e & 32) !== 0) { + + c = z.avail_in - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= c << 3; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + + return Z_STREAM_END; + } else { + z.msg = "invalid literal/length code"; + + c = z.avail_in - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= c << 3; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + + return Z_DATA_ERROR; + } + } while (true); + } while (m >= 258 && n >= 10); + + // not enough input or output--restore pointers and return + c = z.avail_in - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= c << 3; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + + return Z_OK; + } + + that.init = function(bl, bd, tl, tl_index, td, td_index) { + mode = START; + lbits = /* (byte) */bl; + dbits = /* (byte) */bd; + ltree = tl; + ltree_index = tl_index; + dtree = td; + dtree_index = td_index; + tree = null; + }; + + that.proc = function(s, z, r) { + var j; // temporary storage + var tindex; // temporary pointer + var e; // extra bits or operation + var b = 0; // bit buffer + var k = 0; // bits in bit buffer + var p = 0; // input data pointer + var n; // bytes available there + var q; // output window write pointer + var m; // bytes to end of window or read pointer + var f; // pointer to copy strings from + + // copy input/output information to locals (UPDATE macro restores) + p = z.next_in_index; + n = z.avail_in; + b = s.bitb; + k = s.bitk; + q = s.write; + m = q < s.read ? s.read - q - 1 : s.end - q; + + // process input and output based on current state + while (true) { + switch (mode) { + // waiting for "i:"=input, "o:"=output, "x:"=nothing + case START: // x: set up for LEN + if (m >= 258 && n >= 10) { + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + r = inflate_fast(lbits, dbits, ltree, ltree_index, dtree, dtree_index, s, z); + + p = z.next_in_index; + n = z.avail_in; + b = s.bitb; + k = s.bitk; + q = s.write; + m = q < s.read ? s.read - q - 1 : s.end - q; + + if (r != Z_OK) { + mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } + need = lbits; + tree = ltree; + tree_index = ltree_index; + + mode = LEN; + /* falls through */ + case LEN: // i: get length/literal/eob next + j = need; + + while (k < (j)) { + if (n !== 0) + r = Z_OK; + else { + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + tindex = (tree_index + (b & inflate_mask[j])) * 3; + + b >>>= (tree[tindex + 1]); + k -= (tree[tindex + 1]); + + e = tree[tindex]; + + if (e === 0) { // literal + lit = tree[tindex + 2]; + mode = LIT; + break; + } + if ((e & 16) !== 0) { // length + get = e & 15; + len = tree[tindex + 2]; + mode = LENEXT; + break; + } + if ((e & 64) === 0) { // next table + need = e; + tree_index = tindex / 3 + tree[tindex + 2]; + break; + } + if ((e & 32) !== 0) { // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid literal/length code"; + r = Z_DATA_ERROR; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + + case LENEXT: // i: getting length extra (have base) + j = get; + + while (k < (j)) { + if (n !== 0) + r = Z_OK; + else { + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + len += (b & inflate_mask[j]); + + b >>= j; + k -= j; + + need = dbits; + tree = dtree; + tree_index = dtree_index; + mode = DIST; + /* falls through */ + case DIST: // i: get distance next + j = need; + + while (k < (j)) { + if (n !== 0) + r = Z_OK; + else { + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + tindex = (tree_index + (b & inflate_mask[j])) * 3; + + b >>= tree[tindex + 1]; + k -= tree[tindex + 1]; + + e = (tree[tindex]); + if ((e & 16) !== 0) { // distance + get = e & 15; + dist = tree[tindex + 2]; + mode = DISTEXT; + break; + } + if ((e & 64) === 0) { // next table + need = e; + tree_index = tindex / 3 + tree[tindex + 2]; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid distance code"; + r = Z_DATA_ERROR; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + + case DISTEXT: // i: getting distance extra + j = get; + + while (k < (j)) { + if (n !== 0) + r = Z_OK; + else { + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + dist += (b & inflate_mask[j]); + + b >>= j; + k -= j; + + mode = COPY; + /* falls through */ + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while (f < 0) { // modulo window size-"while" instead + f += s.end; // of "if" handles invalid distances + } + while (len !== 0) { + + if (m === 0) { + if (q == s.end && s.read !== 0) { + q = 0; + m = q < s.read ? s.read - q - 1 : s.end - q; + } + if (m === 0) { + s.write = q; + r = s.inflate_flush(z, r); + q = s.write; + m = q < s.read ? s.read - q - 1 : s.end - q; + + if (q == s.end && s.read !== 0) { + q = 0; + m = q < s.read ? s.read - q - 1 : s.end - q; + } + + if (m === 0) { + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + } + } + + s.window[q++] = s.window[f++]; + m--; + + if (f == s.end) + f = 0; + len--; + } + mode = START; + break; + case LIT: // o: got literal, waiting for output space + if (m === 0) { + if (q == s.end && s.read !== 0) { + q = 0; + m = q < s.read ? s.read - q - 1 : s.end - q; + } + if (m === 0) { + s.write = q; + r = s.inflate_flush(z, r); + q = s.write; + m = q < s.read ? s.read - q - 1 : s.end - q; + + if (q == s.end && s.read !== 0) { + q = 0; + m = q < s.read ? s.read - q - 1 : s.end - q; + } + if (m === 0) { + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + } + } + r = Z_OK; + + s.window[q++] = /* (byte) */lit; + m--; + + mode = START; + break; + case WASH: // o: got eob, possibly more output + if (k > 7) { // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + s.write = q; + r = s.inflate_flush(z, r); + q = s.write; + m = q < s.read ? s.read - q - 1 : s.end - q; + + if (s.read != s.write) { + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + mode = END; + /* falls through */ + case END: + r = Z_STREAM_END; + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + + case BADCODE: // x: got error + + r = Z_DATA_ERROR; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + + default: + r = Z_STREAM_ERROR; + + s.bitb = b; + s.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + s.write = q; + return s.inflate_flush(z, r); + } + } + }; + + that.free = function() { + // ZFREE(z, c); + }; + + } + + // InfBlocks + + // Table for deflate from PKZIP's appnote.txt. + var border = [ // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; + + var TYPE = 0; // get type bits (3, including end bit) + var LENS = 1; // get lengths for stored + var STORED = 2;// processing stored block + var TABLE = 3; // get table lengths + var BTREE = 4; // get bit lengths tree for a dynamic + // block + var DTREE = 5; // get length, distance trees for a + // dynamic block + var CODES = 6; // processing fixed or dynamic block + var DRY = 7; // output remaining window bytes + var DONELOCKS = 8; // finished last block, done + var BADBLOCKS = 9; // ot a data error--stuck here + + function InfBlocks(z, w) { + var that = this; + + var mode = TYPE; // current inflate_block mode + + var left = 0; // if STORED, bytes left to copy + + var table = 0; // table lengths (14 bits) + var index = 0; // index into blens (or border) + var blens; // bit lengths of codes + var bb = [ 0 ]; // bit length tree depth + var tb = [ 0 ]; // bit length decoding tree + + var codes = new InfCodes(); // if CODES, current state + + var last = 0; // true if this block is the last block + + var hufts = new Int32Array(MANY * 3); // single malloc for tree space + var check = 0; // check on output + var inftree = new InfTree(); + + that.bitk = 0; // bits in bit buffer + that.bitb = 0; // bit buffer + that.window = new Uint8Array(w); // sliding window + that.end = w; // one byte after sliding window + that.read = 0; // window read pointer + that.write = 0; // window write pointer + + that.reset = function(z, c) { + if (c) + c[0] = check; + // if (mode == BTREE || mode == DTREE) { + // } + if (mode == CODES) { + codes.free(z); + } + mode = TYPE; + that.bitk = 0; + that.bitb = 0; + that.read = that.write = 0; + }; + + that.reset(z, null); + + // copy as much as possible from the sliding window to the output area + that.inflate_flush = function(z, r) { + var n; + var p; + var q; + + // local copies of source and destination pointers + p = z.next_out_index; + q = that.read; + + // compute number of bytes to copy as far as end of window + n = /* (int) */((q <= that.write ? that.write : that.end) - q); + if (n > z.avail_out) + n = z.avail_out; + if (n !== 0 && r == Z_BUF_ERROR) + r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // copy as far as end of window + z.next_out.set(that.window.subarray(q, q + n), p); + p += n; + q += n; + + // see if more to copy at beginning of window + if (q == that.end) { + // wrap pointers + q = 0; + if (that.write == that.end) + that.write = 0; + + // compute bytes to copy + n = that.write - q; + if (n > z.avail_out) + n = z.avail_out; + if (n !== 0 && r == Z_BUF_ERROR) + r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // copy + z.next_out.set(that.window.subarray(q, q + n), p); + p += n; + q += n; + } + + // update pointers + z.next_out_index = p; + that.read = q; + + // done + return r; + }; + + that.proc = function(z, r) { + var t; // temporary storage + var b; // bit buffer + var k; // bits in bit buffer + var p; // input data pointer + var n; // bytes available there + var q; // output window write pointer + var m; // bytes to end of window or read pointer + + var i; + + // copy input/output information to locals (UPDATE macro restores) + // { + p = z.next_in_index; + n = z.avail_in; + b = that.bitb; + k = that.bitk; + // } + // { + q = that.write; + m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q); + // } + + // process input based on current state + // DEBUG dtree + while (true) { + switch (mode) { + case TYPE: + + while (k < (3)) { + if (n !== 0) { + r = Z_OK; + } else { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + t = /* (int) */(b & 7); + last = t & 1; + + switch (t >>> 1) { + case 0: // stored + // { + b >>>= (3); + k -= (3); + // } + t = k & 7; // go to byte boundary + + // { + b >>>= (t); + k -= (t); + // } + mode = LENS; // get length of stored block + break; + case 1: // fixed + // { + var bl = []; // new Array(1); + var bd = []; // new Array(1); + var tl = [ [] ]; // new Array(1); + var td = [ [] ]; // new Array(1); + + InfTree.inflate_trees_fixed(bl, bd, tl, td); + codes.init(bl[0], bd[0], tl[0], 0, td[0], 0); + // } + + // { + b >>>= (3); + k -= (3); + // } + + mode = CODES; + break; + case 2: // dynamic + + // { + b >>>= (3); + k -= (3); + // } + + mode = TABLE; + break; + case 3: // illegal + + // { + b >>>= (3); + k -= (3); + // } + mode = BADBLOCKS; + z.msg = "invalid block type"; + r = Z_DATA_ERROR; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + break; + case LENS: + + while (k < (32)) { + if (n !== 0) { + r = Z_OK; + } else { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + if ((((~b) >>> 16) & 0xffff) != (b & 0xffff)) { + mode = BADBLOCKS; + z.msg = "invalid stored block lengths"; + r = Z_DATA_ERROR; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left !== 0 ? STORED : (last !== 0 ? DRY : TYPE); + break; + case STORED: + if (n === 0) { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + + if (m === 0) { + if (q == that.end && that.read !== 0) { + q = 0; + m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q); + } + if (m === 0) { + that.write = q; + r = that.inflate_flush(z, r); + q = that.write; + m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q); + if (q == that.end && that.read !== 0) { + q = 0; + m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q); + } + if (m === 0) { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + } + } + r = Z_OK; + + t = left; + if (t > n) + t = n; + if (t > m) + t = m; + that.window.set(z.read_buf(p, t), q); + p += t; + n -= t; + q += t; + m -= t; + if ((left -= t) !== 0) + break; + mode = last !== 0 ? DRY : TYPE; + break; + case TABLE: + + while (k < (14)) { + if (n !== 0) { + r = Z_OK; + } else { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + table = t = (b & 0x3fff); + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) { + mode = BADBLOCKS; + z.msg = "too many length or distance symbols"; + r = Z_DATA_ERROR; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if (!blens || blens.length < t) { + blens = []; // new Array(t); + } else { + for (i = 0; i < t; i++) { + blens[i] = 0; + } + } + + // { + b >>>= (14); + k -= (14); + // } + + index = 0; + mode = BTREE; + /* falls through */ + case BTREE: + while (index < 4 + (table >>> 10)) { + while (k < (3)) { + if (n !== 0) { + r = Z_OK; + } else { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + blens[border[index++]] = b & 7; + + // { + b >>>= (3); + k -= (3); + // } + } + + while (index < 19) { + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); + if (t != Z_OK) { + r = t; + if (r == Z_DATA_ERROR) { + blens = null; + mode = BADBLOCKS; + } + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + + index = 0; + mode = DTREE; + /* falls through */ + case DTREE: + while (true) { + t = table; + if (index >= 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) { + break; + } + + var j, c; + + t = bb[0]; + + while (k < (t)) { + if (n !== 0) { + r = Z_OK; + } else { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + // if (tb[0] == -1) { + // System.err.println("null..."); + // } + + t = hufts[(tb[0] + (b & inflate_mask[t])) * 3 + 1]; + c = hufts[(tb[0] + (b & inflate_mask[t])) * 3 + 2]; + + if (c < 16) { + b >>>= (t); + k -= (t); + blens[index++] = c; + } else { // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while (k < (t + i)) { + if (n !== 0) { + r = Z_OK; + } else { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + n--; + b |= (z.read_byte(p++) & 0xff) << k; + k += 8; + } + + b >>>= (t); + k -= (t); + + j += (b & inflate_mask[i]); + + b >>>= (i); + k -= (i); + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) { + blens = null; + mode = BADBLOCKS; + z.msg = "invalid bit length repeat"; + r = Z_DATA_ERROR; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + + c = c == 16 ? blens[i - 1] : 0; + do { + blens[i++] = c; + } while (--j !== 0); + index = i; + } + } + + tb[0] = -1; + // { + var bl_ = []; // new Array(1); + var bd_ = []; // new Array(1); + var tl_ = []; // new Array(1); + var td_ = []; // new Array(1); + bl_[0] = 9; // must be <= 9 for lookahead assumptions + bd_[0] = 6; // must be <= 9 for lookahead assumptions + + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), blens, bl_, bd_, tl_, td_, hufts, z); + + if (t != Z_OK) { + if (t == Z_DATA_ERROR) { + blens = null; + mode = BADBLOCKS; + } + r = t; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + codes.init(bl_[0], bd_[0], hufts, tl_[0], hufts, td_[0]); + // } + mode = CODES; + /* falls through */ + case CODES: + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + + if ((r = codes.proc(that, z, r)) != Z_STREAM_END) { + return that.inflate_flush(z, r); + } + r = Z_OK; + codes.free(z); + + p = z.next_in_index; + n = z.avail_in; + b = that.bitb; + k = that.bitk; + q = that.write; + m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q); + + if (last === 0) { + mode = TYPE; + break; + } + mode = DRY; + /* falls through */ + case DRY: + that.write = q; + r = that.inflate_flush(z, r); + q = that.write; + m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q); + if (that.read != that.write) { + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + mode = DONELOCKS; + /* falls through */ + case DONELOCKS: + r = Z_STREAM_END; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + case BADBLOCKS: + r = Z_DATA_ERROR; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + + default: + r = Z_STREAM_ERROR; + + that.bitb = b; + that.bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + that.write = q; + return that.inflate_flush(z, r); + } + } + }; + + that.free = function(z) { + that.reset(z, null); + that.window = null; + hufts = null; + // ZFREE(z, s); + }; + + that.set_dictionary = function(d, start, n) { + that.window.set(d.subarray(start, start + n), 0); + that.read = that.write = n; + }; + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. + that.sync_point = function() { + return mode == LENS ? 1 : 0; + }; + + } + + // Inflate + + // preset dictionary flag in zlib header + var PRESET_DICT = 0x20; + + var Z_DEFLATED = 8; + + var METHOD = 0; // waiting for method byte + var FLAG = 1; // waiting for flag byte + var DICT4 = 2; // four dictionary check bytes to go + var DICT3 = 3; // three dictionary check bytes to go + var DICT2 = 4; // two dictionary check bytes to go + var DICT1 = 5; // one dictionary check byte to go + var DICT0 = 6; // waiting for inflateSetDictionary + var BLOCKS = 7; // decompressing blocks + var DONE = 12; // finished check, done + var BAD = 13; // got an error--stay here + + var mark = [ 0, 0, 0xff, 0xff ]; + + function Inflate() { + var that = this; + + that.mode = 0; // current inflate mode + + // mode dependent information + that.method = 0; // if FLAGS, method byte + + // if CHECK, check values to compare + that.was = [ 0 ]; // new Array(1); // computed check value + that.need = 0; // stream check value + + // if BAD, inflateSync's marker bytes count + that.marker = 0; + + // mode independent information + that.wbits = 0; // log2(window size) (8..15, defaults to 15) + + // this.blocks; // current inflate_blocks state + + function inflateReset(z) { + if (!z || !z.istate) + return Z_STREAM_ERROR; + + z.total_in = z.total_out = 0; + z.msg = null; + z.istate.mode = BLOCKS; + z.istate.blocks.reset(z, null); + return Z_OK; + } + + that.inflateEnd = function(z) { + if (that.blocks) + that.blocks.free(z); + that.blocks = null; + // ZFREE(z, z->state); + return Z_OK; + }; + + that.inflateInit = function(z, w) { + z.msg = null; + that.blocks = null; + + // set window size + if (w < 8 || w > 15) { + that.inflateEnd(z); + return Z_STREAM_ERROR; + } + that.wbits = w; + + z.istate.blocks = new InfBlocks(z, 1 << w); + + // reset state + inflateReset(z); + return Z_OK; + }; + + that.inflate = function(z, f) { + var r; + var b; + + if (!z || !z.istate || !z.next_in) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + while (true) { + // System.out.println("mode: "+z.istate.mode); + switch (z.istate.mode) { + case METHOD: + + if (z.avail_in === 0) + return r; + r = f; + + z.avail_in--; + z.total_in++; + if (((z.istate.method = z.read_byte(z.next_in_index++)) & 0xf) != Z_DEFLATED) { + z.istate.mode = BAD; + z.msg = "unknown compression method"; + z.istate.marker = 5; // can't try inflateSync + break; + } + if ((z.istate.method >> 4) + 8 > z.istate.wbits) { + z.istate.mode = BAD; + z.msg = "invalid window size"; + z.istate.marker = 5; // can't try inflateSync + break; + } + z.istate.mode = FLAG; + /* falls through */ + case FLAG: + + if (z.avail_in === 0) + return r; + r = f; + + z.avail_in--; + z.total_in++; + b = (z.read_byte(z.next_in_index++)) & 0xff; + + if ((((z.istate.method << 8) + b) % 31) !== 0) { + z.istate.mode = BAD; + z.msg = "incorrect header check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + if ((b & PRESET_DICT) === 0) { + z.istate.mode = BLOCKS; + break; + } + z.istate.mode = DICT4; + /* falls through */ + case DICT4: + + if (z.avail_in === 0) + return r; + r = f; + + z.avail_in--; + z.total_in++; + z.istate.need = ((z.read_byte(z.next_in_index++) & 0xff) << 24) & 0xff000000; + z.istate.mode = DICT3; + /* falls through */ + case DICT3: + + if (z.avail_in === 0) + return r; + r = f; + + z.avail_in--; + z.total_in++; + z.istate.need += ((z.read_byte(z.next_in_index++) & 0xff) << 16) & 0xff0000; + z.istate.mode = DICT2; + /* falls through */ + case DICT2: + + if (z.avail_in === 0) + return r; + r = f; + + z.avail_in--; + z.total_in++; + z.istate.need += ((z.read_byte(z.next_in_index++) & 0xff) << 8) & 0xff00; + z.istate.mode = DICT1; + /* falls through */ + case DICT1: + + if (z.avail_in === 0) + return r; + r = f; + + z.avail_in--; + z.total_in++; + z.istate.need += (z.read_byte(z.next_in_index++) & 0xff); + z.istate.mode = DICT0; + return Z_NEED_DICT; + case DICT0: + z.istate.mode = BAD; + z.msg = "need dictionary"; + z.istate.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case BLOCKS: + + r = z.istate.blocks.proc(z, r); + if (r == Z_DATA_ERROR) { + z.istate.mode = BAD; + z.istate.marker = 0; // can try inflateSync + break; + } + if (r == Z_OK) { + r = f; + } + if (r != Z_STREAM_END) { + return r; + } + r = f; + z.istate.blocks.reset(z, z.istate.was); + z.istate.mode = DONE; + /* falls through */ + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } + } + }; + + that.inflateSetDictionary = function(z, dictionary, dictLength) { + var index = 0; + var length = dictLength; + if (!z || !z.istate || z.istate.mode != DICT0) + return Z_STREAM_ERROR; + + if (length >= (1 << z.istate.wbits)) { + length = (1 << z.istate.wbits) - 1; + index = dictLength - length; + } + z.istate.blocks.set_dictionary(dictionary, index, length); + z.istate.mode = BLOCKS; + return Z_OK; + }; + + that.inflateSync = function(z) { + var n; // number of bytes to look at + var p; // pointer to bytes + var m; // number of marker bytes found in a row + var r, w; // temporaries to save total_in and total_out + + // set up + if (!z || !z.istate) + return Z_STREAM_ERROR; + if (z.istate.mode != BAD) { + z.istate.mode = BAD; + z.istate.marker = 0; + } + if ((n = z.avail_in) === 0) + return Z_BUF_ERROR; + p = z.next_in_index; + m = z.istate.marker; + + // search + while (n !== 0 && m < 4) { + if (z.read_byte(p) == mark[m]) { + m++; + } else if (z.read_byte(p) !== 0) { + m = 0; + } else { + m = 4 - m; + } + p++; + n--; + } + + // restore + z.total_in += p - z.next_in_index; + z.next_in_index = p; + z.avail_in = n; + z.istate.marker = m; + + // return no joy or set up to restart on a new block + if (m != 4) { + return Z_DATA_ERROR; + } + r = z.total_in; + w = z.total_out; + inflateReset(z); + z.total_in = r; + z.total_out = w; + z.istate.mode = BLOCKS; + return Z_OK; + }; + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + // implementation to provide an additional safety check. PPP uses + // Z_SYNC_FLUSH + // but removes the length bytes of the resulting empty stored block. When + // decompressing, PPP checks that at the end of input packet, inflate is + // waiting for these length bytes. + that.inflateSyncPoint = function(z) { + if (!z || !z.istate || !z.istate.blocks) + return Z_STREAM_ERROR; + return z.istate.blocks.sync_point(); + }; + } + + // ZStream + + function ZStream() { + } + + ZStream.prototype = { + inflateInit : function(bits) { + var that = this; + that.istate = new Inflate(); + if (!bits) + bits = MAX_BITS; + return that.istate.inflateInit(that, bits); + }, + + inflate : function(f) { + var that = this; + if (!that.istate) + return Z_STREAM_ERROR; + return that.istate.inflate(that, f); + }, + + inflateEnd : function() { + var that = this; + if (!that.istate) + return Z_STREAM_ERROR; + var ret = that.istate.inflateEnd(that); + that.istate = null; + return ret; + }, + + inflateSync : function() { + var that = this; + if (!that.istate) + return Z_STREAM_ERROR; + return that.istate.inflateSync(that); + }, + inflateSetDictionary : function(dictionary, dictLength) { + var that = this; + if (!that.istate) + return Z_STREAM_ERROR; + return that.istate.inflateSetDictionary(that, dictionary, dictLength); + }, + read_byte : function(start) { + var that = this; + return that.next_in.subarray(start, start + 1)[0]; + }, + read_buf : function(start, size) { + var that = this; + return that.next_in.subarray(start, start + size); + } + }; + + // Inflater + + function Inflater() { + var that = this; + var z = new ZStream(); + var bufsize = 512; + var flush = Z_NO_FLUSH; + var buf = new Uint8Array(bufsize); + var nomoreinput = false; + + z.inflateInit(); + z.next_out = buf; + + that.append = function(data, onprogress) { + var err, buffers = [], lastIndex = 0, bufferIndex = 0, bufferSize = 0, array; + if (data.length === 0) + return; + z.next_in_index = 0; + z.next_in = data; + z.avail_in = data.length; + do { + z.next_out_index = 0; + z.avail_out = bufsize; + if ((z.avail_in === 0) && (!nomoreinput)) { // if buffer is empty and more input is available, refill it + z.next_in_index = 0; + nomoreinput = true; + } + err = z.inflate(flush); + if (nomoreinput && (err === Z_BUF_ERROR)) { + if (z.avail_in !== 0) + throw new Error("inflating: bad input"); + } else if (err !== Z_OK && err !== Z_STREAM_END) + throw new Error("inflating: " + z.msg); + if ((nomoreinput || err === Z_STREAM_END) && (z.avail_in === data.length)) + throw new Error("inflating: bad input"); + if (z.next_out_index) + if (z.next_out_index === bufsize) + buffers.push(new Uint8Array(buf)); + else + buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index))); + bufferSize += z.next_out_index; + if (onprogress && z.next_in_index > 0 && z.next_in_index != lastIndex) { + onprogress(z.next_in_index); + lastIndex = z.next_in_index; + } + } while (z.avail_in > 0 || z.avail_out === 0); + array = new Uint8Array(bufferSize); + buffers.forEach(function(chunk) { + array.set(chunk, bufferIndex); + bufferIndex += chunk.length; + }); + return array; + }; + that.flush = function() { + z.inflateEnd(); + }; + } + + // 'zip' may not be defined in z-worker and some tests + var env = global.zip || global; + env.Inflater = env._jzlib_Inflater = Inflater; +})(this); diff -r 759f9223ef6c -r 6f702f8af5a0 mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/lib/jspdf.debug.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mda_heatmap_viz/templates/mda_heatmap_viz/static/javascript/lib/jspdf.debug.js Thu Apr 07 14:49:07 2016 -0400 @@ -0,0 +1,9359 @@ +/** @preserve + * jsPDF - PDF Document creation from JavaScript + * Version 1.0.272-git Built on 2014-09-29T15:09 + * CommitID d4770725ca + * + * Copyright (c) 2010-2014 James Hall, https://github.com/MrRio/jsPDF + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, willow-systems.com + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * + * 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. + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, + */ + +/** + * Creates new jsPDF document object instance. + * + * @class + * @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l") + * @param unit Measurement unit to be used when coordinates are specified. + * One of "pt" (points), "mm" (Default), "cm", "in" + * @param format One of 'pageFormats' as shown below, default: a4 + * @returns {jsPDF} + * @name jsPDF + */ +var jsPDF = (function(global) { + 'use strict'; + var pdfVersion = '1.3', + pageFormats = { // Size in pt of various paper formats + 'a0' : [2383.94, 3370.39], 'a1' : [1683.78, 2383.94], + 'a2' : [1190.55, 1683.78], 'a3' : [ 841.89, 1190.55], + 'a4' : [ 595.28, 841.89], 'a5' : [ 419.53, 595.28], + 'a6' : [ 297.64, 419.53], 'a7' : [ 209.76, 297.64], + 'a8' : [ 147.40, 209.76], 'a9' : [ 104.88, 147.40], + 'a10' : [ 73.70, 104.88], 'b0' : [2834.65, 4008.19], + 'b1' : [2004.09, 2834.65], 'b2' : [1417.32, 2004.09], + 'b3' : [1000.63, 1417.32], 'b4' : [ 708.66, 1000.63], + 'b5' : [ 498.90, 708.66], 'b6' : [ 354.33, 498.90], + 'b7' : [ 249.45, 354.33], 'b8' : [ 175.75, 249.45], + 'b9' : [ 124.72, 175.75], 'b10' : [ 87.87, 124.72], + 'c0' : [2599.37, 3676.54], 'c1' : [1836.85, 2599.37], + 'c2' : [1298.27, 1836.85], 'c3' : [ 918.43, 1298.27], + 'c4' : [ 649.13, 918.43], 'c5' : [ 459.21, 649.13], + 'c6' : [ 323.15, 459.21], 'c7' : [ 229.61, 323.15], + 'c8' : [ 161.57, 229.61], 'c9' : [ 113.39, 161.57], + 'c10' : [ 79.37, 113.39], 'dl' : [ 311.81, 623.62], + 'letter' : [612, 792], + 'government-letter' : [576, 756], + 'legal' : [612, 1008], + 'junior-legal' : [576, 360], + 'ledger' : [1224, 792], + 'tabloid' : [792, 1224], + 'credit-card' : [153, 243] + }; + + /** + * jsPDF's Internal PubSub Implementation. + * See mrrio.github.io/jsPDF/doc/symbols/PubSub.html + * Backward compatible rewritten on 2014 by + * Diego Casorran, https://github.com/diegocr + * + * @class + * @name PubSub + */ + function PubSub(context) { + var topics = {}; + + this.subscribe = function(topic, callback, once) { + if(typeof callback !== 'function') { + return false; + } + + if(!topics.hasOwnProperty(topic)) { + topics[topic] = {}; + } + + var id = Math.random().toString(35); + topics[topic][id] = [callback,!!once]; + + return id; + }; + + this.unsubscribe = function(token) { + for(var topic in topics) { + if(topics[topic][token]) { + delete topics[topic][token]; + return true; + } + } + return false; + }; + + this.publish = function(topic) { + if(topics.hasOwnProperty(topic)) { + var args = Array.prototype.slice.call(arguments, 1), idr = []; + + for(var id in topics[topic]) { + var sub = topics[topic][id]; + try { + sub[0].apply(context, args); + } catch(ex) { + if(global.console) { + console.error('jsPDF PubSub Error', ex.message, ex); + } + } + if(sub[1]) idr.push(id); + } + if(idr.length) idr.forEach(this.unsubscribe); + } + }; + } + + /** + * @constructor + * @private + */ + function jsPDF(orientation, unit, format, compressPdf) { + var options = {}; + + if (typeof orientation === 'object') { + options = orientation; + + orientation = options.orientation; + unit = options.unit || unit; + format = options.format || format; + compressPdf = options.compress || options.compressPdf || compressPdf; + } + + // Default options + unit = unit || 'mm'; + format = format || 'a4'; + orientation = ('' + (orientation || 'P')).toLowerCase(); + + var format_as_string = ('' + format).toLowerCase(), + compress = !!compressPdf && typeof Uint8Array === 'function', + textColor = options.textColor || '0 g', + drawColor = options.drawColor || '0 G', + activeFontSize = options.fontSize || 16, + lineHeightProportion = options.lineHeight || 1.15, + lineWidth = options.lineWidth || 0.200025, // 2mm + objectNumber = 2, // 'n' Current object number + outToPages = !1, // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content + offsets = [], // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. + fonts = {}, // collection of font objects, where key is fontKey - a dynamically created label for a given font. + fontmap = {}, // mapping structure fontName > fontStyle > font key - performance layer. See addFont() + activeFontKey, // will be string representing the KEY of the font as combination of fontName + fontStyle + k, // Scale factor + tmp, + page = 0, + currentPage, + pages = [], + pagedim = {}, + content = [], + lineCapID = 0, + lineJoinID = 0, + content_length = 0, + pageWidth, + pageHeight, + pageMode, + zoomMode, + layoutMode, + documentProperties = { + 'title' : '', + 'subject' : '', + 'author' : '', + 'keywords' : '', + 'creator' : '' + }, + API = {}, + events = new PubSub(API), + + ///////////////////// + // Private functions + ///////////////////// + f2 = function(number) { + return number.toFixed(2); // Ie, %.2f + }, + f3 = function(number) { + return number.toFixed(3); // Ie, %.3f + }, + padd2 = function(number) { + return ('0' + parseInt(number)).slice(-2); + }, + out = function(string) { + if (outToPages) { + /* set by beginPage */ + pages[currentPage].push(string); + } else { + // +1 for '\n' that will be used to join 'content' + content_length += string.length + 1; + content.push(string); + } + }, + newObject = function() { + // Begin a new object + objectNumber++; + offsets[objectNumber] = content_length; + out(objectNumber + ' 0 obj'); + return objectNumber; + }, + putStream = function(str) { + out('stream'); + out(str); + out('endstream'); + }, + putPages = function() { + var n,p,arr,i,deflater,adler32,adler32cs,wPt,hPt; + + adler32cs = global.adler32cs || jsPDF.adler32cs; + if (compress && typeof adler32cs === 'undefined') { + compress = false; + } + + // outToPages = false as set in endDocument(). out() writes to content. + + for (n = 1; n <= page; n++) { + newObject(); + wPt = (pageWidth = pagedim[n].width) * k; + hPt = (pageHeight = pagedim[n].height) * k; + out('<>'); + out('endobj'); + + // Page content + p = pages[n].join('\n'); + newObject(); + if (compress) { + arr = []; + i = p.length; + while(i--) { + arr[i] = p.charCodeAt(i); + } + adler32 = adler32cs.from(p); + deflater = new Deflater(6); + deflater.append(new Uint8Array(arr)); + p = deflater.flush(); + arr = new Uint8Array(p.length + 6); + arr.set(new Uint8Array([120, 156])), + arr.set(p, 2); + arr.set(new Uint8Array([adler32 & 0xFF, (adler32 >> 8) & 0xFF, (adler32 >> 16) & 0xFF, (adler32 >> 24) & 0xFF]), p.length+2); + p = String.fromCharCode.apply(null, arr); + out('<>'); + } else { + out('<>'); + } + putStream(p); + out('endobj'); + } + offsets[1] = content_length; + out('1 0 obj'); + out('<>'); + out('endobj'); + }, + putFont = function(font) { + font.objectNumber = newObject(); + out('<>'); + out('endobj'); + }, + putFonts = function() { + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + putFont(fonts[fontKey]); + } + } + }, + putXobjectDict = function() { + // Loop through images, or other data objects + events.publish('putXobjectDict'); + }, + putResourceDictionary = function() { + out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + out('/Font <<'); + + // Do this for each font, the '1' bit is the index of the font + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + out('/' + fontKey + ' ' + fonts[fontKey].objectNumber + ' 0 R'); + } + } + out('>>'); + out('/XObject <<'); + putXobjectDict(); + out('>>'); + }, + putResources = function() { + putFonts(); + events.publish('putResources'); + // Resource dictionary + offsets[2] = content_length; + out('2 0 obj'); + out('<<'); + putResourceDictionary(); + out('>>'); + out('endobj'); + events.publish('postPutResources'); + }, + addToFontDictionary = function(fontKey, fontName, fontStyle) { + // this is mapping structure for quick font key lookup. + // returns the KEY of the font (ex: "F1") for a given + // pair of font name and type (ex: "Arial". "Italic") + if (!fontmap.hasOwnProperty(fontName)) { + fontmap[fontName] = {}; + } + fontmap[fontName][fontStyle] = fontKey; + }, + /** + * FontObject describes a particular font as member of an instnace of jsPDF + * + * It's a collection of properties like 'id' (to be used in PDF stream), + * 'fontName' (font's family name), 'fontStyle' (font's style variant label) + * + * @class + * @public + * @property id {String} PDF-document-instance-specific label assinged to the font. + * @property PostScriptName {String} PDF specification full name for the font + * @property encoding {Object} Encoding_name-to-Font_metrics_object mapping. + * @name FontObject + */ + addFont = function(PostScriptName, fontName, fontStyle, encoding) { + var fontKey = 'F' + (Object.keys(fonts).length + 1).toString(10), + // This is FontObject + font = fonts[fontKey] = { + 'id' : fontKey, + 'PostScriptName' : PostScriptName, + 'fontName' : fontName, + 'fontStyle' : fontStyle, + 'encoding' : encoding, + 'metadata' : {} + }; + addToFontDictionary(fontKey, fontName, fontStyle); + events.publish('addFont', font); + + return fontKey; + }, + addFonts = function() { + + var HELVETICA = "helvetica", + TIMES = "times", + COURIER = "courier", + NORMAL = "normal", + BOLD = "bold", + ITALIC = "italic", + BOLD_ITALIC = "bolditalic", + encoding = 'StandardEncoding', + standardFonts = [ + ['Helvetica', HELVETICA, NORMAL], + ['Helvetica-Bold', HELVETICA, BOLD], + ['Helvetica-Oblique', HELVETICA, ITALIC], + ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC], + ['Courier', COURIER, NORMAL], + ['Courier-Bold', COURIER, BOLD], + ['Courier-Oblique', COURIER, ITALIC], + ['Courier-BoldOblique', COURIER, BOLD_ITALIC], + ['Times-Roman', TIMES, NORMAL], + ['Times-Bold', TIMES, BOLD], + ['Times-Italic', TIMES, ITALIC], + ['Times-BoldItalic', TIMES, BOLD_ITALIC] + ]; + + for (var i = 0, l = standardFonts.length; i < l; i++) { + var fontKey = addFont( + standardFonts[i][0], + standardFonts[i][1], + standardFonts[i][2], + encoding); + + // adding aliases for standard fonts, this time matching the capitalization + var parts = standardFonts[i][0].split('-'); + addToFontDictionary(fontKey, parts[0], parts[1] || ''); + } + events.publish('addFonts', { fonts : fonts, dictionary : fontmap }); + }, + SAFE = function __safeCall(fn) { + fn.foo = function __safeCallWrapper() { + try { + return fn.apply(this, arguments); + } catch (e) { + var stack = e.stack || ''; + if(~stack.indexOf(' at ')) stack = stack.split(" at ")[1]; + var m = "Error in function " + stack.split("\n")[0].split('<')[0] + ": " + e.message; + if(global.console) { + global.console.error(m, e); + if(global.alert) alert(m); + } else { + throw new Error(m); + } + } + }; + fn.foo.bar = fn; + return fn.foo; + }, + to8bitStream = function(text, flags) { + /** + * PDF 1.3 spec: + * "For text strings encoded in Unicode, the first two bytes must be 254 followed by + * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts + * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely + * to be a meaningful beginning of a word or phrase.) The remainder of the + * string consists of Unicode character codes, according to the UTF-16 encoding + * specified in the Unicode standard, version 2.0. Commonly used Unicode values + * are represented as 2 bytes per character, with the high-order byte appearing first + * in the string." + * + * In other words, if there are chars in a string with char code above 255, we + * recode the string to UCS2 BE - string doubles in length and BOM is prepended. + * + * HOWEVER! + * Actual *content* (body) text (as opposed to strings used in document properties etc) + * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) + * + * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have + * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could + * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode + * code page. There, however, all characters in the stream are treated as GIDs, + * including BOM, which is the reason we need to skip BOM in content text (i.e. that + * that is tied to a font). + * + * To signal this "special" PDFEscape / to8bitStream handling mode, + * API.text() function sets (unless you overwrite it with manual values + * given to API.text(.., flags) ) + * flags.autoencode = true + * flags.noBOM = true + * + * =================================================================================== + * `flags` properties relied upon: + * .sourceEncoding = string with encoding label. + * "Unicode" by default. = encoding of the incoming text. + * pass some non-existing encoding name + * (ex: 'Do not touch my strings! I know what I am doing.') + * to make encoding code skip the encoding step. + * .outputEncoding = Either valid PDF encoding name + * (must be supported by jsPDF font metrics, otherwise no encoding) + * or a JS object, where key = sourceCharCode, value = outputCharCode + * missing keys will be treated as: sourceCharCode === outputCharCode + * .noBOM + * See comment higher above for explanation for why this is important + * .autoencode + * See comment higher above for explanation for why this is important + */ + + var i,l,sourceEncoding,encodingBlock,outputEncoding,newtext,isUnicode,ch,bch; + + flags = flags || {}; + sourceEncoding = flags.sourceEncoding || 'Unicode'; + outputEncoding = flags.outputEncoding; + + // This 'encoding' section relies on font metrics format + // attached to font objects by, among others, + // "Willow Systems' standard_font_metrics plugin" + // see jspdf.plugin.standard_font_metrics.js for format + // of the font.metadata.encoding Object. + // It should be something like + // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} + // .widths = {0:width, code:width, ..., 'fof':divisor} + // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} + if ((flags.autoencode || outputEncoding) && + fonts[activeFontKey].metadata && + fonts[activeFontKey].metadata[sourceEncoding] && + fonts[activeFontKey].metadata[sourceEncoding].encoding) { + encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; + + // each font has default encoding. Some have it clearly defined. + if (!outputEncoding && fonts[activeFontKey].encoding) { + outputEncoding = fonts[activeFontKey].encoding; + } + + // Hmmm, the above did not work? Let's try again, in different place. + if (!outputEncoding && encodingBlock.codePages) { + outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default + } + + if (typeof outputEncoding === 'string') { + outputEncoding = encodingBlock[outputEncoding]; + } + // we want output encoding to be a JS Object, where + // key = sourceEncoding's character code and + // value = outputEncoding's character code. + if (outputEncoding) { + isUnicode = false; + newtext = []; + for (i = 0, l = text.length; i < l; i++) { + ch = outputEncoding[text.charCodeAt(i)]; + if (ch) { + newtext.push( + String.fromCharCode(ch)); + } else { + newtext.push( + text[i]); + } + + // since we are looping over chars anyway, might as well + // check for residual unicodeness + if (newtext[i].charCodeAt(0) >> 8) { + /* more than 255 */ + isUnicode = true; + } + } + text = newtext.join(''); + } + } + + i = text.length; + // isUnicode may be set to false above. Hence the triple-equal to undefined + while (isUnicode === undefined && i !== 0) { + if (text.charCodeAt(i - 1) >> 8) { + /* more than 255 */ + isUnicode = true; + } + i--; + } + if (!isUnicode) { + return text; + } + + newtext = flags.noBOM ? [] : [254, 255]; + for (i = 0, l = text.length; i < l; i++) { + ch = text.charCodeAt(i); + bch = ch >> 8; // divide by 256 + if (bch >> 8) { + /* something left after dividing by 256 second time */ + throw new Error("Character at position " + i + " of string '" + + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE"); + } + newtext.push(bch); + newtext.push(ch - (bch << 8)); + } + return String.fromCharCode.apply(undefined, newtext); + }, + pdfEscape = function(text, flags) { + /** + * Replace '/', '(', and ')' with pdf-safe versions + * + * Doing to8bitStream does NOT make this PDF display unicode text. For that + * we also need to reference a unicode font and embed it - royal pain in the rear. + * + * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, + * which JavaScript Strings are happy to provide. So, while we still cannot display + * 2-byte characters property, at least CONDITIONALLY converting (entire string containing) + * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF + * is still parseable. + * This will allow immediate support for unicode in document properties strings. + */ + return to8bitStream(text, flags).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); + }, + putInfo = function() { + out('/Producer (jsPDF ' + jsPDF.version + ')'); + for(var key in documentProperties) { + if(documentProperties.hasOwnProperty(key) && documentProperties[key]) { + out('/'+key.substr(0,1).toUpperCase() + key.substr(1) + +' (' + pdfEscape(documentProperties[key]) + ')'); + } + } + var created = new Date(), + tzoffset = created.getTimezoneOffset(), + tzsign = tzoffset < 0 ? '+' : '-', + tzhour = Math.floor(Math.abs(tzoffset / 60)), + tzmin = Math.abs(tzoffset % 60), + tzstr = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(''); + out(['/CreationDate (D:', + created.getFullYear(), + padd2(created.getMonth() + 1), + padd2(created.getDate()), + padd2(created.getHours()), + padd2(created.getMinutes()), + padd2(created.getSeconds()), tzstr, ')'].join('')); + }, + putCatalog = function() { + out('/Type /Catalog'); + out('/Pages 1 0 R'); + // PDF13ref Section 7.2.1 + if (!zoomMode) zoomMode = 'fullwidth'; + switch(zoomMode) { + case 'fullwidth' : out('/OpenAction [3 0 R /FitH null]'); break; + case 'fullheight' : out('/OpenAction [3 0 R /FitV null]'); break; + case 'fullpage' : out('/OpenAction [3 0 R /Fit]'); break; + case 'original' : out('/OpenAction [3 0 R /XYZ null null 1]'); break; + default: + var pcn = '' + zoomMode; + if (pcn.substr(pcn.length-1) === '%') + zoomMode = parseInt(zoomMode) / 100; + if (typeof zoomMode === 'number') { + out('/OpenAction [3 0 R /XYZ null null '+f2(zoomMode)+']'); + } + } + if (!layoutMode) layoutMode = 'continuous'; + switch(layoutMode) { + case 'continuous' : out('/PageLayout /OneColumn'); break; + case 'single' : out('/PageLayout /SinglePage'); break; + case 'two': + case 'twoleft' : out('/PageLayout /TwoColumnLeft'); break; + case 'tworight' : out('/PageLayout /TwoColumnRight'); break; + } + if (pageMode) { + /** + * A name object specifying how the document should be displayed when opened: + * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT + * UseOutlines : Document outline visible + * UseThumbs : Thumbnail images visible + * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible + */ + out('/PageMode /' + pageMode); + } + events.publish('putCatalog'); + }, + putTrailer = function() { + out('/Size ' + (objectNumber + 1)); + out('/Root ' + objectNumber + ' 0 R'); + out('/Info ' + (objectNumber - 1) + ' 0 R'); + }, + beginPage = function(width,height) { + // Dimensions are stored as user units and converted to points on output + var orientation = typeof height === 'string' && height.toLowerCase(); + if (typeof width === 'string') { + var format = width.toLowerCase(); + if (pageFormats.hasOwnProperty(format)) { + width = pageFormats[format][0] / k; + height = pageFormats[format][1] / k; + } + } + if (Array.isArray(width)) { + height = width[1]; + width = width[0]; + } + if (orientation) { + switch(orientation.substr(0,1)) { + case 'l': if (height > width ) orientation = 's'; break; + case 'p': if (width > height ) orientation = 's'; break; + } + if (orientation === 's') { tmp = width; width = height; height = tmp; } + } + outToPages = true; + pages[++page] = []; + pagedim[page] = { + width : Number(width) || pageWidth, + height : Number(height) || pageHeight + }; + _setPage(page); + }, + _addPage = function() { + beginPage.apply(this, arguments); + // Set line width + out(f2(lineWidth * k) + ' w'); + // Set draw color + out(drawColor); + // resurrecting non-default line caps, joins + if (lineCapID !== 0) { + out(lineCapID + ' J'); + } + if (lineJoinID !== 0) { + out(lineJoinID + ' j'); + } + events.publish('addPage', { pageNumber : page }); + }, + _setPage = function(n) { + if (n > 0 && n <= page) { + currentPage = n; + pageWidth = pagedim[n].width; + pageHeight = pagedim[n].height; + } + }, + /** + * Returns a document-specific font key - a label assigned to a + * font name + font type combination at the time the font was added + * to the font inventory. + * + * Font key is used as label for the desired font for a block of text + * to be added to the PDF document stream. + * @private + * @function + * @param fontName {String} can be undefined on "falthy" to indicate "use current" + * @param fontStyle {String} can be undefined on "falthy" to indicate "use current" + * @returns {String} Font key. + */ + getFont = function(fontName, fontStyle) { + var key; + + fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName; + fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; + + try { + // get a string like 'F3' - the KEY corresponding tot he font + type combination. + key = fontmap[fontName][fontStyle]; + } catch (e) {} + + if (!key) { + throw new Error("Unable to look up font label for font '" + fontName + "', '" + + fontStyle + "'. Refer to getFontList() for available fonts."); + } + return key; + }, + buildDocument = function() { + + outToPages = false; // switches out() to content + objectNumber = 2; + content = []; + offsets = []; + + // putHeader() + out('%PDF-' + pdfVersion); + + putPages(); + + putResources(); + + // Info + newObject(); + out('<<'); + putInfo(); + out('>>'); + out('endobj'); + + // Catalog + newObject(); + out('<<'); + putCatalog(); + out('>>'); + out('endobj'); + + // Cross-ref + var o = content_length, i, p = "0000000000"; + out('xref'); + out('0 ' + (objectNumber + 1)); + out(p+' 65535 f '); + for (i = 1; i <= objectNumber; i++) { + out((p + offsets[i]).slice(-10) + ' 00000 n '); + } + // Trailer + out('trailer'); + out('<<'); + putTrailer(); + out('>>'); + out('startxref'); + out(o); + out('%%EOF'); + + outToPages = true; + + return content.join('\n'); + }, + getStyle = function(style) { + // see path-painting operators in PDF spec + var op = 'S'; // stroke + if (style === 'F') { + op = 'f'; // fill + } else if (style === 'FD' || style === 'DF') { + op = 'B'; // both + } else if (style === 'f' || style === 'f*' || style === 'B' || style === 'B*') { + /* + Allow direct use of these PDF path-painting operators: + - f fill using nonzero winding number rule + - f* fill using even-odd rule + - B fill then stroke with fill using non-zero winding number rule + - B* fill then stroke with fill using even-odd rule + */ + op = style; + } + return op; + }, + getArrayBuffer = function() { + var data = buildDocument(), len = data.length, + ab = new ArrayBuffer(len), u8 = new Uint8Array(ab); + + while(len--) u8[len] = data.charCodeAt(len); + return ab; + }, + getBlob = function() { + return new Blob([getArrayBuffer()], { type : "application/pdf" }); + }, + /** + * Generates the PDF document. + * + * If `type` argument is undefined, output is raw body of resulting PDF returned as a string. + * + * @param {String} type A string identifying one of the possible output types. + * @param {Object} options An object providing some additional signalling to PDF generator. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name output + */ + output = SAFE(function(type, options) { + var datauri = ('' + type).substr(0,6) === 'dataur' + ? 'data:application/pdf;base64,'+btoa(buildDocument()):0; + + switch (type) { + case undefined: + return buildDocument(); + case 'save': + if (navigator.getUserMedia) { + if (global.URL === undefined + || global.URL.createObjectURL === undefined) { + return API.output('dataurlnewwindow'); + } + } + saveAs(getBlob(), options); + if(typeof saveAs.unload === 'function') { + if(global.setTimeout) { + setTimeout(saveAs.unload,911); + } + } + break; + case 'arraybuffer': + return getArrayBuffer(); + case 'blob': + return getBlob(); + case 'bloburi': + case 'bloburl': + // User is responsible of calling revokeObjectURL + return global.URL && global.URL.createObjectURL(getBlob()) || void 0; + case 'datauristring': + case 'dataurlstring': + return datauri; + case 'dataurlnewwindow': + var nW = global.open(datauri); + if (nW || typeof safari === "undefined") return nW; + /* pass through */ + case 'datauri': + case 'dataurl': + return global.document.location.href = datauri; + default: + throw new Error('Output type "' + type + '" is not supported.'); + } + // @TODO: Add different output options + }); + + switch (unit) { + case 'pt': k = 1; break; + case 'mm': k = 72 / 25.4; break; + case 'cm': k = 72 / 2.54; break; + case 'in': k = 72; break; + case 'px': k = 96 / 72; break; + case 'pc': k = 12; break; + case 'em': k = 12; break; + case 'ex': k = 6; break; + default: + throw ('Invalid unit: ' + unit); + } + + //--------------------------------------- + // Public API + + /** + * Object exposing internal API to plugins + * @public + */ + API.internal = { + 'pdfEscape' : pdfEscape, + 'getStyle' : getStyle, + /** + * Returns {FontObject} describing a particular font. + * @public + * @function + * @param fontName {String} (Optional) Font's family name + * @param fontStyle {String} (Optional) Font's style variation name (Example:"Italic") + * @returns {FontObject} + */ + 'getFont' : function() { + return fonts[getFont.apply(API, arguments)]; + }, + 'getFontSize' : function() { + return activeFontSize; + }, + 'getLineHeight' : function() { + return activeFontSize * lineHeightProportion; + }, + 'write' : function(string1 /*, string2, string3, etc */) { + out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, ' ')); + }, + 'getCoordinateString' : function(value) { + return f2(value * k); + }, + 'getVerticalCoordinateString' : function(value) { + return f2((pageHeight - value) * k); + }, + 'collections' : {}, + 'newObject' : newObject, + 'putStream' : putStream, + 'events' : events, + // ratio that you use in multiplication of a given "size" number to arrive to 'point' + // units of measurement. + // scaleFactor is set at initialization of the document and calculated against the stated + // default measurement units for the document. + // If default is "mm", k is the number that will turn number in 'mm' into 'points' number. + // through multiplication. + 'scaleFactor' : k, + 'pageSize' : { + get width() { + return pageWidth + }, + get height() { + return pageHeight + } + }, + 'output' : function(type, options) { + return output(type, options); + }, + 'getNumberOfPages' : function() { + return pages.length - 1; + }, + 'pages' : pages + }; + + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @function + * @returns {jsPDF} + * + * @methodOf jsPDF# + * @name addPage + */ + API.addPage = function() { + _addPage.apply(this, arguments); + return this; + }; + API.setPage = function() { + _setPage.apply(this, arguments); + return this; + }; + API.setDisplayMode = function(zoom, layout, pmode) { + zoomMode = zoom; + layoutMode = layout; + pageMode = pmode; + return this; + }, + + /** + * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. + * + * @function + * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call. + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Object} flags Collection of settings signalling how the text must be encoded. Defaults are sane. If you think you want to pass some flags, you likely can read the source. + * @returns {jsPDF} + * @methodOf jsPDF# + * @name text + */ + API.text = function(text, x, y, flags, angle) { + /** + * Inserts something like this into PDF + * BT + * /F1 16 Tf % Font name + size + * 16 TL % How many units down for next line in multiline text + * 0 g % color + * 28.35 813.54 Td % position + * (line one) Tj + * T* (line two) Tj + * T* (line three) Tj + * ET + */ + function ESC(s) { + s = s.split("\t").join(Array(options.TabLen||9).join(" ")); + return pdfEscape(s, flags); + } + + // Pre-August-2012 the order of arguments was function(x, y, text, flags) + // in effort to make all calls have similar signature like + // function(data, coordinates... , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if (typeof text === 'number') { + tmp = y; + y = x; + x = text; + text = tmp; + } + + // If there are any newlines in text, we assume + // the user wanted to print multiple lines, so break the + // text up into an array. If the text is already an array, + // we assume the user knows what they are doing. + if (typeof text === 'string' && text.match(/[\n\r]/)) { + text = text.split(/\r\n|\r|\n/g); + } + if (typeof flags === 'number') { + angle = flags; + flags = null; + } + var xtra = '',mode = 'Td', todo; + if (angle) { + angle *= (Math.PI / 180); + var c = Math.cos(angle), + s = Math.sin(angle); + xtra = [f2(c), f2(s), f2(s * -1), f2(c), ''].join(" "); + mode = 'Tm'; + } + flags = flags || {}; + if (!('noBOM' in flags)) + flags.noBOM = true; + if (!('autoencode' in flags)) + flags.autoencode = true; + + if (typeof text === 'string') { + text = ESC(text); + } else if (text instanceof Array) { + // we don't want to destroy original text array, so cloning it + var sa = text.concat(), da = [], len = sa.length; + // we do array.join('text that must not be PDFescaped") + // thus, pdfEscape each component separately + while (len--) { + da.push(ESC(sa.shift())); + } + var linesLeft = Math.ceil((pageHeight - y) * k / (activeFontSize * lineHeightProportion)); + if (0 <= linesLeft && linesLeft < da.length + 1) { + todo = da.splice(linesLeft-1); + } + text = da.join(") Tj\nT* ("); + } else { + throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.'); + } + // Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates + + // BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET + // if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations) + // Thus, there is NO useful, *reliable* concept of "default" font for a page. + // The fact that "default" (reuse font used before) font worked before in basic cases is an accident + // - readers dealing smartly with brokenness of jsPDF's markup. + out( + 'BT\n/' + + activeFontKey + ' ' + activeFontSize + ' Tf\n' + // font face, style, size + (activeFontSize * lineHeightProportion) + ' TL\n' + // line spacing + textColor + + '\n' + xtra + f2(x * k) + ' ' + f2((pageHeight - y) * k) + ' ' + mode + '\n(' + + text + + ') Tj\nET'); + + if (todo) { + this.addPage(); + this.text( todo, x, activeFontSize * 1.7 / k); + } + + return this; + }; + + API.lstext = function(text, x, y, spacing) { + for (var i = 0, len = text.length ; i < len; i++, x += spacing) this.text(text[i], x, y); + }; + + API.line = function(x1, y1, x2, y2) { + return this.lines([[x2 - x1, y2 - y1]], x1, y1); + }; + + API.clip = function() { + // By patrick-roberts, github.com/MrRio/jsPDF/issues/328 + // Call .clip() after calling .rect() with a style argument of null + out('W') // clip + out('S') // stroke path; necessary for clip to work + }; + + /** + * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. + * All data points in `lines` are relative to last line origin. + * `x`, `y` become x1,y1 for first line / curve in the set. + * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. + * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1. + * + * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line + * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction. + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @param {Boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name lines + */ + API.lines = function(lines, x, y, scale, style, closed) { + var scalex,scaley,i,l,leg,x2,y2,x3,y3,x4,y4; + + // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) + // in effort to make all calls have similar signature like + // function(content, coordinateX, coordinateY , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if (typeof lines === 'number') { + tmp = y; + y = x; + x = lines; + lines = tmp; + } + + scale = scale || [1, 1]; + + // starting point + out(f3(x * k) + ' ' + f3((pageHeight - y) * k) + ' m '); + + scalex = scale[0]; + scaley = scale[1]; + l = lines.length; + //, x2, y2 // bezier only. In page default measurement "units", *after* scaling + //, x3, y3 // bezier only. In page default measurement "units", *after* scaling + // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling + x4 = x; // last / ending point = starting point for first item. + y4 = y; // last / ending point = starting point for first item. + + for (i = 0; i < l; i++) { + leg = lines[i]; + if (leg.length === 2) { + // simple line + x4 = leg[0] * scalex + x4; // here last x4 was prior ending point + y4 = leg[1] * scaley + y4; // here last y4 was prior ending point + out(f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' l'); + } else { + // bezier curve + x2 = leg[0] * scalex + x4; // here last x4 is prior ending point + y2 = leg[1] * scaley + y4; // here last y4 is prior ending point + x3 = leg[2] * scalex + x4; // here last x4 is prior ending point + y3 = leg[3] * scaley + y4; // here last y4 is prior ending point + x4 = leg[4] * scalex + x4; // here last x4 was prior ending point + y4 = leg[5] * scaley + y4; // here last y4 was prior ending point + out( + f3(x2 * k) + ' ' + + f3((pageHeight - y2) * k) + ' ' + + f3(x3 * k) + ' ' + + f3((pageHeight - y3) * k) + ' ' + + f3(x4 * k) + ' ' + + f3((pageHeight - y4) * k) + ' c'); + } + } + + if (closed) { + out(' h'); + } + + // stroking / filling / both the path + if (style !== null) { + out(getStyle(style)); + } + return this; + }; + + /** + * Adds a rectangle to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} w Width (in units declared at inception of PDF document) + * @param {Number} h Height (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name rect + */ + API.rect = function(x, y, w, h, style) { + var op = getStyle(style); + out([ + f2(x * k), + f2((pageHeight - y) * k), + f2(w * k), + f2(-h * k), + 're' + ].join(' ')); + + if (style !== null) { + out(getStyle(style)); + } + + return this; + }; + + /** + * Adds a triangle to PDF + * + * @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name triangle + */ + API.triangle = function(x1, y1, x2, y2, x3, y3, style) { + this.lines( + [ + [x2 - x1, y2 - y1], // vector to point 2 + [x3 - x2, y3 - y2], // vector to point 3 + [x1 - x3, y1 - y3]// closing vector back to point 1 + ], + x1, + y1, // start of path + [1, 1], + style, + true); + return this; + }; + + /** + * Adds a rectangle with rounded corners to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} w Width (in units declared at inception of PDF document) + * @param {Number} h Height (in units declared at inception of PDF document) + * @param {Number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {Number} rx Radius along y axis (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name roundedRect + */ + API.roundedRect = function(x, y, w, h, rx, ry, style) { + var MyArc = 4 / 3 * (Math.SQRT2 - 1); + this.lines( + [ + [(w - 2 * rx), 0], + [(rx * MyArc), 0, rx, ry - (ry * MyArc), rx, ry], + [0, (h - 2 * ry)], + [0, (ry * MyArc), - (rx * MyArc), ry, -rx, ry], + [(-w + 2 * rx), 0], + [ - (rx * MyArc), 0, -rx, - (ry * MyArc), -rx, -ry], + [0, (-h + 2 * ry)], + [0, - (ry * MyArc), (rx * MyArc), -ry, rx, -ry] + ], + x + rx, + y, // start of path + [1, 1], + style); + return this; + }; + + /** + * Adds an ellipse to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {Number} rx Radius along y axis (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name ellipse + */ + API.ellipse = function(x, y, rx, ry, style) { + var lx = 4 / 3 * (Math.SQRT2 - 1) * rx, + ly = 4 / 3 * (Math.SQRT2 - 1) * ry; + + out([ + f2((x + rx) * k), + f2((pageHeight - y) * k), + 'm', + f2((x + rx) * k), + f2((pageHeight - (y - ly)) * k), + f2((x + lx) * k), + f2((pageHeight - (y - ry)) * k), + f2(x * k), + f2((pageHeight - (y - ry)) * k), + 'c' + ].join(' ')); + out([ + f2((x - lx) * k), + f2((pageHeight - (y - ry)) * k), + f2((x - rx) * k), + f2((pageHeight - (y - ly)) * k), + f2((x - rx) * k), + f2((pageHeight - y) * k), + 'c' + ].join(' ')); + out([ + f2((x - rx) * k), + f2((pageHeight - (y + ly)) * k), + f2((x - lx) * k), + f2((pageHeight - (y + ry)) * k), + f2(x * k), + f2((pageHeight - (y + ry)) * k), + 'c' + ].join(' ')); + out([ + f2((x + lx) * k), + f2((pageHeight - (y + ry)) * k), + f2((x + rx) * k), + f2((pageHeight - (y + ly)) * k), + f2((x + rx) * k), + f2((pageHeight - y) * k), + 'c' + ].join(' ')); + + if (style !== null) { + out(getStyle(style)); + } + + return this; + }; + + /** + * Adds an circle to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} r Radius (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name circle + */ + API.circle = function(x, y, r, style) { + return this.ellipse(x, y, r, r, style); + }; + + /** + * Adds a properties to the PDF document + * + * @param {Object} A property_name-to-property_value object structure. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setProperties + */ + API.setProperties = function(properties) { + // copying only those properties we can render. + for (var property in documentProperties) { + if (documentProperties.hasOwnProperty(property) && properties[property]) { + documentProperties[property] = properties[property]; + } + } + return this; + }; + + /** + * Sets font size for upcoming text elements. + * + * @param {Number} size Font size in points. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFontSize + */ + API.setFontSize = function(size) { + activeFontSize = size; + return this; + }; + + /** + * Sets text font face, variant for upcoming text elements. + * See output of jsPDF.getFontList() for possible font names, styles. + * + * @param {String} fontName Font name or family. Example: "times" + * @param {String} fontStyle Font style or variant. Example: "italic" + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFont + */ + API.setFont = function(fontName, fontStyle) { + activeFontKey = getFont(fontName, fontStyle); + // if font is not found, the above line blows up and we never go further + return this; + }; + + /** + * Switches font style or variant for upcoming text elements, + * while keeping the font face or family same. + * See output of jsPDF.getFontList() for possible font names, styles. + * + * @param {String} style Font style or variant. Example: "italic" + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFontStyle + */ + API.setFontStyle = API.setFontType = function(style) { + activeFontKey = getFont(undefined, style); + // if font is not found, the above line blows up and we never go further + return this; + }; + + /** + * Returns an object - a tree of fontName to fontStyle relationships available to + * active PDF document. + * + * @public + * @function + * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } + * @methodOf jsPDF# + * @name getFontList + */ + API.getFontList = function() { + // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added. + var list = {},fontName,fontStyle,tmp; + + for (fontName in fontmap) { + if (fontmap.hasOwnProperty(fontName)) { + list[fontName] = tmp = []; + for (fontStyle in fontmap[fontName]) { + if (fontmap[fontName].hasOwnProperty(fontStyle)) { + tmp.push(fontStyle); + } + } + } + } + + return list; + }; + + /** + * Sets line width for upcoming lines. + * + * @param {Number} width Line width (in units declared at inception of PDF document) + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setLineWidth + */ + API.setLineWidth = function(width) { + out((width * k).toFixed(2) + ' w'); + return this; + }; + + /** + * Sets the stroke color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value + * @param {Number|String} ch2 Color channel value + * @param {Number|String} ch3 Color channel value + * @param {Number|String} ch4 Color channel value + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setDrawColor + */ + API.setDrawColor = function(ch1, ch2, ch3, ch4) { + var color; + if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) { + // Gray color space. + if (typeof ch1 === 'string') { + color = ch1 + ' G'; + } else { + color = f2(ch1 / 255) + ' G'; + } + } else if (ch4 === undefined) { + // RGB + if (typeof ch1 === 'string') { + color = [ch1, ch2, ch3, 'RG'].join(' '); + } else { + color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'RG'].join(' '); + } + } else { + // CMYK + if (typeof ch1 === 'string') { + color = [ch1, ch2, ch3, ch4, 'K'].join(' '); + } else { + color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'K'].join(' '); + } + } + + out(color); + return this; + }; + + /** + * Sets the fill color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value + * @param {Number|String} ch2 Color channel value + * @param {Number|String} ch3 Color channel value + * @param {Number|String} ch4 Color channel value + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFillColor + */ + API.setFillColor = function(ch1, ch2, ch3, ch4) { + var color; + + if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) { + // Gray color space. + if (typeof ch1 === 'string') { + color = ch1 + ' g'; + } else { + color = f2(ch1 / 255) + ' g'; + } + } else if (ch4 === undefined) { + // RGB + if (typeof ch1 === 'string') { + color = [ch1, ch2, ch3, 'rg'].join(' '); + } else { + color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'rg'].join(' '); + } + } else { + // CMYK + if (typeof ch1 === 'string') { + color = [ch1, ch2, ch3, ch4, 'k'].join(' '); + } else { + color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'k'].join(' '); + } + } + + out(color); + return this; + }; + + /** + * Sets the text color for upcoming elements. + * If only one, first argument is given, + * treats the value as gray-scale color value. + * + * @param {Number} r Red channel color value in range 0-255 or {String} r color value in hexadecimal, example: '#FFFFFF' + * @param {Number} g Green channel color value in range 0-255 + * @param {Number} b Blue channel color value in range 0-255 + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setTextColor + */ + API.setTextColor = function(r, g, b) { + if ((typeof r === 'string') && /^#[0-9A-Fa-f]{6}$/.test(r)) { + var hex = parseInt(r.substr(1), 16); + r = (hex >> 16) & 255; + g = (hex >> 8) & 255; + b = (hex & 255); + } + + if ((r === 0 && g === 0 && b === 0) || (typeof g === 'undefined')) { + textColor = f3(r / 255) + ' g'; + } else { + textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' '); + } + return this; + }; + + /** + * Is an Object providing a mapping from human-readable to + * integer flag values designating the varieties of line cap + * and join styles. + * + * @returns {Object} + * @fieldOf jsPDF# + * @name CapJoinStyles + */ + API.CapJoinStyles = { + 0 : 0, + 'butt' : 0, + 'but' : 0, + 'miter' : 0, + 1 : 1, + 'round' : 1, + 'rounded' : 1, + 'circle' : 1, + 2 : 2, + 'projecting' : 2, + 'project' : 2, + 'square' : 2, + 'bevel' : 2 + }; + + /** + * Sets the line cap styles + * See {jsPDF.CapJoinStyles} for variants + * + * @param {String|Number} style A string or number identifying the type of line cap + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setLineCap + */ + API.setLineCap = function(style) { + var id = this.CapJoinStyles[style]; + if (id === undefined) { + throw new Error("Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"); + } + lineCapID = id; + out(id + ' J'); + + return this; + }; + + /** + * Sets the line join styles + * See {jsPDF.CapJoinStyles} for variants + * + * @param {String|Number} style A string or number identifying the type of line join + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setLineJoin + */ + API.setLineJoin = function(style) { + var id = this.CapJoinStyles[style]; + if (id === undefined) { + throw new Error("Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"); + } + lineJoinID = id; + out(id + ' j'); + + return this; + }; + + // Output is both an internal (for plugins) and external function + API.output = output; + + /** + * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf') + * @param {String} filename The filename including extension. + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name save + */ + API.save = function(filename) { + API.output('save', filename); + }; + + // applying plugins (more methods) ON TOP of built-in API. + // this is intentional as we allow plugins to override + // built-ins + for (var plugin in jsPDF.API) { + if (jsPDF.API.hasOwnProperty(plugin)) { + if (plugin === 'events' && jsPDF.API.events.length) { + (function(events, newEvents) { + + // jsPDF.API.events is a JS Array of Arrays + // where each Array is a pair of event name, handler + // Events were added by plugins to the jsPDF instantiator. + // These are always added to the new instance and some ran + // during instantiation. + var eventname,handler_and_args,i; + + for (i = newEvents.length - 1; i !== -1; i--) { + // subscribe takes 3 args: 'topic', function, runonce_flag + // if undefined, runonce is false. + // users can attach callback directly, + // or they can attach an array with [callback, runonce_flag] + // that's what the "apply" magic is for below. + eventname = newEvents[i][0]; + handler_and_args = newEvents[i][1]; + events.subscribe.apply( + events, + [eventname].concat( + typeof handler_and_args === 'function' ? + [handler_and_args] : handler_and_args)); + } + }(events, jsPDF.API.events)); + } else { + API[plugin] = jsPDF.API[plugin]; + } + } + } + + ////////////////////////////////////////////////////// + // continuing initialization of jsPDF Document object + ////////////////////////////////////////////////////// + // Add the first page automatically + addFonts(); + activeFontKey = 'F1'; + _addPage(format, orientation); + + events.publish('initialized'); + return API; + } + + /** + * jsPDF.API is a STATIC property of jsPDF class. + * jsPDF.API is an object you can add methods and properties to. + * The methods / properties you add will show up in new jsPDF objects. + * + * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics, + * callbacks to this object. These will be reassigned to all new instances of jsPDF. + * Examples: + * jsPDF.API.events['initialized'] = function(){ 'this' is API object } + * jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object } + * + * @static + * @public + * @memberOf jsPDF + * @name API + * + * @example + * jsPDF.API.mymethod = function(){ + * // 'this' will be ref to internal API object. see jsPDF source + * // , so you can refer to built-in methods like so: + * // this.line(....) + * // this.text(....) + * } + * var pdfdoc = new jsPDF() + * pdfdoc.mymethod() // <- !!!!!! + */ + jsPDF.API = {events:[]}; + jsPDF.version = "1.0.272-debug 2014-09-29T15:09:diegocr"; + + if (typeof define === 'function' && define.amd) { + define('jsPDF', function() { + return jsPDF; + }); + } else { + global.jsPDF = jsPDF; + } + return jsPDF; +}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this)); +/** + * jsPDF addHTML PlugIn + * Copyright (c) 2014 Diego Casorran + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +(function (jsPDFAPI) { + 'use strict'; + + /** + * Renders an HTML element to canvas object which added as an image to the PDF + * + * This PlugIn requires html2canvas: https://github.com/niklasvh/html2canvas + * OR rasterizeHTML: https://github.com/cburgmer/rasterizeHTML.js + * + * @public + * @function + * @param element {Mixed} HTML Element, or anything supported by html2canvas. + * @param x {Number} starting X coordinate in jsPDF instance's declared units. + * @param y {Number} starting Y coordinate in jsPDF instance's declared units. + * @param options {Object} Additional options, check the code below. + * @param callback {Function} to call when the rendering has finished. + * + * NOTE: Every parameter is optional except 'element' and 'callback', in such + * case the image is positioned at 0x0 covering the whole PDF document + * size. Ie, to easily take screenshoots of webpages saving them to PDF. + */ + jsPDFAPI.addHTML = function (element, x, y, options, callback) { + 'use strict'; + + if(typeof html2canvas === 'undefined' && typeof rasterizeHTML === 'undefined') + throw new Error('You need either ' + +'https://github.com/niklasvh/html2canvas' + +' or https://github.com/cburgmer/rasterizeHTML.js'); + + if(typeof x !== 'number') { + options = x; + callback = y; + } + + if(typeof options === 'function') { + callback = options; + options = null; + } + + var I = this.internal, K = I.scaleFactor, W = I.pageSize.width, H = I.pageSize.height; + + options = options || {}; + options.onrendered = function(obj) { + x = parseInt(x) || 0; + y = parseInt(y) || 0; + var dim = options.dim || {}; + var h = dim.h || 0; + var w = dim.w || Math.min(W,obj.width/K) - x; + + var format = 'JPEG'; + if(options.format) + format = options.format; + + if(obj.height > H && options.pagesplit) { + var crop = function() { + var cy = 0; + while(1) { + var canvas = document.createElement('canvas'); + canvas.width = Math.min(W*K,obj.width); + canvas.height = Math.min(H*K,obj.height-cy); + var ctx = canvas.getContext('2d'); + ctx.drawImage(obj,0,cy,obj.width,canvas.height,0,0,canvas.width,canvas.height); + var args = [canvas, x,cy?0:y,canvas.width/K,canvas.height/K, format,null,'SLOW']; + this.addImage.apply(this, args); + cy += canvas.height; + if(cy >= obj.height) break; + this.addPage(); + } + callback(w,cy,null,args); + }.bind(this); + if(obj.nodeName === 'CANVAS') { + var img = new Image(); + img.onload = crop; + img.src = obj.toDataURL("image/png"); + obj = img; + } else { + crop(); + } + } else { + var alias = Math.random().toString(35); + var args = [obj, x,y,w,h, format,alias,'SLOW']; + + this.addImage.apply(this, args); + + callback(w,h,alias,args); + } + }.bind(this); + + if(typeof html2canvas !== 'undefined' && !options.rstz) { + return html2canvas(element, options); + } + + if(typeof rasterizeHTML !== 'undefined') { + var meth = 'drawDocument'; + if(typeof element === 'string') { + meth = /^http/.test(element) ? 'drawURL' : 'drawHTML'; + } + options.width = options.width || (W*K); + return rasterizeHTML[meth](element, void 0, options).then(function(r) { + options.onrendered(r.image); + }, function(e) { + callback(null,e); + }); + } + + return null; + }; +})(jsPDF.API); +/** @preserve + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + * + * 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(jsPDFAPI) { + 'use strict' + + var namespace = 'addImage_', + supported_image_types = ['jpeg', 'jpg', 'png']; + + // Image functionality ported from pdf.js + var putImage = function(img) { + + var objectNumber = this.internal.newObject() + , out = this.internal.write + , putStream = this.internal.putStream + + img['n'] = objectNumber + + out('<>'); + } + if ('trns' in img && img['trns'].constructor == Array) { + var trns = '', + i = 0, + len = img['trns'].length; + for (; i < len; i++) + trns += (img['trns'][i] + ' ' + img['trns'][i] + ' '); + out('/Mask [' + trns + ']'); + } + if ('smask' in img) { + out('/SMask ' + (objectNumber + 1) + ' 0 R'); + } + out('/Length ' + img['data'].length + '>>'); + + putStream(img['data']); + + out('endobj'); + + // Soft mask + if ('smask' in img) { + var dp = '/Predictor 15 /Colors 1 /BitsPerComponent ' + img['bpc'] + ' /Columns ' + img['w']; + var smask = {'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': img['bpc'], 'dp': dp, 'data': img['smask']}; + if ('f' in img) + smask.f = img['f']; + putImage.call(this, smask); + } + + //Palette + if (img['cs'] === this.color_spaces.INDEXED) { + + this.internal.newObject(); + //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>'); + //putStream(zlib.compress(img['pal'])); + out('<< /Length ' + img['pal'].length + '>>'); + putStream(this.arrayBufferToBinaryString(new Uint8Array(img['pal']))); + out('endobj'); + } + } + , putResourcesCallback = function() { + var images = this.internal.collections[namespace + 'images'] + for ( var i in images ) { + putImage.call(this, images[i]) + } + } + , putXObjectsDictCallback = function(){ + var images = this.internal.collections[namespace + 'images'] + , out = this.internal.write + , image + for (var i in images) { + image = images[i] + out( + '/I' + image['i'] + , image['n'] + , '0' + , 'R' + ) + } + } + , checkCompressValue = function(value) { + if(value && typeof value === 'string') + value = value.toUpperCase(); + return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE; + } + , getImages = function() { + var images = this.internal.collections[namespace + 'images']; + //first run, so initialise stuff + if(!images) { + this.internal.collections[namespace + 'images'] = images = {}; + this.internal.events.subscribe('putResources', putResourcesCallback); + this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback); + } + + return images; + } + , getImageIndex = function(images) { + var imageIndex = 0; + + if (images){ + // this is NOT the first time this method is ran on this instance of jsPDF object. + imageIndex = Object.keys ? + Object.keys(images).length : + (function(o){ + var i = 0 + for (var e in o){if(o.hasOwnProperty(e)){ i++ }} + return i + })(images) + } + + return imageIndex; + } + , notDefined = function(value) { + return typeof value === 'undefined' || value === null; + } + , generateAliasFromData = function(data) { + return typeof data === 'string' && jsPDFAPI.sHashCode(data); + } + , doesNotSupportImageType = function(type) { + return supported_image_types.indexOf(type) === -1; + } + , processMethodNotEnabled = function(type) { + return typeof jsPDFAPI['process' + type.toUpperCase()] !== 'function'; + } + , isDOMElement = function(object) { + return typeof object === 'object' && object.nodeType === 1; + } + , createDataURIFromElement = function(element, format, angle) { + + //if element is an image which uses data url defintion, just return the dataurl + if (element.nodeName === 'IMG' && element.hasAttribute('src')) { + var src = ''+element.getAttribute('src'); + if (!angle && src.indexOf('data:image/') === 0) return src; + + // only if the user doesn't care about a format + if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = 'png'; + } + + if(element.nodeName === 'CANVAS') { + var canvas = element; + } else { + var canvas = document.createElement('canvas'); + canvas.width = element.clientWidth || element.width; + canvas.height = element.clientHeight || element.height; + + var ctx = canvas.getContext('2d'); + if (!ctx) { + throw ('addImage requires canvas to be supported by browser.'); + } + if (angle) { + var x, y, b, c, s, w, h, to_radians = Math.PI/180, angleInRadians; + + if (typeof angle === 'object') { + x = angle.x; + y = angle.y; + b = angle.bg; + angle = angle.angle; + } + angleInRadians = angle*to_radians; + c = Math.abs(Math.cos(angleInRadians)); + s = Math.abs(Math.sin(angleInRadians)); + w = canvas.width; + h = canvas.height; + canvas.width = h * s + w * c; + canvas.height = h * c + w * s; + + if (isNaN(x)) x = canvas.width / 2; + if (isNaN(y)) y = canvas.height / 2; + + ctx.clearRect(0,0,canvas.width, canvas.height); + ctx.fillStyle = b || 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(x, y); + ctx.rotate(angleInRadians); + ctx.drawImage(element, -(w/2), -(h/2)); + ctx.rotate(-angleInRadians); + ctx.translate(-x, -y); + ctx.restore(); + } else { + ctx.drawImage(element, 0, 0, canvas.width, canvas.height); + } + } + return canvas.toDataURL((''+format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg'); + } + ,checkImagesForAlias = function(alias, images) { + var cached_info; + if(images) { + for(var e in images) { + if(alias === images[e].alias) { + cached_info = images[e]; + break; + } + } + } + return cached_info; + } + ,determineWidthAndHeight = function(w, h, info) { + if (!w && !h) { + w = -96; + h = -96; + } + if (w < 0) { + w = (-1) * info['w'] * 72 / w / this.internal.scaleFactor; + } + if (h < 0) { + h = (-1) * info['h'] * 72 / h / this.internal.scaleFactor; + } + if (w === 0) { + w = h * info['w'] / info['h']; + } + if (h === 0) { + h = w * info['h'] / info['w']; + } + + return [w, h]; + } + , writeImageToPDF = function(x, y, w, h, info, index, images) { + var dims = determineWidthAndHeight.call(this, w, h, info), + coord = this.internal.getCoordinateString, + vcoord = this.internal.getVerticalCoordinateString; + + w = dims[0]; + h = dims[1]; + + images[index] = info; + + this.internal.write( + 'q' + , coord(w) + , '0 0' + , coord(h) // TODO: check if this should be shifted by vcoord + , coord(x) + , vcoord(y + h) + , 'cm /I'+info['i'] + , 'Do Q' + ) + }; + + /** + * COLOR SPACES + */ + jsPDFAPI.color_spaces = { + DEVICE_RGB:'DeviceRGB', + DEVICE_GRAY:'DeviceGray', + DEVICE_CMYK:'DeviceCMYK', + CAL_GREY:'CalGray', + CAL_RGB:'CalRGB', + LAB:'Lab', + ICC_BASED:'ICCBased', + INDEXED:'Indexed', + PATTERN:'Pattern', + SEPERATION:'Seperation', + DEVICE_N:'DeviceN' + }; + + /** + * DECODE METHODS + */ + jsPDFAPI.decode = { + DCT_DECODE:'DCTDecode', + FLATE_DECODE:'FlateDecode', + LZW_DECODE:'LZWDecode', + JPX_DECODE:'JPXDecode', + JBIG2_DECODE:'JBIG2Decode', + ASCII85_DECODE:'ASCII85Decode', + ASCII_HEX_DECODE:'ASCIIHexDecode', + RUN_LENGTH_DECODE:'RunLengthDecode', + CCITT_FAX_DECODE:'CCITTFaxDecode' + }; + + /** + * IMAGE COMPRESSION TYPES + */ + jsPDFAPI.image_compression = { + NONE: 'NONE', + FAST: 'FAST', + MEDIUM: 'MEDIUM', + SLOW: 'SLOW' + }; + + jsPDFAPI.sHashCode = function(str) { + return Array.prototype.reduce && str.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0); + }; + + jsPDFAPI.isString = function(object) { + return typeof object === 'string'; + }; + + /** + * Strips out and returns info from a valid base64 data URI + * @param {String[dataURI]} a valid data URI of format 'data:[][;base64],' + * @returns an Array containing the following + * [0] the complete data URI + * [1] + * [2] format - the second part of the mime-type i.e 'png' in 'image/png' + * [4] + */ + jsPDFAPI.extractInfoFromBase64DataURI = function(dataURI) { + return /^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(dataURI); + }; + + /** + * Check to see if ArrayBuffer is supported + */ + jsPDFAPI.supportsArrayBuffer = function() { + return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined'; + }; + + /** + * Tests supplied object to determine if ArrayBuffer + * @param {Object[object]} + */ + jsPDFAPI.isArrayBuffer = function(object) { + if(!this.supportsArrayBuffer()) + return false; + return object instanceof ArrayBuffer; + }; + + /** + * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface + * @param {Object[object]} + */ + jsPDFAPI.isArrayBufferView = function(object) { + if(!this.supportsArrayBuffer()) + return false; + if(typeof Uint32Array === 'undefined') + return false; + return (object instanceof Int8Array || + object instanceof Uint8Array || + (typeof Uint8ClampedArray !== 'undefined' && object instanceof Uint8ClampedArray) || + object instanceof Int16Array || + object instanceof Uint16Array || + object instanceof Int32Array || + object instanceof Uint32Array || + object instanceof Float32Array || + object instanceof Float64Array ); + }; + + /** + * Exactly what it says on the tin + */ + jsPDFAPI.binaryStringToUint8Array = function(binary_string) { + /* + * not sure how efficient this will be will bigger files. Is there a native method? + */ + var len = binary_string.length; + var bytes = new Uint8Array( len ); + for (var i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + }; + + /** + * @see this discussion + * http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers + * + * As stated, i imagine the method below is highly inefficent for large files. + * + * Also of note from Mozilla, + * + * "However, this is slow and error-prone, due to the need for multiple conversions (especially if the binary data is not actually byte-format data, but, for example, 32-bit integers or floats)." + * + * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView + * + * Although i'm strugglig to see how StringView solves this issue? Doesn't appear to be a direct method for conversion? + * + * Async method using Blob and FileReader could be best, but i'm not sure how to fit it into the flow? + */ + jsPDFAPI.arrayBufferToBinaryString = function(buffer) { + if(this.isArrayBuffer(buffer)) + buffer = new Uint8Array(buffer); + + var binary_string = ''; + var len = buffer.byteLength; + for (var i = 0; i < len; i++) { + binary_string += String.fromCharCode(buffer[i]); + } + return binary_string; + /* + * Another solution is the method below - convert array buffer straight to base64 and then use atob + */ + //return atob(this.arrayBufferToBase64(buffer)); + }; + + /** + * Converts an ArrayBuffer directly to base64 + * + * Taken from here + * + * http://jsperf.com/encoding-xhr-image-data/31 + * + * Need to test if this is a better solution for larger files + * + */ + jsPDFAPI.arrayBufferToBase64 = function(arrayBuffer) { + var base64 = '' + var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + + var bytes = new Uint8Array(arrayBuffer) + var byteLength = bytes.byteLength + var byteRemainder = byteLength % 3 + var mainLength = byteLength - byteRemainder + + var a, b, c, d + var chunk + + // Main loop deals with bytes in chunks of 3 + for (var i = 0; i < mainLength; i = i + 3) { + // Combine the three bytes into a single integer + chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2] + + // Use bitmasks to extract 6-bit segments from the triplet + a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18 + b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12 + c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6 + d = chunk & 63 // 63 = 2^6 - 1 + + // Convert the raw binary segments to the appropriate ASCII encoding + base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d] + } + + // Deal with the remaining bytes and padding + if (byteRemainder == 1) { + chunk = bytes[mainLength] + + a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2 + + // Set the 4 least significant bits to zero + b = (chunk & 3) << 4 // 3 = 2^2 - 1 + + base64 += encodings[a] + encodings[b] + '==' + } else if (byteRemainder == 2) { + chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1] + + a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10 + b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4 + + // Set the 2 least significant bits to zero + c = (chunk & 15) << 2 // 15 = 2^4 - 1 + + base64 += encodings[a] + encodings[b] + encodings[c] + '=' + } + + return base64 + }; + + jsPDFAPI.createImageInfo = function(data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask) { + var info = { + alias:alias, + w : wd, + h : ht, + cs : cs, + bpc : bpc, + i : imageIndex, + data : data + // n: objectNumber will be added by putImage code + }; + + if(f) info.f = f; + if(dp) info.dp = dp; + if(trns) info.trns = trns; + if(pal) info.pal = pal; + if(smask) info.smask = smask; + + return info; + }; + + jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression, rotation) { + 'use strict' + + if(typeof format !== 'string') { + var tmp = h; + h = w; + w = y; + y = x; + x = format; + format = tmp; + } + + if (typeof imageData === 'object' && !isDOMElement(imageData) && "imageData" in imageData) { + var options = imageData; + + imageData = options.imageData; + format = options.format || format; + x = options.x || x || 0; + y = options.y || y || 0; + w = options.w || w; + h = options.h || h; + alias = options.alias || alias; + compression = options.compression || compression; + rotation = options.rotation || options.angle || rotation; + } + + if (isNaN(x) || isNaN(y)) + { + console.error('jsPDF.addImage: Invalid coordinates', arguments); + throw new Error('Invalid coordinates passed to jsPDF.addImage'); + } + + var images = getImages.call(this), info; + + if (!(info = checkImagesForAlias(imageData, images))) { + var dataAsBinaryString; + + if(isDOMElement(imageData)) + imageData = createDataURIFromElement(imageData, format, rotation); + + if(notDefined(alias)) + alias = generateAliasFromData(imageData); + + if (!(info = checkImagesForAlias(alias, images))) { + + if(this.isString(imageData)) { + + var base64Info = this.extractInfoFromBase64DataURI(imageData); + + if(base64Info) { + + format = base64Info[2]; + imageData = atob(base64Info[3]);//convert to binary string + + } else { + + if (imageData.charCodeAt(0) === 0x89 && + imageData.charCodeAt(1) === 0x50 && + imageData.charCodeAt(2) === 0x4e && + imageData.charCodeAt(3) === 0x47 ) format = 'png'; + } + } + format = (format || 'JPEG').toLowerCase(); + + if(doesNotSupportImageType(format)) + throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \''+format+'\''); + + if(processMethodNotEnabled(format)) + throw new Error('please ensure that the plugin for \''+format+'\' support is added'); + + /** + * need to test if it's more efficent to convert all binary strings + * to TypedArray - or should we just leave and process as string? + */ + if(this.supportsArrayBuffer()) { + dataAsBinaryString = imageData; + imageData = this.binaryStringToUint8Array(imageData); + } + + info = this['process' + format.toUpperCase()]( + imageData, + getImageIndex(images), + alias, + checkCompressValue(compression), + dataAsBinaryString + ); + + if(!info) + throw new Error('An unkwown error occurred whilst processing the image'); + } + } + + writeImageToPDF.call(this, x, y, w, h, info, info.i, images); + + return this + }; + + /** + * JPEG SUPPORT + **/ + + //takes a string imgData containing the raw bytes of + //a jpeg image and returns [width, height] + //Algorithm from: http://www.64lines.com/jpeg-width-height + var getJpegSize = function(imgData) { + 'use strict' + var width, height, numcomponents; + // Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00 + if (!imgData.charCodeAt(0) === 0xff || + !imgData.charCodeAt(1) === 0xd8 || + !imgData.charCodeAt(2) === 0xff || + !imgData.charCodeAt(3) === 0xe0 || + !imgData.charCodeAt(6) === 'J'.charCodeAt(0) || + !imgData.charCodeAt(7) === 'F'.charCodeAt(0) || + !imgData.charCodeAt(8) === 'I'.charCodeAt(0) || + !imgData.charCodeAt(9) === 'F'.charCodeAt(0) || + !imgData.charCodeAt(10) === 0x00) { + throw new Error('getJpegSize requires a binary string jpeg file') + } + var blockLength = imgData.charCodeAt(4)*256 + imgData.charCodeAt(5); + var i = 4, len = imgData.length; + while ( i < len ) { + i += blockLength; + if (imgData.charCodeAt(i) !== 0xff) { + throw new Error('getJpegSize could not find the size of the image'); + } + if (imgData.charCodeAt(i+1) === 0xc0 || //(SOF) Huffman - Baseline DCT + imgData.charCodeAt(i+1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT + imgData.charCodeAt(i+1) === 0xc2 || // Progressive DCT (SOF2) + imgData.charCodeAt(i+1) === 0xc3 || // Spatial (sequential) lossless (SOF3) + imgData.charCodeAt(i+1) === 0xc4 || // Differential sequential DCT (SOF5) + imgData.charCodeAt(i+1) === 0xc5 || // Differential progressive DCT (SOF6) + imgData.charCodeAt(i+1) === 0xc6 || // Differential spatial (SOF7) + imgData.charCodeAt(i+1) === 0xc7) { + height = imgData.charCodeAt(i+5)*256 + imgData.charCodeAt(i+6); + width = imgData.charCodeAt(i+7)*256 + imgData.charCodeAt(i+8); + numcomponents = imgData.charCodeAt(i+9); + return [width, height, numcomponents]; + } else { + i += 2; + blockLength = imgData.charCodeAt(i)*256 + imgData.charCodeAt(i+1) + } + } + } + , getJpegSizeFromBytes = function(data) { + + var hdr = (data[0] << 8) | data[1]; + + if(hdr !== 0xFFD8) + throw new Error('Supplied data is not a JPEG'); + + var len = data.length, + block = (data[4] << 8) + data[5], + pos = 4, + bytes, width, height, numcomponents; + + while(pos < len) { + pos += block; + bytes = readBytes(data, pos); + block = (bytes[2] << 8) + bytes[3]; + if((bytes[1] === 0xC0 || bytes[1] === 0xC2) && bytes[0] === 0xFF && block > 7) { + bytes = readBytes(data, pos + 5); + width = (bytes[2] << 8) + bytes[3]; + height = (bytes[0] << 8) + bytes[1]; + numcomponents = bytes[4]; + return {width:width, height:height, numcomponents: numcomponents}; + } + + pos+=2; + } + + throw new Error('getJpegSizeFromBytes could not find the size of the image'); + } + , readBytes = function(data, offset) { + return data.subarray(offset, offset+ 5); + }; + + jsPDFAPI.processJPEG = function(data, index, alias, compression, dataAsBinaryString) { + 'use strict' + var colorSpace = this.color_spaces.DEVICE_RGB, + filter = this.decode.DCT_DECODE, + bpc = 8, + dims; + + if(this.isString(data)) { + dims = getJpegSize(data); + return this.createImageInfo(data, dims[0], dims[1], dims[3] == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias); + } + + if(this.isArrayBuffer(data)) + data = new Uint8Array(data); + + if(this.isArrayBufferView(data)) { + + dims = getJpegSizeFromBytes(data); + + // if we already have a stored binary string rep use that + data = dataAsBinaryString || this.arrayBufferToBinaryString(data); + + return this.createImageInfo(data, dims.width, dims.height, dims.numcomponents == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias); + } + + return null; + }; + + jsPDFAPI.processJPG = function(/*data, index, alias, compression, dataAsBinaryString*/) { + return this.processJPEG.apply(this, arguments); + } + +})(jsPDF.API); +(function (jsPDFAPI) { + 'use strict'; + + jsPDFAPI.autoPrint = function () { + 'use strict' + var refAutoPrintTag; + + this.internal.events.subscribe('postPutResources', function () { + refAutoPrintTag = this.internal.newObject() + this.internal.write("<< /S/Named /Type/Action /N/Print >>", "endobj"); + }); + + this.internal.events.subscribe("putCatalog", function () { + this.internal.write("/OpenAction " + refAutoPrintTag + " 0" + " R"); + }); + return this; + }; +})(jsPDF.API); +/** ==================================================================== + * jsPDF Cell plugin + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + * + * 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 (jsPDFAPI) { + 'use strict'; + /*jslint browser:true */ + /*global document: false, jsPDF */ + + var fontName, + fontSize, + fontStyle, + padding = 3, + margin = 13, + headerFunction, + lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, + pages = 1, + setLastCellPosition = function (x, y, w, h, ln) { + lastCellPos = { 'x': x, 'y': y, 'w': w, 'h': h, 'ln': ln }; + }, + getLastCellPosition = function () { + return lastCellPos; + }, + NO_MARGINS = {left:0, top:0, bottom: 0}; + + jsPDFAPI.setHeaderFunction = function (func) { + headerFunction = func; + }; + + jsPDFAPI.getTextDimensions = function (txt) { + fontName = this.internal.getFont().fontName; + fontSize = this.table_font_size || this.internal.getFontSize(); + fontStyle = this.internal.getFont().fontStyle; + // 1 pixel = 0.264583 mm and 1 mm = 72/25.4 point + var px2pt = 0.264583 * 72 / 25.4, + dimensions, + text; + + text = document.createElement('font'); + text.id = "jsPDFCell"; + text.style.fontStyle = fontStyle; + text.style.fontName = fontName; + text.style.fontSize = fontSize + 'pt'; + text.textContent = txt; + + document.body.appendChild(text); + + dimensions = { w: (text.offsetWidth + 1) * px2pt, h: (text.offsetHeight + 1) * px2pt}; + + document.body.removeChild(text); + + return dimensions; + }; + + jsPDFAPI.cellAddPage = function () { + var margins = this.margins || NO_MARGINS; + + this.addPage(); + + setLastCellPosition(margins.left, margins.top, undefined, undefined); + //setLastCellPosition(undefined, undefined, undefined, undefined, undefined); + pages += 1; + }; + + jsPDFAPI.cellInitialize = function () { + lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }; + pages = 1; + }; + + jsPDFAPI.cell = function (x, y, w, h, txt, ln, align) { + var curCell = getLastCellPosition(); + + // If this is not the first cell, we must change its position + if (curCell.ln !== undefined) { + if (curCell.ln === ln) { + //Same line + x = curCell.x + curCell.w; + y = curCell.y; + } else { + //New line + var margins = this.margins || NO_MARGINS; + if ((curCell.y + curCell.h + h + margin) >= this.internal.pageSize.height - margins.bottom) { + this.cellAddPage(); + if (this.printHeaders && this.tableHeaderRow) { + this.printHeaderRow(ln, true); + } + } + //We ignore the passed y: the lines may have diferent heights + y = (getLastCellPosition().y + getLastCellPosition().h); + + } + } + + if (txt[0] !== undefined) { + if (this.printingHeaderRow) { + this.rect(x, y, w, h, 'FD'); + } else { + this.rect(x, y, w, h); + } + if (align === 'right') { + if (txt instanceof Array) { + for(var i = 0; i max) { + max = item; + } + } + } + + return max; + }; + + /** + * Create a table from a set of data. + * @param {Integer} [x] : left-position for top-left corner of table + * @param {Integer} [y] top-position for top-left corner of table + * @param {Object[]} [data] As array of objects containing key-value pairs corresponding to a row of data. + * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost + + * @param {Object} [config.printHeaders] True to print column headers at the top of every page + * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value + * @param {Object} [config.margins] margin values for left, top, bottom, and width + * @param {Object} [config.fontSize] Integer fontSize to use (optional) + */ + + jsPDFAPI.table = function (x,y, data, headers, config) { + if (!data) { + throw 'No data for PDF table'; + } + + var headerNames = [], + headerPrompts = [], + header, + i, + ln, + cln, + columnMatrix = {}, + columnWidths = {}, + columnData, + column, + columnMinWidths = [], + j, + tableHeaderConfigs = [], + model, + jln, + func, + + //set up defaults. If a value is provided in config, defaults will be overwritten: + autoSize = false, + printHeaders = true, + fontSize = 12, + margins = NO_MARGINS; + + margins.width = this.internal.pageSize.width; + + if (config) { + //override config defaults if the user has specified non-default behavior: + if(config.autoSize === true) { + autoSize = true; + } + if(config.printHeaders === false) { + printHeaders = false; + } + if(config.fontSize){ + fontSize = config.fontSize; + } + if(config.margins){ + margins = config.margins; + } + } + + /** + * @property {Number} lnMod + * Keep track of the current line number modifier used when creating cells + */ + this.lnMod = 0; + lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, + pages = 1; + + this.printHeaders = printHeaders; + this.margins = margins; + this.setFontSize(fontSize); + this.table_font_size = fontSize; + + // Set header values + if (headers === undefined || (headers === null)) { + // No headers defined so we derive from data + headerNames = Object.keys(data[0]); + + } else if (headers[0] && (typeof headers[0] !== 'string')) { + var px2pt = 0.264583 * 72 / 25.4; + + // Split header configs into names and prompts + for (i = 0, ln = headers.length; i < ln; i += 1) { + header = headers[i]; + headerNames.push(header.name); + headerPrompts.push(header.prompt); + columnWidths[header.name] = header.width *px2pt; + } + + } else { + headerNames = headers; + } + + if (autoSize) { + // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]} + func = function (rec) { + return rec[header]; + }; + + for (i = 0, ln = headerNames.length; i < ln; i += 1) { + header = headerNames[i]; + + columnMatrix[header] = data.map( + func + ); + + // get header width + columnMinWidths.push(this.getTextDimensions(headerPrompts[i] || header).w); + column = columnMatrix[header]; + + // get cell widths + for (j = 0, cln = column.length; j < cln; j += 1) { + columnData = column[j]; + columnMinWidths.push(this.getTextDimensions(columnData).w); + } + + // get final column width + columnWidths[header] = jsPDFAPI.arrayMax(columnMinWidths); + } + } + + // -- Construct the table + + if (printHeaders) { + var lineHeight = this.calculateLineHeight(headerNames, columnWidths, headerPrompts.length?headerPrompts:headerNames); + + // Construct the header row + for (i = 0, ln = headerNames.length; i < ln; i += 1) { + header = headerNames[i]; + tableHeaderConfigs.push([x, y, columnWidths[header], lineHeight, String(headerPrompts.length ? headerPrompts[i] : header)]); + } + + // Store the table header config + this.setTableHeaderRow(tableHeaderConfigs); + + // Print the header for the start of the table + this.printHeaderRow(1, false); + } + + // Construct the data rows + for (i = 0, ln = data.length; i < ln; i += 1) { + var lineHeight; + model = data[i]; + lineHeight = this.calculateLineHeight(headerNames, columnWidths, model); + + for (j = 0, jln = headerNames.length; j < jln; j += 1) { + header = headerNames[j]; + this.cell(x, y, columnWidths[header], lineHeight, model[header], i + 2, header.align); + } + } + this.lastCellPos = lastCellPos; + this.table_x = x; + this.table_y = y; + return this; + }; + /** + * Calculate the height for containing the highest column + * @param {String[]} headerNames is the header, used as keys to the data + * @param {Integer[]} columnWidths is size of each column + * @param {Object[]} model is the line of data we want to calculate the height of + */ + jsPDFAPI.calculateLineHeight = function (headerNames, columnWidths, model) { + var header, lineHeight = 0; + for (var j = 0; j < headerNames.length; j++) { + header = headerNames[j]; + model[header] = this.splitTextToSize(String(model[header]), columnWidths[header] - padding); + var h = this.internal.getLineHeight() * model[header].length + padding; + if (h > lineHeight) + lineHeight = h; + } + return lineHeight; + }; + + /** + * Store the config for outputting a table header + * @param {Object[]} config + * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell + * except the ln parameter is excluded + */ + jsPDFAPI.setTableHeaderRow = function (config) { + this.tableHeaderRow = config; + }; + + /** + * Output the store header row + * @param lineNumber The line number to output the header at + */ + jsPDFAPI.printHeaderRow = function (lineNumber, new_page) { + if (!this.tableHeaderRow) { + throw 'Property tableHeaderRow does not exist.'; + } + + var tableHeaderCell, + tmpArray, + i, + ln; + + this.printingHeaderRow = true; + if (headerFunction !== undefined) { + var position = headerFunction(this, pages); + setLastCellPosition(position[0], position[1], position[2], position[3], -1); + } + this.setFontStyle('bold'); + var tempHeaderConf = []; + for (i = 0, ln = this.tableHeaderRow.length; i < ln; i += 1) { + this.setFillColor(200,200,200); + + tableHeaderCell = this.tableHeaderRow[i]; + if (new_page) { + tableHeaderCell[1] = this.margins && this.margins.top || 0; + tempHeaderConf.push(tableHeaderCell); + } + tmpArray = [].concat(tableHeaderCell); + this.cell.apply(this, tmpArray.concat(lineNumber)); + } + if (tempHeaderConf.length > 0){ + this.setTableHeaderRow(tempHeaderConf); + } + this.setFontStyle('normal'); + this.printingHeaderRow = false; + }; + +})(jsPDF.API); +/** @preserve + * jsPDF fromHTML plugin. BETA stage. API subject to change. Needs browser + * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Daniel Husar, https://github.com/danielhusar + * 2014 Wolfgang Gassler, https://github.com/woolfg + * + * 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 (jsPDFAPI) { + var clone, + DrillForContent, + FontNameDB, + FontStyleMap, + FontWeightMap, + FloatMap, + ClearMap, + GetCSS, + PurgeWhiteSpace, + Renderer, + ResolveFont, + ResolveUnitedNumber, + UnitedNumberMap, + elementHandledElsewhere, + images, + loadImgs, + checkForFooter, + process, + tableToJson; + clone = (function () { + return function (obj) { + Clone.prototype = obj; + return new Clone() + }; + function Clone() {} + })(); + PurgeWhiteSpace = function (array) { + var fragment, + i, + l, + lTrimmed, + r, + rTrimmed, + trailingSpace; + i = 0; + l = array.length; + fragment = void 0; + lTrimmed = false; + rTrimmed = false; + while (!lTrimmed && i !== l) { + fragment = array[i] = array[i].trimLeft(); + if (fragment) { + lTrimmed = true; + } + i++; + } + i = l - 1; + while (l && !rTrimmed && i !== -1) { + fragment = array[i] = array[i].trimRight(); + if (fragment) { + rTrimmed = true; + } + i--; + } + r = /\s+$/g; + trailingSpace = true; + i = 0; + while (i !== l) { + fragment = array[i].replace(/\s+/g, " "); + if (trailingSpace) { + fragment = fragment.trimLeft(); + } + if (fragment) { + trailingSpace = r.test(fragment); + } + array[i] = fragment; + i++; + } + return array; + }; + Renderer = function (pdf, x, y, settings) { + this.pdf = pdf; + this.x = x; + this.y = y; + this.settings = settings; + //list of functions which are called after each element-rendering process + this.watchFunctions = []; + this.init(); + return this; + }; + ResolveFont = function (css_font_family_string) { + var name, + part, + parts; + name = void 0; + parts = css_font_family_string.split(","); + part = parts.shift(); + while (!name && part) { + name = FontNameDB[part.trim().toLowerCase()]; + part = parts.shift(); + } + return name; + }; + ResolveUnitedNumber = function (css_line_height_string) { + + //IE8 issues + css_line_height_string = css_line_height_string === "auto" ? "0px" : css_line_height_string; + if (css_line_height_string.indexOf("em") > -1 && !isNaN(Number(css_line_height_string.replace("em", "")))) { + css_line_height_string = Number(css_line_height_string.replace("em", "")) * 18.719 + "px"; + } + if (css_line_height_string.indexOf("pt") > -1 && !isNaN(Number(css_line_height_string.replace("pt", "")))) { + css_line_height_string = Number(css_line_height_string.replace("pt", "")) * 1.333 + "px"; + } + + var normal, + undef, + value; + undef = void 0; + normal = 16.00; + value = UnitedNumberMap[css_line_height_string]; + if (value) { + return value; + } + value = { + "xx-small" : 9, + "x-small" : 11, + small : 13, + medium : 16, + large : 19, + "x-large" : 23, + "xx-large" : 28, + auto : 0 + }[{ css_line_height_string : css_line_height_string }]; + + if (value !== undef) { + return UnitedNumberMap[css_line_height_string] = value / normal; + } + if (value = parseFloat(css_line_height_string)) { + return UnitedNumberMap[css_line_height_string] = value / normal; + } + value = css_line_height_string.match(/([\d\.]+)(px)/); + if (value.length === 3) { + return UnitedNumberMap[css_line_height_string] = parseFloat(value[1]) / normal; + } + return UnitedNumberMap[css_line_height_string] = 1; + }; + GetCSS = function (element) { + var css,tmp,computedCSSElement; + computedCSSElement = (function (el) { + var compCSS; + compCSS = (function (el) { + if (document.defaultView && document.defaultView.getComputedStyle) { + return document.defaultView.getComputedStyle(el, null); + } else if (el.currentStyle) { + return el.currentStyle; + } else { + return el.style; + } + })(el); + return function (prop) { + prop = prop.replace(/-\D/g, function (match) { + return match.charAt(1).toUpperCase(); + }); + return compCSS[prop]; + }; + })(element); + css = {}; + tmp = void 0; + css["font-family"] = ResolveFont(computedCSSElement("font-family")) || "times"; + css["font-style"] = FontStyleMap[computedCSSElement("font-style")] || "normal"; + css["text-align"] = TextAlignMap[computedCSSElement("text-align")] || "left"; + tmp = FontWeightMap[computedCSSElement("font-weight")] || "normal"; + if (tmp === "bold") { + if (css["font-style"] === "normal") { + css["font-style"] = tmp; + } else { + css["font-style"] = tmp + css["font-style"]; + } + } + css["font-size"] = ResolveUnitedNumber(computedCSSElement("font-size")) || 1; + css["line-height"] = ResolveUnitedNumber(computedCSSElement("line-height")) || 1; + css["display"] = (computedCSSElement("display") === "inline" ? "inline" : "block"); + + tmp = (css["display"] === "block"); + css["margin-top"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-top")) || 0; + css["margin-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-bottom")) || 0; + css["padding-top"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-top")) || 0; + css["padding-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-bottom")) || 0; + css["margin-left"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-left")) || 0; + css["margin-right"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-right")) || 0; + css["padding-left"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-left")) || 0; + css["padding-right"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-right")) || 0; + + //float and clearing of floats + css["float"] = FloatMap[computedCSSElement("cssFloat")] || "none"; + css["clear"] = ClearMap[computedCSSElement("clear")] || "none"; + return css; + }; + elementHandledElsewhere = function (element, renderer, elementHandlers) { + var handlers, + i, + isHandledElsewhere, + l, + t; + isHandledElsewhere = false; + i = void 0; + l = void 0; + t = void 0; + handlers = elementHandlers["#" + element.id]; + if (handlers) { + if (typeof handlers === "function") { + isHandledElsewhere = handlers(element, renderer); + } else { + i = 0; + l = handlers.length; + while (!isHandledElsewhere && i !== l) { + isHandledElsewhere = handlers[i](element, renderer); + i++; + } + } + } + handlers = elementHandlers[element.nodeName]; + if (!isHandledElsewhere && handlers) { + if (typeof handlers === "function") { + isHandledElsewhere = handlers(element, renderer); + } else { + i = 0; + l = handlers.length; + while (!isHandledElsewhere && i !== l) { + isHandledElsewhere = handlers[i](element, renderer); + i++; + } + } + } + return isHandledElsewhere; + }; + tableToJson = function (table, renderer) { + var data, + headers, + i, + j, + rowData, + tableRow, + table_obj, + table_with, + cell, + l; + data = []; + headers = []; + i = 0; + l = table.rows[0].cells.length; + table_with = table.clientWidth; + while (i < l) { + cell = table.rows[0].cells[i]; + headers[i] = { + name : cell.textContent.toLowerCase().replace(/\s+/g, ''), + prompt : cell.textContent.replace(/\r?\n/g, ''), + width : (cell.clientWidth / table_with) * renderer.pdf.internal.pageSize.width + }; + i++; + } + i = 1; + while (i < table.rows.length) { + tableRow = table.rows[i]; + rowData = {}; + j = 0; + while (j < tableRow.cells.length) { + rowData[headers[j].name] = tableRow.cells[j].textContent.replace(/\r?\n/g, ''); + j++; + } + data.push(rowData); + i++; + } + return table_obj = { + rows : data, + headers : headers + }; + }; + var SkipNode = { + SCRIPT : 1, + STYLE : 1, + NOSCRIPT : 1, + OBJECT : 1, + EMBED : 1, + SELECT : 1 + }; + var listCount = 1; + DrillForContent = function (element, renderer, elementHandlers) { + var cn, + cns, + fragmentCSS, + i, + isBlock, + l, + px2pt, + table2json, + cb; + cns = element.childNodes; + cn = void 0; + fragmentCSS = GetCSS(element); + isBlock = fragmentCSS.display === "block"; + if (isBlock) { + renderer.setBlockBoundary(); + renderer.setBlockStyle(fragmentCSS); + } + px2pt = 0.264583 * 72 / 25.4; + i = 0; + l = cns.length; + while (i < l) { + cn = cns[i]; + if (typeof cn === "object") { + + //execute all watcher functions to e.g. reset floating + renderer.executeWatchFunctions(cn); + + /*** HEADER rendering **/ + if (cn.nodeType === 1 && cn.nodeName === 'HEADER') { + var header = cn; + //store old top margin + var oldMarginTop = renderer.pdf.margins_doc.top; + //subscribe for new page event and render header first on every page + renderer.pdf.internal.events.subscribe('addPage', function (pageInfo) { + //set current y position to old margin + renderer.y = oldMarginTop; + //render all child nodes of the header element + DrillForContent(header, renderer, elementHandlers); + //set margin to old margin + rendered header + 10 space to prevent overlapping + //important for other plugins (e.g. table) to start rendering at correct position after header + renderer.pdf.margins_doc.top = renderer.y + 10; + renderer.y += 10; + }, false); + } + + if (cn.nodeType === 8 && cn.nodeName === "#comment") { + if (~cn.textContent.indexOf("ADD_PAGE")) { + renderer.pdf.addPage(); + renderer.y = renderer.pdf.margins_doc.top; + } + + } else if (cn.nodeType === 1 && !SkipNode[cn.nodeName]) { + /*** IMAGE RENDERING ***/ + var cached_image; + if (cn.nodeName === "IMG") { + var url = cn.getAttribute("src"); + cached_image = images[renderer.pdf.sHashCode(url) || url]; + } + if (cached_image) { + if ((renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom < renderer.y + cn.height) && (renderer.y > renderer.pdf.margins_doc.top)) { + renderer.pdf.addPage(); + renderer.y = renderer.pdf.margins_doc.top; + //check if we have to set back some values due to e.g. header rendering for new page + renderer.executeWatchFunctions(cn); + } + + var imagesCSS = GetCSS(cn); + var imageX = renderer.x; + var fontToUnitRatio = 12 / renderer.pdf.internal.scaleFactor; + + //define additional paddings, margins which have to be taken into account for margin calculations + var additionalSpaceLeft = (imagesCSS["margin-left"] + imagesCSS["padding-left"])*fontToUnitRatio; + var additionalSpaceRight = (imagesCSS["margin-right"] + imagesCSS["padding-right"])*fontToUnitRatio; + var additionalSpaceTop = (imagesCSS["margin-top"] + imagesCSS["padding-top"])*fontToUnitRatio; + var additionalSpaceBottom = (imagesCSS["margin-bottom"] + imagesCSS["padding-bottom"])*fontToUnitRatio; + + //if float is set to right, move the image to the right border + //add space if margin is set + if (imagesCSS['float'] !== undefined && imagesCSS['float'] === 'right') { + imageX += renderer.settings.width - cn.width - additionalSpaceRight; + } else { + imageX += additionalSpaceLeft; + } + + renderer.pdf.addImage(cached_image, imageX, renderer.y + additionalSpaceTop, cn.width, cn.height); + cached_image = undefined; + //if the float prop is specified we have to float the text around the image + if (imagesCSS['float'] === 'right' || imagesCSS['float'] === 'left') { + //add functiont to set back coordinates after image rendering + renderer.watchFunctions.push((function(diffX , thresholdY, diffWidth, el) { + //undo drawing box adaptions which were set by floating + if (renderer.y >= thresholdY) { + renderer.x += diffX; + renderer.settings.width += diffWidth; + return true; + } else if(el && el.nodeType === 1 && !SkipNode[el.nodeName] && renderer.x+el.width > (renderer.pdf.margins_doc.left + renderer.pdf.margins_doc.width)) { + renderer.x += diffX; + renderer.y = thresholdY; + renderer.settings.width += diffWidth; + return true; + } else { + return false; + } + }).bind(this, (imagesCSS['float'] === 'left') ? -cn.width-additionalSpaceLeft-additionalSpaceRight : 0, renderer.y+cn.height+additionalSpaceTop+additionalSpaceBottom, cn.width)); + //reset floating by clear:both divs + //just set cursorY after the floating element + renderer.watchFunctions.push((function(yPositionAfterFloating, pages, el) { + if (renderer.y < yPositionAfterFloating && pages === renderer.pdf.internal.getNumberOfPages()) { + if (el.nodeType === 1 && GetCSS(el).clear === 'both') { + renderer.y = yPositionAfterFloating; + return true; + } else { + return false; + } + } else { + return true; + } + }).bind(this, renderer.y+cn.height, renderer.pdf.internal.getNumberOfPages())); + + //if floating is set we decrease the available width by the image width + renderer.settings.width -= cn.width+additionalSpaceLeft+additionalSpaceRight; + //if left just add the image width to the X coordinate + if (imagesCSS['float'] === 'left') { + renderer.x += cn.width+additionalSpaceLeft+additionalSpaceRight; + } + } else { + //if no floating is set, move the rendering cursor after the image height + renderer.y += cn.height + additionalSpaceBottom; + } + + /*** TABLE RENDERING ***/ + } else if (cn.nodeName === "TABLE") { + table2json = tableToJson(cn, renderer); + renderer.y += 10; + renderer.pdf.table(renderer.x, renderer.y, table2json.rows, table2json.headers, { + autoSize : false, + printHeaders : true, + margins : renderer.pdf.margins_doc + }); + renderer.y = renderer.pdf.lastCellPos.y + renderer.pdf.lastCellPos.h + 20; + } else if (cn.nodeName === "OL" || cn.nodeName === "UL") { + listCount = 1; + if (!elementHandledElsewhere(cn, renderer, elementHandlers)) { + DrillForContent(cn, renderer, elementHandlers); + } + renderer.y += 10; + } else if (cn.nodeName === "LI") { + var temp = renderer.x; + renderer.x += cn.parentNode.nodeName === "UL" ? 22 : 10; + renderer.y += 3; + if (!elementHandledElsewhere(cn, renderer, elementHandlers)) { + DrillForContent(cn, renderer, elementHandlers); + } + renderer.x = temp; + } else if (cn.nodeName === "BR") { + renderer.y += fragmentCSS["font-size"] * renderer.pdf.internal.scaleFactor; + } else { + if (!elementHandledElsewhere(cn, renderer, elementHandlers)) { + DrillForContent(cn, renderer, elementHandlers); + } + } + } else if (cn.nodeType === 3) { + var value = cn.nodeValue; + if (cn.nodeValue && cn.parentNode.nodeName === "LI") { + if (cn.parentNode.parentNode.nodeName === "OL") { + value = listCount++ + '. ' + value; + } else { + var fontPx = fragmentCSS["font-size"] * 16; + var radius = 2; + if (fontPx > 20) { + radius = 3; + } + cb = function (x, y) { + this.pdf.circle(x, y, radius, 'FD'); + }; + } + } + renderer.addText(value, fragmentCSS); + } else if (typeof cn === "string") { + renderer.addText(cn, fragmentCSS); + } + } + i++; + } + + if (isBlock) { + return renderer.setBlockBoundary(cb); + } + }; + images = {}; + loadImgs = function (element, renderer, elementHandlers, cb) { + var imgs = element.getElementsByTagName('img'), + l = imgs.length, found_images, + x = 0; + function done() { + renderer.pdf.internal.events.publish('imagesLoaded'); + cb(found_images); + } + function loadImage(url, width, height) { + if (!url) + return; + var img = new Image(); + found_images = ++x; + img.crossOrigin = ''; + img.onerror = img.onload = function () { + if(img.complete) { + //to support data urls in images, set width and height + //as those values are not recognized automatically + if (img.src.indexOf('data:image/') === 0) { + img.width = width || img.width || 0; + img.height = height || img.height || 0; + } + //if valid image add to known images array + if (img.width + img.height) { + var hash = renderer.pdf.sHashCode(url) || url; + images[hash] = images[hash] || img; + } + } + if(!--x) { + done(); + } + }; + img.src = url; + } + while (l--) + loadImage(imgs[l].getAttribute("src"),imgs[l].width,imgs[l].height); + return x || done(); + }; + checkForFooter = function (elem, renderer, elementHandlers) { + //check if we can found a