{"id":"charts-own-data","slug":"own-data","title":"Charts from your own data","text":"This step puts the module together: a file of rows, some computing, and a chart that shows the result honestly. The last challenge is a small tool that charts any data written in its format.\n\n### From a file to a chart, the whole way\n\nA household writes down its electricity meter's reading at the start of each month. The meter only counts up, so the electricity used in a month is the difference between two readings in a row:\n\n```file meter.csv\nmonth,kWh\nJan,10250\nFeb,10560\nMar,10830\nApr,11040\nMay,11190\n```\n\nThe computing goes in a class of its own, and the window charts what it returns:\n\n```java\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.BarChart;\nimport javafx.scene.chart.CategoryAxis;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> months = new ArrayList<>();\n        List<Integer> readings = new ArrayList<>();\n        List<String> lines = Files.readAllLines(Paths.get(\"meter.csv\"));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            months.add(parts[0]);\n            readings.add(Integer.valueOf(parts[1]));\n        }\n        List<Integer> used = Meter.differences(readings);\n        System.out.println(used);\n\n        CategoryAxis monthAxis = new CategoryAxis();\n        monthAxis.setLabel(\"Month\");\n        NumberAxis kwhAxis = new NumberAxis();\n        kwhAxis.setLabel(\"Electricity used (kWh)\");\n        BarChart<String, Number> chart = new BarChart<>(monthAxis, kwhAxis);\n        chart.setTitle(\"Electricity used per month\");\n        chart.setLegendVisible(false);\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        series.setName(\"kWh\");\n        for (int i = 0; i < used.size(); i++) {\n            series.getData().add(new XYChart.Data<>(months.get(i), used.get(i)));\n        }\n        chart.getData().add(series);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Electricity\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n// ==== Meter.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Meter {\n\n    public static List<Integer> differences(List<Integer> readings) {\n        List<Integer> used = new ArrayList<>();\n        for (int i = 1; i < readings.size(); i++) {\n            used.add(readings.get(i) - readings.get(i - 1));\n        }\n        return used;\n    }\n}\n```\n\n```window\n{\"events\":[],\"window\":{\"windows\":[{\"title\":\"Electricity\",\"scene\":{\"root\":{\"n\":1,\"type\":\"BarChart\",\"title\":\"Electricity used per month\",\"legendVisible\":false,\"xAxis\":{\"type\":\"CategoryAxis\",\"label\":\"Month\",\"autoRanging\":true,\"categories\":[\"Jan\",\"Feb\",\"Mar\",\"Apr\"]},\"yAxis\":{\"type\":\"NumberAxis\",\"label\":\"Electricity used (kWh)\",\"autoRanging\":true,\"lowerBound\":0,\"upperBound\":100,\"tickUnit\":5,\"forceZeroInRange\":true,\"minorTickVisible\":true},\"series\":[{\"name\":\"kWh\",\"data\":[[\"Jan\",310],[\"Feb\",270],[\"Mar\",210],[\"Apr\",150]]}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Electricity\\\"\\n  BarChart \\\"Electricity used per month\\\" (no legend)\\n    x axis CategoryAxis \\\"Month\\\"\\n    y axis NumberAxis \\\"Electricity used (kWh)\\\"\\n    series \\\"kWh\\\": (\\\"Jan\\\", 310) (\\\"Feb\\\", 270) (\\\"Mar\\\", 210) (\\\"Apr\\\", 150)\"}\n```\n\n```output\n[310, 270, 210, 150]\n```\n\nFive readings give four differences: the reading of February minus that of January is what January used, so the bars are January to April. `months.get(i)` pairs difference `i` with the month it was used in.\n\nHere are the choices that every chart of this module made, as a list to go through for a chart of your own:\n\n1. **Read the rows**: skip the header, `split` on the separator, turn the text into numbers with `Integer.valueOf` or `Double.valueOf`, and decide what a missing value (`-`) means: left out, or 0 because nothing happened.\n2. **Compute** what the chart shows (totals, differences, a formula) in a plain class, and print it to check it before any chart exists.\n3. **Choose the chart**: a line chart when x is a number and the values change along it, a bar chart to compare categories, a scatter chart for points that have no order.\n4. **Axes**: labels with units, bars from 0, fixed bounds when a slider changes the data, and the same scale for charts that are compared.\n5. **Series**: a name for each, the legend when there are several, the points from the smallest x up, and series in an order you chose (the file's, or sorted names), never a HashMap's.\n\n### The end of section 14.1\n\nThis module followed the MOOC's section 14.1, on data visualization. You read rows of data into numbers, lists and maps, drew line charts with NumberAxis, XYChart.Series and XYChart.Data, made several series from a file's columns or from a map, computed series from a formula in a plain class, redrew a chart with a Slider and its `valueProperty()` listener, compared categories with a BarChart and a CategoryAxis, and kept charts honest. Module 52 follows the MOOC's section 14.2: it draws on a canvas with colors and the mouse, works with images pixel by pixel, and explains sound.\n","task":"A sensor outside a mountain hut reads the temperature three times a day, in whole degrees, and writes `temps.csv`. A failed reading has a `-` (careful: `-3` is a reading, 3 degrees below zero):\n\n```text\nday,hour,temp\n1,6,4\n1,12,11\n1,18,8\n2,6,-3\n2,12,-\n2,18,6\n3,6,5\n3,12,14\n3,18,10\n```\n\nWrite `start` so that the window, titled `Mountain hut`, shows a line chart (the scene's root) with the title `Coldest and warmest`, an auto-ranging x axis labeled `Day`, an auto-ranging y axis labeled `Temperature (°C)` and a visible legend. It has two series, first `Lowest`, then `Highest`: for each day with at least one reading, `Lowest` has the point (day, the day's lowest reading) and `Highest` the point (day, the day's highest reading), from the smallest day up. The rows may come in any order. The task card shows the window for the file above.\n","kind":"code","seed":"import java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\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.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"temps.csv\"));\n\n        stage.setTitle(\"Mountain hut\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\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.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"temps.csv\"));\n        Map<Integer, Integer> lowest = new HashMap<>();\n        Map<Integer, Integer> highest = new HashMap<>();\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            if (!parts[2].equals(\"-\")) {\n                int day = Integer.valueOf(parts[0]);\n                int temp = Integer.valueOf(parts[2]);\n                if (!lowest.containsKey(day) || temp < lowest.get(day)) {\n                    lowest.put(day, temp);\n                }\n                if (!highest.containsKey(day) || temp > highest.get(day)) {\n                    highest.put(day, temp);\n                }\n            }\n        }\n        List<Integer> days = new ArrayList<>(lowest.keySet());\n        Collections.sort(days);\n\n        NumberAxis dayAxis = new NumberAxis();\n        dayAxis.setLabel(\"Day\");\n        NumberAxis tempAxis = new NumberAxis();\n        tempAxis.setLabel(\"Temperature (°C)\");\n        LineChart<Number, Number> chart = new LineChart<>(dayAxis, tempAxis);\n        chart.setTitle(\"Coldest and warmest\");\n        XYChart.Series<Number, Number> low = new XYChart.Series<>();\n        low.setName(\"Lowest\");\n        XYChart.Series<Number, Number> high = new XYChart.Series<>();\n        high.setName(\"Highest\");\n        for (int day : days) {\n            low.getData().add(new XYChart.Data<>(day, lowest.get(day)));\n            high.getData().add(new XYChart.Data<>(day, highest.get(day)));\n        }\n        chart.getData().add(low);\n        chart.getData().add(high);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Mountain hut\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["Keep two maps from the day to its lowest and its highest reading so far. For each row whose temperature isn't exactly `\"-\"`, update a day's lowest when the map has no value for the day yet or the reading is lower, and the highest the same way.","Then copy the days of a map into a list, sort it with `Collections.sort`, and add a point to both series for each day in that order."],"tests":[{"name":"Test 1","stdin":"","files":{"temps.csv":"day,hour,temp\n1,6,4\n1,12,11\n1,18,8\n2,6,-3\n2,12,-\n2,18,6\n3,6,5\n3,12,14\n3,18,10\n"},"expect":"","hidden":false,"events":[],"window":{"windows":[{"title":"Mountain hut","scene":{"root":{"n":1,"type":"LineChart","title":"Coldest and warmest","xAxis":{"type":"NumberAxis","label":"Day","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Temperature (°C)","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Lowest","data":[[1,4],[2,-3],[3,5]]},{"name":"Highest","data":[[1,11],[2,6],[3,14]]}]}}}],"problems":[]},"outline":"Window \"Mountain hut\"\n  LineChart \"Coldest and warmest\"\n    x axis NumberAxis \"Day\"\n    y axis NumberAxis \"Temperature (°C)\"\n    series \"Lowest\": (1, 4) (2, -3) (3, 5)\n    series \"Highest\": (1, 11) (2, 6) (3, 14)"},{"name":"Test 2","stdin":"","files":{"temps.csv":"day,hour,temp\n12,18,-1\n10,6,-8\n11,6,-\n11,12,-\n10,12,-2\n12,6,-6\n12,12,0\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Mountain hut","scene":{"root":{"n":1,"type":"LineChart","title":"Coldest and warmest","xAxis":{"type":"NumberAxis","label":"Day","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Temperature (°C)","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Lowest","data":[[10,-8],[12,-6]]},{"name":"Highest","data":[[10,-2],[12,0]]}]}}}],"problems":[]},"outline":"Window \"Mountain hut\"\n  LineChart \"Coldest and warmest\"\n    x axis NumberAxis \"Day\"\n    y axis NumberAxis \"Temperature (°C)\"\n    series \"Lowest\": (10, -8) (12, -6)\n    series \"Highest\": (10, -2) (12, 0)"},{"name":"Test 3","stdin":"","files":{"temps.csv":"day,hour,temp\n5,12,20\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Mountain hut","scene":{"root":{"n":1,"type":"LineChart","title":"Coldest and warmest","xAxis":{"type":"NumberAxis","label":"Day","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"Temperature (°C)","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Lowest","data":[[5,20]]},{"name":"Highest","data":[[5,20]]}]}}}],"problems":[]},"outline":"Window \"Mountain hut\"\n  LineChart \"Coldest and warmest\"\n    x axis NumberAxis \"Day\"\n    y axis NumberAxis \"Temperature (°C)\"\n    series \"Lowest\": (5, 20)\n    series \"Highest\": (5, 20)"}],"require":[],"forbid":[],"more":[{"task":"A program sums up a week of litter picking by day: the bags filled by each volunteer group. What does it print?\n","kind":"predict","seed":"import java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.BarChart;\nimport javafx.scene.chart.CategoryAxis;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        String[] rows = {\"Tue,4\", \"Mon,3\", \"Tue,-\", \"Mon,5\", \"Wed,1\"};\n        Map<String, Integer> bags = new HashMap<>();\n        List<String> days = new ArrayList<>();\n        for (String row : rows) {\n            String[] parts = row.split(\",\");\n            if (!days.contains(parts[0])) {\n                days.add(parts[0]);\n            }\n            if (!parts[1].equals(\"-\")) {\n                bags.put(parts[0], bags.getOrDefault(parts[0], 0) + Integer.valueOf(parts[1]));\n            }\n        }\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        for (String day : days) {\n            series.getData().add(new XYChart.Data<>(day, bags.get(day)));\n        }\n        System.out.println(series.getData().size());\n        System.out.println(series.getData().get(0));\n        System.out.println(series.getData().get(1).getYValue());\n\n        BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), new NumberAxis());\n        chart.getData().add(series);\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Litter\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.BarChart;\nimport javafx.scene.chart.CategoryAxis;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        String[] rows = {\"Tue,4\", \"Mon,3\", \"Tue,-\", \"Mon,5\", \"Wed,1\"};\n        Map<String, Integer> bags = new HashMap<>();\n        List<String> days = new ArrayList<>();\n        for (String row : rows) {\n            String[] parts = row.split(\",\");\n            if (!days.contains(parts[0])) {\n                days.add(parts[0]);\n            }\n            if (!parts[1].equals(\"-\")) {\n                bags.put(parts[0], bags.getOrDefault(parts[0], 0) + Integer.valueOf(parts[1]));\n            }\n        }\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        for (String day : days) {\n            series.getData().add(new XYChart.Data<>(day, bags.get(day)));\n        }\n        System.out.println(series.getData().size());\n        System.out.println(series.getData().get(0));\n        System.out.println(series.getData().get(1).getYValue());\n\n        BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), new NumberAxis());\n        chart.getData().add(series);\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Litter\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["The list `days` keeps each day once, in the order it first appears in the rows: Tue, Mon, Wed. The map only holds the totals.","Tuesday's `-` adds nothing, so its total stays 4. Monday's rows add up to 8."],"tests":[{"name":"Output","stdin":"","expect":"3\nData[Tue,4,null]\n8","hidden":false}],"require":[],"forbid":[],"lines":["3","Data[Tue,4,null]","8"]},{"task":"A chart tool for your own data: it reads `chart.csv`, whose first line says the kind of chart and its title, separated by a comma (`bar,Books read` or `line,Books read`). The second line is the header, which names the x values and the y values, and the rows follow. A `-` is a missing value:\n\n```text\nbar,Books read in the club\nmonth,books\nJan,3\nFeb,5\nMar,-\nApr,4\n```\n\nWrite `start`. The window, titled `My chart`, shows the chart (the scene's root) with the title from the first line, no legend and one series, named as the header's second value, with a point (x, y) for each row whose y isn't `-`, in the order of the file. Read the y values as doubles.\n\n- For `bar`, it's a bar chart with a category axis labeled as the header's first value (here `month`) and an auto-ranging number axis labeled as the header's second value (here `books`).\n- For `line`, it's a line chart with two auto-ranging number axes labeled the same way, and the x values are numbers too (read them as doubles).\n\nThe task card shows the window for the file above. The tool charts any file in this form, so on your own computer, with JavaFX installed (module 48 says where to find how), you can give it data of your own.\n","kind":"code","seed":"import java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.BarChart;\nimport javafx.scene.chart.CategoryAxis;\nimport javafx.scene.chart.LineChart;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"chart.csv\"));\n        String[] first = lines.get(0).split(\",\");\n        String[] header = lines.get(1).split(\",\");\n\n        stage.setTitle(\"My chart\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.chart.BarChart;\nimport javafx.scene.chart.CategoryAxis;\nimport javafx.scene.chart.LineChart;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.chart.XYChart;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"chart.csv\"));\n        String[] first = lines.get(0).split(\",\");\n        String[] header = lines.get(1).split(\",\");\n\n        if (first[0].equals(\"bar\")) {\n            stage.setScene(new Scene(this.barChart(first[1], header, lines)));\n        } else {\n            stage.setScene(new Scene(this.lineChart(first[1], header, lines)));\n        }\n        stage.setTitle(\"My chart\");\n        stage.show();\n    }\n\n    private BarChart<String, Number> barChart(String title, String[] header, List<String> lines) {\n        CategoryAxis x = new CategoryAxis();\n        x.setLabel(header[0]);\n        NumberAxis y = new NumberAxis();\n        y.setLabel(header[1]);\n        BarChart<String, Number> chart = new BarChart<>(x, y);\n        chart.setTitle(title);\n        chart.setLegendVisible(false);\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        series.setName(header[1]);\n        for (int i = 2; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            if (!parts[1].equals(\"-\")) {\n                series.getData().add(new XYChart.Data<>(parts[0], Double.valueOf(parts[1])));\n            }\n        }\n        chart.getData().add(series);\n        return chart;\n    }\n\n    private LineChart<Number, Number> lineChart(String title, String[] header, List<String> lines) {\n        NumberAxis x = new NumberAxis();\n        x.setLabel(header[0]);\n        NumberAxis y = new NumberAxis();\n        y.setLabel(header[1]);\n        LineChart<Number, Number> chart = new LineChart<>(x, y);\n        chart.setTitle(title);\n        chart.setLegendVisible(false);\n        XYChart.Series<Number, Number> series = new XYChart.Series<>();\n        series.setName(header[1]);\n        for (int i = 2; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            if (!parts[1].equals(\"-\")) {\n                series.getData().add(new XYChart.Data<>(Double.valueOf(parts[0]), Double.valueOf(parts[1])));\n            }\n        }\n        chart.getData().add(series);\n        return chart;\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["The two kinds of chart have different types (`BarChart<String, Number>` and `LineChart<Number, Number>`), so a method for each keeps them apart. Both are nodes that a Scene can hold.","The rows start at index 2, after the first line and the header. In each method, skip a row whose `parts[1]` is `\"-\"`."],"tests":[{"name":"Test 1","stdin":"","files":{"chart.csv":"bar,Books read in the club\nmonth,books\nJan,3\nFeb,5\nMar,-\nApr,4\n"},"expect":"","hidden":false,"events":[],"window":{"windows":[{"title":"My chart","scene":{"root":{"n":1,"type":"BarChart","title":"Books read in the club","legendVisible":false,"xAxis":{"type":"CategoryAxis","label":"month","autoRanging":true,"categories":["Jan","Feb","Apr"]},"yAxis":{"type":"NumberAxis","label":"books","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"books","data":[["Jan",3],["Feb",5],["Apr",4]]}]}}}],"problems":[]},"outline":"Window \"My chart\"\n  BarChart \"Books read in the club\" (no legend)\n    x axis CategoryAxis \"month\"\n    y axis NumberAxis \"books\"\n    series \"books\": (\"Jan\", 3) (\"Feb\", 5) (\"Apr\", 4)"},{"name":"Test 2","stdin":"","files":{"chart.csv":"line,Seedling height\nday,mm\n1,2\n3,-\n4,9.5\n7,21\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"My chart","scene":{"root":{"n":1,"type":"LineChart","title":"Seedling height","legendVisible":false,"xAxis":{"type":"NumberAxis","label":"day","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"yAxis":{"type":"NumberAxis","label":"mm","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"mm","data":[[1,2],[4,9.5],[7,21]]}]}}}],"problems":[]},"outline":"Window \"My chart\"\n  LineChart \"Seedling height\" (no legend)\n    x axis NumberAxis \"day\"\n    y axis NumberAxis \"mm\"\n    series \"mm\": (1, 2) (4, 9.5) (7, 21)"},{"name":"Test 3","stdin":"","files":{"chart.csv":"bar,Favorite fruit\nfruit,votes\nApple,12\nKiwi,7\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"My chart","scene":{"root":{"n":1,"type":"BarChart","title":"Favorite fruit","legendVisible":false,"xAxis":{"type":"CategoryAxis","label":"fruit","autoRanging":true,"categories":["Apple","Kiwi"]},"yAxis":{"type":"NumberAxis","label":"votes","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"votes","data":[["Apple",12],["Kiwi",7]]}]}}}],"problems":[]},"outline":"Window \"My chart\"\n  BarChart \"Favorite fruit\" (no legend)\n    x axis CategoryAxis \"fruit\"\n    y axis NumberAxis \"votes\"\n    series \"votes\": (\"Apple\", 12) (\"Kiwi\", 7)"}],"require":[],"forbid":[]}]}