{"id":"charts-bar-charts","slug":"bar-charts","title":"Bar charts for categories","text":"A line chart has numbers on both axes, and its line says that the values change from one x to the next. Often the x values are names instead: days of the week, products, kinds of books. Such values are **categories**, and a **bar chart** compares them, with a bar per category whose height is its value.\n\n### A bar chart\n\nA city's bike share counts the trips started on each day of one week:\n\n```java\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        CategoryAxis days = new CategoryAxis();\n        days.setLabel(\"Day\");\n        NumberAxis trips = new NumberAxis();\n        trips.setLabel(\"Trips\");\n        BarChart<String, Number> chart = new BarChart<>(days, trips);\n        chart.setTitle(\"Bike share trips in a week\");\n        chart.setLegendVisible(false);\n\n        XYChart.Series<String, Number> week = new XYChart.Series<>();\n        week.setName(\"Trips\");\n        week.getData().add(new XYChart.Data<>(\"Mon\", 340));\n        week.getData().add(new XYChart.Data<>(\"Tue\", 310));\n        week.getData().add(new XYChart.Data<>(\"Wed\", 365));\n        week.getData().add(new XYChart.Data<>(\"Thu\", 330));\n        week.getData().add(new XYChart.Data<>(\"Fri\", 405));\n        week.getData().add(new XYChart.Data<>(\"Sat\", 520));\n        week.getData().add(new XYChart.Data<>(\"Sun\", 470));\n        chart.getData().add(week);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Bike share\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[],\"window\":{\"windows\":[{\"title\":\"Bike share\",\"scene\":{\"root\":{\"n\":1,\"type\":\"BarChart\",\"title\":\"Bike share trips in a week\",\"legendVisible\":false,\"xAxis\":{\"type\":\"CategoryAxis\",\"label\":\"Day\",\"autoRanging\":true,\"categories\":[\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\",\"Sun\"]},\"yAxis\":{\"type\":\"NumberAxis\",\"label\":\"Trips\",\"autoRanging\":true,\"lowerBound\":0,\"upperBound\":100,\"tickUnit\":5,\"forceZeroInRange\":true,\"minorTickVisible\":true},\"series\":[{\"name\":\"Trips\",\"data\":[[\"Mon\",340],[\"Tue\",310],[\"Wed\",365],[\"Thu\",330],[\"Fri\",405],[\"Sat\",520],[\"Sun\",470]]}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Bike share\\\"\\n  BarChart \\\"Bike share trips in a week\\\" (no legend)\\n    x axis CategoryAxis \\\"Day\\\"\\n    y axis NumberAxis \\\"Trips\\\"\\n    series \\\"Trips\\\": (\\\"Mon\\\", 340) (\\\"Tue\\\", 310) (\\\"Wed\\\", 365) (\\\"Thu\\\", 330) (\\\"Fri\\\", 405) (\\\"Sat\\\", 520) (\\\"Sun\\\", 470)\"}\n```\n\n- A **CategoryAxis** is an axis of text values. Each category gets a place of its own along the axis, in the order the categories first appear in the data: here Mon to Sun, as they were added.\n- `BarChart<String, Number>`: the x values are Strings and the y values Numbers, so the series and the points have the same two types, `XYChart.Series<String, Number>` and `new XYChart.Data<>(\"Mon\", 340)`.\n- Everything else is as in a line chart: titles, labels, series, legend. In the window as text, a category is in quotes: `(\"Mon\", 340)`.\n- A bar chart needs one category axis and one number axis. With the number axis first, `BarChart<Number, String>`, the bars lie sideways, which suits long category names.\n\n### Totals from a file\n\nThe bike share's file has a row per station and day. A station that was closed for the day (for repairs) has a `-`:\n\n```file trips.csv\nday,station,trips\nMon,Harbor,180\nMon,Market,160\nTue,Harbor,150\nTue,Market,-\nWed,Harbor,170\nWed,Market,195\nThu,Harbor,165\nThu,Market,165\n```\n\nThe chart of the days needs each day's **total**, so the program adds the rows up in a map from the day to its total, as module 31 counted things. It also needs the days in order, and a HashMap's keys don't keep one, so a list keeps the days in the order they first appear in the file:\n\n```java\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport 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) throws Exception {\n        Map<String, Integer> totals = new HashMap<>();\n        List<String> days = new ArrayList<>();\n        List<String> lines = Files.readAllLines(Paths.get(\"trips.csv\"));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            String day = parts[0];\n            if (!days.contains(day)) {\n                days.add(day);\n                totals.put(day, 0);\n            }\n            if (!parts[2].equals(\"-\")) {\n                totals.put(day, totals.get(day) + Integer.valueOf(parts[2]));\n            }\n        }\n\n        CategoryAxis dayAxis = new CategoryAxis();\n        dayAxis.setLabel(\"Day\");\n        NumberAxis tripAxis = new NumberAxis();\n        tripAxis.setLabel(\"Trips\");\n        BarChart<String, Number> chart = new BarChart<>(dayAxis, tripAxis);\n        chart.setTitle(\"Trips per day, all stations\");\n        chart.setLegendVisible(false);\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        series.setName(\"Trips\");\n        for (String day : days) {\n            series.getData().add(new XYChart.Data<>(day, totals.get(day)));\n        }\n        chart.getData().add(series);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Bike share\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[],\"window\":{\"windows\":[{\"title\":\"Bike share\",\"scene\":{\"root\":{\"n\":1,\"type\":\"BarChart\",\"title\":\"Trips per day, all stations\",\"legendVisible\":false,\"xAxis\":{\"type\":\"CategoryAxis\",\"label\":\"Day\",\"autoRanging\":true,\"categories\":[\"Mon\",\"Tue\",\"Wed\",\"Thu\"]},\"yAxis\":{\"type\":\"NumberAxis\",\"label\":\"Trips\",\"autoRanging\":true,\"lowerBound\":0,\"upperBound\":100,\"tickUnit\":5,\"forceZeroInRange\":true,\"minorTickVisible\":true},\"series\":[{\"name\":\"Trips\",\"data\":[[\"Mon\",340],[\"Tue\",150],[\"Wed\",365],[\"Thu\",330]]}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Bike share\\\"\\n  BarChart \\\"Trips per day, all stations\\\" (no legend)\\n    x axis CategoryAxis \\\"Day\\\"\\n    y axis NumberAxis \\\"Trips\\\"\\n    series \\\"Trips\\\": (\\\"Mon\\\", 340) (\\\"Tue\\\", 150) (\\\"Wed\\\", 365) (\\\"Thu\\\", 330)\"}\n```\n\n- A day enters the list and the map (with the total 0) at its first row, and every row with a number adds to its day's total. A closed station adds nothing to the total: no trips started there.\n- The bars come from the list, in its order, and each bar's value from the map.\n\n### Bars side by side\n\nTo compare the stations, each station gets a series of its own, as each plant did in step 3. A bar chart puts the bars of a category side by side, one per series, and the legend tells the series apart:\n\n```file trips.csv\nday,station,trips\nMon,Harbor,180\nMon,Market,160\nTue,Harbor,150\nTue,Market,-\nWed,Harbor,170\nWed,Market,195\nThu,Harbor,165\nThu,Market,165\n```\n\n```java\nimport 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.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        Map<String, XYChart.Series<String, Number>> byStation = new HashMap<>();\n        List<String> lines = Files.readAllLines(Paths.get(\"trips.csv\"));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            String station = parts[1];\n            if (!byStation.containsKey(station)) {\n                XYChart.Series<String, Number> series = new XYChart.Series<>();\n                series.setName(station);\n                byStation.put(station, series);\n            }\n            int trips = 0;\n            if (!parts[2].equals(\"-\")) {\n                trips = Integer.valueOf(parts[2]);\n            }\n            byStation.get(station).getData().add(new XYChart.Data<>(parts[0], trips));\n        }\n\n        CategoryAxis dayAxis = new CategoryAxis();\n        dayAxis.setLabel(\"Day\");\n        NumberAxis tripAxis = new NumberAxis();\n        tripAxis.setLabel(\"Trips\");\n        BarChart<String, Number> chart = new BarChart<>(dayAxis, tripAxis);\n        chart.setTitle(\"Trips per station\");\n        List<String> stations = new ArrayList<>(byStation.keySet());\n        Collections.sort(stations);\n        for (String station : stations) {\n            chart.getData().add(byStation.get(station));\n        }\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Bike share\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[],\"window\":{\"windows\":[{\"title\":\"Bike share\",\"scene\":{\"root\":{\"n\":1,\"type\":\"BarChart\",\"title\":\"Trips per station\",\"xAxis\":{\"type\":\"CategoryAxis\",\"label\":\"Day\",\"autoRanging\":true,\"categories\":[\"Mon\",\"Tue\",\"Wed\",\"Thu\"]},\"yAxis\":{\"type\":\"NumberAxis\",\"label\":\"Trips\",\"autoRanging\":true,\"lowerBound\":0,\"upperBound\":100,\"tickUnit\":5,\"forceZeroInRange\":true,\"minorTickVisible\":true},\"series\":[{\"name\":\"Harbor\",\"data\":[[\"Mon\",180],[\"Tue\",150],[\"Wed\",170],[\"Thu\",165]]},{\"name\":\"Market\",\"data\":[[\"Mon\",160],[\"Tue\",0],[\"Wed\",195],[\"Thu\",165]]}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Bike share\\\"\\n  BarChart \\\"Trips per station\\\"\\n    x axis CategoryAxis \\\"Day\\\"\\n    y axis NumberAxis \\\"Trips\\\"\\n    series \\\"Harbor\\\": (\\\"Mon\\\", 180) (\\\"Tue\\\", 150) (\\\"Wed\\\", 170) (\\\"Thu\\\", 165)\\n    series \\\"Market\\\": (\\\"Mon\\\", 160) (\\\"Tue\\\", 0) (\\\"Wed\\\", 195) (\\\"Thu\\\", 165)\"}\n```\n\n- Here a closed station gets a bar of 0 for its day, on purpose: every series then has the same categories in the same order, and the empty place next to Harbor's Tuesday bar shows that Market had no trips. (A `-` that meant \"not measured\" would instead be left out, as in step 1: then no bar, rather than a bar of 0.)\n- The stations are sorted by name, as the plants were, so the series are in a fixed order.\n","task":"The school library counts its loans per genre in `genres.csv`:\n\n```text\ngenre,loans\nFantasy,128\nMystery,96\nComics,152\nScience,61\n```\n\nFill in the blanks of a bar chart with a category axis labeled `Genre`, an auto-ranging number axis labeled `Loans`, the title `Loans by genre`, no legend, and one series, `Loans`, with a bar for each row, in the order of the file.\n","kind":"fill","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.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        CategoryAxis genres = new [[CategoryAxis]]();\n        genres.setLabel(\"Genre\");\n        NumberAxis loans = new NumberAxis();\n        loans.setLabel(\"Loans\");\n        BarChart<[[String]], Number> chart = new [[BarChart]]<>(genres, loans);\n        chart.setTitle(\"Loans by genre\");\n        chart.setLegendVisible(false);\n\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        series.setName(\"Loans\");\n        List<String> lines = Files.readAllLines(Paths.get(\"genres.csv\"));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            series.getData().add(new XYChart.Data<>(parts[0], Integer.valueOf(parts[1])));\n        }\n        chart.getData().add(series);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Library\");\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.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        CategoryAxis genres = new CategoryAxis();\n        genres.setLabel(\"Genre\");\n        NumberAxis loans = new NumberAxis();\n        loans.setLabel(\"Loans\");\n        BarChart<String, Number> chart = new BarChart<>(genres, loans);\n        chart.setTitle(\"Loans by genre\");\n        chart.setLegendVisible(false);\n\n        XYChart.Series<String, Number> series = new XYChart.Series<>();\n        series.setName(\"Loans\");\n        List<String> lines = Files.readAllLines(Paths.get(\"genres.csv\"));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            series.getData().add(new XYChart.Data<>(parts[0], Integer.valueOf(parts[1])));\n        }\n        chart.getData().add(series);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Library\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["What kind of axis holds text values, and which chart class draws bars? The chart's first type is the type of its x values.","The axis of text values is a `CategoryAxis`, and the chart a `BarChart`. The genres are text, so the x type is `String`."],"tests":[{"name":"Test 1","stdin":"","files":{"genres.csv":"genre,loans\nFantasy,128\nMystery,96\nComics,152\nScience,61\n"},"expect":"","hidden":false,"events":[],"window":{"windows":[{"title":"Library","scene":{"root":{"n":1,"type":"BarChart","title":"Loans by genre","legendVisible":false,"xAxis":{"type":"CategoryAxis","label":"Genre","autoRanging":true,"categories":["Fantasy","Mystery","Comics","Science"]},"yAxis":{"type":"NumberAxis","label":"Loans","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Loans","data":[["Fantasy",128],["Mystery",96],["Comics",152],["Science",61]]}]}}}],"problems":[]},"outline":"Window \"Library\"\n  BarChart \"Loans by genre\" (no legend)\n    x axis CategoryAxis \"Genre\"\n    y axis NumberAxis \"Loans\"\n    series \"Loans\": (\"Fantasy\", 128) (\"Mystery\", 96) (\"Comics\", 152) (\"Science\", 61)"},{"name":"Test 2","stdin":"","files":{"genres.csv":"genre,loans\nPoetry,12\nHistory,40\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Library","scene":{"root":{"n":1,"type":"BarChart","title":"Loans by genre","legendVisible":false,"xAxis":{"type":"CategoryAxis","label":"Genre","autoRanging":true,"categories":["Poetry","History"]},"yAxis":{"type":"NumberAxis","label":"Loans","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Loans","data":[["Poetry",12],["History",40]]}]}}}],"problems":[]},"outline":"Window \"Library\"\n  BarChart \"Loans by genre\" (no legend)\n    x axis CategoryAxis \"Genre\"\n    y axis NumberAxis \"Loans\"\n    series \"Loans\": (\"Poetry\", 12) (\"History\", 40)"},{"name":"Test 3","stdin":"","files":{"genres.csv":"genre,loans\nSports,77\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Library","scene":{"root":{"n":1,"type":"BarChart","title":"Loans by genre","legendVisible":false,"xAxis":{"type":"CategoryAxis","label":"Genre","autoRanging":true,"categories":["Sports"]},"yAxis":{"type":"NumberAxis","label":"Loans","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Loans","data":[["Sports",77]]}]}}}],"problems":[]},"outline":"Window \"Library\"\n  BarChart \"Loans by genre\" (no legend)\n    x axis CategoryAxis \"Genre\"\n    y axis NumberAxis \"Loans\"\n    series \"Loans\": (\"Sports\", 77)"}],"require":[],"forbid":[],"more":[{"task":"A stand at a farmers' market writes down each sale as a row, `apples,3`: the product and the kilograms (whole numbers). For a bar chart of the day, it needs the totals and the products in order. Write the class `SalesLog` in `SalesLog.java`, with two methods:\n\n- `public static Map<String, Integer> totals(List<String> rows)`: a map from each product to the sum of its kilograms\n- `public static List<String> products(List<String> rows)`: each product once, in the order it first appears in the rows\n\nThe rows have no header. No JavaFX in it. The check runs:\n\n```java\nList<String> rows = new ArrayList<>();\nrows.add(\"apples,3\");\nrows.add(\"pears,2\");\nrows.add(\"apples,5\");\nrows.add(\"plums,1\");\nrows.add(\"pears,4\");\nSystem.out.println(SalesLog.products(rows));\nMap<String, Integer> totals = SalesLog.totals(rows);\nSystem.out.println(totals.get(\"apples\") + \" \" + totals.get(\"pears\") + \" \" + totals.get(\"plums\"));\n```\n\nand expects:\n\n```\n[apples, pears, plums]\n8 6 1\n```\n","kind":"code","seed":"// ==== SalesLog.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class SalesLog {\n\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<String> rows = new ArrayList<>();\n        rows.add(\"apples,3\");\n        rows.add(\"pears,2\");\n        rows.add(\"apples,5\");\n        rows.add(\"plums,1\");\n        rows.add(\"pears,4\");\n        System.out.println(SalesLog.products(rows));\n        Map<String, Integer> totals = SalesLog.totals(rows);\n        System.out.println(totals.get(\"apples\") + \" \" + totals.get(\"pears\") + \" \" + totals.get(\"plums\"));\n    }\n}\n","solution":"// ==== SalesLog.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class SalesLog {\n\n    public static Map<String, Integer> totals(List<String> rows) {\n        Map<String, Integer> totals = new HashMap<>();\n        for (String row : rows) {\n            String[] parts = row.split(\",\");\n            int kilos = Integer.valueOf(parts[1]);\n            totals.put(parts[0], totals.getOrDefault(parts[0], 0) + kilos);\n        }\n        return totals;\n    }\n\n    public static List<String> products(List<String> rows) {\n        List<String> products = new ArrayList<>();\n        for (String row : rows) {\n            String product = row.split(\",\")[0];\n            if (!products.contains(product)) {\n                products.add(product);\n            }\n        }\n        return products;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<String> rows = new ArrayList<>();\n        rows.add(\"apples,3\");\n        rows.add(\"pears,2\");\n        rows.add(\"apples,5\");\n        rows.add(\"plums,1\");\n        rows.add(\"pears,4\");\n        System.out.println(SalesLog.products(rows));\n        Map<String, Integer> totals = SalesLog.totals(rows);\n        System.out.println(totals.get(\"apples\") + \" \" + totals.get(\"pears\") + \" \" + totals.get(\"plums\"));\n    }\n}\n","hints":["In `totals`, split each row and add its kilograms to the product's total: `totals.getOrDefault(product, 0)` gives 0 for a product not seen yet.","In `products`, add a product to the list only when `products.contains(product)` is false, so each one is there once, in the order of its first row."],"tests":[{"name":"Test 1","stdin":"","call":"List<String> rows = new ArrayList<>();\nrows.add(\"apples,3\");\nrows.add(\"pears,2\");\nrows.add(\"apples,5\");\nrows.add(\"plums,1\");\nrows.add(\"pears,4\");\nSystem.out.println(SalesLog.products(rows));\nMap<String, Integer> totals = SalesLog.totals(rows);\nSystem.out.println(totals.get(\"apples\") + \" \" + totals.get(\"pears\") + \" \" + totals.get(\"plums\"));\n","expect":"[apples, pears, plums]\n8 6 1","hidden":false},{"name":"Test 2","stdin":"","call":"List<String> rows = new ArrayList<>();\nrows.add(\"honey,1\");\nrows.add(\"honey,1\");\nrows.add(\"eggs,2\");\nrows.add(\"honey,10\");\nSystem.out.println(SalesLog.products(rows));\nMap<String, Integer> totals = SalesLog.totals(rows);\nSystem.out.println(totals.size() + \" \" + totals.get(\"honey\") + \" \" + totals.get(\"eggs\"));\n","expect":"[honey, eggs]\n2 12 2","hidden":true},{"name":"Test 3","stdin":"","call":"List<String> rows = new ArrayList<>();\nSystem.out.println(SalesLog.products(rows));\nSystem.out.println(SalesLog.totals(rows).isEmpty());\nrows.add(\"kale,7\");\nSystem.out.println(SalesLog.products(rows) + \" \" + SalesLog.totals(rows).get(\"kale\"));\n","expect":"[]\ntrue\n[kale] 7","hidden":true}],"require":[],"forbid":[{"pattern":"\\bjavafx\\b","message":"Keep SalesLog free of JavaFX: it adds the numbers up, and a window can chart them."}]},{"task":"A small cinema shows three films, each in an afternoon and an evening show, and writes the tickets sold in `tickets.csv`, one column per show. A show that was canceled has a `-`:\n\n```text\nfilm,Afternoon,Evening\nCartoon,64,22\nNature film,31,48\nComedy,40,-\n```\n\nWrite `start` so that the window, titled `Cinema`, shows a bar chart (the scene's root) with the title `Tickets sold`, a category axis labeled `Film`, an auto-ranging number axis labeled `Tickets` and a visible legend. It has a series for each show, named as in the header and in its order, with a bar (film, tickets) for each film, in the order of the file; a canceled show has no bar for that film. The file may have any number of shows. 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.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> lines = Files.readAllLines(Paths.get(\"tickets.csv\"));\n\n        stage.setTitle(\"Cinema\");\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.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> lines = Files.readAllLines(Paths.get(\"tickets.csv\"));\n        CategoryAxis films = new CategoryAxis();\n        films.setLabel(\"Film\");\n        NumberAxis tickets = new NumberAxis();\n        tickets.setLabel(\"Tickets\");\n        BarChart<String, Number> chart = new BarChart<>(films, tickets);\n        chart.setTitle(\"Tickets sold\");\n\n        String[] shows = lines.get(0).split(\",\");\n        List<XYChart.Series<String, Number>> series = new ArrayList<>();\n        for (int c = 1; c < shows.length; c++) {\n            XYChart.Series<String, Number> show = new XYChart.Series<>();\n            show.setName(shows[c]);\n            series.add(show);\n        }\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            for (int c = 1; c < parts.length; c++) {\n                if (!parts[c].equals(\"-\")) {\n                    series.get(c - 1).getData().add(new XYChart.Data<>(parts[0], Integer.valueOf(parts[c])));\n                }\n            }\n        }\n        chart.getData().addAll(series);\n\n        stage.setScene(new Scene(chart));\n        stage.setTitle(\"Cinema\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["This is step 3's bakery chart with categories: a series per column of the header (from index 1), and in each row, column `c` goes to the series at index `c - 1`, as the point `(parts[0], tickets)`.","The chart is a `BarChart<String, Number>` made with a `CategoryAxis` and a `NumberAxis`, and every series is `XYChart.Series<String, Number>`."],"tests":[{"name":"Test 1","stdin":"","files":{"tickets.csv":"film,Afternoon,Evening\nCartoon,64,22\nNature film,31,48\nComedy,40,-\n"},"expect":"","hidden":false,"events":[],"window":{"windows":[{"title":"Cinema","scene":{"root":{"n":1,"type":"BarChart","title":"Tickets sold","xAxis":{"type":"CategoryAxis","label":"Film","autoRanging":true,"categories":["Cartoon","Nature film","Comedy"]},"yAxis":{"type":"NumberAxis","label":"Tickets","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Afternoon","data":[["Cartoon",64],["Nature film",31],["Comedy",40]]},{"name":"Evening","data":[["Cartoon",22],["Nature film",48]]}]}}}],"problems":[]},"outline":"Window \"Cinema\"\n  BarChart \"Tickets sold\"\n    x axis CategoryAxis \"Film\"\n    y axis NumberAxis \"Tickets\"\n    series \"Afternoon\": (\"Cartoon\", 64) (\"Nature film\", 31) (\"Comedy\", 40)\n    series \"Evening\": (\"Cartoon\", 22) (\"Nature film\", 48)"},{"name":"Test 2","stdin":"","files":{"tickets.csv":"film,Morning,Afternoon,Evening\nMusical,-,55,90\nWestern,12,18,-\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Cinema","scene":{"root":{"n":1,"type":"BarChart","title":"Tickets sold","xAxis":{"type":"CategoryAxis","label":"Film","autoRanging":true,"categories":["Western","Musical"]},"yAxis":{"type":"NumberAxis","label":"Tickets","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Morning","data":[["Western",12]]},{"name":"Afternoon","data":[["Musical",55],["Western",18]]},{"name":"Evening","data":[["Musical",90]]}]}}}],"problems":[]},"outline":"Window \"Cinema\"\n  BarChart \"Tickets sold\"\n    x axis CategoryAxis \"Film\"\n    y axis NumberAxis \"Tickets\"\n    series \"Morning\": (\"Western\", 12)\n    series \"Afternoon\": (\"Musical\", 55) (\"Western\", 18)\n    series \"Evening\": (\"Musical\", 90)"},{"name":"Test 3","stdin":"","files":{"tickets.csv":"film,Matinee\nSilent film,9\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Cinema","scene":{"root":{"n":1,"type":"BarChart","title":"Tickets sold","xAxis":{"type":"CategoryAxis","label":"Film","autoRanging":true,"categories":["Silent film"]},"yAxis":{"type":"NumberAxis","label":"Tickets","autoRanging":true,"lowerBound":0,"upperBound":100,"tickUnit":5,"forceZeroInRange":true,"minorTickVisible":true},"series":[{"name":"Matinee","data":[["Silent film",9]]}]}}}],"problems":[]},"outline":"Window \"Cinema\"\n  BarChart \"Tickets sold\"\n    x axis CategoryAxis \"Film\"\n    y axis NumberAxis \"Tickets\"\n    series \"Matinee\": (\"Silent film\", 9)"}],"require":[],"forbid":[]}]}