Beispiel #1
0
  /**
   * Find the color band that this value falls in and assign that color.
   *
   * @param v
   * @param color
   */
  private final void absoluteValue(final double v, final int[] color) {
    final double search;
    switch (scaling) {
      case Absolute:
        search = v;
        break;
      case MinMax:
        search = (v - min) / (max - min);
        break;
      case Modulo:
        search = v % (max - min);
        break;
      default:
        search = 0;
        break;
    }

    final Map.Entry<Double, Color> lower = floorEntry(search);

    Color c;
    if (lower == null) {
      c = entrySet().iterator().next().getValue();
    } else {
      c = lower.getValue();
    }

    color[R] = c.getRed();
    color[G] = c.getGreen();
    color[B] = c.getBlue();
    color[A] = c.getAlpha();
  }
Beispiel #2
0
  /**
   * Interpolate the color value for the given scalar value. The result is placed in color.
   *
   * @param v
   * @param color
   * @return
   */
  private final void interpolateValue(final double v, final int[] color) {
    final double search;
    switch (scaling) {
      case Absolute:
        search = v;
        break;
      case MinMax:
        search = (v - min) / (max - min);
        break;
      case Modulo:
        search = (v - min) % (max - min);
        break;
      default:
        search = 0;
        break;
    }

    final Map.Entry<Double, Color> lower = floorEntry(search);
    final Map.Entry<Double, Color> upper = higherEntry(search);

    assert (upper != null || lower != null);

    if (upper == null) {
      final Color c = lower.getValue();
      color[R] = c.getRed();
      color[G] = c.getGreen();
      color[B] = c.getBlue();
      color[A] = c.getAlpha();
    } else if (lower == null) {
      final Color c = upper.getValue();
      color[R] = c.getRed();
      color[G] = c.getGreen();
      color[B] = c.getBlue();
      color[A] = c.getAlpha();
    } else {
      final double diff = upper.getKey().doubleValue() - lower.getKey().doubleValue();
      final double lw = 1.0 - ((search - lower.getKey().doubleValue()) / diff);
      final double uw = 1.0 - lw;

      final Color lc = lower.getValue();
      final Color uc = upper.getValue();
      color[R] = (int) Math.round(lc.getRed() * lw + uc.getRed() * uw);
      color[G] = (int) Math.round(lc.getGreen() * lw + uc.getGreen() * uw);
      color[B] = (int) Math.round(lc.getBlue() * lw + uc.getBlue() * uw);
      color[A] = (int) Math.round(lc.getAlpha() * lw + uc.getAlpha() * uw);
    }
  }
 @Override
 public void write(final OutputStream out) throws IOException {
   out.write(TYPE_COLOR);
   super.write(out);
   out.write(color.getRed());
   out.write(color.getGreen());
   out.write(color.getBlue());
   out.write(color.getAlpha());
 }
