Looking for advice on performance and code structure - Thermometer class
I've developed the following Thermometer.class.
This is my first class intended for reuse, so I'd appreciate tips on how to improve it, simplify it, etc
One of the problems I had was to decide if I should extend some existing class or not. I ended up extending JPanel mainly because I wanted to be able to lay on it labels and panels. But this lead me to a Thermometer class having all those inherited methods that I don't need and don't want to bother with. In fact, I only take advantage of a few, related with position and size, such as: setSize(), getPreferredSize(),getBounds(), getInsets().
...So extending JPanel was probably not the best option. Could I do otherwise and still lay my labels and panels?
package ul.cftc.physics.common.graph;
import java.awt.Color;
import java.awt.geom.*;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import ul.cftc.physics.common.Side;
publicclass Thermometerextends JPanelimplements MouseListener{
/*-- Constructor vars --*/
// temperature
private String unit;// temperature unit used in all the settings; will show in the current temp display
privatedouble minTemp, maxTemp;// min and max temp values; will be labeled
privatedouble minEnabledTemp;// min temperature that is possible to set
privatedouble temp;//current temperature
privatedouble oneUnitHeight;// height corresponding to 1 unit of temperature
// scale
privateint unitsBetweenLargeStrokes;// temperature range between small/large scale strokes
privateint largeToSmallStrokeRangeFactor;//factor between unitsBetweenLargeStrokes and unitsBetweenSmallStrokes
privateint labeledTempToLargeStrokeRangeFactor;//factor between unitsBetweenLabeledTemp and unitsBetweenLargeStrokes
privateboolean scaleOnRightSide;//on which side of the column will scale be drawn
// drawing sizes
privateint insetWidth;//border width (space left all around without drawing)
privateint columnWidth, columnHeight;// width and height of "mercury" column
privateint enclosureThickness;// width of thermometer enclosure around "mercury" column
privateint bulbDiameter;// bulb diameter
privateint smallStrokeSize, largeStrokeSize;// width of small/large scale strokes
privateint labeledTempGapFromStrokes;// distance between scale strokes and labeled temperatures
privateint labeledTempWidth;// width available for labeling temperatures
//drawing colours
private Color fluidColor;//"mercury" colour
/*- Drawing auxiliary vars --*/
// all x,y values are relative to newOrigin (set at thermometer bulb centre, y axis upward)
privatedouble newOriginX, newOriginY;// coordinates of newOrigin
// mercury column drawing aux
privatedouble columnRadius;//columnWidth/2
privatedouble columnUsefulHeight;//height available for displaying the temp range from minTemp to maxTemp
privatedouble columnUnusedHeight;//columnHeight - columnUsefulHeight
// scale strokes drawing aux
privateint totalNumLargeStrokes;
privatedouble heightBetweenSmallStrokes, heightBetweenLargeStrokes;
privatedouble strokesX;
double largeStrokesEndX;
double smallStrokesEndX;
double labeledTempX;
// thermometer bulb drawing aux
privatedouble bulbRadius;
privatedouble clippedBulbHalfHeight;
privatedouble bulbStartAngleRelativeToNorth;// rad
privatedouble bulbSolidAngle;// rad
// bulb enclosure drawing aux
privatedouble bulbEnclosureRadius;
privatedouble bulbEnclosureStartAngleRelativeToNorth;// rad
privatedouble dblBulbEnclosureSolidAngle;// rad
//MouseListener shape
private Shape shpTempChangeEnabledArea;
//Components
private JLabel lblCurrentTemp;
private JPanel fluid;
/* Default constructor -*/
public Thermometer(String unit,double minTemp,double maxTemp,
int unitsBetweenLargeStrokes,int largeToSmallStrokeRangeFactor,
int labeledTempToLargeStrokeRangeFactor,int bulbDiameter,
int enclosureThickness,int columnWidth,int columnHeight,
Side scaleSide, Color fluidColor){
// initialize required vars with default values
this.unit = unit;
this.minTemp = minTemp;
this.maxTemp = maxTemp;
this.minEnabledTemp = minTemp;
this.unitsBetweenLargeStrokes = unitsBetweenLargeStrokes;
this.largeToSmallStrokeRangeFactor = largeToSmallStrokeRangeFactor;
this.labeledTempToLargeStrokeRangeFactor = labeledTempToLargeStrokeRangeFactor;
this.bulbDiameter = bulbDiameter;
this.enclosureThickness = enclosureThickness;
this.columnWidth = columnWidth;
this.columnHeight = columnHeight;
this.largeStrokeSize = 5;
this.smallStrokeSize = 2;
this.labeledTempGapFromStrokes = 2;
this.labeledTempWidth = 30;
this.fluidColor = fluidColor;
this.insetWidth = 20;
this.temp = maxTemp;
if (scaleSide == Side.RIGHT_SIDE){
this.scaleOnRightSide =true;
}else{
this.scaleOnRightSide =false;
}
// calculate mercury column variables values
columnRadius = columnWidth / 2;
columnUnusedHeight = columnRadius;
columnUsefulHeight = columnHeight - columnUnusedHeight;
oneUnitHeight = columnUsefulHeight / (maxTemp - minTemp);
// calculate thermometer bulb variables values
bulbRadius = bulbDiameter / 2;
bulbStartAngleRelativeToNorth = Math.asin(columnRadius
/ bulbRadius);
bulbSolidAngle = 2 * Math.PI - 2 * bulbStartAngleRelativeToNorth;
clippedBulbHalfHeight = bulbRadius
* Math.cos(bulbStartAngleRelativeToNorth);
// calculate bulb enclosure variables values
bulbEnclosureRadius = bulbRadius + enclosureThickness;
bulbEnclosureStartAngleRelativeToNorth = Math
.asin((columnRadius + enclosureThickness)
/ bulbEnclosureRadius);
dblBulbEnclosureSolidAngle = 2 * Math.PI - 2
* bulbEnclosureStartAngleRelativeToNorth;
this.setLayout(null);
this.setOpaque(true);
this.setBorder(BorderFactory.createEmptyBorder(insetWidth,insetWidth, insetWidth, insetWidth));
this.setBackground(Color.LIGHT_GRAY);
// calculate thermometer size (without insets from border)
double widthInternal, heightInternal;
widthInternal = Math.max(bulbDiameter + 2 * enclosureThickness,
bulbRadius + enclosureThickness + columnRadius
+ enclosureThickness + largeStrokeSize
+ labeledTempGapFromStrokes + labeledTempWidth);
heightInternal= columnHeight + clippedBulbHalfHeight + bulbRadius
+ 2 * enclosureThickness;
// component Dimensions
double width, height;
width = widthInternal + this.getInsets().left + this.getInsets().right;
height = heightInternal + this.getInsets().top
+ this.getInsets().bottom;
Dimension dim =new Dimension((int) Math.round(width), (int) Math.round(height));
this.setPreferredSize( dim);
// set new Origin at thermometer bulb centre, y axis upward
setNewOrigin();
setStrokesPositions();
// add Shape for hit test
setShapeForHitTest() ;
this.addMouseListener(this);
// add Label for displaying current temp positioned at bulb centre
lblCurrentTemp =new JLabel("");
lblCurrentTemp.setBounds(getX(-bulbRadius - 20),
getY(-clippedBulbHalfHeight + 10), bulbDiameter + 40, 20);
lblCurrentTemp.setHorizontalAlignment(JLabel.CENTER);
lblCurrentTemp.setOpaque(false);
lblCurrentTemp.setForeground(Color.BLACK);
this.add(lblCurrentTemp);
// add fluid panel
fluid =new JPanel();
fluid.setOpaque(true);
fluid.setBackground(fluidColor);
// this.add(fluid,new Integer(1));
this.add(fluid);
setTemperature(temp);
// calculate scale strokes variables values
totalNumLargeStrokes = (int) Math.floor((maxTemp - minTemp)
/ unitsBetweenLargeStrokes);// does not count minTemp stroke
heightBetweenLargeStrokes = columnUsefulHeight
/ totalNumLargeStrokes;
heightBetweenSmallStrokes = heightBetweenLargeStrokes
/ largeToSmallStrokeRangeFactor;
}
public Thermometer(){
this("K", 0.0, 8000.0,
500, 5, 2,
50,3, 20, 300,
Side.RIGHT_SIDE ,Color.RED );
}
/* Methods -*/
publicvoid setScale(Side scaleSide){
scaleSide.checkNull(scaleSide);
if (scaleSide==Side.RIGHT_SIDE ){
scaleOnRightSide =true;
}else{
scaleOnRightSide =false;
}
setNewOrigin();
lblCurrentTemp.setBounds(getX(-bulbRadius - 20),
getY(-clippedBulbHalfHeight + 10), bulbDiameter + 40, 20);
setStrokesPositions();
setShapeForHitTest();
resizeFluid();
repaint();
}
privatevoid setStrokesPositions(){
strokesX = columnRadius + enclosureThickness;
if (scaleOnRightSide){
smallStrokesEndX = strokesX + smallStrokeSize;
largeStrokesEndX = strokesX + largeStrokeSize;
labeledTempX = largeStrokesEndX + labeledTempGapFromStrokes;
}else{
strokesX = -strokesX;
smallStrokesEndX = strokesX - smallStrokeSize;
largeStrokesEndX = strokesX - largeStrokeSize;
labeledTempX = largeStrokesEndX - labeledTempGapFromStrokes
- labeledTempWidth;
}
}
privatevoid setShapeForHitTest(){
int shpLeft=(scaleOnRightSide?getX(-columnRadius): getX(largeStrokesEndX));
shpTempChangeEnabledArea =new Rectangle2D.Double(shpLeft, getY(columnHeight),
columnWidth + enclosureThickness + largeStrokeSize,
columnHeight);
}
privatevoid setNewOrigin(){
// set origin at thermometer bulb centre, y axis upward
newOriginX=(scaleOnRightSide?
(enclosureThickness + bulbRadius + this.getInsets().left):
( this.getInsets().left + Math.max(enclosureThickness + bulbRadius, columnRadius
+ enclosureThickness + largeStrokeSize + labeledTempGapFromStrokes + labeledTempWidth)));
newOriginY =columnHeight + enclosureThickness + this.getInsets().top;
}
privateint getX(double X){
return (int) Math.round(newOriginX + X);
}
privateint getY(double Y){
return (int) Math.round(newOriginY - Y);
}
publicdouble getTemperature(){
return temp;
}
publicvoid setTemperature(double t){
// sets temp, "fills" thermometer with fluid and writes temp current value
if (t <=maxTemp && t >= minEnabledTemp ){
temp = t;
resizeFluid();
lblCurrentTemp.setText(Math.round(temp ) +" " + unit);
}
}
publicvoid resizeFluid(){
fluid.setSize(columnWidth - 1, (int) Math.round(temp * oneUnitHeight));
fluid.setLocation(getX(-columnRadius + 1), getY(fluid.getHeight()));
}
publicdouble getMaxTemp(){
return maxTemp;
}
publicdouble getMinTemp(){
return minTemp;
}
publicdouble getMinEnabledTemp(){
return minEnabledTemp;
}
publicvoid setMinEnabledTemp(double t){
minEnabledTemp=t;
}
publicvoid mousePressed(MouseEvent e){
}
publicvoid mouseClicked(MouseEvent e){
if (shpTempChangeEnabledArea.contains( e.getX(), e.getY())){
temp = Math.max(Math.min((newOriginY-e.getY())/oneUnitHeight, maxTemp), minEnabledTemp);
setTemperature(temp );
}
}
publicvoid mouseEntered(MouseEvent e){
}
publicvoid mouseExited(MouseEvent e){
}
publicvoid mouseReleased(MouseEvent e){
}
protectedvoid paintComponent(Graphics g){
// garantee background of component is painted, if opaque
super.paintComponent(g);
// draw static parts of thermometer, i.e., everything except fluid and
// temp current value
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(Color.BLACK);
// g2d.setStroke(new BasicStroke(2));
// draw column
g2d.draw(new Line2D.Double(getX(-columnRadius),
getY(columnUsefulHeight), getX(-columnRadius), getY(0)));
g2d.draw(new Line2D.Double(getX(columnRadius),
getY(columnUsefulHeight), getX(columnRadius), getY(0)));
// draw arc on top of column
g2d.draw(new Arc2D.Double(getX(-columnRadius), getY(columnHeight),
columnWidth, 2 * columnUnusedHeight, 0, 180, Arc2D.OPEN));
// draw bulb
g2d.setPaint(fluidColor);
g2d.fill(new Arc2D.Double(getX(-bulbRadius), getY(bulbRadius
- clippedBulbHalfHeight), bulbDiameter, bulbDiameter, -270
+ bulbStartAngleRelativeToNorth * 180 / Math.PI, bulbSolidAngle
* 180 / Math.PI, Arc2D.OPEN));
g2d.setPaint(Color.BLACK);
g2d.draw(new Arc2D.Double(getX(-bulbRadius), getY(bulbRadius
- clippedBulbHalfHeight), bulbDiameter, bulbDiameter, -270
+ bulbStartAngleRelativeToNorth * 180 / Math.PI, bulbSolidAngle
* 180 / Math.PI, Arc2D.OPEN));
// draw column enclosure
g2d.draw(new Line2D.Double(getX(-columnRadius - enclosureThickness),
getY(columnUsefulHeight), getX(-columnRadius
- enclosureThickness), getY(0) - 1));
g2d.draw(new Line2D.Double(getX(columnRadius + enclosureThickness),
getY(columnUsefulHeight), getX(columnRadius
+ enclosureThickness), getY(0) - 1));
// draw enclosure arc on top of column
g2d.draw(new Arc2D.Double(getX(-columnRadius - enclosureThickness),
getY(columnHeight + enclosureThickness), columnWidth + 2
* enclosureThickness,
2 * (columnUnusedHeight + enclosureThickness), 0, 180,
Arc2D.OPEN));
// draw bulb enclosure
g2d.draw(new Arc2D.Double(getX(-bulbEnclosureRadius), getY(bulbRadius
- clippedBulbHalfHeight + enclosureThickness),
bulbEnclosureRadius * 2, bulbEnclosureRadius * 2, -270
+ bulbEnclosureStartAngleRelativeToNorth * 180
/ Math.PI, dblBulbEnclosureSolidAngle * 180 / Math.PI,
Arc2D.OPEN));
// draw scale strokes and temperature Labels
double h = 0.0;
double nextLargeStrokeHeight;
int n = 0;
StringBuffer sb =new StringBuffer();
StringBuffer sbMax =new StringBuffer();
StringBuffer sbSpaces =new StringBuffer();
int labeledTempStringLength = sbMax.append(maxTemp).length();
for (int i = 1; i < labeledTempStringLength; i++){
sbSpaces.append(" ");
}
int countLargeStrokes=0 ;
int countSmallStrokes=0;
// large strokes
while (countLargeStrokes<=totalNumLargeStrokes){
g2d.drawLine(getX(strokesX), getY(h), getX(largeStrokesEndX),getY(h));
n++;
if (n==labeledTempToLargeStrokeRangeFactor || h == 0){
sb.append((countLargeStrokes * unitsBetweenLargeStrokes));
if (sb.length() < labeledTempStringLength){
sb.insert(0, sbSpaces, 1, labeledTempStringLength
- sb.length());
}
g2d.drawString(sb.toString(), getX(labeledTempX), getY(h) + 3);
sb.delete(1, sb.length());
n=0;
}
// h=h+heightBetweenLargeStrokes;
countLargeStrokes++;
if (countLargeStrokes>totalNumLargeStrokes )break;
nextLargeStrokeHeight=h+heightBetweenLargeStrokes;
//small strokes
countSmallStrokes=1;
h = h + heightBetweenSmallStrokes;
while (countSmallStrokes<largeToSmallStrokeRangeFactor){
g2d.drawLine(getX(strokesX), getY(h), getX(smallStrokesEndX),
getY(h));
h = h + heightBetweenSmallStrokes;
countSmallStrokes++;
}
h = nextLargeStrokeHeight;
}
g2d.dispose();
}
}
Error handling code is still missing (I have to study the exceptions chapter yet...). I'll add that later.
I'd appreciate some advice/comments.
Thank you.>

