---
category: swing
folder: SemicircleGauge
title: JProgressBarの形状をドーナツ状の半円に変更してスピードメーターを作成する
tags: [JProgressBar, SwingWorker, LinearGradientPaint]
author: aterai
pubdate: 2019-12-30T00:46:24+09:00
description: JProgressBarの形状をドーナツ状の半円に変更して、その値に応じてゲージの色を変更するスピードメーターを作成します。
image: https://drive.google.com/uc?id=1MSxJ37yRyZGWHdJFNWAWMYXdEfyZyixR
hreflang:
    href: https://java-swing-tips.blogspot.com/2019/12/create-speedometer-by-changing-shape-of.html
    lang: en
---
* 概要 [#summary]
`JProgressBar`の形状をドーナツ状の半円に変更して、その値に応じてゲージの色を変更するスピードメーターを作成します。

#download(https://drive.google.com/uc?id=1MSxJ37yRyZGWHdJFNWAWMYXdEfyZyixR)

* サンプルコード [#sourcecode]
#code(link){{
class SolidGaugeUI extends BasicProgressBarUI {
  private final int[] pallet;
  private final double extent;

  protected SolidGaugeUI(int range, double extent) {
    super();
    this.pallet = makeGradientPallet(range);
    this.extent = extent;
  }

  @Override public void paint(Graphics g, JComponent c) {
    Rectangle rect = SwingUtilities.calculateInnerArea(progressBar, null);
    if (rect.isEmpty()) {
      return;
    }
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    // double extent = -150d;
    double start = 90d + extent * .5;
    double degree = extent * progressBar.getPercentComplete();
    double or = Math.min(rect.width, rect.height);
    double cx = rect.getCenterX();
    double cy = rect.getMaxY();
    double sz = or * 2d;
    double ir = or * .6;
    Shape inner = new Arc2D.Double(cx - ir, cy - ir, ir * 2d, ir * 2d, start, -extent, Arc2D.PIE);
    Shape outer = new Arc2D.Double(cx - or, cy - or, sz, sz, start, -extent, Arc2D.PIE);
    Shape sector = new Arc2D.Double(cx - or, cy - or, sz, sz, start, -degree, Arc2D.PIE);

    Area foreground = new Area(sector);
    Area background = new Area(outer);
    Area hole = new Area(inner);

    foreground.subtract(hole);
    background.subtract(hole);

    // Draw the track
    g2.setPaint(new Color(0xDD_DD_DD));
    g2.fill(background);

    // Draw the circular sector
    g2.setPaint(getColorFromPallet(pallet, progressBar.getPercentComplete()));
    g2.fill(foreground);

    // Draw minimum, maximum
    Font font = progressBar.getFont();
    float fsz = font.getSize2D();
    float min = (float) (cx - or - fsz);
    float max = (float) (cx + or + 4d);
    g2.setPaint(progressBar.getForeground());
    g2.drawString(Objects.toString(progressBar.getMinimum()), min, (float) cy);
    g2.drawString(Objects.toString(progressBar.getMaximum()), max, (float) cy);

    // Deal with possible text painting
    if (progressBar.isStringPainted()) {
      float h = (float) cy - fsz;
      String str = String.format("%d", progressBar.getValue());
      float vx = (float) cx - g2.getFontMetrics().stringWidth(str) * .5f;
      g2.drawString(str, vx, h);
      float ksz = fsz * 2f / 3f;
      g2.setFont(font.deriveFont(ksz));
      String kmh = "㎞/h";
      float tx = (float) cx - g2.getFontMetrics().stringWidth(kmh) * .5f;
      g2.drawString(kmh, tx, h + ksz);
    }
    g2.dispose();
  }

  private static int[] makeGradientPallet(int range) {
    BufferedImage image = new BufferedImage(range, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = image.createGraphics();
    Point2D start = new Point2D.Float();
    Point2D end = new Point2D.Float(range - 1f, 0f);
    float[] dist = {0f, .8f, .9f, 1f};
    Color[] colors = {Color.GREEN, Color.YELLOW, Color.ORANGE, Color.RED};
    g2.setPaint(new LinearGradientPaint(start, end, dist, colors));
    g2.fillRect(0, 0, range, 1);
    g2.dispose();

    int width = image.getWidth(null);
    int[] pallet = new int[width];
    PixelGrabber pg = new PixelGrabber(image, 0, 0, width, 1, pallet, 0, width);
    try {
      pg.grabPixels();
    } catch (InterruptedException ex) {
      ex.printStackTrace();
      Toolkit.getDefaultToolkit().beep();
      Thread.currentThread().interrupt();
    }
    return pallet;
  }

  private static Color getColorFromPallet(int[] pallet, double pos) {
    if (pos < 0d || pos > 1d) {
      throw new IllegalArgumentException("Parameter outside of expected range");
    }
    int i = (int) (pallet.length * pos);
    int max = pallet.length - 1;
    int index = Math.min(Math.max(i, 0), max);
    return new Color(pallet[index] & 0x00_FF_FF_FF);
  }

  // @Override protected Color getSelectionBackground() {
  //   return new Color(0xAA_75_FF_3C, true); // a progress string color
  // }
}
}}

* 解説 [#explanation]
上記のサンプルでは、`JProgressBar`の形状を半円に変更し、その値に応じてゲージの色を変化させるスピードメーター風のコンポーネントを作成しています。

- ドーナツ状の半円は`Arc2D.PIE`を使用
-- `start`(弧の始角)は反時計回りに`180°`を設定し、`extent`(弧の角)は時計周りなので`0°`からマイナス`180°`まで
-- `start`(弧の始角)は反時計回りに`180°`を設定し`extent`(弧の角)は時計周りなので`0°`からマイナス`180°`まで
-- 参考: [[JProgressBarの進捗状況を円形で表示する>Swing/ProgressCircle]]
- 現在値の表示は`JProgressBar`の`setString(...)`メソッドで設定可能な進捗文字列が`html`タグなどに対応していないので使用せず、独自に`BasicProgressBarUI#paint(...)`メソッド中で値と単位(文字サイズを値の`2/3`に縮小)を`2`行に分けて描画
- 最小値、最大値の描画も`BasicProgressBarUI#paint(...)`メソッド中で描画
- 値に応じた色の変更は`LinearGradientPaint`から生成したパレットから取得
-- 参考: [[JSliderのスタイルを変更する>Swing/GradientTrackSlider]]

* 参考リンク [#reference]
- [[JProgressBarの進捗状況を円形で表示する>Swing/ProgressCircle]]
- [[JSliderのスタイルを変更する>Swing/GradientTrackSlider]]

* コメント [#comment]
#comment
#comment