Beispiel #4
0
 /**
  * Turn a (possibly) translucent or indexed image into a display-compatible bitmask image using
  * the given alpha threshold and render-to-background colour, or display-compatible translucent
  * image. The alpha values in the image are set to either 0 (below threshold) or 255 (above
  * threshold). The render-to-background colour bg_col is used to determine how the pixels
  * overlapping transparent pixels should be rendered. The fast algorithm just sets the colour
  * behind the transparent pixels in the image (for bitmask source images); the slow algorithm
  * actually renders the image to a background of bg_col (for translucent sources).
  *
  * @param thresh alpha threshold between 0 and 255
  * @param fast use fast algorithm (only set bg_col behind transp. pixels)
  * @param bitmask true=use bitmask, false=use translucent
  */
 public JGImage toDisplayCompatible(int thresh, JGColor bg_col, boolean fast, boolean bitmask) {
   Color bgcol = new Color(bg_col.r, bg_col.g, bg_col.b);
   int bgcol_rgb = (bgcol.getRed() << 16) | (bgcol.getGreen() << 8) | bgcol.getBlue();
   JGPoint size = getSize();
   int[] buffer = getPixels();
   // render image to bg depending on bgcol
   BufferedImage img_bg;
   if (bitmask) {
     img_bg = createCompatibleImage(size.x, size.y, Transparency.BITMASK);
   } else {
     img_bg = createCompatibleImage(size.x, size.y, Transparency.TRANSLUCENT);
   }
   int[] bg_buf;
   if (!fast) {
     Graphics g = img_bg.getGraphics();
     g.setColor(bgcol);
     // the docs say I could use bgcol in the drawImage as an
     // equivalent to the following two lines, but this
     // doesn't handle translucency properly and is _slower_
     g.fillRect(0, 0, size.x, size.y);
     g.drawImage(img, 0, 0, null);
     bg_buf = new JREImage(img_bg).getPixels();
   } else {
     bg_buf = buffer;
   }
   // g.dispose();
   // ColorModel rgb_bitmask = ColorModel.getRGBdefault();
   // rgb_bitmask = new PackedColorModel(
   //		rgb_bitmask.getColorSpace(),25,0xff0000,0x00ff00,0x0000ff,
   //		0x1000000, false, Transparency.BITMASK, DataBuffer.TYPE_INT);
   //		ColorSpace space, int bits, int rmask, int gmask, int bmask, int amask, boolean
   // isAlphaPremultiplied, int trans, int transferType)
   int[] thrsbuf = new int[size.x * size.y];
   for (int y = 0; y < size.y; y++) {
     for (int x = 0; x < size.x; x++) {
       if (((buffer[y * size.x + x] >> 24) & 0xff) >= thresh) {
         thrsbuf[y * size.x + x] = bg_buf[y * size.x + x] | (0xff << 24);
       } else {
         // explicitly set the colour of the transparent pixel.
         // This makes a difference when scaling!
         // thrsbuf[y*size.x+x]=bg_buf[y*size.x+x]&~(0xff<<24);
         thrsbuf[y * size.x + x] = bgcol_rgb;
       }
     }
   }
   return new JREImage(
       output_comp.createImage(
           new MemoryImageSource(
               size.x,
               size.y,
               // rgb_bitmask,
               img_bg.getColorModel(), // display compatible bitmask
               bitmask ? thrsbuf : bg_buf,
               0,
               size.x)));
 }
Beispiel #5
0
 public void drawTileNumC(int tileNum, int x, int y, Color c, Graphics g) {
   BufferedImage coloredTile = tiles[tileNum];
   for (int i = 0; i < this.tW; i++) {
     for (int j = 0; j < this.tH; j++) {
       Color originalColor = new Color(coloredTile.getRGB(i, j), true);
       Color nc = new Color(c.getRed(), c.getGreen(), c.getBlue(), originalColor.getAlpha());
       coloredTile.setRGB(i, j, nc.getRGB());
     }
   }
   g.drawImage(tiles[tileNum], x, y, null);
 }
  // Get Color as String
  public static String getColor(Color color) {
    ArrayList<String> colors = new ArrayList<String>();
    colors.add(Long.toHexString(color.getRed()));
    colors.add(Long.toHexString(color.getGreen()));
    colors.add(Long.toHexString(color.getBlue()));

    StringBuilder buffer = new StringBuilder();
    for (String c : colors) {
      if (c.length() == 1) {
        buffer.append("0");
      }
      buffer.append(c);
    }
    return ("#" + buffer.toString());
  }
