Java Arena
Step 4 of 8

Series from a formula

Not every chart shows measurements. A chart can show what a formula computes, and then the points come from a loop. Module 50 kept an application's logic in a plain class without JavaFX, which can be checked without a window; computed points belong in such a class too, and the window only draws them.

Computing the points

A cup of tea cools down. A simple model of it (a model, not an exact law of nature): each minute, the tea loses a tenth of how much warmer than the room it is. The class Cooling computes the temperature at each minute:

Main.java
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Double> tea = Cooling.temperatures(90, 20, 5);
        System.out.println(tea);
        System.out.println("After 5 minutes: " + tea.get(5));
    }
}
Cooling.java
import java.util.ArrayList;
import java.util.List;

public class Cooling {

    public static List<Double> temperatures(double start, double room, int minutes) {
        List<Double> temps = new ArrayList<>();
        double tea = start;
        for (int minute = 0; minute <= minutes; minute++) {
            temps.add(Math.round(tea * 10) / 10.0);
            tea = room + (tea - room) * 0.9;
        }
        return temps;
    }
}
Output
[90.0, 83.0, 76.7, 71.0, 65.9, 61.3]
After 5 minutes: 61.3
  • The list holds the temperature at minute 0, 1, 2 and so on: the index is the minute. So the x of each point is its index, and the list needs no x values of its own.
  • Math.round(x) rounds a double to the nearest whole number and gives it as a long (2.5 goes up to 3). So Math.round(tea * 10) / 10.0 keeps one decimal: 65.927 becomes 659.27, then 659, then 65.9. Dividing by 10.0, a double, is what keeps the decimal. Computing with doubles leaves tiny errors (0.1 + 0.2 is 0.30000000000000004), and the window as text would show them. Rounding what goes into the chart keeps the numbers readable.
  • The program prints the numbers before any chart exists. Checking them first is easier than reading them off a drawing.

Charting them

The window makes a series from each list. The tea in a 20 °C kitchen, and the same tea taken out to a 5 °C balcony:

