docs: def fieldHistogram2d(self, colname, rowname, q="*:*", fq=None, ncols=10, nrows=10):
          """Generates a 2d histogram of values. Expects the field to be integer or
          floating point.
  
          @param name1(string) Name of field1 columns to compute
          @param name2(string) Name of field2 rows to compute
          @param q(string) The query identifying the set of records for the histogram
          @param fq(string) Filter query to restrict application of query
          @param nbins1(int) Number of columns in resulting histogram
          @param nbins2(int) Number of rows in resulting histogram
  
          @return dict of {colname:  name of column index
                           rowname:  name of row index
                           cols: [] list of min values for each column bin
                           rows: [] list of min values for each row bin
                           z: [[],
                               []]
  
          """
  
          def _mkQterm(name, minv, maxv, isint, isfirst, islast):
              q = ''
              if isint:
                  minv = int(minv)
                  maxv = int(maxv)
                  if isfirst:
                      q = '%s:[* TO %d]' % (name, maxv)
                  elif islast:
                      q = '%s:[%d TO *]' % (name, maxv)
                  else:
                      q = '%s:[%d TO %d]' % (name, minv, maxv)
              else:
                  if isfirst:
                      q = '%s:[* TO %f]' % (name, maxv)
                  elif islast:
                      q = '%s:[%f TO *]' % (name, maxv)
                  else:
                      q = '%s:[%f TO %f]' % (name, minv, maxv)
              return q
  
          oldpersist = self.persistent
          self.persistent = True
          ftype_col = self.getftype(colname)
          ftype_row = self.getftype(rowname)
          result = {
              'colname': colname,
              'rowname': rowname,
              'cols': [],
              'rows': [],
              'z': [],
          }
          minoffsetcol = 1
          minoffsetrow = 1
          if ftype_col == float:
              minoffsetcol = 0.00001  # noqa: F841
          if ftype_row == float:
              minoffsetrow = 0.00001  # noqa: F841
          try:
              rowminmax = self.fieldMinMax(rowname, q=q, fq=fq)
              rowminmax[0] = float(rowminmax[0])
              rowminmax[1] = float(rowminmax[1])
              colminmax = self.fieldMinMax(colname, q=q, fq=fq)
              colminmax[0] = float(colminmax[0])
              colminmax[1] = float(colminmax[1])
              rowdelta = (rowminmax[1] - rowminmax[0]) / nrows
              coldelta = (colminmax[1] - colminmax[0]) / ncols
  
              for rowidx in range(0, nrows):
                  rmin = rowminmax[0] + (rowidx * rowdelta)
                  result['rows'].append(rmin)
                  rmax = rmin + rowdelta
                  rowq = _mkQterm(
                      rowname,
                      rmin,
                      rmax,
                      (ftype_row == int),
                      (rowidx == 0),
                      (rowidx == nrows - 1),
                  )
                  qq = "%s AND %s" % (q, rowq)
                  logging.debug("row=%d, q= %s" % (rowidx, qq))
                  bins = []
                  cline = []
                  for colidx in range(0, ncols):
                      cmin = colminmax[0] + (colidx * coldelta)
                      result['cols'].append(cmin)
                      cmax = cmin + coldelta
                      colq = _mkQterm(
                          colname,
                          cmin,
                          cmax,
                          (ftype_col == int),
                          (colidx == 0),
                          (colidx == ncols - 1),
                      )
                      bin = [colidx, rowidx, cmin, rmin, cmax, rmax, 0, colq]
                      bins.append(bin)
                  # now execute the facet query request
                  params = {
                      'q': qq,
                      'rows': '0',
                      'facet': 'true',
                      'facet.field': colname,
                      'facet.limit': '1',
                      'facet.mincount': 1,
                      'wt': 'python',
                  }
                  if fq is not None:
                      params['fq'] = fq
                  request = urllib.parse.urlencode(params, doseq=True)
                  for bin in bins:
                      request = request + '&%s' % urllib.parse.urlencode(
                          {'facet.query': bin[7]}
                      )
                  rsp = self.doPost(self.solrBase + '', request, self.formheaders)
                  data = eval(rsp.read())
                  for bin in bins:
                      v = data['facet_counts']['facet_queries'][bin[7]]
                      cline.append(v)
                  result['z'].append(cline)
          finally:
              self.persistent = oldpersist
              if not self.persistent:
                  self.conn.close()
          return result