Continuous IOS Code
by Will Moss
| |
ABOUT the Continuous IOS |
JAVA TEST
| See the DEMO | See
EXAMPLES | DOCUMENTATION | DOWNLOAD
| CODE
|
|
Formulas used by the applet for calculating the overlap and the location of
the text within the circle:

r is the radius of the circle and d is the distance between the two circles. The area of the overlap (the numerator) is divided by the area of one circle to yield a percentage.
![]()
r is the radius of the circle and c is the length of the text. The result is the distance from the center of the circle where that length text will fit.
Code for the applet:
/**
* Continuous IOS Applet, written by William Moss and designed by Benjamin Le
* Copyright (C) 2006 William Moss, Benjamin Le
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* http://www.gnu.org/licenses/gpl.txt
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.Math;
import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.ListIterator;
public class slider extends JApplet implements MouseMotionListener, MouseListener
{
private int x1, x2, y, radius;
private int x1Initial, xMin;
private int xOffset;
private boolean selected;
private LinkedList label1Lines, label2Lines;
private int label1YOffset, label2YOffset, labelTextHeight;
private Font labelFont;
private FontMetrics labelFontMetrics;
private String distanceFormat, overlapFormat;
private Color color1, color2, bgcolor, textcolor;
private int width, height;
private boolean demoMode;
public void init()
{
JRootPane rootPane = this.getRootPane();
rootPane.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
//Make the applet listen to mouse click and mouse drag events (among others)
addMouseListener(this);
addMouseMotionListener(this);
}
public void start()
{
width = getSize().width;
height = getSize().height;
selected = false;
y = height / 2;
radius = getParameter("radius") == null ? height / 2: Integer.parseInt(getParameter("radius"));
//Get the starting locations of the circles from the parameters or use the default values
x2 = getParameter("x2") == null ? width - radius : Integer.parseInt(getParameter("x2"));
x1 = getParameter("x1") == null ? x2 - 2 * radius : Math.min(Integer.parseInt(getParameter("x1")), x2); //Make sure they don't try to put the left circle to the right of the right circle
x1Initial = x1;
xOffset = 0;
String label1 = getParameter("label1") == null ? "Self" : getParameter("label1");
String label2 = getParameter("label2") == null ? "Other" : getParameter("label2");
String wrapText = getParameter("wraplabels") == null ? "false" : getParameter("wraplabels");
labelFont = new Font(getParameter("fontname") == null ? "SansSerif" : getParameter("fontname"), //Font Name
Font.BOLD, //Font Face
getParameter("fontsize") == null ? 10 : Integer.parseInt(getParameter("fontsize"))); //Font Size
setupLabels(label1, label2, wrapText);
if (getParameter("color1") != null) {
color1 = getColor(getParameter("color1"));
if (color1 == null) { showStatus("Unknown Color for Circle 1"); color1 = Color.green; stop(); }
} else {
color1 = Color.green;
}
if (getParameter("color2") != null) {
color2 = getColor(getParameter("color2"));
if (color2 == null) { showStatus("Unknown Color for Circle 2"); color2 = Color.lightGray; stop(); }
} else {
color2 = Color.lightGray;
}
if (getParameter("bgcolor") != null) {
bgcolor = getColor(getParameter("bgcolor"));
if (bgcolor == null) { showStatus("Unknown Color for Background Color"); bgcolor = Color.white; stop(); }
} else {
bgcolor = Color.white;
}
if (getParameter("textcolor") != null) {
textcolor = getColor(getParameter("textcolor"));
if (textcolor == null) { showStatus("Unknown Color for Text Color"); textcolor = Color.black; stop(); }
} else {
textcolor = Color.black;
}
//reverse defaults to true, so set the minimum value for x to edge of the
//Ask Ben about this....
xMin = getParameter("reverse") != null && getParameter("reverse").equalsIgnoreCase("false") ? x1 : radius;
distanceFormat = getParameter("distanceformat") == null ? "#0.0" : getParameter("distanceformat");
overlapFormat = getParameter("overlapformat") == null ? "0.00" : getParameter("overlapformat");
demoMode = getParameter("demomode") != null && getParameter("demomode").equalsIgnoreCase("true") ? true : false;
}
private void setupLabels(String label1, String label2, String wrapText)
{
//Get the font metrics object for the label's font for use in this calculation and when the labels are drawn
Graphics g = getGraphics();
g.setFont(labelFont);
labelFontMetrics = g.getFontMetrics();
labelTextHeight = labelFontMetrics.getAscent(); //Get the height of the label's text
label1Lines = new LinkedList();
label2Lines = new LinkedList();
if (wrapText.equalsIgnoreCase("manual")) {
label1YOffset = y - radius; //Set the starting point to the top of the circle
int startIndex = 0, endIndex = 0;
int currentLine = 0;
while (endIndex < label1.length()) {
endIndex = label1.indexOf("
", startIndex); //Find the first
in the string
if (endIndex == -1) { endIndex = label1.length(); }
String line = label1.substring(startIndex, endIndex);
int line_length = labelFontMetrics.stringWidth(line);
/*Find the distance this width of text needs to be from the top of the circle to fit within the circle using:
d = 1/2 * sqrt(4r^2 - c^2) where r is the radius and c is the length of the chord (in this case, our line of text)
Subtract the width available to this text, given what line it is on in the label
*/
int height_diff = (int)(y - 0.5 * Math.sqrt(4 * sqr(radius) - sqr(line_length))) - (label1YOffset + currentLine * labelTextHeight);
if (height_diff > 0) {
//This line of text requires extra width, so move the starting y value for all the text down to accomodate the line
label1YOffset += height_diff;
}
label1Lines.addLast(line); //Add the line to the list of lines so it can be reused when writing the text to the screen
startIndex = endIndex + 4; //Set the start index for the next time to the character after the
currentLine++;
}
//Now do the same thing for the other label
label2YOffset = y + radius; //Set the starting point to the bottom of the circle
startIndex = endIndex = label2.length();
currentLine = 0;
while (startIndex > 0) {
startIndex = label2.lastIndexOf("
", endIndex - 1);
if (startIndex < 0) { //We are going backwards through the string, so if we cannot find any more
's then
startIndex = 0; //Set the start to the beginning of the string
} else {
startIndex += 4; //Otherwise, add 4 to the start so be skip the
}
String line = label2.substring(startIndex, endIndex);
int line_length = labelFontMetrics.stringWidth(line);
int height_diff = (int)(y + 0.5 * Math.sqrt(4 * sqr(radius) - sqr(line_length))) - (label2YOffset - currentLine * labelTextHeight);
if (height_diff < 0) { //This time, we check if the difference is negative, since we are moving up the circle not down it
label2YOffset += height_diff;
}
label2Lines.addLast(line);
endIndex = startIndex - 4; //Set the start index for the next time to the character after the
currentLine++;
}
} else if (wrapText.equalsIgnoreCase("auto")) {
label1YOffset = y - radius; //Set the starting point to the top of the circle
int startIndex = 0, endIndex = 0;
int currentLine = 0;
while (endIndex < label1.length()) {
int nextEndIndex = endIndex;
int numberOfWords = 0;
while (endIndex < label1.length()) { //Loops until we have processes the while string (or break on the condition below)
nextEndIndex = label1.indexOf(" ", endIndex + 1); //Find the next space in the string
numberOfWords++; //Keep track of the number of words, since it is important if this is the first word on the line
if (nextEndIndex == -1)
nextEndIndex = label1.length();
String line = label1.substring(startIndex, nextEndIndex);
int line_length = labelFontMetrics.stringWidth(line);
int height_diff = (int)(y - 0.5 * Math.sqrt(4 * sqr(radius) - sqr(line_length))) - (label1YOffset + currentLine * labelTextHeight);
if (height_diff > 0 || line_length > 2 * radius) { //Check if this many words will fit on the line
if (numberOfWords < 2) { //If this is the first word on the line then
if (line_length < 2 * radius)
label1YOffset += height_diff; //Add to the offset if the offset is defined
endIndex = nextEndIndex; //Set the end of this line to the current index
}
break; //Stop processing this line
}
endIndex = nextEndIndex; //Set the endIndex to the value we just tested and then go back and grab another word
}
label1Lines.addLast(label1.substring(startIndex, endIndex));
startIndex = endIndex + 1;
currentLine++;
}
//Now do the same thing for the other label
label2YOffset = y + radius; //Set the starting point to the bottom of the circle
startIndex = endIndex = label2.length();;
currentLine = 0;
while (startIndex > 0) {
int nextStartIndex = startIndex;
int numberOfWords = 0;
while (startIndex > 0) {
nextStartIndex = label2.lastIndexOf(" ", startIndex - 2);
numberOfWords++;
if (nextStartIndex < 0) {
nextStartIndex = 0;
} else {
nextStartIndex++;
}
String line = label2.substring(nextStartIndex, endIndex);
int line_length = labelFontMetrics.stringWidth(line);
int height_diff = (int)(y + 0.5 * Math.sqrt(4 * sqr(radius) - sqr(line_length))) - (label2YOffset - currentLine * labelTextHeight);
if (height_diff < 0 || line_length > 2 * radius) { //Since we are moving up from the bottom, check if he value is negative, not positive
if (numberOfWords < 2) {
if (line_length < 2 * radius)
label2YOffset += height_diff;
startIndex = nextStartIndex;
}
break;
}
startIndex = nextStartIndex;
}
label2Lines.addLast(label2.substring(startIndex, endIndex));
endIndex = startIndex;
currentLine++;
}
} else {
//The user selected the default behavior, so draw the labels on one line in the location on the circle where they best fit
int label1Width = labelFontMetrics.stringWidth(label1); //Find the width of label1
label1YOffset = label1Width < 2 * radius ? //Check is the width is large than the radius
(int)(y - 0.5 * Math.sqrt(4 * sqr(radius) - sqr(label1Width))) : //if not, find the place it fits best
y - labelFontMetrics.getAscent(); //if yes, draw it just above the center of the circle
label1Lines.addLast(label1);
int label2Width = labelFontMetrics.stringWidth(label2); //Find the width of label2
label2YOffset = label2Width < 2 * radius ? //Check is the width is large than the radius
(int)(y + 0.5 * Math.sqrt(4 * sqr(radius) - sqr(label2Width))) : //if not, find the place it fits best
y + labelFontMetrics.getAscent(); //if yes, draw it just below the center of the circle
label2Lines.addLast(label2);
}
}
private Color getColor(String color) {
if (color.substring(0, 1).equalsIgnoreCase("#")) {
//It is an RGB color, so parse the Hex values for red, green and blue an pass them to a Color object
Color return_val;
try {
//Does not look like this can really throw, look into it...
return_val = new Color(Integer.parseInt(color.substring(1, 3), 16),
Integer.parseInt(color.substring(3, 5), 16),
Integer.parseInt(color.substring(5, 7), 16));
} catch (IllegalArgumentException e) {
//One of the arguments is not within the range 0 - 255, so return null
return null;
}
return return_val;
} else {
//It is a string, so try to match it to one of the built in color primatives
if (color.equalsIgnoreCase("black")) {
return Color.black;
} else if (color.equalsIgnoreCase("blue")) {
return Color.blue;
} else if (color.equalsIgnoreCase("cyan")) {
return Color.cyan;
} else if (color.equalsIgnoreCase("darkgray")) {
return Color.darkGray;
} else if (color.equalsIgnoreCase("green")) {
return Color.green;
} else if (color.equalsIgnoreCase("lightgray")) {
return Color.lightGray;
} else if (color.equalsIgnoreCase("magenta")) {
return Color.magenta;
} else if (color.equalsIgnoreCase("orange")) {
return Color.orange;
} else if (color.equalsIgnoreCase("pink")) {
return Color.pink;
} else if (color.equalsIgnoreCase("red")) {
return Color.red;
} else if (color.equalsIgnoreCase("white")) {
return Color.white;
} else if (color.equalsIgnoreCase("yellow")) {
return Color.yellow;
} else {
return null;
}
}
}
//Nothing to do on exit
public void stop() {}
//These functions are not necessary for our functionality, however, we are required to implemented them by the MouseListener and MouseMotionListener interfaces.
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
private int sqr(int x)
{
return x * x;
}
private double sqr(double x)
{
return x * x;
}
public void mousePressed(MouseEvent e) {
int xClicked = e.getX(), yClicked = e.getY();
//Check to see if they clicked within the left circle
if (Math.sqrt(sqr(xClicked - x1) + sqr(yClicked - y)) <= radius) {
//Get the difference between where the user clicked and the center of the circle.
//That way we know where to put the center of the circle relative to the the location that is returned by mouseDragged
xOffset = x1 - xClicked;
selected = true;
repaint(); //Redraw the circles
} else {
//They didn't
selected = false;
}
}
public void mouseDragged(MouseEvent e) {
if (selected) { //Check to make sure they had clicked within the left circle
int xCenter = e.getX() + xOffset;
if (xCenter < xMin) {
//If they are trying to move the circle too far to the left, keep the circle at the minimum value
x1 = xMin;
} else if (xCenter > x2) {
//If they are trying to drag the circle past the right circle, keep the circle completely covering the right circle
x1 = x2;
} else {
//They are dragging the circle within the allowed area, so set the location of the circle to where the mouse is
x1 = xCenter;
}
repaint(); //Redraw the circles
}
}
public void paint(Graphics g)
{
update(g); //Call the update function to redraw the circles
}
public void update(Graphics g)
{
//Cover the entire canvas with the background color
g.setColor(bgcolor);
g.fillRect(0, 0, width, height);
//Draw the first (left) circle
g.setColor(color1);
g.fillArc(x1 - radius, y - radius, 2 * radius, 2 * radius, 0, 360);
//Set the color back to white so that the XOR draw mode draws the correct color in the overlap
g.setColor(Color.white);
//Draw the second (right) circle
g.setXORMode(color2);
g.fillArc(x2 - radius, y - radius, 2 * radius, 2 * radius , 0, 360);
g.setPaintMode();
g.setColor(textcolor);
g.setFont(labelFont);
//Draw the label1 to the screen
int lineY = label1YOffset + labelTextHeight;
ListIterator label1Iterator = label1Lines.listIterator();
while (label1Iterator.hasNext()) {
String text = (String)label1Iterator.next();
//Find the x location to start drawing the text by dividing the line width by two and subtracting that from the x value of the center of the cirlce
int lineX = x1 - labelFontMetrics.stringWidth(text) / 2;
g.drawString(text, lineX, lineY); //Draw the string
lineY += labelTextHeight; //Move on to the next line
}
//Draw the label2 to the screen
lineY = label2YOffset;
ListIterator label2Iterator = label2Lines.listIterator();
while (label2Iterator.hasNext()) {
String text = (String)label2Iterator.next();
//Find the x location to start drawing the text by dividing the line width by two and subtracting that from the x value of the center of the cirlce
int lineX = x2 - labelFontMetrics.stringWidth(text) / 2;
g.drawString(text, lineX, lineY); //Draw the string
lineY -= labelTextHeight; //Move on to the next line
}
if (demoMode) {
g.drawString("Distance: " + getDistance() + " Overlap: " + getOverlapArea(), 0, height);
}
}
public void destroy() {}
public String getAppletInfo()
{
return "Title: IOS Slider Applet\nAuthor: Will Moss";
}
public String[][] getParameterInfo()
{
String paramInfo[][] = {
{"radius", "integer", "The radius of the two circles."},
{"x1", "integer", "The starting location of circle 1."},
{"x2", "integer", "The starting location of circle 1."},
{"label1", "String", "The label on circle 1."},
{"label2", "String", "The label on circle 2."},
{"color1", "Color String", "The color of circle 1, represented in a color string (black, blue, cyan, darkgray, green, lightgray, magenta, orange, pink, red, white, yellow) or an RGB Hex value."},
{"color2", "Color String", "The color of circle 2, represented in a color string (black, blue, cyan, darkgray, green, lightgray, magenta, orange, pink, red, white, yellow) or an RGB Hex value."},
{"bgcolor", "Color String", "The background color, represented in a color string (black, blue, cyan, darkgray, green, lightgray, magenta, orange, pink, red, white, yellow) or an RGB Hex value."},
{"textcolor", "Color String", "The text color, represented in a color string (black, blue, cyan, darkgray, green, lightgray, magenta, orange, pink, red, white, yellow) or an RGB Hex value."}
};
return paramInfo;
}
public String getDistance() {
DecimalFormat myFormatter = new DecimalFormat(distanceFormat);
return myFormatter.format(100 * (double)(x1 - x1Initial) / (x2 - x1Initial));
}
public String getOverlapArea() {
double dist = x2 - x1; //Make the distance a double so that we don't perform integer division later
DecimalFormat myFormatter = new DecimalFormat(overlapFormat);
if (dist < 2 * radius) {
return myFormatter.format((2 * sqr(radius) * Math.acos(dist / (2 * radius)) - .5 * dist * Math.sqrt(4 * sqr(radius) - sqr(dist))) / (Math.PI * sqr(radius)));
} else {
return myFormatter.format(0);
}
}
}