content: /*
   * Copyright (C) 2021 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package androidx.constraintLayout.desktop.motion.graphs;
  import java.awt.*;
  import java.awt.event.*;
  import java.awt.geom.Point2D;
  import java.awt.image.BufferedImage;
  import java.io.File;
  import java.io.IOException;
  import java.text.DecimalFormat;
  import java.util.Arrays;
  import java.util.Vector;
  import java.util.prefs.Preferences;
  import javax.imageio.ImageIO;
  import javax.swing.*;
  
  import androidx.constraintlayout.core.motion.utils.TypedValues;
  import androidx.constraintlayout.core.motion.utils.*;
  
  public class CycleGraph  extends  JPanel {
  
      CycleModel myCycle;
      private static final Color COLOR_CLEAR = new Color(100, 150, 250, 0);
      private static final Color COLOR_BLUE = new Color(100, 150, 250, 85);
      private static final Color SELECTED_COLOR = new Color(100, 150, 250);
      private static final Color REGION_COLOR = new Color(0, 0, 0, 20);
  
      Color mBackground = new Color(35, 77, 110);
      int ins_top = 30;
      int ins_left = 30;
      int ins_botom = 30;
      int ins_right = 30;
      boolean draw_axis = true;
      boolean draw_grid = true;
      boolean simple_background = true;
      float[][] xPoints = new float[0][];
      float[][] yPoints = new float[0][];
      Color[] pointColor = new Color[0];
      public static final int RANGE_MODE = 21;
  
      int mTextGap = 2;
      int fcount = 0;
      Color drawing = new Color(0xAAAAAA);
      Color mGridColor = new Color(0x224422);
      Stroke stroke = new BasicStroke(4f,
              BasicStroke.CAP_ROUND,
              BasicStroke.JOIN_ROUND);
      Vector<DrawItem> p = new Vector<DrawItem>();
      private int[] pointMode = new int[0];
      public float actual_miny;
      public float actual_maxx;
      public float actual_maxy;
      public float actual_minx;
      private float last_minx;
      private float mTickY;
      private float mTickX;
      private float minx;
      private float last_maxx;
      private float maxx;
      private float maxy;
      private float last_miny;
      private float last_maxy;
      private float miny;
      private int selected_node;
      private int selected_graph;
      Point2D last_click = new Point2D.Double();
  
      void waveGen(int base) {
          float fb = base / 100f;
          float sc = ((base) % 10000) / 10000f + 1;
          float[] xPoints = new float[1024];
          float[] yPoints = new float[xPoints.length];
          for (int i = 0; i < xPoints.length; i++) {
              float x = (float) ((i + fb) * 10 * Math.PI / xPoints.length);
              xPoints[i] = x;
              yPoints[i] = (float) Math.sin(x) * sc;
          }
          addGraph(0, xPoints, yPoints, Color.WHITE, 0);
      }
  
      public CycleGraph(CycleModel cycle) {
          myCycle = cycle;
          p.addElement(mGrid);
          p.addElement(mAxis);
          p.addElement(mAxisVLabel);
          p.addElement(mAxisHLabel);
  
          p.addElement(mDrawGraph);
          p.addElement(drawDot);
          waveGen(0);
          calcRange();
          cycle.addModelListener((e)->updateGraph(e));
          addMouseListener(new MouseAdapter() {
              @Override
              public void mouseClicked(MouseEvent e) {
                  screenToGraph(e.getPoint(), last_click);
              }
          });
  
      }
  
      private void updateGraph(CycleModel model) {
          setCycle(0, model.values[model.POS],
                  model.values[model.PERIOD],
                  model.values[model.AMP],
                  model.values[model.OFFSET],
                  model.values[model.PHASE],
                  model.selected,
                  model.mModeValue);
      }
  
      //
      public void addGraph(int n, double[] x, double[] y, Color c, int mode) {
          float[] xf = new float[x.length];
          float[] yf = new float[y.length];
          for (int i = 0; i < yf.length; i++) {
              xf[i] = (float) x[i];
              yf[i] = (float) y[i];
          }
          addGraph(n, xf, yf, c, mode);
          calcRange();
          repaint();
      }
  
      public void addGraph(int n, float[] x, double[] y, Color c, int mode) {
          float[] yf = new float[y.length];
          for (int i = 0; i < yf.length; i++) {
              yf[i] = (float) y[i];
          }
          addGraph(n, x, yf, c, mode);
          calcRange();
          repaint();
      }
  
      public void addGraph(int n, double[] x, float[] y, Color c, int mode) {
          float[] xf = new float[x.length];
          for (int i = 0; i < x.length; i++) {
              xf[i] = (float) x[i];
          }
          addGraph(n, xf, y, c, mode);
          calcRange();
          repaint();
      }
  
      //
      //
      public void addGraph(int n, double[][] p, Color c, int mode) {
          float[] xf = new float[p.length];
          float[] yf = new float[p.length];
          for (int i = 0; i < yf.length; i++) {
              xf[i] = (float) p[i][0];
              yf[i] = (float) p[i][1];
          }
          addGraph(n, xf, yf, c, mode);
          calcRange();
          repaint();
      }
  
      public void addGraph(int n, float[] x, float[] y, Color c, int mode) {
          if (xPoints.length <= n) {
              float[][] yp = new float[n + 1][];
              float[][] xp = new float[n + 1][];
              Color[] ncol = new Color[n + 1];
              int[] m = new int[n + 1];
              for (int i = 0; i < xPoints.length; i++) {
                  xp[i] = xPoints[i];
                  yp[i] = yPoints[i];
                  ncol[i] = pointColor[i];
                  m[i] = pointMode[i];
              }
              pointColor = ncol;
              xPoints = xp;
              yPoints = yp;
              pointMode = m;
          }
          xPoints[n] = x;
          yPoints[n] = y;
          pointColor[n] = c;
  
          pointMode[n] = mode;
          calcRange();
          repaint();
      }
  
      public void addRange(int n, float[] x, float[] y1, float[] y2, Color c) {
          if (xPoints.length <= n) {
              float[][] yp = new float[n + 2][];
              float[][] xp = new float[n + 2][];
              Color[] ncol = new Color[n + 2];
              int[] m = new int[n + 2];
              for (int i = 0; i < xPoints.length; i++) {
                  xp[i] = xPoints[i];
                  yp[i] = yPoints[i];
                  ncol[i] = pointColor[i];
                  m[i] = pointMode[i];
              }
              pointColor = ncol;
              xPoints = xp;
              yPoints = yp;
              pointMode = m;
          }
          xPoints[n] = x;
          yPoints[n] = y1;
          pointColor[n] = c;
          pointMode[n] = -1;
  
          xPoints[n + 1] = x;
          yPoints[n + 1] = y2;
          pointColor[n + 1] = c;
          pointMode[n + 1] = RANGE_MODE;
          calcRange();
          repaint();
      }
  
      //
      public void calcRange() {
          actual_minx = Float.MAX_VALUE;
          actual_miny = Float.MAX_VALUE;
          actual_maxx = -Float.MAX_VALUE;
          actual_maxy = -Float.MAX_VALUE;
          for (int g = 0; g < xPoints.length; g++) {
              if (xPoints[g] == null | yPoints[g] == null) {
                  continue;
              }
              for (int i = 0; i < xPoints[g].length; i++) {
                  float x = xPoints[g][i];
                  float y = yPoints[g][i];
                  actual_minx = Math.min(actual_minx, x);
                  actual_miny = Math.min(actual_miny, y);
                  actual_maxx = Math.max(actual_maxx, x);
                  actual_maxy = Math.max(actual_maxy, y);
              }
          }
      }
  
      //
      void calcRangeTicks() {
          double dx = actual_maxx - actual_minx;
          double dy = actual_maxy - actual_miny;
          int sw = getWidth();
          int sh = getHeight();
  
          double border = 1.09345;
  
          if (Math.abs(last_minx - actual_minx)
                  + Math.abs(last_maxx - actual_maxx) > 0.1 * (actual_maxx - actual_minx)) {
              mTickX = (float) calcTick(sw, dx);
              dx = mTickX * Math.ceil(border * dx / mTickX);
              double tx = (actual_minx + actual_maxx - dx) / 2;
              tx = mTickX * Math.floor(tx / mTickX);
              minx = (float) tx;
              tx = (actual_minx + actual_maxx + dx) / 2;
              tx = mTickX * Math.ceil(tx / mTickX);
              maxx = (float) tx;
  
              last_minx = actual_minx;
              last_maxx = actual_maxx;
  
          }
          if (Math.abs(last_miny - actual_miny)
                  + Math.abs(last_maxy - actual_maxy) > 0.1 * (actual_maxy - actual_miny)) {
              mTickY = (float) calcTick(sh, dy);
              dy = mTickY * Math.ceil(border * dy / mTickY);
              double ty = (actual_miny + actual_maxy - dy) / 2;
              ty = mTickY * Math.floor(ty / mTickY);
              miny = (float) ty;
              ty = (actual_miny + actual_maxy + dy) / 2;
              ty = mTickY * Math.ceil(ty / mTickY);
              maxy = (float) ty;
  
              last_miny = actual_miny;
              last_maxy = actual_maxy;
          }
      }
  
      //
      static public double calcTick(int scr, double range) {
          int aprox_x_ticks = scr / 100;
          int type = 1;
          double best = Math.log10(range / (aprox_x_ticks));
          double n = Math.log10(range / (aprox_x_ticks * 2));
          if (frac(n) < frac(best)) {
              best = n;
              type = 2;
          }
          n = Math.log10(range / (aprox_x_ticks * 5));
          if (frac(n) < frac(best)) {
              best = n;
              type = 5;
          }
          return type * Math.pow(10, Math.floor(best));
      }
  
      static double frac(double x) {
          return x - Math.floor(x);
      }
  
      interface DrawItem {
  
          public void paint(Graphics2D g, int w, int h);
      }
  
      @Override
      protected void paintComponent(Graphics g) {
          fcount++;
          calcRangeTicks();
          int w = getWidth();
          int h = getHeight();
          Graphics2D g2d = (Graphics2D) g;
          g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                  RenderingHints.VALUE_ANTIALIAS_ON);
          if (simple_background) {
              g.setColor(getBackground());
              g.fillRect(0, 0, w, h);
          } else {
              Paint paint = new GradientPaint(0, 0, Color.BLACK, 0, (h * 2) / 3, new Color(
                      0x182323), true);
              g2d.setPaint(paint);
              g.fillRect(0, 0, w, h);
          }
          g.setColor(drawing);
          for (DrawItem drawItem : p) {
              drawItem.paint(g2d, w, h);
          }
      }
  
      //
      public void screenToGraph(Point scr, Point2D graph) {
          int draw_width = getWidth() - ins_left - ins_right;
          int draw_height = getHeight() - ins_top - ins_left;
          float x = minx + (maxx - minx) * ((scr.x - ins_left) / (float) draw_width);
          float y = miny + (maxy - miny) * ((ins_botom + draw_height - scr.y) / (float) draw_height);
          graph.setLocation(x, y);
      }
  
      //
      DrawItem mAxis = new DrawItem() {
          @Override
          public void paint(Graphics2D g, int w, int h) {
              if (!draw_axis) {
                  return;
              }
              g.setColor(drawing);
  
              g.drawLine(ins_left, ins_top, ins_left, h - ins_botom);
              g.drawLine(ins_left, h - ins_botom, w - ins_right, h - ins_botom);
  
          }
  
      };
  
      DrawItem mGrid = new DrawItem() {
          DecimalFormat df = new DecimalFormat("###.#");
  
          @Override
          public void paint(Graphics2D g, int w, int h) {
              if (!draw_grid) {
                  return;
              }
              g.setColor(mGridColor);
              int draw_width = w - ins_left - ins_right;
              float e = 0.0001f * (maxx - minx);
              FontMetrics fm = g.getFontMetrics();
              int ascent = fm.getAscent();
  
              for (float i = minx; i <= maxx + e; i += mTickX) {
                  int ix = (int) (draw_width * (i - minx) / (maxx - minx) + ins_left);
                  g.drawLine(ix, ins_top, ix, h - ins_botom);
                  String str = df.format(i);
                  int sw = fm.stringWidth(str) / 2;
  
                  g.drawString(str, ix - sw, h - ins_botom + ascent + mTextGap);
  
              }
              int draw_height = h - ins_top - ins_left;
              e = 0.0001f * (maxy - miny);
              int hightoff = -fm.getHeight() / 2 + ascent;
              for (float i = miny; i <= maxy + e; i += mTickY) {
                  int iy = (int) (draw_height * (1 - (i - miny) / (maxy - miny)) + ins_top);
                  g.drawLine(ins_left, iy, w - ins_right, iy);
                  String str = df.format(i);
                  int sw = fm.stringWidth(str);
  
                  g.drawString(str, ins_left - sw - mTextGap, iy + hightoff);
              }
          }
  
      };
  
      DrawItem mAxisVLabel = new DrawItem() {
  
          @Override
          public void paint(Graphics2D g, int w, int h) {
  
          }
  
      };
      DrawItem mAxisHLabel = new DrawItem() {
  
          @Override
          public void paint(Graphics2D g, int w, int h) {
  
          }
  
      };
      float dot_x = Float.NaN, dot_y;
  
      public void setDot(float x, float y) {
          dot_x = x;
          dot_y = y;
          repaint();
      }
  
      DrawItem drawDot = new DrawItem() {
          int[] lastDotsX = new int[4];
          int[] lastDotsY = new int[lastDotsX.length];
          Color base = Color.YELLOW;
          Color[] dotsColor = new Color[lastDotsX.length];
  
          {
              Color tmp = base;
              for (int i = dotsColor.length - 1; i >= 0; i--) {
                  dotsColor[i] = tmp = tmp.darker();
  
              }
          }
  
          @Override
          public void paint(Graphics2D g, int w, int h) {
              if (Float.isNaN(dot_x)) {
                  return;
              }
              int draw_width = w - ins_left - ins_right;
              int draw_height = h - ins_top - ins_left;
              float x = draw_width * (dot_x - minx)
                      / (maxx - minx) + ins_left;
              float y = draw_height
                      * (1 - (dot_y - miny) / (maxy - miny))
                      + ins_top;
              int dot_x = (int) x - 3;
              int dot_y = (int) y - 3;
  
              for (int i = 0; i < lastDotsX.length; i++) {
                  g.setColor(dotsColor[i]);
                  g.fillRoundRect(lastDotsX[i], lastDotsY[i], 7, 7, 6, 6);
                  if (i < lastDotsX.length - 1) {
                      lastDotsX[i] = lastDotsX[i + 1];
                      lastDotsY[i] = lastDotsY[i + 1];
                  } else {
                      lastDotsX[i] = dot_x;
                      lastDotsY[i] = dot_y;
                  }
              }
              g.setColor(base);
  
              g.fillRoundRect(dot_x, dot_y, 7, 7, 6, 6);
  
          }
  
      };
  
      DrawItem mDrawGraph = new DrawItem() {
          int[] xp = new int[0];
          int[] yp = new int[0];
  
          @Override
          public void paint(Graphics2D g, int w, int h) {
              g.setColor(drawing);
              if (xPoints.length == 0) {
                  return;
              }
              if (xp.length < xPoints[0].length * 2) {
                  xp = new int[xPoints[0].length * 2];
                  yp = new int[xPoints[0].length * 2];
              }
              int draw_width = w - ins_left - ins_right;
              int draw_height = h - ins_top - ins_left;
  
              for (int k = 0; k < xPoints.length; k++) {
                  if (xPoints[k] == null || yPoints[k] == null) {
                      continue;
                  }
                  for (int i = 0; i < xPoints[k].length; i++) {
                      float x = draw_width * (xPoints[k][i] - minx)
                              / (maxx - minx) + ins_left;
                      float y = draw_height
                              * (1 - (yPoints[k][i] - miny) / (maxy - miny))
                              + ins_top;
                      xp[i] = (int) x;
                      yp[i] = (int) y;
                  }
                  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                  g.setColor(pointColor[k]);
                  g.setStroke(stroke);
                  switch (pointMode[k]) {
                      case -1:
                          break;
                      case RANGE_MODE:
                          for (int i = 0; i < xPoints[k].length; i++) {
                              float x = draw_width * (xPoints[k][i] - minx)
                                      / (maxx - minx) + ins_left;
                              float y1 = draw_height
                                      * (1 - (yPoints[k][i] - miny) / (maxy - miny))
                                      + ins_top;
                              float y2 = draw_height
                                      * (1 - (yPoints[k - 1][i] - miny) / (maxy - miny))
                                      + ins_top;
                              xp[i] = (int) x;
                              yp[i] = (int) y1;
                              int i2 = xPoints[k].length * 2 - i - 1;
                              xp[i2] = (int) x;
                              yp[i2] = (int) y2;
                          }
                          g.fillPolygon(xp, yp, xPoints[k].length * 2);
  
                          if (selected_node >= 0) {
                              float x_center = draw_width * (xPoints[selected_graph][selected_node] - minx)
                                      / (maxx - minx) + ins_left;
                              float x_min = x_center - 0.01f;
                              if (selected_node > 0) {
                                  x_min = draw_width * (xPoints[selected_graph][selected_node - 1] - minx)
                                          / (maxx - minx) + ins_left;
                              }
                              float x_max = x_center;
                              if (selected_node < xPoints[selected_graph].length - 1) {
                                  x_max = draw_width * (xPoints[selected_graph][selected_node + 1] - minx)
                                          / (maxx - minx) + ins_left;
                              }
                              if (x_center >= x_max) {
                                  float[] spacing = new float[]{0, 0.999f, 1};
  
                                  LinearGradientPaint gradientPaint = new LinearGradientPaint(x_min - 1, 1, x_center,
                                          1,
                                          spacing, new Color[]{COLOR_CLEAR, COLOR_BLUE, COLOR_CLEAR});
                                  g.setPaint(gradientPaint);
                              } else if (x_center <= x_min) {
                                  float[] spacing = new float[]{0, 0.001f, 1};
                                  LinearGradientPaint gradientPaint = new LinearGradientPaint(x_center, 1, x_max + 1,
                                          1,
                                          spacing, new Color[]{COLOR_CLEAR, COLOR_BLUE, COLOR_CLEAR});
                                  g.setPaint(gradientPaint);
                              } else {
                                  float[] spacing = new float[]{0, (float) (x_center - x_min) / (x_max - x_min), 1};
  
                                  LinearGradientPaint gradientPaint = new LinearGradientPaint(x_min, 1, x_max, 1,
                                          spacing, new Color[]{COLOR_CLEAR, COLOR_BLUE, COLOR_CLEAR});
                                  g.setPaint(gradientPaint);
  
                              }
  
                              g.fillPolygon(xp, yp, xPoints[k].length * 2);
                              g.drawPolygon(xp, yp, xPoints[k].length * 2);
                              g.setColor(pointColor[k]);
                              g.drawPolygon(xp, yp, xPoints[k].length * 2);
                          }
  
                          break;
                      case 2: { // ticks
                          final int TICK_SIZE = 2;
                          for (int i = 0; i < xPoints[k].length; i++) {
                              int pointX = xp[i];
                              int pointY = yp[i];
                              g.fillRoundRect(pointX - TICK_SIZE, pointY - TICK_SIZE * 4, TICK_SIZE * 2,
                                      TICK_SIZE * 8, TICK_SIZE * 2, TICK_SIZE * 8);
                          }
                          break;
                      }
                      case 1: {
  
                          final int CIRCLE_SIZE = 10;
                          for (int i = 0; i < xPoints[k].length; i++) { // CIRCLE
                              int pointX = xp[i];
                              int pointY = yp[i];
                              if (selected_graph == k && selected_node == i) {
                                  g.setColor(SELECTED_COLOR);
                              } else {
                                  g.setColor(pointColor[k]);
                              }
                              g.fillRoundRect(pointX - CIRCLE_SIZE, pointY - CIRCLE_SIZE, CIRCLE_SIZE * 2,
                                      CIRCLE_SIZE * 2, CIRCLE_SIZE * 2, CIRCLE_SIZE * 2);
                          }
                      }
                      break;
                      default:
                          g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  
                          g.drawPolyline(xp, yp, xPoints[k].length);
                  }
              }
          }
  
      };
  
      public static void save(ActionEvent e, CycleGraph graph) {
          int w = graph.getWidth();
          int h = graph.getHeight();
          BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
          graph.paint(img.createGraphics());
          JFileChooser chooser = new JFileChooser(new File(System.getProperty("user.home")));
          int c = chooser.showSaveDialog(graph);
          if (c == JFileChooser.CANCEL_OPTION) {
              System.out.println("cancel");
              return;
          }
          try {
              File f = chooser.getSelectedFile();
              ImageIO.write(img, "png", f);
              System.out.println(f.getAbsolutePath());
  
              Desktop.getDesktop().open(f.getParentFile());
          } catch (IOException e1) {
              e1.printStackTrace();
          }
      }
  
  
      MonotonicCurveFit mMonotoneSpline;
      Oscillator mOscillator;
  
      public void setCycle(int n, double[] pos, double[] period, double[] amplitude, double[] offset,double []phase,
                           int selected, int curveType) {
          double[] t = new double[pos.length];
          double[][] v = new double[pos.length][3];
  
          for (int i = 0; i < pos.length; i++) {
              t[i] = pos[i];
              v[i][0] = amplitude[i];
              v[i][1] = offset[i];
              v[i][2] = phase[i];
          }
          MonotonicCurveFit ms = new MonotonicCurveFit(t, v);
          Oscillator osc = new Oscillator();
  
          osc.setType(curveType, null);
          for (int i = 0; i < pos.length; i++) {
  
              osc.addPoint(pos[i], (float) period[i]);
  
          }
          osc.normalize();
  
          mMonotoneSpline = ms;
          mOscillator = osc;
          float[] x = new float[400];
          double[] y1 = new double[x.length];
          float[] yMax = new float[x.length];
          float[] yMin = new float[x.length];
          for (int i = 0; i < x.length; i++) {
              x[i] = (float) (i / (x.length - 1.0f));
              double amp = mMonotoneSpline.getPos(x[i], 0);
              double off = mMonotoneSpline.getPos(x[i], 1);
              // TODO add Phase mMonotoneSpline.getPos(v, 2);
              y1[i] = mOscillator.getValue(x[i],0) * amp + off;
              yMax[i] = (float) (amp + off);
              yMin[i] = (float) (-amp + off);
  
          }
          n = n * 4;
          addGraph(n, x, y1, Color.BLUE, 0);
          addRange(n + 1, x, yMin, yMax, REGION_COLOR);
          addGraph(n + 3, pos, offset, Color.PINK, 1);
          selected_node = selected;
          selected_graph = n + 3;
      }
  
      float getComputedValue(float v) {
          if (mMonotoneSpline == null) {
              return 0;
          }
          double amp = mMonotoneSpline.getPos(v, 0);
          double off = mMonotoneSpline.getPos(v, 1);
          double phase = 0; // TODO add phase mMonotoneSpline.getPos(v, 2);
          return (float) (mOscillator.getValue(v, phase) * amp + off);
      }
  
      enum Prop {
          PATH_ROTATE,
          ALPHA,
          ELEVATION,
          ROTATION,
          ROTATION_X,
          ROTATION_Y,
          SCALE_X,
          SCALE_Y,
          TRANSLATION_X,
          TRANSLATION_Y,
          TRANSLATION_Z,
          PROGRESS
      }
  
      static class MainAttribute {
  
          static String[] Names = {
                  "motion:transitionPathRotate",
                  "android:alpha",
                  "android:elevation",
                  "android:rotation",
                  "android:rotationX",
                  "android:rotationY",
                  "android:scaleX",
                  "android:scaleY",
                  "android:translationX",
                  "android:translationY",
                  "android:translationZ",
                  "motion:progress",
          };
          static String[] ShortNames = {
                  "PathRotate",
                  "alpha",
                  "elevation",
                  "rotation",
                  "rotationX",
                  "rotationY",
                  "scaleX",
                  "scaleY",
                  "translationX",
                  "translationY",
                  "translationZ",
                  "progress",
          };
          static float[][] typicalRange = {
                  {-360, 360},
                  {0, 1},
                  {0, 100},
                  {-360, 360},
                  {-360, 360},
                  {-360, 360},
                  {0, 10},
                  {0, 10},
                  {-200, 200},
                  {-200, 200},
                  {0, 100},
                  {0, 1}
          };
          static boolean[] mapTo100 = {
                  false,
                  true,
                  false,
                  false,
                  false,
                  false,
                  true,
                  true,
                  false,
                  false,
                  false,
                  true,
          };
          static boolean[] makeInt = {
                  true,
                  false,
                  true,
                  true,
                  true,
                  true,
                  false,
                  false,
                  true,
                  true,
                  true,
                  false
          };
  
          static boolean[] isDp = {
                  false,
                  false,
                  true,
                  false,
                  false,
                  false,
                  false,
                  false,
                  true,
                  true,
                  true,
                  false
          };
  
          public static String process(double amp, int mAttrIndex) {
              if (isDp[mAttrIndex]) {
                  return ((int) amp) + "dp";
              }
              DecimalFormat format = new DecimalFormat("###.####");
              if (typicalRange[mAttrIndex][1] == 1.0f) {
  
              }
              return format.format(amp);
  
          }
  
      }
      public static void main(String[] args) {
          JFrame frame = new JFrame();
          CycleModel model = new CycleModel();
          frame.setContentPane(new CycleGraph(model
                  ));
          frame.setTitle("Simple Editor");
          Preferences pref = Preferences.userNodeForPackage(CycleGraph.class);
          Rectangle pos = new Rectangle(200, 100, 700, 600);
          if (pref != null && pref.getInt("base_x", -1) != -1) {
              pos.x = pref.getInt("base_x", pos.x);
              pos.y = pref.getInt("base_x", pos.y);
              pos.width = pref.getInt("base_x", pos.width);
              pos.height = pref.getInt("base_x", pos.height);
          }
          frame.setBounds(pos);
          frame.addComponentListener(new ComponentAdapter() {
              public void componentMoved(ComponentEvent evt) {
                  pref.putInt("base_x", pos.x);
                  pref.putInt("base_x", pos.y);
                  pref.putInt("base_x", pos.width);
                  pref.putInt("base_x", pos.height);
              }
          });
          frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
          frame.setVisible(true);
          model.update();
  
      }
  }
  