<commit_before>package org.timepedia.chronoscope.client.axis;

import org.timepedia.chronoscope.client.Dataset;
import org.timepedia.chronoscope.client.Datasets;
import org.timepedia.chronoscope.client.XYPlot;
import org.timepedia.chronoscope.client.canvas.View;
import org.timepedia.chronoscope.client.data.MipMap;
import org.timepedia.chronoscope.client.plot.DefaultXYPlot;
import org.timepedia.chronoscope.client.render.DatasetRenderer;
import org.timepedia.chronoscope.client.render.RangeAxisPanel;
import org.timepedia.chronoscope.client.util.Interval;
import org.timepedia.chronoscope.client.util.MathUtil;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;

/**
 * A RangeAxis is an ValueAxis that represents values, typically on the y-axis.
 */
@ExportPackage("chronoscope")
// TODO - might have to expose a way to change these or otherwise choose the
// most appropriate ones for the locale or units.  E.g. "billion" and "trillion" aren't
// the same magnitude everywhere.

public class RangeAxis extends ValueAxis implements Exportable {

  private static int MAX_TICKS = 8; // the default max number of ticks

  private static final String posExponentLabels[] = {"", "(tens)", "(hundreds)",
        "(thousands)", "(tens of thousands)", "(hundreds of thousands)",
        "(millions)", "(tens of millions)", "(hundreds of millions)",
        "(billions)", "(tens of billions)", "(hundreds of billions)",
        "(trillions)", "(tens of trillions)", "(hundreds of trillions)"};

  private static final String negExponentLabels[] = {"", "(tenths)",
        "(hundredths)", "(thousandths)", "(ten thousandths)",
        "(hundred thousandths)", "(millionths)", "(ten millionths)",
        "(hundred millionths)", "(billionths)", "(ten billionths)",
        "(hundred billionths)", "(trillionths)", "(ten trillionths)",
        "(hundred trillionths)"};