Beispiel #7
0
    /**
     * Calculates the apparent color of this polygon. We ask the camera how much light falls on a
     * surface wiith this normal and then darken the color accordingly.
     */
    private Color calcLight() {
      if (noShade) {
        return c;
      }

      int r = c.getRed();
      int g = c.getGreen();
      int b = c.getBlue();

      float light = modelViewer.cameraMan.surfaceLight(normal);

      r *= light;
      g *= light;
      b *= light;

      return new Color(r, g, b);
    }
    private Color getColor(Element shape) {
      Color color;
      if (shape.hasAttribute(ATR_COLOUR)) {
        String s = shape.getAttribute(ATR_COLOUR);
        if (s.indexOf(',') > -1) {
          String[] rgb = s.split(",");
          color =
              new Color(
                  Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
        } else {
          color = new Color(Integer.parseInt(s));
        }
      } else color = IMPLIED_COLOR;

      if (shape.hasAttribute(ATR_TRANSPARENCY)) {
        int alpha = Integer.parseInt(shape.getAttribute(ATR_TRANSPARENCY));
        if (alpha < 255) return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
      }

      return color;
    }
 /** Returns index of palette color closest to c */
 protected int findClosest(Color c) {
   if (colorTab == null) return -1;
   int r = c.getRed();
   int g = c.getGreen();
   int b = c.getBlue();
   int minpos = 0;
   int dmin = 256 * 256 * 256;
   int len = colorTab.length;
   for (int i = 0; i < len; ) {
     int dr = r - (colorTab[i++] & 0xff);
     int dg = g - (colorTab[i++] & 0xff);
     int db = b - (colorTab[i] & 0xff);
     int d = dr * dr + dg * dg + db * db;
     int index = i / 3;
     if (usedEntry[index] && (d < dmin)) {
       dmin = d;
       minpos = index;
     }
     i++;
   }
   return minpos;
 }
Beispiel #10
0
  /**
   * Computes neuron or link color, depending on its value and selected state.
   *
   * @param v neuron activation
   * @return neuron color
   */
  public Color getColor(double v) {
    Color ci = null_color, ca = pos_color, cn = neg_color;
    int red, green, blue;

    if (v >= 0) {
      red = (int) (ci.getRed() + v * (ca.getRed() - ci.getRed()));
      red = limit(red);
      green = (int) (ci.getGreen() + v * (ca.getGreen() - ci.getGreen()));
      green = limit(green);
      blue = (int) (ci.getBlue() + v * (ca.getBlue() - ci.getBlue()));
      blue = limit(blue);
    } else {
      red = (int) (ci.getRed() - v * (cn.getRed() - ci.getRed()));
      red = limit(red);
      green = (int) (ci.getGreen() - v * (cn.getGreen() - ci.getGreen()));
      green = limit(green);
      blue = (int) (ci.getBlue() - v * (cn.getBlue() - ci.getBlue()));
      blue = limit(blue);
    }
    return new Color(red, green, blue);
  }
Beispiel #11
0
 /**
  * Returns the red value of the image at the given coordinates.
  *
  * <p>The coordinates should be non-negative and less than the width (x) or height (y) of the
  * image. The red value is returned as an integer in the range [0,255].
  *
  * @param x the horizontal coordinate of the pixel
  * @param y the vertical coordinate of the pixel
  * @return the red value at the given coordinates
  */
 public int getRed(int x, int y) {
   // works!
   Color c = new Color(img.getRGB(x, y));
   return c.getRed(); // (img.getRGB(x, y) & 0x00ff0000) >> 16;
 }
  public GroundTexture(int size, Ground grnd) {

    System.out.println("Calculating Ground Texture...");

    img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);

    double r, g, b;
    Random rnd = new Random();

    System.out.print("Initializing...");

    for (int x = 0; x < size; x++) {
      for (int y = 0; y < size; y++) {
        double slope = 0;
        for (int nx = x - 1; nx < x + 1; nx++)
          for (int ny = y - 1; ny < y + 1; ny++)
            if ((nx >= 0) && (nx < size))
              if ((ny >= 0) && (ny < size)) slope += Math.abs(grnd.topo[nx][ny] - grnd.topo[x][y]);
        if (slope < 1) slope = 1;
        g = 5d + 80d / (grnd.topo[x][y] / 2000d + 1d) + rnd.nextDouble() * 30d / (slope);
        r =
            g
                * (1.17
                    + (-.3 + (rnd.nextDouble() * .3))
                        / (grnd.topo[x][y] / 200d + 1d)
                        / (slope / 5 + 1));
        b =
            r
                * (.7
                    + (-.3 + (rnd.nextDouble() * .2))
                        / (grnd.topo[x][y] / 200d + 1d)
                        / (slope / 5 + 1));

        img.setRGB(x, y, colorRGB((int) r, (int) g, (int) b));
      }
    }

    /**/

    //		save("bodentextur2","png");

    System.out.print("                \rRandom Growth");

    for (int i = 0; i < size * size * 8; i++) {
      if (i % (size * size / 2) == 0) System.out.print(".");
      int x = 3 + rnd.nextInt(size - 6);
      int y = 3 + rnd.nextInt(size - 6);
      int nx = x - 1 + rnd.nextInt(3);
      int ny = y - 1 + rnd.nextInt(3);
      while ((nx < 0) || (nx >= size) || (ny < 0) || (ny >= size)) {
        nx = x - 1 + rnd.nextInt(3);
        ny = y - 1 + rnd.nextInt(3);
      }
      Color dis = getColorAt(x, y);
      Color col = getColorAt(nx, ny);
      if (grnd.topo[nx][ny] >= 4.5)
        if (col.getGreen() / col.getRed() > dis.getGreen() / dis.getRed())
          if (Math.abs(grnd.topo[x][y] - grnd.topo[nx][ny]) < .65) {
            int c = colorRGB(col.getRed(), col.getGreen(), col.getBlue());
            // img.setRGB(x,y, colorRGB(col.getRed(), col.getGreen(), col.getBlue()));
            // img.setRGB(x-1+rnd.nextInt(3),y-1+rnd.nextInt(3), colorRGB(col.getRed(),
            // col.getGreen(), col.getBlue()));
            for (nx = x - 1 - rnd.nextInt(3); nx < 1 + x + rnd.nextInt(3); nx++)
              for (ny = y - 1 - rnd.nextInt(3); ny < y + 1 + rnd.nextInt(3); ny++) {
                img.setRGB(nx, ny, c);
                grnd.topo[nx][ny] += 1.8;
              }
          }
    }

    System.out.print("                 \rAntialiasing...");
    /*		for (int x = 0; x < size; x++){
    	for (int y = 0; y < size; y++){
    		double sumr = 0;
    		double sumg = 0;
    		double sumb = 0;
    		double div = 0;
    		for (int nx=x-1;nx<x+1;nx++)
    			for (int ny=y-1;ny<y+1;ny++)
    				if ((nx>=0) && (nx < size)) if ((ny>=0) && (ny < size)) {
    					Color col = getColorAt(nx,ny);
    					sumr+=col.getRed();
    					sumg+=col.getGreen();
    					sumb+=col.getBlue();
    					div++;
    				}
    		r=sumr/div;
    		g=sumg/div;
    		b=sumb/div;
    		img.setRGB(x,y, colorRGB((int)r, (int)g, (int)b) );

    	}
    }*/

    System.out.print("        \r");
    //		save("bodentextur3","png");
    save("bodentextur", "png");
  }
