Added weight chart renderer, infrastructure for more dynamic charts.

This commit is contained in:
Andrew Lalis 2023-04-20 15:21:04 +02:00
parent cc91f617c9
commit 2858b83a1a
7 changed files with 131 additions and 24 deletions

View File

@ -2,6 +2,8 @@ package com.github.andrewlalis.running_every_day.data.db;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public final class Queries { public final class Queries {
@ -66,6 +68,18 @@ public final class Queries {
return pageCount; return pageCount;
} }
public static <T> List<T> findAll(Connection c, String query, ResultSetMapper<T> mapper) throws SQLException {
try (var stmt = c.prepareStatement(query)) {
try (var rs = stmt.executeQuery()) {
List<T> items = new ArrayList<>();
while (rs.next()) {
items.add(mapper.map(rs));
}
return items;
}
}
}
public static int update(Connection c, String query) throws SQLException { public static int update(Connection c, String query) throws SQLException {
try (var stmt = c.prepareStatement(query)) { try (var stmt = c.prepareStatement(query)) {
return stmt.executeUpdate(); return stmt.executeUpdate();

View File

@ -1,5 +1,8 @@
package com.github.andrewlalis.running_every_day.view; package com.github.andrewlalis.running_every_day.view;
import com.github.andrewlalis.running_every_day.data.db.DataSource;
import com.github.andrewlalis.running_every_day.view.chart.ChartRenderingPanel;
import com.github.andrewlalis.running_every_day.view.chart.WeightChartRenderer;
import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart; import org.jfree.chart.JFreeChart;
import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.data.category.DefaultCategoryDataset;
@ -13,36 +16,18 @@ import java.awt.*;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
public class ChartsPanel extends JPanel { public class ChartsPanel extends JPanel {
private final JPanel drawingPanel = new JPanel(); private final DataSource dataSource;
public ChartsPanel() { public ChartsPanel(DataSource dataSource) {
super(new BorderLayout()); super(new BorderLayout());
this.dataSource = dataSource;
var drawingPanel = new ChartRenderingPanel(new WeightChartRenderer(dataSource));
this.add(drawingPanel, BorderLayout.CENTER); this.add(drawingPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel(); JPanel buttonPanel = new JPanel();
JButton drawButton = new JButton("Draw"); JButton drawButton = new JButton("Draw");
drawButton.addActionListener(e -> draw()); // drawButton.addActionListener(e -> draw());
buttonPanel.add(drawButton); buttonPanel.add(drawButton);
this.add(buttonPanel, BorderLayout.NORTH); this.add(buttonPanel, BorderLayout.NORTH);
} }
public void draw() {
TimeSeriesCollection ds = new TimeSeriesCollection();
TimeSeries ts = new TimeSeries("Data");
ts.add(new Day(16, 4, 2023), 45);
ts.add(new Day(17, 4, 2023), 50);
ts.add(new Day(18, 4, 2023), 52);
ts.add(new Day(19, 4, 2023), 65);
ds.addSeries(ts);
JFreeChart chart = ChartFactory.createXYLineChart(
"Test XY Line Chart",
"Date",
"Value",
ds
);
Graphics2D g2 = (Graphics2D) drawingPanel.getGraphics();
Rectangle2D area = new Rectangle2D.Float(0, 0, drawingPanel.getWidth(), drawingPanel.getHeight());
chart.draw(g2, area);
}
} }

View File

@ -19,7 +19,7 @@ public class RecorderAppWindow extends JFrame {
JTabbedPane tabbedPane = new JTabbedPane(); JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Run Records", new RunRecordsPanel(dataSource)); tabbedPane.addTab("Run Records", new RunRecordsPanel(dataSource));
tabbedPane.addTab("Aggregate Statistics", new AggregateStatisticsPanel(dataSource)); tabbedPane.addTab("Aggregate Statistics", new AggregateStatisticsPanel(dataSource));
tabbedPane.addTab("Charts", new ChartsPanel()); tabbedPane.addTab("Charts", new ChartsPanel(dataSource));
return tabbedPane; return tabbedPane;
} }
} }

View File

@ -0,0 +1,8 @@
package com.github.andrewlalis.running_every_day.view.chart;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public interface ChartRenderer {
void render(Graphics2D graphics, Rectangle2D area);
}

View File

@ -0,0 +1,21 @@
package com.github.andrewlalis.running_every_day.view.chart;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class ChartRenderingPanel extends JPanel {
private final ChartRenderer renderer;
public ChartRenderingPanel(ChartRenderer renderer) {
this.renderer = renderer;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Rectangle2D area = new Rectangle2D.Float(0, 0, this.getWidth(), this.getHeight());
renderer.render(g2, area);
}
}

View File

@ -0,0 +1,24 @@
package com.github.andrewlalis.running_every_day.view.chart;
import org.jfree.chart.JFreeChart;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public abstract class JFreeChartRenderer implements ChartRenderer {
protected abstract JFreeChart getChart() throws Exception;
@Override
public void render(Graphics2D graphics, Rectangle2D area) {
try {
var chart = getChart();
// Apply theme to the chart.
chart.draw(graphics, area);
} catch (Exception e) {
graphics.setColor(Color.BLACK);
graphics.setBackground(Color.WHITE);
graphics.fill(area);
graphics.drawString("Error: " + e.getMessage(), 20, 40);
}
}
}

View File

@ -0,0 +1,55 @@
package com.github.andrewlalis.running_every_day.view.chart;
import com.github.andrewlalis.running_every_day.data.db.DataSource;
import com.github.andrewlalis.running_every_day.data.db.Queries;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import java.text.DateFormat;
import java.time.LocalDate;
import java.util.List;
public class WeightChartRenderer extends JFreeChartRenderer {
private record WeightDatapoint(int weightGrams, LocalDate date){}
private final DataSource dataSource;
public WeightChartRenderer(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
protected JFreeChart getChart() throws Exception {
TimeSeries series = new TimeSeries("Weight", "Date", "Weight (Kg)");
List<WeightDatapoint> datapoints = Queries.findAll(
dataSource.conn(),
"SELECT date, weight FROM run ORDER BY date ASC",
rs -> new WeightDatapoint(
rs.getInt(2),
LocalDate.parse(rs.getString(1))
)
);
for (var dp : datapoints) {
series.add(new Day(dp.date.getDayOfMonth(), dp.date.getMonthValue(), dp.date.getYear()), dp.weightGrams);
}
TimeSeriesCollection dataset = new TimeSeriesCollection(series);
DateAxis domainAxis = new DateAxis("Date");
domainAxis.setVerticalTickLabels(true);
domainAxis.setDateFormatOverride(DateFormat.getDateInstance());
NumberAxis rangeAxis = new NumberAxis("Weight (Kg)");
rangeAxis.setAutoRange(true);
XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
XYPlot plot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);
return new JFreeChart(plot);
}
}