  // by 1000^n: 1000^0, 1000^1, 1000^2, ...
  private static final String SIposSymbol[] = {"", "k", "M", "G", "T", "P", "E", "Z", "Y"};
  private static final String SIposPrefix[] = {"", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"};

  private static final String SInegSymbol[] = {"", "m", "μ", "n", "p", "f", "a", "z", "y"};
  private static final String SInegPrefix[] =  {"", "milli", "micro", "nano", "pico", "femto", "atto", "zepto", "yocto"};

  private class DefaultTickLabelNumberFormatter implements TickLabelNumberFormatter {
    String labelFormat = "###0.####";
    // String labelFormat = "0." + "0#########".substring(MAX_DIGITS);

    public String format(double value) {
      String symbol = ""; // , prefix = "";
      double rscale = getScale();
      int scaleLog10 = (int) Math.log10(Math.abs(rscale));
      int sigdig = getTickLabelSigDig();
      if (isCalcRangeAsPercent()) {
        scientificNotationOn = false;
        labelFormat = zeroish(value) ? "0" : "###.##";
        symbol = "%";
        // value = value/getRangeExtrema.length
      } else if (isForceScientificNotation() ||
            (isAllowScientificNotation() && (Math.abs(scaleLog10) >= MAX_DIGITS))) {
        scientificNotationOn = true;
        // labelFormat = "0." + "##########".substring(MAX_DIGITS-1) + "E0";
        labelFormat = zeroish(value) ? "0" : "0."+"00000000".substring(0, sigdig-1)+"E0";
      } else if (isScaleSI() || (isAllowAutoScale() && (Math.abs(scaleLog10) >= MAX_DIGITS))) {
        scientificNotationOn = false;
        int SIscale = scaleLog10/3;
        // prefix = (rangeAxisScale<0) ? SInegPrefix[-SIscale] : SIposPrefix[SIscale];
        // symbol = (rangeAxisScale<0) ? SInegSymbol[-SIscale] : SIposSymbol[SIscale];
        if (zeroish(value - getRangeInterval().getEnd())) {
          symbol = (scaleLog10<0) ? SInegSymbol [-SIscale] + getAxisId() + negExponentLabels[-scaleLog10] : SIposSymbol[SIscale];
        } else {
          symbol = (scaleLog10<0) ? SInegSymbol [-SIscale] + getAxisId() : SIposSymbol[SIscale];
        }
        value = value/rscale;
        labelFormat = zeroish(value) ? "0" : sigformat(rscale, sigdig);
      }
      return view.numberFormat(labelFormat + symbol, value);
    }
    public String getFormat() {
      return labelFormat;
    }
  }

  private class UserTickLabelNumberFormatter implements TickLabelNumberFormatter {

    private View view;

    private String format;

    public UserTickLabelNumberFormatter(View view, String format) {
      this.view = view;
      this.format = format;
    }

    public String format(double value) {
      return view.numberFormat(format, value);
    }

    public String getFormat() {
      return format;
    }
  }

  public static final int MAX_DIGITS = 5;


  /**
   * Calculates the percentage difference from value v1 to value v2.  For
   * example, if v1 = 10 and v2 = 15, then the % difference is 50% ( 15.0/10.0 )
   */
  public static double calcRatio(double ref, double delta) {
    return Math.abs(delta-ref)/Math.abs(ref);
  }

   /**
   * Calculates the percentage difference from value v1 to value v2 in a given interval
   * example, if v1 = 10 and v2 = 15 on an interval from 0,100, then percent difference
   * is 5% ( 5.0/100.0 )
   */
  public static double calcPercentDifference(Interval length, double ref, double delta) {
    return Math.abs(delta-ref)/length.length();
  }
  // private static double[] computeLinearTickPositions(double lrangeLow,
  private double[] computeLinearTickPositions(double lrangeLow,
      double lrangeHigh, double axisHeight, double tickLabelHeight,
      boolean forceLowestTick, boolean forceHighestTick) {

    // TESTING
    forceHighestTick = true;
    forceLowestTick = true; // isCalcRangeAsPercent() ? true : forceLowestTick;

    if (zeroish(lrangeHigh - lrangeLow)) {
      if (lrangeHigh != 0.0) {
        int logRange = ((int) Math.floor(Math.log10(lrangeHigh)));
        if (logRange < 0) {
          logRange += 1;
        }
        double exponent = Math.pow(10, logRange);
        double rounded = Math.floor(lrangeHigh / exponent);
        lrangeHigh = forceHighestTick ? lrangeHigh : (rounded + 1) * exponent;
        lrangeLow = forceLowestTick ? lrangeLow : (rounded - 1) * exponent;
      } else {
        lrangeHigh = 1.0;
        lrangeLow = -1.0;
      }
    }


    int numTicks = (int) (axisHeight/(1.75 * tickLabelHeight));
    numTicks = MAX_TICKS < numTicks ? MAX_TICKS : numTicks;
    // if values straddle zero, try to make zero one of the ticks
    boolean spansZero = !zeroish(lrangeHigh) && !zeroish(lrangeLow) && (lrangeHigh * lrangeLow < 0);
    double range = Math.abs(lrangeHigh - lrangeLow);
    double larger = Math.max(lrangeLow, lrangeHigh);
    // int ticksInSmaller = (int)(numTicks * (range-larger)/range);
    // double roughInterval = spansZero ? range/(numTicks - 2): range/(numTicks - 2);
    double roughInterval = spansZero? larger/(numTicks - 3) : range/(numTicks - 2);
    final int logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
    final double exponent = Math.pow(10, logRange);
    int smoothSigDigits = (int) (roughInterval / exponent);
    smoothSigDigits = smoothSigDigits % 5 == 0 ? smoothSigDigits : smoothSigDigits - (int) MathUtil.mod(smoothSigDigits, 5.0);
    double smoothInterval = smoothSigDigits * exponent;
    double roundedStart = lrangeLow - MathUtil.mod(lrangeLow, 5.0 * exponent);
    double axisStart = forceLowestTick ? lrangeLow : roundedStart;
    double epsilon = smoothInterval/2.0;
    double edge;
    if (forceHighestTick) {
      if (spansZero) {
        // back off if crowded
        edge = larger % smoothInterval;
        if (!zeroish(edge) && (epsilon > edge)) {
            // numTicks = numTicks > 3 ? numTicks - 1 : numTicks + 1;
            numTicks = numTicks > 5 ? numTicks - 2 : numTicks++;
            roughInterval = (range/(numTicks-1));
            smoothSigDigits = (int) (roughInterval / exponent);
            smoothInterval = smoothSigDigits * exponent;
        }
      } else {
        // back off if the top is crowded
        edge = lrangeHigh % smoothInterval;
        if (!zeroish(edge) && (epsilon > edge)) {
            // numTicks = numTicks > 2 ? numTicks - 1 : numTicks + 1;
            numTicks--;
            roughInterval = range/(numTicks - 2);
            smoothSigDigits = (int) (roughInterval / exponent);
            smoothInterval = smoothSigDigits * exponent;
        }
      }
    }

    tickLabelSigDig = String.valueOf(smoothSigDigits*numTicks).length();
    double tickPositions[] = new double[numTicks];
    double tickValue = axisStart;
    tickPositions[0] = axisStart;
    for (int i = 1; i < tickPositions.length; i++) {
      if (tickValue < 0) {
         int negIntervals = (int) ((Math.abs(tickValue)-epsilon)/smoothInterval);
         if (negIntervals > 0) {
           tickValue = - smoothInterval * negIntervals;
         } else {
           double testtick = tickValue + smoothInterval;
           if (Math.abs(testtick) < Math.abs(epsilon)) { // if close to zero but still negative, use zero
               tickValue = 0;
           } else if ((testtick * tickValue) < 0) { // if crossed from neg to pos, use zero
               tickValue = 0;<commit_msg>better layout of range axis ticks when crossing zero<commit_after>package org.timepedia.chronoscope.client.axis;

import org.timepedia.chronoscope.client.Dataset;
import org.timepedia.chronoscope.client.Datasets;
import org.timepedia.chronoscope.client.XYPlot;
import org.timepedia.chronoscope.client.canvas.View;
import org.timepedia.chronoscope.client.data.MipMap;
import org.timepedia.chronoscope.client.plot.DefaultXYPlot;
import org.timepedia.chronoscope.client.render.DatasetRenderer;
import org.timepedia.chronoscope.client.render.RangeAxisPanel;
import org.timepedia.chronoscope.client.util.Interval;
import org.timepedia.chronoscope.client.util.MathUtil;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;

/**
 * A RangeAxis is an ValueAxis that represents values, typically on the y-axis.
 */
@ExportPackage("chronoscope")
// TODO - might have to expose a way to change these or otherwise choose the
// most appropriate ones for the locale or units.  E.g. "billion" and "trillion" aren't
// the same magnitude everywhere.

public class RangeAxis extends ValueAxis implements Exportable {

  private static int MAX_TICKS = 8; // the default max number of ticks

  private static final String posExponentLabels[] = {"", "(tens)", "(hundreds)",
        "(thousands)", "(tens of thousands)", "(hundreds of thousands)",
        "(millions)", "(tens of millions)", "(hundreds of millions)",
        "(billions)", "(tens of billions)", "(hundreds of billions)",
        "(trillions)", "(tens of trillions)", "(hundreds of trillions)"};

  private static final String negExponentLabels[] = {"", "(tenths)",
        "(hundredths)", "(thousandths)", "(ten thousandths)",
        "(hundred thousandths)", "(millionths)", "(ten millionths)",
        "(hundred millionths)", "(billionths)", "(ten billionths)",
        "(hundred billionths)", "(trillionths)", "(ten trillionths)",
        "(hundred trillionths)"};

  // by 1000^n: 1000^0, 1000^1, 1000^2, ...
  private static final String SIposSymbol[] = {"", "k", "M", "G", "T", "P", "E", "Z", "Y"};
  private static final String SIposPrefix[] = {"", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"};

  private static final String SInegSymbol[] = {"", "m", "μ", "n", "p", "f", "a", "z", "y"};
  private static final String SInegPrefix[] =  {"", "milli", "micro", "nano", "pico", "femto", "atto", "zepto", "yocto"};

  private class DefaultTickLabelNumberFormatter implements TickLabelNumberFormatter {
    String labelFormat = "###0.####";
    // String labelFormat = "0." + "0#########".substring(MAX_DIGITS);

    public String format(double value) {
      String symbol = ""; // , prefix = "";
      double rscale = getScale();
      int scaleLog10 = (int) Math.log10(Math.abs(rscale));
      int sigdig = getTickLabelSigDig();
      if (isCalcRangeAsPercent()) {
        scientificNotationOn = false;
        labelFormat = zeroish(value) ? "0" : "###.##";
        symbol = "%";
        // value = value/getRangeExtrema.length
      } else if (isForceScientificNotation() ||
            (isAllowScientificNotation() && (Math.abs(scaleLog10) >= MAX_DIGITS))) {
        scientificNotationOn = true;
        // labelFormat = "0." + "##########".substring(MAX_DIGITS-1) + "E0";
        labelFormat = zeroish(value) ? "0" : "0."+"00000000".substring(0, sigdig-1)+"E0";
      } else if (isScaleSI() || (isAllowAutoScale() && (Math.abs(scaleLog10) >= MAX_DIGITS))) {
        scientificNotationOn = false;
        int SIscale = scaleLog10/3;
        // prefix = (rangeAxisScale<0) ? SInegPrefix[-SIscale] : SIposPrefix[SIscale];
        // symbol = (rangeAxisScale<0) ? SInegSymbol[-SIscale] : SIposSymbol[SIscale];
        if (zeroish(value - getRangeInterval().getEnd())) {
          symbol = (scaleLog10<0) ? SInegSymbol [-SIscale] + getAxisId() + negExponentLabels[-scaleLog10] : SIposSymbol[SIscale];
        } else {
          symbol = (scaleLog10<0) ? SInegSymbol [-SIscale] + getAxisId() : SIposSymbol[SIscale];
        }
        value = value/rscale;
        labelFormat = zeroish(value) ? "0" : sigformat(rscale, sigdig);
      }
      return view.numberFormat(labelFormat + symbol, value);
    }
    public String getFormat() {
      return labelFormat;
    }
  }

  private class UserTickLabelNumberFormatter implements TickLabelNumberFormatter {

    private View view;

    private String format;

    public UserTickLabelNumberFormatter(View view, String format) {
      this.view = view;
      this.format = format;
    }

    public String format(double value) {
      return view.numberFormat(format, value);
    }

    public String getFormat() {
      return format;
    }
  }

  public static final int MAX_DIGITS = 5;


  /**
   * Calculates the percentage difference from value v1 to value v2.  For
   * example, if v1 = 10 and v2 = 15, then the % difference is 50% ( 15.0/10.0 )
   */
  public static double calcRatio(double ref, double delta) {
    return Math.abs(delta-ref)/Math.abs(ref);
  }

   /**
   * Calculates the percentage difference from value v1 to value v2 in a given interval
   * example, if v1 = 10 and v2 = 15 on an interval from 0,100, then percent difference
   * is 5% ( 5.0/100.0 )
   */
  public static double calcPercentDifference(Interval length, double ref, double delta) {
    return Math.abs(delta-ref)/length.length();
  }
  // private static double[] computeLinearTickPositions(double lrangeLow,
  private double[] computeLinearTickPositions(double lrangeLow,
      double lrangeHigh, double axisHeight, double tickLabelHeight,
      boolean forceLowestTick, boolean forceHighestTick) {

    // TESTING
    forceHighestTick = true;
    forceLowestTick = true; // isCalcRangeAsPercent() ? true : forceLowestTick;

    if (zeroish(lrangeHigh - lrangeLow)) {
      if (lrangeHigh != 0.0) {
        int logRange = ((int) Math.floor(Math.log10(lrangeHigh)));
        if (logRange < 0) {
          logRange += 1;
        }
        double exponent = Math.pow(10, logRange);
        double rounded = Math.floor(lrangeHigh / exponent);
        lrangeHigh = forceHighestTick ? lrangeHigh : (rounded + 1) * exponent;
        lrangeLow = forceLowestTick ? lrangeLow : (rounded - 1) * exponent;
      } else {
        lrangeHigh = 1.0;
        lrangeLow = -1.0;
      }
    }


    int numTicks = (int) (axisHeight/(2.0 * tickLabelHeight));
    numTicks = MAX_TICKS < numTicks ? MAX_TICKS : numTicks;
    // if values straddle zero, try to make zero one of the ticks
    boolean spansZero = !zeroish(lrangeHigh) && !zeroish(lrangeLow) && (lrangeHigh * lrangeLow < 0);
    double range = Math.abs(lrangeHigh - lrangeLow);
    double larger = Math.max(lrangeLow, lrangeHigh);
    // int ticksInSmaller = (int)(numTicks * (range-larger)/range);
    // double roughInterval = spansZero ? range/(numTicks - 2): range/(numTicks - 2);
    // double roughInterval = spansZero? larger/(numTicks - 3) : range/(numTicks - 2);

    double roughInterval = range / (1.0 * (numTicks - 1));

    int logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
    double exponent = Math.pow(10, logRange);
    int smoothSigDigits = (int) (roughInterval / exponent);
    smoothSigDigits = smoothSigDigits % 5 == 0 ? smoothSigDigits : smoothSigDigits - (int) MathUtil.mod(smoothSigDigits, 5.0);
    double smoothInterval = smoothSigDigits * exponent;
    double epsilon = smoothInterval/2.0;
    double edge = larger % smoothInterval;
      /*
    if (forceHighestTick) {
      if (spansZero) {
        // back off if crowded
        edge = larger % smoothInterval;
        if (!zeroish(edge) && (epsilon > edge)) {
            // numTicks = numTicks > 3 ? numTicks - 1 : numTicks + 1;
            numTicks = numTicks > 5 ? numTicks - 2 : numTicks++;
            roughInterval = range/(numTicks-1);
            numTicks = (int) (range / roughInterval) + 1;

            logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
            exponent = Math.pow(10, logRange);
            roughInterval = range/(numTicks -1);
            smoothSigDigits = (int) (roughInterval / exponent);
            smoothInterval = smoothSigDigits * exponent;
        }
      } else {
        // back off if the top is crowded
        edge = lrangeHigh % smoothInterval;
        if (!zeroish(edge) && (epsilon > edge)) {
            // numTicks = numTicks > 2 ? numTicks - 1 : numTicks + 1;
            // numTicks--;
            roughInterval = range/(numTicks - 1);
            numTicks = (int) (range/roughInterval) + 1;

            logRange = ((int) Math.floor(Math.log10(roughInterval))) - 1;
            exponent = Math.pow(10, logRange);
            roughInterval = range/(numTicks-1);
            smoothSigDigits = (int) (roughInterval / exponent);
            smoothInterval = smoothSigDigits * exponent;
        }
      }
    } */

    tickLabelSigDig = String.valueOf(smoothSigDigits*numTicks).length();
    // double roundedStart = lrangeLow - MathUtil.mod(lrangeLow, 5.0 * exponent);
    double roundedStart = lrangeLow < 0 ? Math.ceil(-lrangeLow/smoothInterval) * smoothInterval :
            Math.floor(lrangeLow/smoothInterval) * smoothInterval;
    double tickValue = forceLowestTick ? lrangeLow : roundedStart;

    if (spansZero) {
      int posTicks = numTicks - (int)Math.floor(-tickValue/smoothInterval);
      double delta = lrangeHigh - posTicks*smoothInterval;
      if (delta > epsilon) {
        numTicks += (int) Math.ceil((delta-epsilon)/smoothInterval);
      }
    } else {
      double delta = lrangeHigh - numTicks*smoothInterval - tickValue;
      if (delta > epsilon) {
        numTicks += (int) Math.ceil((delta-epsilon)/smoothInterval);
      }
    }

    double tickPositions[] = new double[numTicks];

    tickPositions[0] = tickValue;
    for (int i = 1; i < tickPositions.length; i++) {
      if (tickValue < 0) {
         int negIntervals = (int) ((Math.abs(tickValue)-epsilon)/smoothInterval);
         if (negIntervals > 0) {
           tickValue = - smoothInterval * negIntervals;
         } else {
           double testtick = tickValue + smoothInterval;
           if (Math.abs(testtick) < Math.abs(epsilon)) { // if close to zero but still negative, use zero
               tickValue = 0;
           } else if ((testtick * tickValue) < 0) { // if crossed from neg to pos, use zero
               tickValue = 0;
{'fraction_non_alphanumeric': 0.1357008586089519, 'fraction_numerical': 0.016105268505817203, 'mean_word_length': 3.0138662316476346, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2773356', 'n_tokens_mistral': 6310, 'n_tokens_neox': 5917, 'n_words': 1540}