Beispiel #13
0
 static ColorUIResource getColorTercio(Color a, Color b) {
   return new ColorUIResource(
       propInt(a.getRed(), b.getRed(), 3),
       propInt(a.getGreen(), b.getGreen(), 3),
       propInt(a.getBlue(), b.getBlue(), 3));
 }
Beispiel #14
0
 static Color getColorMedio(Color a, Color b) {
   return new Color(
       propInt(a.getRed(), b.getRed(), 2),
       propInt(a.getGreen(), b.getGreen(), 2),
       propInt(a.getBlue(), b.getBlue(), 2));
 }
Beispiel #15
0
 static Color getColorAlfa(Color col, int alfa) {
   return new Color(col.getRed(), col.getGreen(), col.getBlue(), alfa);
 }
Beispiel #16
0
  protected void drawIcon(DrawContext dc) {
    if (this.getIconFilePath() == null) return;

    GL gl = dc.getGL();
    OGLStackHandler ogsh = new OGLStackHandler();

    try {
      // Initialize texture if necessary
      Texture iconTexture = dc.getTextureCache().getTexture(this.getIconFilePath());
      if (iconTexture == null) {
        this.initializeTexture(dc);
        iconTexture = dc.getTextureCache().getTexture(this.getIconFilePath());
        if (iconTexture == null) {
          String msg = Logging.getMessage("generic.ImageReadFailed");
          Logging.logger().finer(msg);
          return;
        }
      }
      gl.glDisable(GL.GL_DEPTH_TEST);

      double width = this.getScaledIconWidth();
      double height = this.getScaledIconHeight();

      // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
      // into the GL projection matrix.
      java.awt.Rectangle viewport = dc.getView().getViewport();
      ogsh.pushProjectionIdentity(gl);
      double maxwh = width > height ? width : height;
      gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh);

      // Translate and scale
      ogsh.pushModelviewIdentity(gl);
      double scale = this.computeScale(viewport);
      Vec4 locationSW = this.computeLocation(viewport, scale);
      gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
      // Scale to 0..1 space
      gl.glScaled(scale, scale, 1);
      gl.glScaled(width, height, 1d);

      if (!dc.isPickingMode()) {
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

        // Draw background color behind the map
        gl.glColor4ub(
            (byte) this.backColor.getRed(),
            (byte) this.backColor.getGreen(),
            (byte) this.backColor.getBlue(),
            (byte) (this.backColor.getAlpha() * this.getOpacity()));
        dc.drawUnitQuad();

        // Draw world map icon
        gl.glColor4d(1d, 1d, 1d, this.getOpacity());
        gl.glEnable(GL.GL_TEXTURE_2D);
        iconTexture.bind();

        TextureCoords texCoords = iconTexture.getImageTexCoords();
        dc.drawUnitQuad(texCoords);
        gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
        gl.glDisable(GL.GL_TEXTURE_2D);

        // Draw crosshair for current location
        gl.glLoadIdentity();
        gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
        // Scale to width x height space
        gl.glScaled(scale, scale, 1);
        // Set color
        float[] colorRGB = this.color.getRGBColorComponents(null);
        gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity());

        // Draw crosshair
        Position groundPos = this.computeGroundPosition(dc, dc.getView());
        if (groundPos != null) {
          int x = (int) (width * (groundPos.getLongitude().degrees + 180) / 360);
          int y = (int) (height * (groundPos.getLatitude().degrees + 90) / 180);
          int w = 10; // cross branch length
          // Draw
          gl.glBegin(GL.GL_LINE_STRIP);
          gl.glVertex3d(x - w, y, 0);
          gl.glVertex3d(x + w + 1, y, 0);
          gl.glEnd();
          gl.glBegin(GL.GL_LINE_STRIP);
          gl.glVertex3d(x, y - w, 0);
          gl.glVertex3d(x, y + w + 1, 0);
          gl.glEnd();
        }

        // Draw view footprint in map icon space
        if (this.showFootprint) {
          this.footPrintPositions = this.computeViewFootPrint(dc, 32);
          if (this.footPrintPositions != null) {
            gl.glBegin(GL.GL_LINE_STRIP);
            LatLon p1 = this.footPrintPositions.get(0);
            for (LatLon p2 : this.footPrintPositions) {
              int x = (int) (width * (p2.getLongitude().degrees + 180) / 360);
              int y = (int) (height * (p2.getLatitude().degrees + 90) / 180);
              // Draw
              if (LatLon.locationsCrossDateline(p1, p2)) {
                int y1 = (int) (height * (p1.getLatitude().degrees + 90) / 180);
                gl.glVertex3d(x < width / 2 ? width : 0, (y1 + y) / 2, 0);
                gl.glEnd();
                gl.glBegin(GL.GL_LINE_STRIP);
                gl.glVertex3d(x < width / 2 ? 0 : width, (y1 + y) / 2, 0);
              }
              gl.glVertex3d(x, y, 0);
              p1 = p2;
            }
            gl.glEnd();
          }
        }
        // Draw 1px border around and inside the map
        gl.glBegin(GL.GL_LINE_STRIP);
        gl.glVertex3d(0, 0, 0);
        gl.glVertex3d(width, 0, 0);
        gl.glVertex3d(width, height - 1, 0);
        gl.glVertex3d(0, height - 1, 0);
        gl.glVertex3d(0, 0, 0);
        gl.glEnd();
      } else {
        // Picking
        this.pickSupport.clearPickList();
        this.pickSupport.beginPicking(dc);
        // Where in the world are we picking ?
        Position pickPosition =
            computePickPosition(
                dc, locationSW, new Dimension((int) (width * scale), (int) (height * scale)));
        Color color = dc.getUniquePickColor();
        int colorCode = color.getRGB();
        this.pickSupport.addPickableObject(colorCode, this, pickPosition, false);
        gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
        dc.drawUnitQuad();
        this.pickSupport.endPicking(dc);
        this.pickSupport.resolvePick(dc, dc.getPickPoint(), this);
      }
    } finally {
      dc.restoreDefaultDepthTesting();
      dc.restoreDefaultCurrentColor();
      if (dc.isPickingMode()) dc.restoreDefaultBlending();
      ogsh.pop(gl);
    }
  }
