docs: function (options) {
      if (morpheus.Util.isHeadless()) {
        // hack to force creation of color scheme
        for (var i = 0, length = this.rowTracks.length; i < length; i++) {
          var track = this.rowTracks[i];
          track.setInvalid(true);
          if (track.isVisible()) {
            track.paint({
              x: 0,
              y: 0,
              height: 10,
              width: 10
            });
          }
        }
        for (var i = 0, length = this.columnTracks.length; i < length; i++) {
          var track = this.columnTracks[i];
          track.setInvalid(true);
          if (track.isVisible()) {
            track.paint({
              x: 0,
              y: 0,
              height: 10,
              width: 10
            });
          }
        }
        return;
      }
      options = $.extend({}, {
        paint: true
      }, options);
      this.updatingScroll = true;
      var availableHeight = this.getAvailableHeight();
      var availableWidth = this.getAvailableWidth();
      var heatmapPrefSize = this.heatmap.getPreferredSize();
  
      var columnDendrogramHeight = 0;
      var rowDendrogramWidth = 0;
      if (this.columnDendrogram) {
        columnDendrogramHeight = morpheus.CanvasUtil.getPreferredSize(this.columnDendrogram).height;
      }
      if (this.rowDendrogram) {
        rowDendrogramWidth = morpheus.CanvasUtil.getPreferredSize(this.rowDendrogram).width;
      }
      var rowTrackWidthSum = 0;
      for (var i = 0, length = this.rowTracks.length; i < length; i++) {
        if (this.rowTracks[i].isVisible()) {
          // was manually resized
          if (this.rowTracks[i].getPrefWidth() !== undefined) {
            this.rowTrackHeaders[i].setPrefWidth(this.rowTracks[i].getPrefWidth());
          }
          rowTrackWidthSum += Math.max(morpheus.CanvasUtil.getPreferredSize(this.rowTrackHeaders[i]).width,
            morpheus.CanvasUtil.getPreferredSize(this.rowTracks[i]).width);
        }
      }
      if (availableWidth !== -1 && (rowTrackWidthSum + rowDendrogramWidth + heatmapPrefSize.width) > availableWidth) {
        // shrink row tracks
        //var over = (rowTrackWidthSum + rowDendrogramWidth + heatmapPrefSize.width) - availableWidth;
        rowTrackWidthSum = 0;
        for (var i = 0, length = this.rowTracks.length; i < length; i++) {
          if (this.rowTracks[i].isVisible()) {
            var rowTrackHeaderSize = morpheus.CanvasUtil.getPreferredSize(this.rowTrackHeaders[i]);
            var width = Math.max(rowTrackHeaderSize.width, morpheus.CanvasUtil.getPreferredSize(this.rowTracks[i]).width);
            if (!rowTrackHeaderSize.widthSet) {
              width = Math.min(400, width);
              this.rowTracks[i].setPrefWidth(width);
              this.rowTrackHeaders[i].setPrefWidth(width);
            }
            rowTrackWidthSum += width;
          }
        }
      }
  
      var ypos = columnDendrogramHeight;
      var maxHeaderWidth = 0;
      // get max column header width
      for (var i = 0, length = this.columnTracks.length; i < length; i++) {
        if (this.columnTracks[i].isVisible()) {
          if (this.columnTracks[i].getPrefHeight() !== undefined) {
            this.columnTrackHeaders[i].setPrefHeight(this.columnTracks[i].getPrefHeight());
          }
          var width = morpheus.CanvasUtil.getPreferredSize(this.columnTrackHeaders[i]).width;
          maxHeaderWidth = Math.max(maxHeaderWidth, width);
        }
      }
      var xpos = Math.max(rowDendrogramWidth, maxHeaderWidth);
      var heatMapWidth = heatmapPrefSize.width;
      var maxHeatMapWidth = Math.max(50, availableWidth === -1 ? Number.MAX_VALUE : (availableWidth - rowTrackWidthSum
        - xpos
        - morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS));
      if (maxHeatMapWidth > 0 && heatMapWidth > maxHeatMapWidth) {
        heatMapWidth = maxHeatMapWidth;
        heatMapWidth = Math.min(heatMapWidth, heatmapPrefSize.width); // can't
        // go
        // bigger
        // than
        // pref
        // width
      }
      if (this.heatmap.prefWidth !== undefined) { // heat map was manually
        // resized
        heatMapWidth = Math.min(heatmapPrefSize.width,
          this.heatmap.prefWidth);
      }
      if (this.columnDendrogram !== undefined) {
        this.columnDendrogram.setBounds({
          width: heatMapWidth,
          height: columnDendrogramHeight,
          left: xpos,
          top: 0
        });
        this.columnDendrogram.$label.css('left',
          xpos + this.columnDendrogram.getUnscaledWidth() + 10).css(
          'top', 2);
        this.columnDendrogram.$squishedLabel.css('left',
          xpos + this.columnDendrogram.getUnscaledWidth() + 10).css(
          'top', 18);
  
        this.beforeColumnTrackDivider.setVisible(true);
        this.beforeColumnTrackDivider.setBounds({
          left: xpos - maxHeaderWidth,
          top: ypos,
          width: maxHeaderWidth
        });
        ypos++;
      } else {
        this.beforeColumnTrackDivider.setVisible(false);
      }
  
      for (var i = 0, length = this.columnTracks.length; i < length; i++) {
        var track = this.columnTracks[i];
        if (track.isVisible()) {
          var size = morpheus.CanvasUtil.getPreferredSize(track);
          var headerSize = morpheus.CanvasUtil.getPreferredSize(this.columnTrackHeaders[i]);
          size.height = Math.max(size.height, headerSize.height);
          track.setBounds({
            width: heatMapWidth,
            height: size.height,
            left: xpos,
            top: ypos
          });
          this.columnTrackHeaders[i].setBounds({
            width: maxHeaderWidth,
            height: size.height,
            left: xpos - maxHeaderWidth,
            top: ypos
          });
          ypos += size.height;
        }
      }
      this.$whitespace[0].style.left = Math.ceil(xpos + heatMapWidth + 10) + 'px';
      this.$whitespace[0].style.top = '0px';
      ypos += morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
      var heatMapHeight = heatmapPrefSize.height;
      if (availableHeight !== -1 && heatMapHeight > (availableHeight - ypos)) {
        heatMapHeight = Math.max(100, Math.min(heatmapPrefSize.height,
          availableHeight - ypos));
      }
      if (ypos < 0) {
        ypos = 0;
      }
      if (this.rowDendrogram) {
        this.rowDendrogram.setBounds({
          width: Math.max(rowDendrogramWidth, maxHeaderWidth),
          height: heatMapHeight,
          left: 0,
          top: ypos
        });
        this.rowDendrogram.$label.css('left', 0).css('top', 2);
        this.afterRowDendrogramDivider.setVisible(true);
        this.afterRowDendrogramDivider.setBounds({
          height: heatMapHeight,
          left: this.rowDendrogram.getUnscaledWidth(),
          top: ypos
        });
        xpos++;
      } else {
        this.afterRowDendrogramDivider.setVisible(false);
      }
      this.heatmap.setBounds({
        width: heatMapWidth,
        height: heatMapHeight,
        left: xpos,
        top: ypos
      });
      this.hSortByValuesIndicator.setBounds({
        height: 4,
        width: heatMapWidth,
        left: xpos,
        top: ypos - 4
      });
      this.hscroll.setVisible(heatMapWidth < heatmapPrefSize.width);
      this.hscroll.setExtent(heatMapWidth, heatmapPrefSize.width,
        options.scrollLeft !== undefined ? options.scrollLeft
          : (heatmapPrefSize.width === this.hscroll.getTotalExtent() ? this.hscroll.getValue()
          : heatmapPrefSize.width
          * this.hscroll.getValue()
          / this.hscroll.getMaxValue()));
      this.hscroll.setBounds({
        left: xpos,
        top: ypos + heatMapHeight + 2
      });
      xpos += heatMapWidth;
      var nvisibleRowTracks = 0;
      for (var i = 0, length = this.rowTracks.length; i < length; i++) {
        var track = this.rowTracks[i];
        if (track.isVisible()) {
          nvisibleRowTracks++;
          break;
        }
      }
      this.vSortByValuesIndicator.setBounds({
        width: 4,
        height: heatMapHeight,
        left: xpos,
        top: ypos
      });
      if (nvisibleRowTracks > 0) {
        xpos = xpos
          + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; // leave
        // space
        // after
        // afterVerticalScrollBarDivider
      }
      var rowAnnotationXStart = xpos;
      // set row track bounds
      for (var i = 0, length = this.rowTracks.length; i < length; i++) {
        var track = this.rowTracks[i];
        if (track.isVisible()) {
          var size = morpheus.CanvasUtil.getPreferredSize(track);
          var headerSize = morpheus.CanvasUtil.getPreferredSize(this.rowTrackHeaders[i]);
          size.width = Math.max(headerSize.width, size.width);
          size.height = heatMapHeight;
          track.setBounds({
            width: size.width,
            height: size.height,
            left: xpos,
            top: ypos
          });
  
          this.rowTrackHeaders[i].setBounds({
            width: size.width,
            left: xpos,
            top: ypos - headerSize.height - 5,
            height: headerSize.height
          });
          xpos += size.width;
        }
      }
      this.afterVerticalScrollBarDivider.setVisible(nvisibleRowTracks > 0 ? true : false);
      this.afterVerticalScrollBarDivider.setBounds({
        left: rowAnnotationXStart - 2,
        top: ypos - 18
      });
      this.vscroll.setVisible(heatMapHeight < heatmapPrefSize.height);
      this.vscroll.setExtent(heatMapHeight, heatmapPrefSize.height,
        options.scrollTop !== undefined ? options.scrollTop
          : (heatmapPrefSize.height === this.vscroll.getTotalExtent() ? this.vscroll.getValue()
          : heatmapPrefSize.height
          * this.vscroll.getValue()
          / this.vscroll.getMaxValue()));
      xpos += 2;
      this.vscroll.setBounds({
        left: xpos,
        top: ypos
      });
      xpos += this.vscroll.getUnscaledWidth();
      if (this.hscroll.isVisible()) {
        ypos += this.hscroll.getUnscaledHeight() + 2;
      }
      var totalHeight = 2 + ypos + heatMapHeight;
      if (options.paint) {
        this.paintAll({
          paintRows: true,
          paintColumns: true,
          invalidateRows: true,
          invalidateColumns: true
        });
      }
      this.$parent.css({
        height: Math.ceil(totalHeight) + 'px',
        width: availableWidth === -1 ? (Math.ceil(xpos + 2) + 'px') : ''
      });
  
      this.updatingScroll = false;
      this.trigger('change', {
        name: 'revalidate',
        source: this,
        arguments: arguments
      });
    }