Chart.java (5403B)
1 package population; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.Iterator; 6 import java.util.List; 7 import java.util.Map; 8 import javafx.geometry.Insets; 9 import javafx.geometry.Pos; 10 import javafx.scene.Scene; 11 import javafx.scene.chart.LineChart; 12 import javafx.scene.chart.NumberAxis; 13 import javafx.scene.chart.XYChart; 14 import javafx.scene.control.Button; 15 import javafx.scene.control.ComboBox; 16 import javafx.scene.layout.HBox; 17 import javafx.scene.layout.VBox; 18 import javafx.stage.Modality; 19 import javafx.stage.Stage; 20 import javafx.stage.StageStyle; 21 22 public class Chart { 23 private final int WIDTH = 1280; 24 private final int HEIGHT = 1000; 25 private final String TITLE = "Population Chart"; 26 private Stage stg; 27 private VBox vb; 28 private HBox hb; 29 private Button btn_close; 30 private Button btn_clear; 31 private ComboBox<String> cb_ctry; 32 private ComboBox<Integer> cb_yearfrom; 33 private ComboBox<Integer> cb_yearto; 34 private NumberAxis xaxis; 35 private NumberAxis yaxis; 36 private LineChart<Number, Number> lc; 37 private ArrayList<XYChart.Series<Number, Number>> sr; 38 39 Chart(List<Country> countries) { 40 sr = new ArrayList<XYChart.Series<Number, Number>>(); 41 mkstage(); 42 mkxaxis("Year"); 43 mkyaxis("Population"); 44 mklinechart(); 45 mkclosebtn("Close"); 46 mkclearbtn("Clear"); 47 mkctrylist(countries, "Country"); 48 cb_yearfrom = mkyearcb(Country.STARTING_YEAR); 49 cb_yearto = mkyearcb(Country.LAST_YEAR); 50 51 /* Change timeline values. */ 52 cb_yearfrom.setOnAction(ev -> { 53 if (cb_yearfrom.getValue() < cb_yearto.getValue()) 54 xaxis.setLowerBound(cb_yearfrom.getValue()); 55 }); 56 cb_yearto.setOnAction(ev -> { 57 if (cb_yearto.getValue() > cb_yearfrom.getValue()) 58 xaxis.setUpperBound(cb_yearto.getValue()); 59 }); 60 } 61 62 private void mkstage() { 63 stg = new Stage(); 64 stg.setWidth(WIDTH); 65 stg.setHeight(HEIGHT); 66 stg.initStyle(StageStyle.UTILITY); 67 stg.initModality(Modality.WINDOW_MODAL); 68 } 69 70 private void mkxaxis(String str) { 71 xaxis = new NumberAxis(); 72 xaxis.setLabel(str); 73 /* 74 * By default, the chart isn't resized properly 75 * so we'll do it ourselves. 76 */ 77 xaxis.setLowerBound(Country.STARTING_YEAR); 78 xaxis.setUpperBound(Country.LAST_YEAR); 79 /* No. Stop auto resizing. */ 80 xaxis.setAutoRanging(false); 81 } 82 83 private void mkyaxis(String str) { 84 yaxis = new NumberAxis(); 85 yaxis.setLabel(str); 86 } 87 88 private void mklinechart() { 89 lc = new LineChart<Number, Number>(xaxis, yaxis); 90 91 /* Fit to screen. */ 92 lc.prefWidthProperty().bind(stg.widthProperty()); 93 lc.prefHeightProperty().bind(stg.heightProperty()); 94 95 /* 96 * The animations look stupid when there's already another 97 * chart present. 98 */ 99 lc.setAnimated(false); 100 101 /* Do not draw nodes, just the line. */ 102 lc.setCreateSymbols(false); 103 } 104 105 private void mkclosebtn(String str) { 106 btn_close = new Button(str); 107 btn_close.setOnAction(ev -> { 108 stg.close(); 109 }); 110 } 111 112 private void mkclearbtn(String str) { 113 btn_clear = new Button(str); 114 btn_clear.setOnAction(ev -> { 115 lc.getData().clear(); 116 sr.forEach(s -> s.getData().clear()); 117 sr.clear(); 118 }); 119 } 120 121 private ComboBox<Integer> mkyearcb(Integer n) { 122 ComboBox<Integer> cb = new ComboBox<Integer>(); 123 for (int i = Country.STARTING_YEAR; i <= Country.LAST_YEAR; i++) 124 cb.getItems().add(i); 125 /* 126 * Set default values from the beginning, otherwise the 127 * very first action will not work properly as the value 128 * in one of the ComboBoxes will be empty. 129 */ 130 cb.setValue(n); 131 return cb; 132 } 133 134 private void mkctrylist(List<Country> countries, String str) { 135 cb_ctry = new ComboBox<String>(); 136 countries.forEach(c -> cb_ctry.getItems().add(c.getName())); 137 cb_ctry.setPromptText(str); 138 /* 139 * Sort countries by name, since by default 140 * each country is sorted by its index. 141 */ 142 Collections.sort(cb_ctry.getItems()); 143 144 /* Add population data for selected country to the chart. */ 145 cb_ctry.setOnAction(ev -> { 146 Country c = null; 147 Iterator<Map.Entry<Integer, Integer>> it; 148 Map.Entry<Integer, Integer> ent; 149 String ctryname; 150 151 /* Search country by selection name. */ 152 ctryname = cb_ctry.getValue(); 153 for (Country ctry : countries) { 154 if (ctry.getName().equals(ctryname)) { 155 c = ctry; 156 break; 157 } 158 } 159 /* Possibly a useless check. */ 160 if (c == null) 161 return; 162 163 sr.add(mkseries(c.getName())); 164 /* 165 * Iterate through the population data and make 166 * the chart. 167 */ 168 it = c.getPopulation().entrySet().iterator(); 169 while (it.hasNext()) { 170 ent = it.next(); 171 sr.get(sr.size()-1).getData().add( 172 new XYChart.Data<Number, Number>( 173 ent.getKey(), ent.getValue())); 174 } 175 lc.getData().add(sr.get(sr.size()-1)); 176 }); 177 } 178 179 private XYChart.Series<Number, Number> mkseries(String str) { 180 XYChart.Series<Number, Number> series; 181 182 series = new XYChart.Series<Number, Number>(); 183 series.setName(str); 184 return series; 185 } 186 187 public void show() { 188 /* Create bottom panel. */ 189 hb = new HBox(); 190 hb.setSpacing(5); 191 hb.setAlignment(Pos.CENTER); 192 hb.getChildren().addAll(cb_yearfrom, cb_yearto, 193 cb_ctry, btn_clear, btn_close); 194 /* 195 * Combine the chart and bottom panel, and display 196 * the scene. 197 */ 198 vb = new VBox(); 199 vb.setSpacing(5); 200 vb.setPadding(new Insets(10, 10, 10, 10)); 201 vb.getChildren().addAll(lc, hb); 202 stg.setTitle(TITLE); 203 stg.setScene(new Scene(vb, 400, 200)); 204 stg.show(); 205 } 206 }