Main.java
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        NumberAxis minutes = new NumberAxis("Minutes", 0, 30, 5);
        NumberAxis degrees = new NumberAxis();
        degrees.setLabel("Temperature (°C)");
        LineChart<Number, Number> chart = new LineChart<>(minutes, degrees);
        chart.setTitle("A cup of tea cools down");
        chart.setCreateSymbols(false);

        chart.getData().add(series("In the kitchen", Cooling.temperatures(90, 20, 30)));
        chart.getData().add(series("On the balcony", Cooling.temperatures(90, 5, 30)));

        stage.setScene(new Scene(chart));
        stage.setTitle("Tea");
        stage.show();
    }

    private XYChart.Series<Number, Number> series(String name, List<Double> values) {
        XYChart.Series<Number, Number> series = new XYChart.Series<>();
        series.setName(name);
        for (int x = 0; x < values.size(); x++) {
            series.getData().add(new XYChart.Data<>(x, values.get(x)));
        }
        return series;
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
Cooling.java
import java.util.ArrayList;
import java.util.List;

public class Cooling {

    public static List<Double> temperatures(double start, double room, int minutes) {
        List<Double> temps = new ArrayList<>();
        double tea = start;
        for (int minute = 0; minute <= minutes; minute++) {
            temps.add(Math.round(tea * 10) / 10.0);
            tea = room + (tea - room) * 0.9;
        }
        return temps;
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Tea"
  LineChart "A cup of tea cools down" (no symbols)
    x axis NumberAxis "Minutes" 0 to 30 by 5
    y axis NumberAxis "Temperature (°C)"
    series "In the kitchen": (0, 90) (1, 83) (2, 76.7) (3, 71) (4, 65.9) (5, 61.3) (6, 57.2) (7, 53.5) (8, 50.1) (9, 47.1) (10, 44.4) (11, 42) (12, 39.8) (13, 37.8) (14, 36) (15, 34.4) (16, 33) (17, 31.7) (18, 30.5) (19, 29.5) (20, 28.5) (21, 27.7) (22, 26.9) (23, 26.2) (24, 25.6) (25, 25) (26, 24.5) (27, 24.1) (28, 23.7) (29, 23.3) (30, 23)
    series "On the balcony": (0, 90) (1, 81.5) (2, 73.9) (3, 67) (4, 60.8) (5, 55.2) (6, 50.2) (7, 45.7) (8, 41.6) (9, 37.9) (10, 34.6) (11, 31.7) (12, 29) (13, 26.6) (14, 24.4) (15, 22.5) (16, 20.8) (17, 19.2) (18, 17.8) (19, 16.5) (20, 15.3) (21, 14.3) (22, 13.4) (23, 12.5) (24, 11.8) (25, 11.1) (26, 10.5) (27, 9.9) (28, 9.4) (29, 9) (30, 8.6)
  • The method series turns a name and a list of values into a series, so both lines are made the same way. It's an instance method of Main, as start is.
  • chart.setCreateSymbols(false) leaves out the marks that a line chart puts on each point. With 31 points per line, the marks would only clutter it. The window as text says (no symbols).
  • Both series share the axes, so the lines can be compared: the tea on the balcony cools faster, and toward a lower temperature.

Points without a line: ScatterChart

A ScatterChart draws the points and no line. It suits points that have no order, where a line from one to the next would mean nothing. Fifteen raindrops land on a paving stone of 50 by 50 cm. Where each one lands is random, so the program uses a Random with a seed (module 46): the drops land in the same places at every run, and the window is the same at every run, as the window of a check must be.

import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        ScatterChart<Number, Number> chart = new ScatterChart<>(new NumberAxis("cm", 0, 50, 10), new NumberAxis("cm", 0, 50, 10));
        chart.setTitle("Raindrops on a paving stone");
        chart.setLegendVisible(false);

        Random random = new Random(14);
        XYChart.Series<Number, Number> drops = new XYChart.Series<>();
        drops.setName("Drops");
        for (int i = 0; i < 15; i++) {
            int x = random.nextInt(51);
            int y = random.nextInt(51);
            drops.getData().add(new XYChart.Data<>(x, y));
        }
        chart.getData().add(drops);

        stage.setScene(new Scene(chart));
        stage.setTitle("Rain");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Rain"
  ScatterChart "Raindrops on a paving stone" (no legend)
    x axis NumberAxis "cm" 0 to 50 by 10
    y axis NumberAxis "cm" 0 to 50 by 10
    series "Drops": (44, 30) (2, 5) (50, 2) (14, 41) (43, 21) (36, 35) (49, 5) (15, 27) (9, 28) (9, 31) (50, 31) (8, 10) (5, 48) (42, 50) (39, 20)

A ScatterChart is made and filled exactly like a LineChart: axes, series, data points.

Your task

Challenge 1 of 3

A café stacks paper cups into a pyramid for its window: one cup on top, two under it, three under those, and so on. Write the class CupPyramid in CupPyramid.java, with the method public static List<Integer> cups(int maxRows). It returns a list of maxRows + 1 numbers: at index r, the number of cups that a pyramid of r rows needs (so 0 at index 0, then 1, then 1 + 2, then 1 + 2 + 3, ...). No JavaFX in it: a chart can use it later.

The check runs System.out.println(CupPyramid.cups(5)); and expects:

[0, 1, 3, 6, 10, 15]

Challenge 2

A ball is thrown straight up. With the pull of gravity rounded to 10 m/s² and no air to slow it, its height in meters after t seconds is start + speed * t - 5 * t * t, where start is the height it leaves the hand at and speed its speed upward in m/s. The file throw.txt holds both, separated by a comma, such as 2,14. Main already reads them and has the method height.

Finish start: the window, titled Ball, shows a line chart (the scene's root) with the title A thrown ball, an auto-ranging x axis labeled Seconds, an auto-ranging y axis labeled Height (m), no symbols and no legend. Its one series, Ball, has a point (t, height) for t = 0, 0.25, 0.5, 0.75 and so on, as long as the height is at least 0: the first point whose height is below 0 isn't added, and the series ends there. The task card shows the window for 2,14.

Challenge 3

A program should put points at x = 0, 0.5, 1, 1.5 and 2, but its author wrote i / 2. What does it print?