import java.awt.*; import java.applet.*; import java.util.*; import java.net.*; import corejava.*; /* This applet implements a spoke calculator for computing spoke lengths, tensions, and pull angles for bicycle wheels. It supports unusual wheels such as offset frames and rims, high-low hubs, and mixed lacing patterns. The user can enter measurements directly or she can choose one of the existing rims, hubs, or spokes whose data are read from files. The files all use property format with + to indicate space. There are three main files: rims.dat, hubs.dat, and spokes.dat. These contain filenames for each rim, hub, and spoke. The files mentioned therein contain all the attributes of the rim/hub/spoke, such as rimDiameter. See spokeCalculator.html for information on how to use this calculator. */ public class SpokeCalculator extends Applet { /* Private helper method to make using a grid bag layout easier. */ private void add (Component c, GridBagLayout gbl, GridBagConstraints gbc, int x, int y, int w, int h, int fill, int anchor) { gbc.fill = fill; gbc.anchor = anchor; gbc.gridx = x; gbc.gridy = y; gbc.gridwidth = w; gbc.gridheight = h; gbl.setConstraints (c, gbc); add (c); } /* Start up the applet. Create the widgets and load rim, hub, and spoke data from files. */ public void init () { /* Constants. */ String UNCOMPUTED = " "; int CENTER = GridBagConstraints.CENTER; int EAST = GridBagConstraints.EAST; int WEST = GridBagConstraints.WEST; int NORTH = GridBagConstraints.NORTH; int NONE = GridBagConstraints.NONE; int HORIZONTAL = GridBagConstraints.HORIZONTAL; /* Wheel widgets. */ numberOfSpokesField = new IntTextField (36, 2, 100, 10); driveLacingField = new IntTextField (3, 0, 10, 10); nonDriveLacingField = new IntTextField (3, 0, 10, 10); offsetField = new DoubleTextField (0.0, -100.0, 100.0, 10); /* Rim widgets. */ rimChoice = new Choice (); rimDiameterField = new DoubleTextField (0.0, 1.0, 10000.0, 10); driveHoleOffsetField = new DoubleTextField (0.0, -100.0, 100.0, 10); nonDriveHoleOffsetField = new DoubleTextField (0.0, 0.0, 100.0, 10); /* Hub widgets. */ hubChoice = new Choice (); axleLengthField = new DoubleTextField (0.0, 10.0, 1000.0, 10); driveFlangeInsetField = new DoubleTextField (0.0, 0.0, 1000.0, 10); nonDriveFlangeInsetField = new DoubleTextField (0.0, 0.0, 1000.0, 10); driveFlangeRadiusField = new DoubleTextField (0.0, 0.0, 1000.0, 10); nonDriveFlangeRadiusField = new DoubleTextField (0.0, 0.0, 1000.0, 10); holeDiameterField = new DoubleTextField (0.0, 0.0, 10.0, 10); /* Spoke widgets. */ driveSpokeChoice = new Choice (); nonDriveSpokeChoice = new Choice (); driveSpokeDiameterField = new DoubleTextField (0.0, 0.1, 10.0, 10); nonDriveSpokeDiameterField = new DoubleTextField (0.0, 0.1, 10.0, 10); driveButtDiameterField = new DoubleTextField (0.0, 0.1, 10.0, 10); nonDriveButtDiameterField = new DoubleTextField (0.0, 0.1, 10.0, 10); driveButtLengthField = new DoubleTextField (0.0, 0.0, 10000.0, 10); nonDriveButtLengthField = new DoubleTextField (0.0, 0.0, 10000.0, 10); /* Result widgets. */ Button calculateButton = new Button ("Calculate"); drivePullAngleField = new Label (UNCOMPUTED); nonDrivePullAngleField = new Label (UNCOMPUTED); driveSpokeTensionField = new Label (UNCOMPUTED); nonDriveSpokeTensionField = new Label (UNCOMPUTED); driveSpokeStretchField = new Label (UNCOMPUTED); nonDriveSpokeStretchField = new Label (UNCOMPUTED); driveSpokeLengthField = new Label (UNCOMPUTED); nonDriveSpokeLengthField = new Label (UNCOMPUTED); /* Lay out the widgets. */ GridBagLayout gbl = new GridBagLayout (); setLayout (gbl); GridBagConstraints gbc = new GridBagConstraints (); gbc.weightx = 1.0; gbc.weighty = 1.0; int row = 0; add (new Label ("WHEEL DATA:"), gbl, gbc, 0, row++, 1, 1, NONE, WEST); add (new Label ("Number of spokes ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (numberOfSpokesField, gbl, gbc, 1, row++, 1, 1, NONE, WEST); add (new Label ("Drive lacing ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveLacingField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive lacing ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveLacingField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Offset ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (offsetField, gbl, gbc, 1, row++, 1, 1, NONE, WEST); row++; add (new Label ("RIM DATA:"), gbl, gbc, 0, row, 1, 1, NONE, WEST); add (rimChoice, gbl, gbc, 1, row++, 3, 1, HORIZONTAL, CENTER); add (new Label ("Diameter ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (rimDiameterField, gbl, gbc, 1, row++, 1, 1, NONE, WEST); add (new Label ("Drive hole offset ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveHoleOffsetField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive hole offset ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveHoleOffsetField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); row++; add (new Label ("HUB DATA:"), gbl, gbc, 0, row, 1, 1, NONE, WEST); add (hubChoice, gbl, gbc, 1, row++, 3, 1, HORIZONTAL, CENTER); add (new Label ("Axle length ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (axleLengthField, gbl, gbc, 1, row++, 1, 1, NONE, WEST); add (new Label ("Drive flange inset ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveFlangeInsetField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive flange inset ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveFlangeInsetField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Drive flange radius ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveFlangeRadiusField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive flange radius ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveFlangeRadiusField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Hole diameter ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (holeDiameterField, gbl, gbc, 1, row++, 1, 1, NONE, WEST); row++; add (new Label ("SPOKE DATA:"), gbl, gbc, 0, row, 1, 1, NONE, WEST); add (driveSpokeChoice, gbl, gbc, 1, row, 1, 1, HORIZONTAL, CENTER); add (nonDriveSpokeChoice, gbl, gbc, 3, row++, 1, 1, HORIZONTAL, CENTER); add (new Label ("Drive diameter ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveSpokeDiameterField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive diameter ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveSpokeDiameterField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Drive butt diameter ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveButtDiameterField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive butt diameter ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveButtDiameterField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Drive butt length ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveButtLengthField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive butt length ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveButtLengthField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); row++; add (new Label ("RESULTS:"), gbl, gbc, 0, row, 1, 1, NONE, WEST); add (calculateButton, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("<- press this button to update results"), gbl, gbc, 2, row++, 2, 1, NONE, WEST); add (new Label ("Drive pull angle ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (drivePullAngleField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive pull angle ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDrivePullAngleField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Drive spoke tension ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveSpokeTensionField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive spoke tension ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveSpokeTensionField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Drive spoke stretch ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveSpokeStretchField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive spoke stretch ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveSpokeStretchField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); add (new Label ("Drive spoke length ="), gbl, gbc, 0, row, 1, 1, NONE, EAST); add (driveSpokeLengthField, gbl, gbc, 1, row, 1, 1, NONE, WEST); add (new Label ("Non-drive spoke length ="), gbl, gbc, 2, row, 1, 1, NONE, EAST); add (nonDriveSpokeLengthField, gbl, gbc, 3, row++, 1, 1, NONE, WEST); loadRims (); loadHubs (); loadSpokes (); } /* Load the rim data from rims.dat and files named therein. Set up the rim choices. */ private void loadRims () { showStatus ("Loading rim data.."); Properties rimNames = new Properties (); Properties defaultProperties = new Properties (); defaultProperties.put ("rimDiameter", ""); defaultProperties.put ("driveHoleOffset", ""); defaultProperties.put ("nonDriveHoleOffset", ""); String defaultRimName = "*Choose Rim*"; rimChoice.addItem (defaultRimName); rims.put (defaultRimName, defaultProperties); try { rimNames.load ((new URL (getDocumentBase (), "Rims/rims.dat")).openStream ()); } catch (Exception e) {} /* ignore errors */ Enumeration e = rimNames.propertyNames (); while (e.hasMoreElements ()) { String rimName = (String) e.nextElement (); String fileName = rimNames.getProperty (rimName); Properties rimData = new Properties (defaultProperties); try { rimData.load ((new URL (getDocumentBase (), "Rims/" + fileName)).openStream ()); } catch (Exception ex) {} /* ignore errors */ rimName = rimName.replace ('+', ' '); /* + represents space */ rimChoice.addItem (rimName); rims.put (rimName, rimData); } showStatus ("Loading rim data....done."); } /* Load the hub data from hubs.dat and files named therein. Set up the hub choices. */ private void loadHubs () { showStatus ("Loading hub data.."); Properties hubNames = new Properties (); Properties defaultProperties = new Properties (); defaultProperties.put ("axleLength", ""); defaultProperties.put ("driveFlangeInset", ""); defaultProperties.put ("nonDriveFlangeInset", ""); defaultProperties.put ("driveFlangeRadius", ""); defaultProperties.put ("nonDriveFlangeRadius", ""); defaultProperties.put ("holeDiameter", ""); String defaultHubName = "*Choose Hub*"; hubChoice.addItem (defaultHubName); hubs.put (defaultHubName, defaultProperties); try { hubNames.load ((new URL (getDocumentBase (), "Hubs/hubs.dat")).openStream ()); } catch (Exception e) {} /* ignore errors */ Enumeration e = hubNames.propertyNames (); while (e.hasMoreElements ()) { String hubName = (String) e.nextElement (); String fileName = hubNames.getProperty (hubName); Properties hubData = new Properties (defaultProperties); try { hubData.load ((new URL (getDocumentBase (), "Hubs/" + fileName)).openStream ()); } catch (Exception ex) {} /* ignore errors */ hubName = hubName.replace ('+', ' '); /* + represents space */ hubChoice.addItem (hubName); hubs.put (hubName, hubData); } showStatus ("Loading hub data....done."); } /* Load the spoke data from Spokes/spokes.dat and files named therein. Set up the spoke choices. */ private void loadSpokes () { showStatus ("Loading spoke data.."); Properties spokeNames = new Properties (); Properties defaultProperties = new Properties (); defaultProperties.put ("spokeDiameter", ""); defaultProperties.put ("buttDiameter", ""); defaultProperties.put ("buttLength", ""); String defaultSpokeName = "*Choose Spoke*"; driveSpokeChoice.addItem (defaultSpokeName); nonDriveSpokeChoice.addItem (defaultSpokeName); spokes.put (defaultSpokeName, defaultProperties); try { spokeNames.load ((new URL (getDocumentBase (), "Spokes/spokes.dat")).openStream ()); } catch (Exception e) {} /* ignore errors */ Enumeration e = spokeNames.propertyNames (); while (e.hasMoreElements ()) { String spokeName = (String) e.nextElement (); String fileName = spokeNames.getProperty (spokeName); Properties spokeData = new Properties (defaultProperties); try { spokeData.load ((new URL (getDocumentBase (), "Spokes/" + fileName)).openStream ()); } catch (Exception ex) {} /* ignore errors */ spokeName = spokeName.replace ('+', ' '); /* + represents space */ driveSpokeChoice.addItem (spokeName); nonDriveSpokeChoice.addItem (spokeName); spokes.put (spokeName, spokeData); } showStatus ("Loading spoke data....done."); } public boolean handleEvent (Event evt) { if (evt.id == Event.WINDOW_DESTROY) System.exit (0); return super.handleEvent (evt); } /* Handle user button presses and choice selections. */ public boolean action (Event evt, Object arg) { if (arg.equals ("Calculate")) { showStatus ("Calculating spoke data.."); if ( numberOfSpokesField.isValid () && driveLacingField.isValid () && nonDriveLacingField.isValid () && offsetField.isValid () && rimDiameterField.isValid () && driveHoleOffsetField.isValid () && nonDriveHoleOffsetField.isValid () && axleLengthField.isValid () && driveFlangeInsetField.isValid () && nonDriveFlangeInsetField.isValid () && driveFlangeRadiusField.isValid () && nonDriveFlangeRadiusField.isValid () && holeDiameterField.isValid () && driveSpokeDiameterField.isValid () && nonDriveSpokeDiameterField.isValid () && driveButtDiameterField.isValid () && nonDriveButtDiameterField.isValid () && driveButtLengthField.isValid () && nonDriveButtLengthField.isValid () ) { Rim rim = new Rim (rimDiameterField.getValue (), driveHoleOffsetField.getValue (), nonDriveHoleOffsetField.getValue ()); Hub hub = new Hub (axleLengthField.getValue (), driveFlangeInsetField.getValue (), nonDriveFlangeInsetField.getValue (), driveFlangeRadiusField.getValue (), nonDriveFlangeRadiusField.getValue (), holeDiameterField.getValue ()); Spoke driveSpoke = new Spoke (driveSpokeDiameterField.getValue (), driveButtDiameterField.getValue (), driveButtLengthField.getValue ()); Spoke nonDriveSpoke = new Spoke (nonDriveSpokeDiameterField.getValue (), nonDriveButtDiameterField.getValue (), nonDriveButtLengthField.getValue ()); Wheel wheel = new Wheel (numberOfSpokesField.getValue (), driveLacingField.getValue (), nonDriveLacingField.getValue (), offsetField.getValue (), rim, hub, driveSpoke, nonDriveSpoke); drivePullAngleField.setText (new Format ("%3.1f deg").form (wheel.drivePullAngle ())); nonDrivePullAngleField.setText (new Format ("%3.1f deg").form (wheel.nonDrivePullAngle ())); driveSpokeTensionField.setText (new Format ("%3.1f kg").form (wheel.driveSpokeTension ())); nonDriveSpokeTensionField.setText (new Format ("%3.1f kg").form (wheel.nonDriveSpokeTension ())); driveSpokeStretchField.setText (new Format ("%3.2f mm").form (wheel.driveSpokeStretch ())); nonDriveSpokeStretchField.setText (new Format ("%3.2f mm").form (wheel.nonDriveSpokeStretch ())); driveSpokeLengthField.setText (new Format ("%3.2f mm").form (wheel.driveSpokeLength ())); nonDriveSpokeLengthField.setText (new Format ("%3.2f mm").form (wheel.nonDriveSpokeLength ())); showStatus ("Calculating spoke data....done."); } else showStatus ("Invalid data."); return true; } else if (evt.target.equals (rimChoice)) { Properties rimData = (Properties) rims.get (arg); rimDiameterField.setText (rimData.getProperty ("rimDiameter")); driveHoleOffsetField.setText (rimData.getProperty ("driveHoleOffset")); nonDriveHoleOffsetField.setText (rimData.getProperty ("nonDriveHoleOffset")); showStatus ("Loaded rim data for " + ((String) arg)); return true; } else if (evt.target.equals (hubChoice)) { Properties hubData = (Properties) hubs.get (arg); axleLengthField.setText (hubData.getProperty ("axleLength")); driveFlangeInsetField.setText (hubData.getProperty ("driveFlangeInset")); nonDriveFlangeInsetField.setText (hubData.getProperty ("nonDriveFlangeInset")); driveFlangeRadiusField.setText (hubData.getProperty ("driveFlangeRadius")); nonDriveFlangeRadiusField.setText (hubData.getProperty ("nonDriveFlangeRadius")); holeDiameterField.setText (hubData.getProperty ("holeDiameter")); showStatus ("Loaded hub data for " + ((String) arg)); return true; } else if (evt.target.equals (driveSpokeChoice)) { Properties spokeData = (Properties) spokes.get (arg); driveSpokeDiameterField.setText (spokeData.getProperty ("spokeDiameter")); driveButtDiameterField.setText (spokeData.getProperty ("buttDiameter")); driveButtLengthField.setText (spokeData.getProperty ("buttLength")); showStatus ("Loaded drive spoke data for " + ((String) arg)); return true; } else if (evt.target.equals (nonDriveSpokeChoice)) { Properties spokeData = (Properties) spokes.get (arg); nonDriveSpokeDiameterField.setText (spokeData.getProperty ("spokeDiameter")); nonDriveButtDiameterField.setText (spokeData.getProperty ("buttDiameter")); nonDriveButtLengthField.setText (spokeData.getProperty ("buttLength")); showStatus ("Loaded non-drive spoke data for " + ((String) arg)); return true; } else return super.action (evt, arg); } /* These map from rim/hub/spoke name to their properties. */ private Hashtable rims = new Hashtable (); private Hashtable hubs = new Hashtable (); private Hashtable spokes = new Hashtable (); /* The wheel widgets. */ private IntTextField numberOfSpokesField; private IntTextField driveLacingField; private IntTextField nonDriveLacingField; private DoubleTextField offsetField; /* The rim widgets. */ private Choice rimChoice; private DoubleTextField rimDiameterField; private DoubleTextField driveHoleOffsetField; private DoubleTextField nonDriveHoleOffsetField; /* The hub widgets. */ private Choice hubChoice; private DoubleTextField axleLengthField; private DoubleTextField driveFlangeInsetField; private DoubleTextField nonDriveFlangeInsetField; private DoubleTextField driveFlangeRadiusField; private DoubleTextField nonDriveFlangeRadiusField; private DoubleTextField holeDiameterField; /* The spoke widgets. */ private Choice driveSpokeChoice; private Choice nonDriveSpokeChoice; private DoubleTextField driveSpokeDiameterField; private DoubleTextField nonDriveSpokeDiameterField; private DoubleTextField driveButtDiameterField; private DoubleTextField nonDriveButtDiameterField; private DoubleTextField driveButtLengthField; private DoubleTextField nonDriveButtLengthField; /* The result widgets. */ private Label drivePullAngleField; private Label nonDrivePullAngleField; private Label driveSpokeTensionField; private Label nonDriveSpokeTensionField; private Label driveSpokeStretchField; private Label nonDriveSpokeStretchField; private Label driveSpokeLengthField; private Label nonDriveSpokeLengthField; }