code: public Histogram toHistogram(final float bucketSize, final float offset)
    {
      final float minFloor = (float) Math.floor((min() - offset) / bucketSize) * bucketSize + offset;
      final float lowerLimitFloor = (float) Math.floor((lowerLimit - offset) / bucketSize) * bucketSize + offset;
      final float firstBreak = Math.max(minFloor, lowerLimitFloor);
  
      final float maxCeil = (float) Math.ceil((max() - offset) / bucketSize) * bucketSize + offset;
      final float upperLimitCeil = (float) Math.ceil((upperLimit - offset) / bucketSize) * bucketSize + offset;
      final float lastBreak = Math.min(maxCeil, upperLimitCeil);
  
      final float cutoff = 0.1f;
  
      final ArrayList<Float> breaks = new ArrayList<Float>();
  
      // to deal with left inclusivity when the min is the same as a break
      final float bottomBreak = minFloor - bucketSize;
      if (bottomBreak != firstBreak && (sum(firstBreak) - sum(bottomBreak) > cutoff)) {
        breaks.add(bottomBreak);
      }
  
      float left = firstBreak;
      boolean leftSet = false;
  
      //the + bucketSize / 10 is because floating point addition is always slightly incorrect and so we need to account for that
      while (left + bucketSize <= lastBreak + (bucketSize / 10)) {
        final float right = left + bucketSize;
  
        if (sum(right) - sum(left) > cutoff) {
          if (!leftSet) {
            breaks.add(left);
          }
          breaks.add(right);
          leftSet = true;
        } else {
          leftSet = false;
        }
  
        left = right;
      }
  
      if (breaks.get(breaks.size() - 1) != maxCeil && (sum(maxCeil) - sum(breaks.get(breaks.size() - 1)) > cutoff)) {
        breaks.add(maxCeil);
      }
  
      return toHistogram(Floats.toArray(breaks));
    }
docs: Computes a visual representation given an initial breakpoint, offset, and a bucket size.
  
  @param bucketSize the size of each bucket
  @param offset     the location of one breakpoint
  
  @return visual representation of the histogram