Beispiel #17
0
  @Override
  public void paintComponent(Graphics g) {
    super.paintComponent(g);

    int width = getWidth() - rightMargin - leftMargin - 10;
    int height = getHeight() - topMargin - bottomMargin;
    if (width <= 0 || height <= 0) {
      // not enough room to paint anything
      return;
    }

    Color oldColor = g.getColor();
    Font oldFont = g.getFont();
    Color fg = getForeground();
    Color bg = getBackground();
    boolean bgIsLight = (bg.getRed() > 200 && bg.getGreen() > 200 && bg.getBlue() > 200);

    ((Graphics2D) g)
        .setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    if (smallFont == null) {
      smallFont = oldFont.deriveFont(9.0F);
    }

    r.x = leftMargin - 5;
    r.y = topMargin - 8;
    r.width = getWidth() - leftMargin - rightMargin;
    r.height = getHeight() - topMargin - bottomMargin + 16;

    if (border == null) {
      // By setting colors here, we avoid recalculating them
      // over and over.
      border =
          new BevelBorder(
              BevelBorder.LOWERED,
              getBackground().brighter().brighter(),
              getBackground().brighter(),
              getBackground().darker().darker(),
              getBackground().darker());
    }

    border.paintBorder(this, g, r.x, r.y, r.width, r.height);

    // Fill background color
    g.setColor(bgColor);
    g.fillRect(r.x + 2, r.y + 2, r.width - 4, r.height - 4);
    g.setColor(oldColor);

    long tMin = Long.MAX_VALUE;
    long tMax = Long.MIN_VALUE;
    long vMin = Long.MAX_VALUE;
    long vMax = 1;

    int w = getWidth() - rightMargin - leftMargin - 10;
    int h = getHeight() - topMargin - bottomMargin;

    if (times.size > 1) {
      tMin = Math.min(tMin, times.time(0));
      tMax = Math.max(tMax, times.time(times.size - 1));
    }
    long viewRangeMS;
    if (viewRange > 0) {
      viewRangeMS = viewRange * MINUTE;
    } else {
      // Display full time range, but no less than a minute
      viewRangeMS = Math.max(tMax - tMin, 1 * MINUTE);
    }

    // Calculate min/max values
    for (Sequence seq : seqs) {
      if (seq.size > 0) {
        for (int i = 0; i < seq.size; i++) {
          if (seq.size == 1 || times.time(i) >= tMax - viewRangeMS) {
            long val = seq.value(i);
            if (val > Long.MIN_VALUE) {
              vMax = Math.max(vMax, val);
              vMin = Math.min(vMin, val);
            }
          }
        }
      } else {
        vMin = 0L;
      }
      if (unit == Unit.BYTES || !seq.isPlotted) {
        // We'll scale only to the first (main) value set.
        // TODO: Use a separate property for this.
        break;
      }
    }

    // Normalize scale
    vMax = normalizeMax(vMax);
    if (vMin > 0) {
      if (vMax / vMin > 4) {
        vMin = 0;
      } else {
        vMin = normalizeMin(vMin);
      }
    }

    g.setColor(fg);

    // Axes
    // Draw vertical axis
    int x = leftMargin - 18;
    int y = topMargin;
    FontMetrics fm = g.getFontMetrics();

    g.drawLine(x, y, x, y + h);

    int n = 5;
    if (("" + vMax).startsWith("2")) {
      n = 4;
    } else if (("" + vMax).startsWith("3")) {
      n = 6;
    } else if (("" + vMax).startsWith("4")) {
      n = 4;
    } else if (("" + vMax).startsWith("6")) {
      n = 6;
    } else if (("" + vMax).startsWith("7")) {
      n = 7;
    } else if (("" + vMax).startsWith("8")) {
      n = 8;
    } else if (("" + vMax).startsWith("9")) {
      n = 3;
    }

    // Ticks
    ArrayList<Long> tickValues = new ArrayList<Long>();
    tickValues.add(vMin);
    for (int i = 0; i < n; i++) {
      long v = i * vMax / n;
      if (v > vMin) {
        tickValues.add(v);
      }
    }
    tickValues.add(vMax);
    n = tickValues.size();

    String[] tickStrings = new String[n];
    for (int i = 0; i < n; i++) {
      long v = tickValues.get(i);
      tickStrings[i] = getSizeString(v, vMax);
    }

    // Trim trailing decimal zeroes.
    if (decimals > 0) {
      boolean trimLast = true;
      boolean removedDecimalPoint = false;
      do {
        for (String str : tickStrings) {
          if (!(str.endsWith("0") || str.endsWith("."))) {
            trimLast = false;
            break;
          }
        }
        if (trimLast) {
          if (tickStrings[0].endsWith(".")) {
            removedDecimalPoint = true;
          }
          for (int i = 0; i < n; i++) {
            String str = tickStrings[i];
            tickStrings[i] = str.substring(0, str.length() - 1);
          }
        }
      } while (trimLast && !removedDecimalPoint);
    }

    // Draw ticks
    int lastY = Integer.MAX_VALUE;
    for (int i = 0; i < n; i++) {
      long v = tickValues.get(i);
      y = topMargin + h - (int) (h * (v - vMin) / (vMax - vMin));
      g.drawLine(x - 2, y, x + 2, y);
      String s = tickStrings[i];
      if (unit == Unit.PERCENT) {
        s += "%";
      }
      int sx = x - 6 - fm.stringWidth(s);
      if (y < lastY - 13) {
        if (checkLeftMargin(sx)) {
          // Wait for next repaint
          return;
        }
        g.drawString(s, sx, y + 4);
      }
      // Draw horizontal grid line
      g.setColor(Color.lightGray);
      g.drawLine(r.x + 4, y, r.x + r.width - 4, y);
      g.setColor(fg);
      lastY = y;
    }

    // Draw horizontal axis
    x = leftMargin;
    y = topMargin + h + 15;
    g.drawLine(x, y, x + w, y);

    long t1 = tMax;
    if (t1 <= 0L) {
      // No data yet, so draw current time
      t1 = System.currentTimeMillis();
    }
    long tz = timeDF.getTimeZone().getOffset(t1);
    long tickInterval = calculateTickInterval(w, 40, viewRangeMS);
    if (tickInterval > 3 * HOUR) {
      tickInterval = calculateTickInterval(w, 80, viewRangeMS);
    }
    long t0 = tickInterval - (t1 - viewRangeMS + tz) % tickInterval;
    while (t0 < viewRangeMS) {
      x = leftMargin + (int) (w * t0 / viewRangeMS);
      g.drawLine(x, y - 2, x, y + 2);

      long t = t1 - viewRangeMS + t0;
      String str = formatClockTime(t);
      g.drawString(str, x, y + 16);
      // if (tickInterval > (1 * HOUR) && t % (1 * DAY) == 0) {
      if ((t + tz) % (1 * DAY) == 0) {
        str = formatDate(t);
        g.drawString(str, x, y + 27);
      }
      // Draw vertical grid line
      g.setColor(Color.lightGray);
      g.drawLine(x, topMargin, x, topMargin + h);
      g.setColor(fg);
      t0 += tickInterval;
    }

    // Plot values
    int start = 0;
    int nValues = 0;
    int nLists = seqs.size();
    if (nLists > 0) {
      nValues = seqs.get(0).size;
    }
    if (nValues == 0) {
      g.setColor(oldColor);
      return;
    } else {
      Sequence seq = seqs.get(0);
      // Find starting point
      for (int p = 0; p < seq.size; p++) {
        if (times.time(p) >= tMax - viewRangeMS) {
          start = p;
          break;
        }
      }
    }

    // Optimization: collapse plot of more than four values per pixel
    int pointsPerPixel = (nValues - start) / w;
    if (pointsPerPixel < 4) {
      pointsPerPixel = 1;
    }

    // Draw graphs
    // Loop backwards over sequences because the first needs to be painted on top
    for (int i = nLists - 1; i >= 0; i--) {
      int x0 = leftMargin;
      int y0 = topMargin + h + 1;

      Sequence seq = seqs.get(i);
      if (seq.isPlotted && seq.size > 0) {
        // Paint twice, with white and with color
        for (int pass = 0; pass < 2; pass++) {
          g.setColor((pass == 0) ? Color.white : seq.color);
          int x1 = -1;
          long v1 = -1;
          for (int p = start; p < nValues; p += pointsPerPixel) {
            // Make sure we get the last value
            if (pointsPerPixel > 1 && p >= nValues - pointsPerPixel) {
              p = nValues - 1;
            }
            int x2 = (int) (w * (times.time(p) - (t1 - viewRangeMS)) / viewRangeMS);
            long v2 = seq.value(p);
            if (v2 >= vMin && v2 <= vMax) {
              int y2 = (int) (h * (v2 - vMin) / (vMax - vMin));
              if (x1 >= 0 && v1 >= vMin && v1 <= vMax) {
                int y1 = (int) (h * (v1 - vMin) / (vMax - vMin));

                if (y1 == y2) {
                  // fillrect is much faster
                  g.fillRect(x0 + x1, y0 - y1 - pass, x2 - x1, 1);
                } else {
                  Graphics2D g2d = (Graphics2D) g;
                  Stroke oldStroke = null;
                  if (seq.transitionStroke != null) {
                    oldStroke = g2d.getStroke();
                    g2d.setStroke(seq.transitionStroke);
                  }
                  g.drawLine(x0 + x1, y0 - y1 - pass, x0 + x2, y0 - y2 - pass);
                  if (oldStroke != null) {
                    g2d.setStroke(oldStroke);
                  }
                }
              }
            }
            x1 = x2;
            v1 = v2;
          }
        }

        // Current value
        long v = seq.value(seq.size - 1);
        if (v >= vMin && v <= vMax) {
          if (bgIsLight) {
            g.setColor(seq.color);
          } else {
            g.setColor(fg);
          }
          x = r.x + r.width + 2;
          y = topMargin + h - (int) (h * (v - vMin) / (vMax - vMin));
          // a small triangle/arrow
          g.fillPolygon(new int[] {x + 2, x + 6, x + 6}, new int[] {y, y + 3, y - 3}, 3);
        }
        g.setColor(fg);
      }
    }

    int[] valueStringSlots = new int[nLists];
    for (int i = 0; i < nLists; i++) valueStringSlots[i] = -1;
    for (int i = 0; i < nLists; i++) {
      Sequence seq = seqs.get(i);
      if (seq.isPlotted && seq.size > 0) {
        // Draw current value

        // TODO: collapse values if pointsPerPixel >= 4

        long v = seq.value(seq.size - 1);
        if (v >= vMin && v <= vMax) {
          x = r.x + r.width + 2;
          y = topMargin + h - (int) (h * (v - vMin) / (vMax - vMin));
          int y2 = getValueStringSlot(valueStringSlots, y, 2 * 10, i);
          g.setFont(smallFont);
          if (bgIsLight) {
            g.setColor(seq.color);
          } else {
            g.setColor(fg);
          }
          String curValue = getFormattedValue(v, true);
          if (unit == Unit.PERCENT) {
            curValue += "%";
          }
          int valWidth = fm.stringWidth(curValue);
          String legend = (displayLegend ? seq.name : "");
          int legendWidth = fm.stringWidth(legend);
          if (checkRightMargin(valWidth) || checkRightMargin(legendWidth)) {
            // Wait for next repaint
            return;
          }
          g.drawString(legend, x + 17, Math.min(topMargin + h, y2 + 3 - 10));
          g.drawString(curValue, x + 17, Math.min(topMargin + h + 10, y2 + 3));

          // Maybe draw a short line to value
          if (y2 > y + 3) {
            g.drawLine(x + 9, y + 2, x + 14, y2);
          } else if (y2 < y - 3) {
            g.drawLine(x + 9, y - 2, x + 14, y2);
          }
        }
        g.setFont(oldFont);
        g.setColor(fg);
      }
    }
    g.setColor(oldColor);
  }