import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.BorderFactory;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class DialInstrument extends JComponent {
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 300;
private Color scaleColor = Color.BLACK;
private Color pointerColor = Color.RED;
private Color pointerOutlineColor = Color.WHITE;
private float tickLineWidth = 1;
private float dialLineWidth = 5;
private double value = 0;
private final Area pointer;
private final int angle;
private final int startAngle;
public DialInstrument(int angle) {
this.angle = angle;
this.startAngle = 90 - angle / 2;
Shape arrow = new Polygon(new int[]{5, 85, 85, 100, 85, 85, 5, 5},
new int[]{-5, -5, -10, 0, 10, 5, 5, -5},
8);
Shape knob = new Arc2D.Double(-10, -10, 20, 20, 0, 360, Arc2D.CHORD);
Shape hole = new Arc2D.Double(-5, -5, 10, 10, 0, 360, Arc2D.CHORD);
this.pointer = new Area(knob);
pointer.subtract(new Area(hole));
pointer.add(new Area(arrow));
setBackground(Color.WHITE);
}
public void setValue(double value) {
this.value = value;
repaint();
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
Insets insets = getInsets();
int w = DEFAULT_WIDTH + insets.left + insets.right;
int h = DEFAULT_HEIGHT + insets.top + insets.bottom;
return new Dimension(w, h);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Insets insets = getInsets();
Dimension size = getSize();
Graphics2D g2 = (Graphics2D) g.create(insets.left, insets.top,
size.width - insets.left - insets.right, size.height - insets.top - insets.bottom);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
paintDial(g2);
g2.dispose();
}
private void paintDial(Graphics2D g) {
Rectangle r = g.getClipBounds();
int radius = (int) Math.floor(Math.min(r.width, r.height) / 2 - dialLineWidth);
int cx = r.x + r.width / 2;
int cy = r.y + r.height / 2;
int x = cx - radius;
int y = cy - radius;
if (!isOpaque()) {
g.setColor(getBackground());
g.fillArc(x, y, 2*radius, 2*radius, 0, 360);
}
g.setColor(scaleColor);
g.setStroke(new BasicStroke(dialLineWidth));
g.drawArc(x, y, 2*radius, 2*radius, 0, 360);
paintTicks(g, cx, cy, radius);
paintPointer(g, cx, cy, radius, value);
}
private void paintTicks(Graphics2D g, int cx, int cy, int radius) {
g.setColor(scaleColor);
g.setStroke(new BasicStroke(tickLineWidth));
int innerRadius = (int)(radius * 0.9);
int circumference = (int)(Math.toRadians(angle) * innerRadius);
int ticks = Math.min(100, circumference / 10);
for (int i = 0; i < ticks; i++) {
int areasBetweenTicks = Math.max(ticks - 1, 1);
double rad = Math.toRadians(startAngle + i * (double) angle / areasBetweenTicks);
double cos = Math.cos(rad);
double sin = Math.sin(rad);
Line2D tick = new Line2D.Double(cx + cos * innerRadius, cy - sin * innerRadius, cx + cos * radius, cy - sin * radius);
g.draw(tick);
}
}
private void paintPointer(Graphics2D g, int cx, int cy, int radius, double value) {
double pointerAngle = Math.toRadians(startAngle + angle - value);
Graphics2D gRot = (Graphics2D) g.create();
gRot.translate(cx, cy);
gRot.rotate(-pointerAngle);
gRot.scale(radius * 0.8 / 100.0, radius * 0.8 / 100.0);
gRot.setColor(pointerColor);
gRot.fill(pointer);
gRot.setColor(pointerOutlineColor);
gRot.setStroke(new BasicStroke(1f));
gRot.draw(pointer);
gRot.dispose();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
int MAX = 240;
DialInstrument dial = new DialInstrument(MAX);
dial.setBackground(Color.DARK_GRAY);
dial.scaleColor = Color.CYAN;
dial.pointerColor = Color.RED;
JSlider slider = new JSlider(JSlider.HORIZONTAL);
slider.setModel(new DefaultBoundedRangeModel(0, 0, 0, MAX));
slider.getModel().addChangeListener(e -> dial.setValue(slider.getModel().getValue()));
dial.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
slider.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().setBackground(Color.DARK_GRAY);
frame.add(dial);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
});
}
}