{"id":"charts-slider","slug":"slider","title":"A slider that redraws a chart","text":"A chart can answer \"what if?\" questions: what if I paid more each month, what if the price were lower? A **slider** lets the user pick a number by dragging, and the program redraws the chart for it.\n\n### A slider and its value\n\nAna borrows 1200 euros from a friend, without interest, and pays the same amount back each month. A slider picks the amount:\n\n```java\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider payment = new Slider(50, 300, 100);\n        payment.setShowTickLabels(true);\n        payment.setShowTickMarks(true);\n        payment.setMajorTickUnit(50);\n        Label amount = new Label(\"Pay 100 euros a month\");\n        Label months = new Label(\"Paid back in 12 months\");\n        payment.valueProperty().addListener((observable, oldValue, newValue) -> {\n            int euros = newValue.intValue();\n            amount.setText(\"Pay \" + euros + \" euros a month\");\n            months.setText(\"Paid back in \" + this.monthsToPay(1200, euros) + \" months\");\n        });\n\n        stage.setScene(new Scene(new VBox(10, payment, amount, months)));\n        stage.setTitle(\"Paying back\");\n        stage.show();\n    }\n\n    private int monthsToPay(int debt, int perMonth) {\n        return (debt + perMonth - 1) / perMonth;\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[\"set Slider 1 150\"],\"window\":{\"windows\":[{\"title\":\"Paying back\",\"scene\":{\"root\":{\"n\":1,\"type\":\"VBox\",\"spacing\":10,\"children\":[{\"n\":2,\"type\":\"Slider\",\"min\":50,\"max\":300,\"value\":150,\"majorTickUnit\":50,\"minorTickCount\":3,\"blockIncrement\":10,\"showTickLabels\":true,\"showTickMarks\":true},{\"n\":3,\"type\":\"Label\",\"text\":\"Pay 150 euros a month\"},{\"n\":4,\"type\":\"Label\",\"text\":\"Paid back in 8 months\"}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Paying back\\\"\\n  VBox spacing 10\\n    Slider 50 to 300, value 150, tick labels, tick marks, major tick 50\\n    Label \\\"Pay 150 euros a month\\\"\\n    Label \\\"Paid back in 8 months\\\"\"}\n```\n\nThe window is drawn after the event in the block under the code, `set Slider 1 150`: the first slider of the window, moved to 150. The checks of the challenges move sliders with such lines. When you run a program yourself (\"Run with my input\" in a challenge, or the Playground), you can drag its slider: when you let go of it, the program runs again from the start with the slider moved to where you let go, as a click on a button did in part 13.\n\n- `new Slider(50, 300, 100)` goes from 50 (its **min**) to 300 (its **max**) and starts at 100. `getValue()` returns the value, a double, and `setValue(...)` moves the slider. A value outside the range is kept inside it: `setValue(500)` moves this slider to 300.\n- `setShowTickMarks(true)` draws tick marks along the slider and `setShowTickLabels(true)` numbers under them, a number every `setMajorTickUnit(50)`, counted from the min.\n- `payment.valueProperty()` is the value as a property, like the `textProperty()` of a text field in module 49, and `addListener` gives it a listener that runs at each real change. The old and the new value are **Numbers** here, not doubles: `newValue.intValue()` gives the whole part (137.8 gives 137), and `newValue.doubleValue()` the double. `newValue * 2` doesn't compile, since a Number isn't a double: get a double or an int out of it first.\n- The listener runs only when the value changes. The labels must match the slider before anyone moves it, so `start` gives them the texts for 100.\n- `(debt + perMonth - 1) / perMonth` rounds the division up: 1200 at 150 a month takes exactly 8 months, and 1200 at 250 takes 5 (the last month pays less).\n\n### Snapping to ticks\n\nA payment of 137.4 euros is odd. With `setSnapToTicks(true)`, a slider that is let go moves to the nearest **tick**. The ticks are the major ticks, plus `setMinorTickCount(...)` small ticks between each two of them (3 by default). So with a major tick unit of 50 and a minor tick count of 4, there's a tick every 10: 50, 60, 70 and so on, and `set Slider 1 137` moves the slider to 140. With a minor tick count of 0, only the major ticks are left. Halfway between two ticks goes up.\n\nIn the window as text, a slider's line says its min, max and value, then what was set. For the payment slider above, with `setMinorTickCount(4)` and `setSnapToTicks(true)` added, after `set Slider 1 137`, it reads `Slider 50 to 300, value 140, tick labels, tick marks, major tick 50, snaps` (the minor tick count isn't listed, but it decides where the slider snaps).\n\n### Redrawing a chart\n\nNow Ana's debt month by month, as a line chart that follows the slider. When the payment changes, the program **clears** the series and fills it again with the new points:\n\n```java\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.LineChart;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider payment = new Slider(50, 300, 100);\n        payment.setShowTickLabels(true);\n        payment.setMajorTickUnit(50);\n        payment.setMinorTickCount(4);\n        payment.setSnapToTicks(true);\n        Label amount = new Label(\"Pay 100 euros a month\");\n\n        NumberAxis months = new NumberAxis(\"Month\", 0, 24, 2);\n        NumberAxis owed = new NumberAxis(\"Still owed (euros)\", 0, 1200, 200);\n        LineChart<Number, Number> chart = new LineChart<>(months, owed);\n        chart.setTitle(\"Paying back 1200 euros\");\n        chart.setLegendVisible(false);\n        XYChart.Series<Number, Number> debt = new XYChart.Series<>();\n        debt.setName(\"Debt\");\n        this.fill(debt, 100);\n        chart.getData().add(debt);\n\n        payment.valueProperty().addListener((observable, oldValue, newValue) -> {\n            int euros = newValue.intValue();\n            amount.setText(\"Pay \" + euros + \" euros a month\");\n            this.fill(debt, euros);\n        });\n\n        stage.setScene(new Scene(new VBox(10, payment, amount, chart)));\n        stage.setTitle(\"Paying back\");\n        stage.show();\n    }\n\n    private void fill(XYChart.Series<Number, Number> debt, int perMonth) {\n        debt.getData().clear();\n        int owed = 1200;\n        int month = 0;\n        debt.getData().add(new XYChart.Data<>(month, owed));\n        while (owed > 0) {\n            owed = Math.max(0, owed - perMonth);\n            month++;\n            debt.getData().add(new XYChart.Data<>(month, owed));\n        }\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[\"set Slider 1 237\"],\"window\":{\"windows\":[{\"title\":\"Paying back\",\"scene\":{\"root\":{\"n\":1,\"type\":\"VBox\",\"spacing\":10,\"children\":[{\"n\":2,\"type\":\"Slider\",\"min\":50,\"max\":300,\"value\":240,\"majorTickUnit\":50,\"minorTickCount\":4,\"blockIncrement\":10,\"showTickLabels\":true,\"snapToTicks\":true},{\"n\":3,\"type\":\"Label\",\"text\":\"Pay 240 euros a month\"},{\"n\":4,\"type\":\"LineChart\",\"title\":\"Paying back 1200 euros\",\"legendVisible\":false,\"xAxis\":{\"type\":\"NumberAxis\",\"label\":\"Month\",\"autoRanging\":false,\"lowerBound\":0,\"upperBound\":24,\"tickUnit\":2,\"forceZeroInRange\":true,\"minorTickVisible\":true},\"yAxis\":{\"type\":\"NumberAxis\",\"label\":\"Still owed (euros)\",\"autoRanging\":false,\"lowerBound\":0,\"upperBound\":1200,\"tickUnit\":200,\"forceZeroInRange\":true,\"minorTickVisible\":true},\"series\":[{\"name\":\"Debt\",\"data\":[[0,1200],[1,960],[2,720],[3,480],[4,240],[5,0]]}]}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Paying back\\\"\\n  VBox spacing 10\\n    Slider 50 to 300, value 240, tick labels, major tick 50, snaps\\n    Label \\\"Pay 240 euros a month\\\"\\n    LineChart \\\"Paying back 1200 euros\\\" (no legend)\\n      x axis NumberAxis \\\"Month\\\" 0 to 24 by 2\\n      y axis NumberAxis \\\"Still owed (euros)\\\" 0 to 1200 by 200\\n      series \\\"Debt\\\": (0, 1200) (1, 960) (2, 720) (3, 480) (4, 240) (5, 0)\"}\n```\n\n- The slider snaps: 237 becomes 240, so the debt is gone in 5 months.\n- `fill` empties the series with `debt.getData().clear()`, then adds the points for the new payment: the debt at month 0, and after each month until nothing is owed. Without the `clear()`, the new points would come after the old ones, and the line would jump back to the start.\n- `fill` runs once in `start`, for the first payment, and again in the listener at each change. Writing the filling once, in a method, keeps the two the same.\n- The series stays in the chart the whole time, with its name and its color: only its points change.\n- Both axes have fixed bounds. An auto-ranging axis would pick a new range at every move, and the line would seem to stay put while the numbers under it changed. Fixed bounds make the moves visible: a bigger payment makes the line fall faster.\n","task":"A pancake recipe needs, per person, 60 g of flour and 150 ml of milk. Fill in the blanks of the recipe scaler: its slider goes from 1 to 8 people, starts at 4 and snaps to whole numbers (a major tick every 1, no minor ticks), and its labels follow the slider. The task card shows the window after the slider is moved to 6, where the labels say `Pancakes for 6`, `Flour: 360 g` and `Milk: 900 ml`.\n","kind":"fill","seed":"import javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider people = new Slider(1, 8, 4);\n        people.setShowTickLabels(true);\n        people.setMajorTickUnit(1);\n        people.setMinorTickCount(0);\n        people.setSnapToTicks([[true]]);\n        Label title = new Label(\"Pancakes for 4\");\n        Label flour = new Label(\"Flour: 240 g\");\n        Label milk = new Label(\"Milk: 600 ml\");\n        people.[[valueProperty]]().addListener((observable, oldValue, newValue) -> {\n            int count = newValue.[[intValue]]();\n            title.setText(\"Pancakes for \" + count);\n            flour.setText(\"Flour: \" + count * 60 + \" g\");\n            milk.setText(\"Milk: \" + count * 150 + \" ml\");\n        });\n\n        stage.setScene(new Scene(new VBox(10, people, title, flour, milk)));\n        stage.setTitle(\"Pancakes\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider people = new Slider(1, 8, 4);\n        people.setShowTickLabels(true);\n        people.setMajorTickUnit(1);\n        people.setMinorTickCount(0);\n        people.setSnapToTicks(true);\n        Label title = new Label(\"Pancakes for 4\");\n        Label flour = new Label(\"Flour: 240 g\");\n        Label milk = new Label(\"Milk: 600 ml\");\n        people.valueProperty().addListener((observable, oldValue, newValue) -> {\n            int count = newValue.intValue();\n            title.setText(\"Pancakes for \" + count);\n            flour.setText(\"Flour: \" + count * 60 + \" g\");\n            milk.setText(\"Milk: \" + count * 150 + \" ml\");\n        });\n\n        stage.setScene(new Scene(new VBox(10, people, title, flour, milk)));\n        stage.setTitle(\"Pancakes\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["Which value turns snapping on? Which method of the slider gives its value as a property that a listener can go on, and which method of a Number gives an int?","A slider snaps to its ticks when `setSnapToTicks` is given `true`. The listener goes on the slider's value as a property, `valueProperty()`, and the new value is a Number: `intValue()` gives it as an int."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["set Slider 1 6"],"window":{"windows":[{"title":"Pancakes","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":8,"value":6,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Pancakes for 6"},{"n":4,"type":"Label","text":"Flour: 360 g"},{"n":5,"type":"Label","text":"Milk: 900 ml"}]}}}],"problems":[]},"outline":"Window \"Pancakes\"\n  VBox spacing 10\n    Slider 1 to 8, value 6, tick labels, major tick 1, snaps\n    Label \"Pancakes for 6\"\n    Label \"Flour: 360 g\"\n    Label \"Milk: 900 ml\""},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":["set Slider 1 2.4"],"window":{"windows":[{"title":"Pancakes","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":8,"value":2,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Pancakes for 2"},{"n":4,"type":"Label","text":"Flour: 120 g"},{"n":5,"type":"Label","text":"Milk: 300 ml"}]}}}],"problems":[]},"outline":"Window \"Pancakes\"\n  VBox spacing 10\n    Slider 1 to 8, value 2, tick labels, major tick 1, snaps\n    Label \"Pancakes for 2\"\n    Label \"Flour: 120 g\"\n    Label \"Milk: 300 ml\""},{"name":"Test 3","stdin":"","expect":"","hidden":true,"events":["set Slider 1 8","set Slider 1 1"],"window":{"windows":[{"title":"Pancakes","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":8,"value":1,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Pancakes for 1"},{"n":4,"type":"Label","text":"Flour: 60 g"},{"n":5,"type":"Label","text":"Milk: 150 ml"}]}}}],"problems":[]},"outline":"Window \"Pancakes\"\n  VBox spacing 10\n    Slider 1 to 8, value 1, tick labels, major tick 1, snaps\n    Label \"Pancakes for 1\"\n    Label \"Flour: 60 g\"\n    Label \"Milk: 150 ml\""},{"name":"Test 4","stdin":"","expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Pancakes","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":8,"value":4,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Pancakes for 4"},{"n":4,"type":"Label","text":"Flour: 240 g"},{"n":5,"type":"Label","text":"Milk: 600 ml"}]}}}],"problems":[]},"outline":"Window \"Pancakes\"\n  VBox spacing 10\n    Slider 1 to 8, value 4, tick labels, major tick 1, snaps\n    Label \"Pancakes for 4\"\n    Label \"Flour: 240 g\"\n    Label \"Milk: 600 ml\""}],"require":[],"forbid":[],"more":[{"task":"A music app has a volume slider. What does this program print?\n","kind":"predict","seed":"import javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider volume = new Slider(0, 10, 5);\n        volume.valueProperty().addListener((observable, oldValue, newValue) -> System.out.println(oldValue + \" to \" + newValue));\n        volume.setValue(8);\n        volume.setValue(12);\n        volume.setValue(10);\n        System.out.println(volume.getValue());\n\n        stage.setScene(new Scene(new VBox(volume)));\n        stage.setTitle(\"Volume\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider volume = new Slider(0, 10, 5);\n        volume.valueProperty().addListener((observable, oldValue, newValue) -> System.out.println(oldValue + \" to \" + newValue));\n        volume.setValue(8);\n        volume.setValue(12);\n        volume.setValue(10);\n        System.out.println(volume.getValue());\n\n        stage.setScene(new Scene(new VBox(volume)));\n        stage.setTitle(\"Volume\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["The value is a double, and the listener gets Numbers that print as doubles do: `5.0`, not `5`. A value above the max is kept at the max.","So `setValue(12)` moves the slider to 10. Then `setValue(10)` changes nothing, and a listener runs only at a real change."],"tests":[{"name":"Output","stdin":"","expect":"5.0 to 8.0\n8.0 to 10.0\n10.0","hidden":false}],"require":[],"forbid":[],"lines":["5.0 to 8.0","8.0 to 10.0","10.0"]},{"task":"A phone company has two plans. **Plan A** costs 10 euros a month plus 2 euros per GB of data. **Plan B** has no monthly fee, only a price per GB, and a slider tries out that price. The window is partly built: the slider, the label and the chart with Plan A. Add Plan B:\n\n- a second series, `Plan B`, added to the chart after Plan A, with a point (gb, price * gb) for each gb from 0 to 10, where the price is the slider's value as an int\n- when the slider moves, Plan B gets the points for the new price (it stays the second series), and the label says `Plan B: 4 euros per GB` with the new price\n\nThe slider goes from 1 to 5, starts at 3 and snaps to whole numbers. The task card shows the window after the slider is moved to 4.\n","kind":"code","seed":"import javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.LineChart;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider price = new Slider(1, 5, 3);\n        price.setShowTickLabels(true);\n        price.setMajorTickUnit(1);\n        price.setMinorTickCount(0);\n        price.setSnapToTicks(true);\n        Label label = new Label(\"Plan B: 3 euros per GB\");\n\n        NumberAxis gigabytes = new NumberAxis(\"GB\", 0, 10, 1);\n        NumberAxis euros = new NumberAxis();\n        euros.setLabel(\"Euros\");\n        LineChart<Number, Number> chart = new LineChart<>(gigabytes, euros);\n        chart.setTitle(\"Monthly cost\");\n        XYChart.Series<Number, Number> planA = new XYChart.Series<>();\n        planA.setName(\"Plan A\");\n        for (int gb = 0; gb <= 10; gb++) {\n            planA.getData().add(new XYChart.Data<>(gb, 10 + 2 * gb));\n        }\n        chart.getData().add(planA);\n\n        stage.setScene(new Scene(new VBox(10, price, label, chart)));\n        stage.setTitle(\"Phone plans\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.LineChart;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Slider price = new Slider(1, 5, 3);\n        price.setShowTickLabels(true);\n        price.setMajorTickUnit(1);\n        price.setMinorTickCount(0);\n        price.setSnapToTicks(true);\n        Label label = new Label(\"Plan B: 3 euros per GB\");\n\n        NumberAxis gigabytes = new NumberAxis(\"GB\", 0, 10, 1);\n        NumberAxis euros = new NumberAxis();\n        euros.setLabel(\"Euros\");\n        LineChart<Number, Number> chart = new LineChart<>(gigabytes, euros);\n        chart.setTitle(\"Monthly cost\");\n        XYChart.Series<Number, Number> planA = new XYChart.Series<>();\n        planA.setName(\"Plan A\");\n        for (int gb = 0; gb <= 10; gb++) {\n            planA.getData().add(new XYChart.Data<>(gb, 10 + 2 * gb));\n        }\n        chart.getData().add(planA);\n\n        XYChart.Series<Number, Number> planB = new XYChart.Series<>();\n        planB.setName(\"Plan B\");\n        this.fill(planB, 3);\n        chart.getData().add(planB);\n        price.valueProperty().addListener((observable, oldValue, newValue) -> {\n            int perGb = newValue.intValue();\n            label.setText(\"Plan B: \" + perGb + \" euros per GB\");\n            this.fill(planB, perGb);\n        });\n\n        stage.setScene(new Scene(new VBox(10, price, label, chart)));\n        stage.setTitle(\"Phone plans\");\n        stage.show();\n    }\n\n    private void fill(XYChart.Series<Number, Number> plan, int perGb) {\n        plan.getData().clear();\n        for (int gb = 0; gb <= 10; gb++) {\n            plan.getData().add(new XYChart.Data<>(gb, perGb * gb));\n        }\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["Write a method `fill(XYChart.Series<Number, Number> plan, int perGb)` that clears the series and adds the 11 points. Call it in `start` with 3, before adding Plan B to the chart.","In the listener of `price.valueProperty()`, take `newValue.intValue()`, set the label's text and call `fill` again with the new price."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["set Slider 1 4"],"window":{"windows":[{"title":"Phone plans","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":5,"value":4,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Plan B: 4 euros per GB"},{"n":4,"type":"LineChart","title":"Monthly cost","xAxis":{"type":"NumberAxis","label":"GB","autoRanging":false,"lowerBound":0,"upperBound":10,"tickUnit":1,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Euros","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Plan A","data":[[0,10],[1,12],[2,14],[3,16],[4,18],[5,20],[6,22],[7,24],[8,26],[9,28],[10,30]]},{"name":"Plan B","data":[[0,0],[1,4],[2,8],[3,12],[4,16],[5,20],[6,24],[7,28],[8,32],[9,36],[10,40]]}]}]}}}],"problems":[]},"outline":"Window \"Phone plans\"\n  VBox spacing 10\n    Slider 1 to 5, value 4, tick labels, major tick 1, snaps\n    Label \"Plan B: 4 euros per GB\"\n    LineChart \"Monthly cost\"\n      x axis NumberAxis \"GB\" 0 to 10 by 1\n      y axis NumberAxis \"Euros\"\n      series \"Plan A\": (0, 10) (1, 12) (2, 14) (3, 16) (4, 18) (5, 20) (6, 22) (7, 24) (8, 26) (9, 28) (10, 30)\n      series \"Plan B\": (0, 0) (1, 4) (2, 8) (3, 12) (4, 16) (5, 20) (6, 24) (7, 28) (8, 32) (9, 36) (10, 40)"},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Phone plans","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":5,"value":3,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Plan B: 3 euros per GB"},{"n":4,"type":"LineChart","title":"Monthly cost","xAxis":{"type":"NumberAxis","label":"GB","autoRanging":false,"lowerBound":0,"upperBound":10,"tickUnit":1,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Euros","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Plan A","data":[[0,10],[1,12],[2,14],[3,16],[4,18],[5,20],[6,22],[7,24],[8,26],[9,28],[10,30]]},{"name":"Plan B","data":[[0,0],[1,3],[2,6],[3,9],[4,12],[5,15],[6,18],[7,21],[8,24],[9,27],[10,30]]}]}]}}}],"problems":[]},"outline":"Window \"Phone plans\"\n  VBox spacing 10\n    Slider 1 to 5, value 3, tick labels, major tick 1, snaps\n    Label \"Plan B: 3 euros per GB\"\n    LineChart \"Monthly cost\"\n      x axis NumberAxis \"GB\" 0 to 10 by 1\n      y axis NumberAxis \"Euros\"\n      series \"Plan A\": (0, 10) (1, 12) (2, 14) (3, 16) (4, 18) (5, 20) (6, 22) (7, 24) (8, 26) (9, 28) (10, 30)\n      series \"Plan B\": (0, 0) (1, 3) (2, 6) (3, 9) (4, 12) (5, 15) (6, 18) (7, 21) (8, 24) (9, 27) (10, 30)"},{"name":"Test 3","stdin":"","expect":"","hidden":true,"events":["set Slider 1 1.4"],"window":{"windows":[{"title":"Phone plans","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":5,"value":1,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Plan B: 1 euros per GB"},{"n":4,"type":"LineChart","title":"Monthly cost","xAxis":{"type":"NumberAxis","label":"GB","autoRanging":false,"lowerBound":0,"upperBound":10,"tickUnit":1,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Euros","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Plan A","data":[[0,10],[1,12],[2,14],[3,16],[4,18],[5,20],[6,22],[7,24],[8,26],[9,28],[10,30]]},{"name":"Plan B","data":[[0,0],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10]]}]}]}}}],"problems":[]},"outline":"Window \"Phone plans\"\n  VBox spacing 10\n    Slider 1 to 5, value 1, tick labels, major tick 1, snaps\n    Label \"Plan B: 1 euros per GB\"\n    LineChart \"Monthly cost\"\n      x axis NumberAxis \"GB\" 0 to 10 by 1\n      y axis NumberAxis \"Euros\"\n      series \"Plan A\": (0, 10) (1, 12) (2, 14) (3, 16) (4, 18) (5, 20) (6, 22) (7, 24) (8, 26) (9, 28) (10, 30)\n      series \"Plan B\": (0, 0) (1, 1) (2, 2) (3, 3) (4, 4) (5, 5) (6, 6) (7, 7) (8, 8) (9, 9) (10, 10)"},{"name":"Test 4","stdin":"","expect":"","hidden":true,"events":["set Slider 1 5","set Slider 1 2"],"window":{"windows":[{"title":"Phone plans","scene":{"root":{"n":1,"type":"VBox","spacing":10,"children":[{"n":2,"type":"Slider","min":1,"max":5,"value":2,"majorTickUnit":1,"minorTickCount":0,"blockIncrement":10,"showTickLabels":true,"snapToTicks":true},{"n":3,"type":"Label","text":"Plan B: 2 euros per GB"},{"n":4,"type":"LineChart","title":"Monthly cost","xAxis":{"type":"NumberAxis","label":"GB","autoRanging":false,"lowerBound":0,"upperBound":10,"tickUnit":1,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Euros","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Plan A","data":[[0,10],[1,12],[2,14],[3,16],[4,18],[5,20],[6,22],[7,24],[8,26],[9,28],[10,30]]},{"name":"Plan B","data":[[0,0],[1,2],[2,4],[3,6],[4,8],[5,10],[6,12],[7,14],[8,16],[9,18],[10,20]]}]}]}}}],"problems":[]},"outline":"Window \"Phone plans\"\n  VBox spacing 10\n    Slider 1 to 5, value 2, tick labels, major tick 1, snaps\n    Label \"Plan B: 2 euros per GB\"\n    LineChart \"Monthly cost\"\n      x axis NumberAxis \"GB\" 0 to 10 by 1\n      y axis NumberAxis \"Euros\"\n      series \"Plan A\": (0, 10) (1, 12) (2, 14) (3, 16) (4, 18) (5, 20) (6, 22) (7, 24) (8, 26) (9, 28) (10, 30)\n      series \"Plan B\": (0, 0) (1, 2) (2, 4) (3, 6) (4, 8) (5, 10) (6, 12) (7, 14) (8, 16) (9, 18) (10, 20)"}],"require":[],"forbid":[]}]}