Continuous IOS Code

by Will Moss




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); } } }