/* * [DrawingPanel.java] * * Summary: Calculates and Plots 4 Biorhythm Sine Waves. * * Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 2.6 2009-03-24 fix layout bug, use anti-aliasing. */ package com.mindprod.bio; import com.mindprod.common18.BigDate; import com.mindprod.common18.FontFactory; import javax.swing.JPanel; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; /** * Calculates and Plots 4 Biorhythm Sine Waves. *

* * @author Roedy Green, Canadian Mind Products * @version 2.6 2009-03-24 fix layout bug, use anti-aliasing. * @noinspection FieldCanBeLocal * @since 1999 */ final class DrawingPanel extends JPanel { /** * leave left and right margin for potential y axes. In pixels. */ private static final int xmargin = 0; /** * leave room under x axis for some labelling. In pixels. */ private static final int ymargin = 25; /** * where to plot the graph. */ private Graphics g; /** * how many pixels to represent one day. */ private double scalex; /** * how many pixels to represent one data unit. */ private double scaley; /** * highest y to plot in data units. * * @noinspection UnusedDeclaration */ private double yhigh; /** * lowest y to plot in data units. */ private double ylow; /** * day BIO wanted for. */ private int forOrdinal; /** * Ordinal of first date to plot. */ private int fromOrdinal; /** * number of days from birthDate to fromOrdinal. */ private int phase; /** * Ordinal of last date to plot. */ private int toOrdinal; /** * highest x value to plot. Ordinal. * * @see BigDate */ private int xhigh; /** * lowest x value to plot. Ordinal. * * @see BigDate */ private int xlow; /** * pixel to from the left of the window to plot the lowest x value. */ private int xPixelBase; /** * pixels below the top of the window to plot the lowest y value. */ private int yPixelBase; /** * plot one sine wave. * * @param period Number of days for the period of the sine wave, 0 = combi. * @param start number of days since the birthdate base to start plotting. * @param colour colour of sine wave to draw. */ private void plotSine( int period, int start, Color colour ) { // plot sine wave with given period g.setColor( colour ); final int days = xhigh - xlow + 1; int oldx = 0; int oldy = 0; for ( int i = 0; i < days; i++ ) { final int newx = toPixelX( i + xlow ); final int newy = toPixelY( CalcBiorhythms.level( start + i, period ) ); if ( i > 0 ) { g.drawLine( oldx, oldy, newx, newy ); } oldx = newx; oldy = newy; } // end for } // end plotSine /** * Draw the graph of the sine waves, Draws all four. * * @param g where to paint, screen, printer etc. */ final void render( Graphics g ) { // adjust what we paint to make sure it will fit in allotted space // getWidth not supported in JDK 1.1 // Netscape, I suspect sometimes won't tell you the size, or tells you // 0. int daysToPlot = toOrdinal - fromOrdinal; final Dimension d = this.getSize(); int width; // chop or expand if have access to a width. if ( d != null && ( width = d.width ) != 0 ) { daysToPlot = width / 5; } setScale( g, fromOrdinal, fromOrdinal + daysToPlot, /* scalex */ 5, /* ylow */ -1, /* yhigh */ +1, /* scaley */ 50 ); xaxis( Color.black ); yaxis( Color.black ); // physical red, 23 day period plotSine( CalcBiorhythms.PHYSICAL_PERIOD, phase, CalcBiorhythms.FOREGROUND_FOR_PHYSICAL ); // emotional blue, 28 day period plotSine( CalcBiorhythms.EMOTIONAL_PERIOD, phase, CalcBiorhythms.FOREGROUND_FOR_EMOTIONAL ); // intellectual green, 33 day period plotSine( CalcBiorhythms.INTELLECTUAL_PERIOD, phase, CalcBiorhythms.FOREGROUND_FOR_INTELLECTUAL ); // combined plotSine( CalcBiorhythms.COMBI_PERIOD, phase, CalcBiorhythms.FOREGROUND_FOR_COMBI ); } // end paint /** * Set scale of subsequent graphs to be drawn. * * @param g where to plot the graph. * @param xlow ordinal of lowest date to plot. See BigDate. * @param xhigh ordinal of highest x to plot. See BigDate. * @param scalex how many pixels to represent one day. * @param ylow lowest y to plot in data units. * @param yhigh highest y to plot in data units. * @param scaley how many pixels to represent one y data unit. * * @noinspection SameParameterValue */ private void setScale( Graphics g, int xlow, int xhigh, double scalex, double ylow, double yhigh, double scaley ) { this.g = g; this.xlow = xlow; this.xhigh = xhigh; this.scalex = scalex; this.ylow = ylow; this.yhigh = yhigh; this.scaley = scaley; xPixelBase = xmargin; yPixelBase = this.getBounds().height - ymargin; } /** * transform from data x coordinates to pixel coordinates. * * @param x ordinal days (see BigDate). * * @return corresponding pixel x coordinate. */ private int toPixelX( int x ) { return ( int ) Math.round( ( x - xlow ) * scalex ) + xPixelBase; } /** * transform from data y coordinates to pixel coordinates. * * @param y y coordinate in data units. * * @return corresponding pixel y coordinate. */ private int toPixelY( double y ) { return yPixelBase - ( int ) Math.round( ( y - ylow ) * scaley ); } /** * Draw an x axis. Automatically marked with months and weekly ticks. * * @param colour colour of the axis. * * @noinspection SameParameterValue */ private void xaxis( Color colour ) { // draw x axis showing ticks on 1st of month and month names centered g.setColor( colour ); // draw the x axis itself g.drawLine( toPixelX( xlow ), toPixelY( ylow ), toPixelX( xhigh ), toPixelY( ylow ) ); String wording = ""; g.setFont( FontFactory.build( "Dialog", Font.BOLD, 12 ) ); FontMetrics fm = getFontMetrics( g.getFont() ); final BigDate firstDate = new BigDate( xlow ); final int firstYear = firstDate.getYYYY(); final int firstMonth = firstDate.getMM(); final int firstSunday = BigDate.nthXXXDay( 1 /* first */, 0 /* sunday */, firstYear, firstMonth ) + xlow - 1; int prevXtick = 0; for ( int month = firstMonth;/* x<=xhigh */ ; month++ ) { // draw vertical down long tick at each first of month, and label // with month name BigDate d = new BigDate( firstYear, month, 1, BigDate.NORMALISE ); final int x = d.getOrdinal(); if ( x > xhigh ) { break; } final int xtick = toPixelX( d.getOrdinal() ); final int ytick = toPixelY( ylow ); g.drawLine( xtick, ytick, xtick, ytick + 25 ); // place wording for previous month, centered behind this tick final int centerXtick = ( xtick + prevXtick ) / 2; final int xadj = fm.stringWidth( wording ) / 2; // x,y is bottom left corner of text g.drawString( wording, centerXtick - xadj, ytick + 21 ); // prepare for next month. wording = BigDate.monthAbbr( d.getMM() ); prevXtick = xtick; } g.setFont( FontFactory.build( "Dialog", Font.PLAIN, 8 ) ); fm = getFontMetrics( g.getFont() ); // add short down ticks for every Sunday to mark weeks for ( int week = firstSunday; week <= xhigh; week += 7 ) { // draw short vertical tick at each Sunday final int xtick = toPixelX( week ); final int ytick = toPixelY( ylow ); g.drawLine( xtick, ytick, xtick, ytick + 4 ); // label below tick with Sunday's day number wording = Integer.toString( new BigDate( week ).getDD() ); final int xadj = fm.stringWidth( wording ) / 2; g.drawString( wording, xtick - xadj, ytick + 12 ); } // add ultra short down ticks for every day for ( int day = xlow; day <= xhigh; day++ ) { // draw ultra short vertical tick at each day final int xtick = toPixelX( day ); final int ytick = toPixelY( ylow ); g.drawLine( xtick, ytick, xtick, ytick + 2 ); } // draw 0-line g.drawLine( toPixelX( xlow ), toPixelY( 0 ), toPixelX( xhigh ), toPixelY( 0 ) ); } // end xaxis /** * Draw an y axis. Vertical bar at forDate (usually today) * * @param colour colour of the axis. * * @noinspection SameParameterValue */ private void yaxis( Color colour ) { g.setColor( colour ); // draw the y axis itself g.drawLine( toPixelX( forOrdinal ), toPixelY( -1 ), toPixelX( forOrdinal ), toPixelY( 1 ) ); // label axis with word "today" centered above it if it is today. if ( forOrdinal == BigDate.localToday().getOrdinal() ) { g.setFont( FontFactory.build( "Dialog", Font.BOLD, 10 ) ); final FontMetrics fm = getFontMetrics( g.getFont() ); final String wording = "today"; final int xadj = fm.stringWidth( wording ) / 2; g.drawString( wording, toPixelX( forOrdinal ) - xadj, toPixelY( 1 ) - 2 ); } } // end yaxis /** * arrange for anti-alias, then do a custom render * * @param g where to paint */ public void paintComponent( Graphics g ) { Graphics2D g2d = ( Graphics2D ) g; g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); g2d.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY ); // smooth geometric shapes too g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); render( g2d ); } /** * Configure the sine waves to draw. * * @param fromOrdinal First day to plot. * @param toOrdinal last day to plot. * @param birthOrdinal birth date of person. * @param forOrdinal as of date ordinal, usually today. */ public final void set( int fromOrdinal, int toOrdinal, int birthOrdinal, int forOrdinal ) { this.fromOrdinal = fromOrdinal; this.toOrdinal = toOrdinal; this.phase = fromOrdinal - birthOrdinal; this.forOrdinal = forOrdinal; } }