/* * [Weight.java] * * Summary: Plots weight in red and body fat in blue. * * Copyright: (c) 1995-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: * 1.5 2006-03-05 * 1.6 2014-12-06 drop fats */ package com.mindprod.stats; import com.mindprod.common18.BigDate; import com.mindprod.common18.Build; import com.mindprod.common18.Common18; import com.mindprod.common18.FontFactory; import com.mindprod.common18.HybridJ; import com.mindprod.common18.VersionCheck; import com.mindprod.csv.CSVReader; import javax.swing.JApplet; import javax.swing.JLabel; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.io.BufferedReader; import java.io.EOFException; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.TimeZone; import static java.lang.System.*; /** * Plots weight in red and body fat in blue. * * @author Roedy Green, Canadian Mind Products * @version 1.6 2014-12-06 drop fats * @since 1995 */ public final class Weight extends JApplet { private static final int APPLET_HEIGHT = 900; private static final int APPLET_WIDTH = 1880; // also change DAYS_TO_PLOT /** * how many day to plot, including today. No scrolling so must fit. 2 pixels per day */ private static final int DAYS_TO_PLOT = 895; private static final String TITLE_STRING = "Canadian Mind Products Weight Monitor"; private static final String VERSION_STRING = "1.2"; private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 ); /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 ); /** * title of graph */ private JLabel title; /** * where to draw the graph of weight and body fat. */ private WeightGraphCanvas canvas; /** * Get readings to plot from a CSV file. * * @return array of Readings of weights. */ @SuppressWarnings( { "UnnecessaryLocalVariable" } ) private static Reading[] getWeightReadings() { // range that we will plot, only get Data for that range. final int todayOrdinal = BigDate.today( TimeZone.getDefault() ).getOrdinal(); // first day to plot final int fromOrdinal = todayOrdinal - DAYS_TO_PLOT + 1;// last n days // last day to plot. final int toOrdinal = todayOrdinal; final File weightsFile = new File( Build.MINDPROD_SOURCE + "/stats", "weights.csv" ); // temporary ArrayList to accumulate the readings. final ArrayList a = new ArrayList<>( DAYS_TO_PLOT ); try { // reader, separatorChar, quoteChar, commentChars, hideComments, trimQuoted, allowMultipleLineFields final CSVReader r = new CSVReader( new BufferedReader( new FileReader( weightsFile ) ) ); // get readings from CSV file int prevOrdinal = Integer.MIN_VALUE; try { // loop till hit eof while ( true ) { final String yyyymmdd = r.getYYYYMMDD(); final double weight = r.getDouble(); final int ordinal = BigDate.toOrdinal( yyyymmdd ); if ( prevOrdinal < ordinal ) { prevOrdinal = ordinal; } else { throw new IllegalArgumentException( "weights out of date order near " + new BigDate( ordinal ) + " " + weight + " near line " + r.lineCount() ); } if ( fromOrdinal <= ordinal && ordinal <= toOrdinal ) { a.add( new Reading( ordinal, weight ) ); } r.skipToNextLine(); } } catch ( EOFException e ) { r.close(); } } catch ( IOException e ) { e.printStackTrace( err ); err.println( "trouble reading weights.csv " + e.getMessage() ); err.println(); return null; } // one reading per sampling, not necessarily one per day. out.println( a.get( a.size() - 1 ) ); return a.toArray( new Reading[ a.size() ] ); } private void layoutGridBag( Container contentPane ) { contentPane.add( title, new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 2, 10, 5, 5 ), 340, 13 ) ); contentPane.add( canvas, new GridBagConstraints( 0, 1, 1, 1, 100.0, 100.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); } /** * main * * @param args not used */ public static void main( String args[] ) { HybridJ.fireup( new Weight(), TITLE_STRING + " " + VERSION_STRING, APPLET_WIDTH, APPLET_HEIGHT ); } // end main /** * Called by the browser or Applet viewer to inform * this Applet that it is being reclaimed and that it should destroy * any resources that it has allocated. */ public void destroy() { title = null; canvas = null; } /** * Called by the browser or Applet viewer to inform * this Applet that it has been loaded into the system. */ @Override public void init() { if ( !VersionCheck.isJavaVersionOK( 1, 8, 0, this ) ) { return; } Common18.setLaf(); Container contentPane = this.getContentPane(); contentPane.setLayout( new GridBagLayout() ); title = new JLabel( TITLE_STRING + " " + VERSION_STRING + " build:" + Build.BUILD_NUMBER, JLabel.CENTER ); title.setFont( FONT_FOR_TITLE ); title.setForeground( FOREGROUND_FOR_TITLE ); canvas = new WeightGraphCanvas( getWeightReadings() ); canvas.setForeground( FOREGROUND_FOR_LABEL ); layoutGridBag( contentPane ); this.validate(); this.setVisible( true ); } // end init } /** * canvas to draw both daily weight and percent bodyfat */ final class WeightGraphCanvas extends DailyGraphCanvas { /** * how many horizontal pixels before the data after the left Y axis */ private static final int LEFT_MARGIN = 0; /** * how many horizontal pixels after the data before the right Y axis */ private static final int RIGHT_MARGIN = 2; /** * daily readings. */ private final Reading[] weightsToPlot; /** * constructor * * @param weightsToPlot array of daily weight readings to plot */ WeightGraphCanvas( Reading[] weightsToPlot ) { this.weightsToPlot = weightsToPlot; } /** * @param g where to draw the graph */ public void paint( Graphics g ) { // determine the range of data to plot from the range of data supplied // in the constructor, will usually be shrunk from what was is csv. final int fromOrdinal = weightsToPlot[ 0 ].x; final int toOrdinal = weightsToPlot[ weightsToPlot.length - 1 ].x; setScale( g, fromOrdinal - LEFT_MARGIN/* xlow day */, toOrdinal + RIGHT_MARGIN/* xhigh day */, 2/* 2 pixels per day */, 190/* low pounds */, 250, /* high pounds */ 10/* ten ticks per pound */ ); xaxis( Color.BLACK ); yaxis( 1, 10, false, Color.BLACK ); plot( weightsToPlot, 0, 0/* no average */, new Color( 0x8888ff ) ); plot( weightsToPlot, 30, 0/* average over month */, Color.BLUE ); } // end paint }