package mara.mybox.controller; import java.awt.image.BufferedImage; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import javafx.application.Platform; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.embed.swing.SwingFXUtils; import javafx.fxml.FXML; import javafx.geometry.Bounds; import javafx.geometry.Pos; import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.SnapshotParameters; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.PieChart; import javafx.scene.chart.XYChart; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.WritableImage; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.transform.Transform; import javafx.stage.Modality; import javafx.stage.Screen; import static mara.mybox.controller.BaseController.openImageViewer; import mara.mybox.data.EpidemicReport; import mara.mybox.data.GeographyCode; import mara.mybox.data.QueryCondition; import static mara.mybox.db.DerbyBase.dbHome; import static mara.mybox.db.DerbyBase.login; import static mara.mybox.db.DerbyBase.protocol; import mara.mybox.db.TableStringValue; import mara.mybox.fxml.ControlStyle; import mara.mybox.fxml.FxmlColor; import mara.mybox.fxml.FxmlControl; import mara.mybox.fxml.FxmlControl.ChartCoordinate; import mara.mybox.fxml.FxmlControl.LabelType; import static mara.mybox.fxml.FxmlControl.badStyle; import mara.mybox.fxml.Logarithmic10Coordinate; import mara.mybox.fxml.LogarithmicECoordinate; import mara.mybox.fxml.SquareRootCoordinate; import mara.mybox.image.ImageManufacture; import mara.mybox.image.file.ImageFileWriters; import mara.mybox.image.file.ImageGifFile; import mara.mybox.tools.DoubleTools; import mara.mybox.tools.FileTools; import mara.mybox.tools.LocationTools; import mara.mybox.tools.StringTools; import mara.mybox.value.AppVariables; import static mara.mybox.value.AppVariables.logger; import static mara.mybox.value.AppVariables.message; import mara.mybox.value.CommonValues; import thridparty.LabeledBarChart; import thridparty.LabeledHorizontalBarChart; /** * @Author Mara * @CreateDate 2020-2-3 * @License Apache License Version 2.0 */ public class EpidemicReportsChartController extends LocationsMapController { protected EpidemicReportsController reportsController; protected EpidemicReportsSettingsController settingsController; protected QueryCondition queryCondition; protected String chartQuerySQL, chartName; protected List<String> orderNames, valuesNames; protected int interval, topNumber, snapWidth, chartLoadTime, mapLoadTime, totalSize, chartIndex; protected List<String> datasets, chartTimes; protected Map<String, List<EpidemicReport>> timesReports, locationsReports; protected List<GeographyCode> chartLocations; protected boolean multipleDatasets, mapCentered; protected double maxValue; protected Side legendSide; protected float lenUnit; protected LabelType labelType; protected ChartCoordinate chartCoordinate; protected LoadingController loading; @FXML protected StackPane chartPane; @FXML protected ComboBox<String> intervalSelector, labelSizeSelector, frameSelector; @FXML protected ToggleGroup chartGroup, labelGroup, legendGroup, coordinateGroup; @FXML protected HBox chartTypeBox, playBox; @FXML protected VBox valueTypeBox, optionsVBox, legendBox, labelBox, chartSnapBox; @FXML protected CheckBox categoryAxisCheck, confirmedCheck, healedCheck, deadCheck, increasedConfirmedCheck, increasedHealedCheck, increasedDeadCheck, HealedConfirmedPermillageCheck, DeadConfirmedPermillageCheck, ConfirmedPopulationPermillageCheck, DeadPopulationPermillageCheck, HealedPopulationPermillageCheck, ConfirmedAreaPermillageCheck, HealedAreaPermillageCheck, DeadAreaPermillageCheck, hlinesCheck, vlinesCheck; @FXML protected RadioButton numbersRadio, ratiosRadio, increasedRadio, confirmedRatio, increasedConfirmedRadio, healedRadio, healedRatioRadio, increasedHealedRadio, deadRadio, deadRatioRadio, increasedDeadRadio, horizontalBarsChartRadio, verticalBarsChartRadio, linesChartRadio, linesChartHRadio, pieRadio, mapRadio, cartesianRadio, logarithmicERadio, logarithmic10Radio, squareRootRadio; @FXML protected VBox chartBox, mapBox; @FXML protected Button pauseButton; public EpidemicReportsChartController() { baseTitle = AppVariables.message("EpidemicReport"); } @Override public void initializeNext() { try { super.initializeNext(); valuesNames = new ArrayList(); initOptions(); } catch (Exception e) { logger.error(e.toString()); } } protected void initOptions() { try { chartGroup.selectedToggleProperty().addListener( (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> { if (isSettingValues || newValue == null) { return; } drawChart(); }); chartCoordinate = ChartCoordinate.Cartesian; coordinateGroup.selectedToggleProperty().addListener( (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> { if (isSettingValues || newValue == null) { return; } if (logarithmicERadio.isSelected()) { chartCoordinate = ChartCoordinate.LogarithmicE; } else if (logarithmic10Radio.isSelected()) { chartCoordinate = ChartCoordinate.Logarithmic10; } else if (squareRootRadio.isSelected()) { chartCoordinate = ChartCoordinate.SquareRoot; } else { chartCoordinate = ChartCoordinate.Cartesian; } drawChart(); }); frameSelector.getSelectionModel().selectedItemProperty().addListener( (ObservableValue<? extends String> ov, String oldValue, String newValue) -> { if (isSettingValues) { return; } if (timer != null) { timer.cancel(); timer = null; } drawFrame(newValue); }); labelType = LabelType.NameAndValue; labelGroup.selectedToggleProperty().addListener((ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> { if (isSettingValues || newValue == null) { return; } String value = ((RadioButton) newValue).getText(); if (message("NameAndValue").equals(value)) { labelType = LabelType.NameAndValue; } else if (message("Value").equals(value)) { labelType = LabelType.Value; } else if (message("Name").equals(value)) { labelType = LabelType.Name; } else if (message("NotDisplay").equals(value)) { labelType = LabelType.NotDisplay; } else if (message("Pop").equals(value)) { labelType = LabelType.Pop; } else { labelType = LabelType.NameAndValue; } drawChart(); }); textSize = 12; labelSizeSelector.getItems().addAll(Arrays.asList( "12", "14", "10", "15", "16", "18", "9", "8", "18", "20", "24" )); labelSizeSelector.getSelectionModel().selectedItemProperty().addListener( (ObservableValue<? extends String> ov, String oldValue, String newValue) -> { try { int v = Integer.parseInt(newValue); if (v > 0) { textSize = v; labelSizeSelector.getEditor().setStyle(null); AppVariables.setUserConfigInt("EpidemicReportChartTextSize", textSize); if (!isSettingValues) { drawChart(); } } else { labelSizeSelector.getEditor().setStyle(badStyle); } } catch (Exception e) { labelSizeSelector.getEditor().setStyle(badStyle); } }); legendSide = null; legendGroup.selectedToggleProperty().addListener( (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> { if (isSettingValues || newValue == null) { return; } String value = ((RadioButton) newValue).getText(); if (message("NotDisplay").equals(value)) { legendSide = null; } else if (message("Left").equals(value)) { legendSide = Side.LEFT; } else if (message("Top").equals(value)) { legendSide = Side.TOP; } else if (message("Bottom").equals(value)) { legendSide = Side.BOTTOM; } else { legendSide = Side.RIGHT; } drawChart(); }); categoryAxisCheck.selectedProperty().addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (isSettingValues) { return; } AppVariables.setUserConfigValue("EpidemicReportDisplayCategoryAxis", newValue); drawChart(); }); hlinesCheck.selectedProperty().addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (isSettingValues) { return; } AppVariables.setUserConfigValue("EpidemicReportDisplayHlines", newValue); drawChart(); }); vlinesCheck.selectedProperty().addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (isSettingValues) { return; } AppVariables.setUserConfigValue("EpidemicReportDisplayVlines", newValue); drawChart(); }); interval = 200; intervalSelector.getItems().addAll(Arrays.asList( "200", "500", "1000", "50", "100", "300", "800", "1500", "2000", "3000", "5000", "10000" )); intervalSelector.getSelectionModel().selectedItemProperty().addListener( (ObservableValue<? extends String> ov, String oldValue, String newValue) -> { try { int v = Integer.valueOf(intervalSelector.getValue()); if (v > 0) { interval = v; AppVariables.setUserConfigValue("EpidemicReportInterval", interval + ""); FxmlControl.setEditorNormal(intervalSelector); if (isSettingValues) { return; } drawChart(); } else { FxmlControl.setEditorBadStyle(intervalSelector); } } catch (Exception e) { logger.error(e.toString()); } }); isSettingValues = true; labelSizeSelector.getSelectionModel().select(AppVariables.getUserConfigValue("EpidemicReportChartTextSize", "12")); categoryAxisCheck.setSelected(AppVariables.getUserConfigBoolean("EpidemicReportDisplayCategoryAxis", false)); hlinesCheck.setSelected(AppVariables.getUserConfigBoolean("EpidemicReportDisplayHlines", true)); vlinesCheck.setSelected(AppVariables.getUserConfigBoolean("EpidemicReportDisplayVlines", true)); intervalSelector.getSelectionModel().select(AppVariables.getUserConfigValue("EpidemicReportInterval", "200")); isSettingValues = false; } catch (Exception e) { logger.error(e.toString()); } } protected void loadCharts() { synchronized (this) { if (task != null) { return; } if (timer != null) { timer.cancel(); timer = null; } if (!initCharts()) { return; } task = new SingletonTask<Void>() { @Override protected boolean handle() { try ( Connection conn = DriverManager.getConnection(protocol + dbHome() + login)) { conn.setReadOnly(true); Map<String, String> saved = TableStringValue.readWithPrefix(conn, "EpidemicReportsLocationColor"); Collection<String> savedColors = saved.values(); Map<String, String> locationColors = new HashMap(); for (GeographyCode location : chartLocations) { String name = location.getFullName(); String color = saved.get("EpidemicReportsLocationColor" + name); if (color == null) { color = FxmlColor.randomColorExcept(savedColors); } locationColors.put(name, color); } TableStringValue.writeWithPrefix("EpidemicReportsLocationColor", locationColors); settingsController.locationColors = locationColors; if (loading != null) { loading.setInfo(message("DateNumber") + ": " + chartTimes.size() + " " + message("TotalSize") + ": " + totalSize); } return true; } catch (Exception e) { if (loading != null) { loading.setInfo(e.toString()); } error = e.toString(); return false; } } @Override protected void whenSucceeded() { settingsController.initColors(); drawChart(); } }; loading = reportsController.openHandlingStage(task, Modality.WINDOW_MODAL, message("LoadingChartData")); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } } protected boolean initCharts() { clearChart(); if (reportsController == null || reportsController.topNumber <= 0 || reportsController.dataTimes.isEmpty() || reportsController.orderNames.isEmpty() || reportsController.timesReports.isEmpty()) { return false; } topNumber = reportsController.topNumber; queryCondition = reportsController.queryCondition; chartQuerySQL = reportsController.dataQuerySQL; datasets = reportsController.datasets; timesReports = reportsController.timesReports; locationsReports = reportsController.locationsReports; chartLocations = reportsController.dataLocations; multipleDatasets = reportsController.datasets.size() > 1; maxValue = reportsController.maxValue; totalSize = reportsController.totalSize; chartTimes = new ArrayList<>(); chartTimes.addAll(reportsController.dataTimes); Collections.reverse(chartTimes); mapSize = 3; chartIndex = 0; orderNames = new ArrayList<>(); orderNames.addAll(reportsController.orderNames); orderNames.remove("time"); if (orderNames.isEmpty()) { return false; } setValuesChecks(); if (chartTimes.size() > 1) { playBox.setVisible(true); isSettingValues = true; setPause(false); List<String> frames = new ArrayList<>(); for (int i = chartTimes.size() - 1; i >= 0; i--) { String date = chartTimes.get(i); frames.add(date); } frameSelector.getItems().clear(); frameSelector.getItems().addAll(frames); isSettingValues = false; } else { playBox.setVisible(false); } return true; } public void clearChart() { if (timer != null) { timer.cancel(); timer = null; } chartBox.getChildren().clear(); optionsVBox.getChildren().clear(); chartBox.setVisible(false); mapBox.setVisible(false); valuesNames.clear(); titleLabel.setText(""); mapCentered = false; } protected void setValuesChecks() { for (Node node : valueTypeBox.getChildren()) { if (node instanceof CheckBox) { ((CheckBox) node).setSelected(false); ((CheckBox) node).setDisable(false); } } if (orderNames == null) { return; } boolean first = false; for (String name : orderNames) { if ("confirmed".equals(name)) { confirmedCheck.setSelected(true); if (!first) { confirmedCheck.setDisable(true); first = true; } } else if ("healed".equals(name)) { healedCheck.setSelected(true); if (!first) { healedCheck.setDisable(true); first = true; } } else if ("dead".equals(name)) { deadCheck.setSelected(true); if (!first) { deadCheck.setDisable(true); first = true; } } else if ("increased_confirmed".equals(name)) { increasedConfirmedCheck.setSelected(true); if (!first) { increasedConfirmedCheck.setDisable(true); first = true; } } else if ("increased_healed".equals(name)) { increasedHealedCheck.setSelected(true); if (!first) { increasedHealedCheck.setDisable(true); first = true; } } else if ("increased_dead".equals(name)) { increasedDeadCheck.setSelected(true); if (!first) { increasedDeadCheck.setDisable(true); first = true; } } else if ("healed_confirmed_permillage".equals(name)) { HealedConfirmedPermillageCheck.setSelected(true); if (!first) { HealedConfirmedPermillageCheck.setDisable(true); first = true; } } else if ("dead_confirmed_permillage".equals(name)) { DeadConfirmedPermillageCheck.setSelected(true); if (!first) { DeadConfirmedPermillageCheck.setDisable(true); first = true; } } else if ("confirmed_population_permillage".equals(name)) { ConfirmedPopulationPermillageCheck.setSelected(true); if (!first) { ConfirmedPopulationPermillageCheck.setDisable(true); first = true; } } else if ("healed_population_permillage".equals(name)) { HealedPopulationPermillageCheck.setSelected(true); if (!first) { HealedPopulationPermillageCheck.setDisable(true); first = true; } } else if ("dead_population_permillage".equals(name)) { DeadPopulationPermillageCheck.setSelected(true); if (!first) { DeadPopulationPermillageCheck.setDisable(true); first = true; } } else if ("confirmed_area_permillage".equals(name)) { ConfirmedAreaPermillageCheck.setSelected(true); if (!first) { ConfirmedAreaPermillageCheck.setDisable(true); first = true; } } else if ("healed_area_permillage".equals(name)) { HealedAreaPermillageCheck.setSelected(true); if (!first) { HealedAreaPermillageCheck.setDisable(true); first = true; } } else if ("dead_area_permillage".equals(name)) { DeadAreaPermillageCheck.setSelected(true); if (!first) { DeadAreaPermillageCheck.setDisable(true); first = true; } } } } protected void checkValues() { valuesNames.clear(); if (confirmedCheck.isSelected()) { valuesNames.add(message("Confirmed")); } if (healedCheck.isSelected()) { valuesNames.add(message("Healed")); } if (deadCheck.isSelected()) { valuesNames.add(message("Dead")); } if (increasedConfirmedCheck.isSelected()) { valuesNames.add(message("IncreasedConfirmed")); } if (increasedHealedCheck.isSelected()) { valuesNames.add(message("IncreasedHealed")); } if (increasedDeadCheck.isSelected()) { valuesNames.add(message("IncreasedDead")); } if (HealedConfirmedPermillageCheck.isSelected()) { valuesNames.add(message("HealedConfirmedPermillage")); } if (DeadConfirmedPermillageCheck.isSelected()) { valuesNames.add(message("DeadConfirmedPermillage")); } if (ConfirmedPopulationPermillageCheck.isSelected()) { valuesNames.add(message("ConfirmedPopulationPermillage")); } if (HealedPopulationPermillageCheck.isSelected()) { valuesNames.add(message("HealedPopulationPermillage")); } if (DeadPopulationPermillageCheck.isSelected()) { valuesNames.add(message("DeadPopulationPermillage")); } if (ConfirmedAreaPermillageCheck.isSelected()) { valuesNames.add(message("ConfirmedAreaPermillage")); } if (HealedAreaPermillageCheck.isSelected()) { valuesNames.add(message("HealedAreaPermillage")); } if (DeadAreaPermillageCheck.isSelected()) { valuesNames.add(message("DeadAreaPermillage")); } } @FXML public void drawChart() { if (isSettingValues) { return; } try { clearChart(); checkValues(); if (chartTimes.isEmpty() || valuesNames.isEmpty() || chartLocations.isEmpty() || timesReports.isEmpty()) { return; } setPause(false); if (mapRadio.isSelected()) { optionsVBox.getChildren().addAll(labelBox, mapOptionsBox); chartName = message("Map"); chartBox.getChildren().clear(); chartBox.setVisible(false); mapBox.setVisible(true); mapBox.toFront(); webEngine.executeScript("setZoom(" + mapSize + ");"); } else { optionsVBox.getChildren().addAll(legendBox, labelBox); mapBox.setVisible(false); chartBox.setVisible(true); chartBox.toFront(); if (horizontalBarsChartRadio.isSelected()) { chartName = message("HorizontalBarsChart"); } else if (verticalBarsChartRadio.isSelected()) { chartName = message("VerticalBarsChart"); } else if (linesChartRadio.isSelected()) { if (locationsReports == null) { return; } chartName = message("VerticalLinesChart"); } else if (linesChartHRadio.isSelected()) { if (locationsReports == null) { return; } chartName = message("HorizontalLinesChart"); } else if (pieRadio.isSelected()) { chartName = message("PieChart"); } } try { if (timer != null) { timer.cancel(); timer = null; } if (interval <= 0) { interval = 1000; } byte[] lock = new byte[0]; timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { Platform.runLater(() -> { if (chartTimes == null || chartTimes.isEmpty() || timesReports == null || locationsReports.isEmpty()) { if (timer != null) { timer.cancel(); timer = null; } return; } synchronized (lock) { fixChartIndex(); String time = chartTimes.get(chartIndex); List<EpidemicReport> timeReports = timesReports.get(time); drawChart(time, timeReports); if (chartTimes.size() == 1) { if (timer != null) { timer.cancel(); timer = null; } return; } ++chartIndex; } }); } }, 0, interval); } catch (Exception e) { logger.debug(e.toString()); } } catch (Exception e) { logger.debug(e.toString()); } } protected void drawChart(String time, List<EpidemicReport> timeReports) { try { if (valuesNames.isEmpty() || time == null || timeReports == null || timeReports.isEmpty()) { return; } titleLabel.setText(queryCondition.getTitle().replaceAll("\n", " ") + " - " + time); if (mapRadio.isSelected()) { drawMap(timeReports); } else { if (horizontalBarsChartRadio.isSelected()) { if (valuesNames.size() == 1) { drawValueBarsHorizontal(timeReports); } else { drawValuesBarsHorizontal(timeReports); } } else if (verticalBarsChartRadio.isSelected()) { if (valuesNames.size() == 1) { drawValueBarsVertical(timeReports); } else { drawValuesBarsVertical(timeReports); } } else if (linesChartRadio.isSelected()) { drawValuesLines(time, timeReports, true); } else if (linesChartHRadio.isSelected()) { drawValuesLines(time, timeReports, false); } else if (pieRadio.isSelected()) { drawPies(time, timeReports); } } } catch (Exception e) { logger.debug(e.toString()); } } protected void drawFrame(int index) { chartIndex = index; drawFrame(); } protected void drawFrame(String time) { chartIndex = chartTimes.indexOf(time); drawFrame(); } protected void drawFrame() { try { setPause(true); fixChartIndex(); String time = chartTimes.get(chartIndex); List<EpidemicReport> timeReports = timesReports.get(time); drawChart(time, timeReports); } catch (Exception e) { logger.debug(e.toString()); } } protected void fixChartIndex() { if (chartIndex > chartTimes.size() - 1) { chartIndex -= chartTimes.size(); } else if (chartIndex < 0) { chartIndex += chartTimes.size(); } } protected void drawPies(String time, List<EpidemicReport> timeReports) { try { if (time == null || timeReports == null || timeReports.isEmpty()) { return; } chartBox.getChildren().clear(); HBox line = new HBox(); int colsNum = (int) Math.sqrt(valuesNames.size()); colsNum = Math.max(colsNum, valuesNames.size() / colsNum); for (int i = 0; i < valuesNames.size(); i++) { String valueName = valuesNames.get(i); if (i % colsNum == 0) { line = new HBox(); line.setAlignment(Pos.TOP_CENTER); line.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); line.setSpacing(5); VBox.setVgrow(line, Priority.ALWAYS); HBox.setHgrow(line, Priority.ALWAYS); chartBox.getChildren().add(line); } PieChart pie = new PieChart(); pie.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); VBox.setVgrow(pie, Priority.ALWAYS); HBox.setHgrow(pie, Priority.ALWAYS); pie.setAnimated(false); if (legendSide == null) { pie.setLegendVisible(false); } else { pie.setLegendVisible(true); pie.setLegendSide(legendSide); } pie.setTitle(valueName + " - " + time); // pie.setClockwise(true); pie.setLabelLineLength(0d); line.getChildren().add(pie); drawPie(valueName, pie, time, timeReports); } } catch (Exception e) { logger.debug(e.toString()); } } protected void drawPie(String valueName, PieChart pie, String time, List<EpidemicReport> timeReports) { try { if (valueName == null || time == null || pie == null || timeReports == null || timeReports.isEmpty()) { return; } float total = 0; for (EpidemicReport report : timeReports) { Number value = report.value(valueName); if (value == null) { continue; } total += value.floatValue(); } if (total == 0) { return; } ObservableList<PieChart.Data> pieData = FXCollections.observableArrayList(); pie.setData(pieData); String label; List<String> palette = new ArrayList(); for (EpidemicReport report : timeReports) { String name = (multipleDatasets ? report.getDataSet() + " - " : "") + report.getLocationFullName(); Number value = report.value(valueName); if (value == null) { continue; } double d = value.doubleValue(); double percent = DoubleTools.scale(d * 100 / total, 1); String labelValue = StringTools.format(d); switch (labelType) { case Name: label = name; break; case Value: label = percent + "% " + labelValue; break; case NameAndValue: label = name + " " + percent + "% " + labelValue; break; case NotDisplay: default: label = ""; break; } PieChart.Data item = new PieChart.Data(label, d); pieData.add(item); if (labelType == LabelType.Pop) { FxmlControl.setTooltip(item.getNode(), name + " " + percent + "% " + labelValue); } palette.add(settingsController.locationColors.get(report.getLocationFullName())); } FxmlControl.setPieColors(pie, palette, legendSide != null); } catch (Exception e) { logger.debug(e.toString()); } } protected LineChart addLinesChart(boolean vertical) { CategoryAxis categoryAxis = new CategoryAxis(); categoryAxis.setSide(Side.BOTTOM); categoryAxis.setTickLabelsVisible(categoryAxisCheck.isSelected()); categoryAxis.setGapStartAndEnd(true); categoryAxis.setTickLabelRotation(90); categoryAxis.setAnimated(false); NumberAxis numberAxis = new NumberAxis(); numberAxis.setSide(Side.LEFT); numberAxis.setAnimated(false); LineChart lineChart; if (vertical) { categoryAxis.setEndMargin(100); lineChart = new LineChart(categoryAxis, numberAxis); } else { categoryAxis.setEndMargin(20); lineChart = new LineChart(numberAxis, categoryAxis); } lineChart.setAlternativeRowFillVisible(false); lineChart.setAlternativeColumnFillVisible(false); lineChart.setAnimated(false); lineChart.setCreateSymbols(true); lineChart.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); lineChart.setVerticalGridLinesVisible(vlinesCheck.isSelected()); lineChart.setHorizontalGridLinesVisible(hlinesCheck.isSelected()); VBox.setVgrow(lineChart, Priority.ALWAYS); HBox.setHgrow(lineChart, Priority.ALWAYS); if (legendSide == null) { lineChart.setLegendVisible(false); } else { lineChart.setLegendVisible(true); lineChart.setLegendSide(legendSide); } switch (chartCoordinate) { case LogarithmicE: numberAxis.setTickLabelFormatter(new LogarithmicECoordinate()); break; case Logarithmic10: numberAxis.setTickLabelFormatter(new Logarithmic10Coordinate()); break; case SquareRoot: numberAxis.setTickLabelFormatter(new SquareRootCoordinate()); break; } return lineChart; } protected XYChart.Data lineDataNode(String label, String prefix, double value, boolean vertical, boolean last) { double coordinateValue = FxmlControl.coordinateValue(chartCoordinate, value); XYChart.Data data; if (vertical) { data = new XYChart.Data(label, coordinateValue); } else { data = new XYChart.Data(coordinateValue, label); } String finalLabel = prefix == null ? label : prefix + "\n" + label; String finalValue = StringTools.format(value); if (last) { String valueLabel = finalLabel + " " + finalValue; Label text = new Label(valueLabel); text.setStyle("-fx-background-color: transparent; -fx-font-size: " + textSize + "px; -fx-font-weight: bolder;"); data.setNode(text); } else if (labelType == LabelType.Pop) { Label text = new Label(""); text.setStyle("-fx-background-color: transparent; -fx-font-size: " + textSize + "px; -fx-font-weight: bolder;"); data.setNode(text); FxmlControl.setTooltip(text, finalLabel + " " + finalValue); } else { String valueLabel; switch (labelType) { case Name: valueLabel = finalLabel; break; case NameAndValue: valueLabel = finalLabel + " " + finalValue; break; case Value: valueLabel = finalValue; break; default: valueLabel = ""; break; } Label text = new Label(valueLabel); text.setStyle("-fx-background-color: transparent; -fx-font-size: " + textSize + "px;"); data.setNode(text); } return data; } protected void drawValuesLines(String time, List<EpidemicReport> timeReports, boolean vertical) { try { if (locationsReports == null || time == null || timeReports == null || timeReports.isEmpty()) { return; } chartBox.getChildren().clear(); HBox line = new HBox(); int colsNum = (int) Math.sqrt(valuesNames.size()); colsNum = Math.max(colsNum, valuesNames.size() / colsNum); for (int i = 0; i < valuesNames.size(); i++) { String valueName = valuesNames.get(i); if (i % colsNum == 0) { line = new HBox(); line.setAlignment(Pos.TOP_CENTER); line.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); line.setSpacing(5); VBox.setVgrow(line, Priority.ALWAYS); HBox.setHgrow(line, Priority.ALWAYS); chartBox.getChildren().add(line); } LineChart lineChart = addLinesChart(vertical); lineChart.setTitle(valueName + " - " + time); lineChart.setAlternativeRowFillVisible(false); lineChart.setAlternativeColumnFillVisible(false); lineChart.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); VBox.setVgrow(lineChart, Priority.ALWAYS); HBox.setHgrow(lineChart, Priority.ALWAYS); line.getChildren().add(lineChart); drawValueLines(valueName, lineChart, time, vertical); } } catch (Exception e) { logger.debug(e.toString()); } } protected void drawValueLines(String valueName, LineChart lineChart, String time, boolean vertical) { try { if (valueName == null || lineChart == null || time == null || locationsReports == null) { return; } Map<String, String> palette = new HashMap(); Map<String, XYChart.Series> seriesMap = new HashMap(); for (int i = 0; i < chartTimes.size(); i++) { String date = chartTimes.get(i); int dateCompare = date.compareTo(time); if (dateCompare > 0) { break; } List<EpidemicReport> reports = locationsReports.get(date); if (reports == null || reports.isEmpty()) { continue; } for (int j = 0; j < reports.size(); j++) { EpidemicReport report = reports.get(j); Number value = report.value(valueName); if (value == null) { continue; } GeographyCode location = report.getLocation(); String locationName = location.getFullName(); String lineName = (multipleDatasets ? report.getDataSet() + " - " : "") + locationName; XYChart.Series series = seriesMap.get(lineName); if (series == null) { series = new XYChart.Series(); series.setName(lineName); lineChart.getData().add(series); seriesMap.put(lineName, series); palette.put(lineName, settingsController.locationColors.get(locationName)); } if (dateCompare == 0) { series.getData().add(lineDataNode(date, lineName, value.doubleValue(), vertical, true)); } else if (multipleDatasets) { series.getData().add(lineDataNode(date, report.getDataSet(), value.doubleValue(), vertical, false)); } else { series.getData().add(lineDataNode(date, null, value.doubleValue(), vertical, false)); } } } FxmlControl.setLineChartColors(lineChart, palette, legendSide != null); } catch (Exception e) { logger.debug(e.toString()); } } protected LabeledBarChart addVerticalBarChart() { chartBox.getChildren().clear(); // boolean intValue = !message("HealedRatio").equals(valueName) // && !message("DeadRatio").equals(valueName); LabeledBarChart barChart = LabeledBarChart.create(categoryAxisCheck.isSelected(), chartCoordinate) .setIntValue(false) .setLabelType(labelType) .setTextSize(textSize); barChart.setAlternativeRowFillVisible(false); barChart.setAlternativeColumnFillVisible(false); barChart.setBarGap(0.0); barChart.setCategoryGap(0.0); barChart.setAnimated(false); barChart.getXAxis().setAnimated(false); barChart.getYAxis().setAnimated(false); barChart.getXAxis().setTickLabelRotation(90); barChart.setVerticalGridLinesVisible(vlinesCheck.isSelected()); barChart.setHorizontalGridLinesVisible(hlinesCheck.isSelected()); barChart.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); VBox.setVgrow(barChart, Priority.ALWAYS); HBox.setHgrow(barChart, Priority.ALWAYS); if (legendSide == null) { barChart.setLegendVisible(false); } else { barChart.setLegendVisible(true); barChart.setLegendSide(legendSide); } chartBox.getChildren().add(barChart); return barChart; } protected LabeledHorizontalBarChart addHorizontalBarChart() { chartBox.getChildren().clear(); LabeledHorizontalBarChart barChart = LabeledHorizontalBarChart.create(categoryAxisCheck.isSelected(), chartCoordinate) .setIntValue(false) .setLabelType(labelType) .setTextSize(textSize); barChart.setAlternativeRowFillVisible(false); barChart.setAlternativeColumnFillVisible(false); barChart.setBarGap(0.0); barChart.setCategoryGap(0.0); barChart.setAnimated(false); barChart.getXAxis().setAnimated(false); barChart.getYAxis().setAnimated(false); barChart.setVerticalGridLinesVisible(vlinesCheck.isSelected()); barChart.setHorizontalGridLinesVisible(hlinesCheck.isSelected()); barChart.getXAxis().setTickLabelRotation(90); barChart.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); VBox.setVgrow(barChart, Priority.ALWAYS); HBox.setHgrow(barChart, Priority.ALWAYS); if (legendSide == null) { barChart.setLegendVisible(false); } else { barChart.setLegendVisible(true); barChart.setLegendSide(legendSide); } chartBox.getChildren().add(barChart); return barChart; } protected void drawValuesBarsVertical(List<EpidemicReport> timeReports) { try { if (valuesNames.size() <= 1 || timeReports == null || timeReports.isEmpty()) { return; } LabeledBarChart barChart = addVerticalBarChart(); Map<String, String> palette = new HashMap(); List<XYChart.Series> seriesList = new ArrayList<>(); for (int i = 0; i < valuesNames.size(); i++) { String valueName = valuesNames.get(i); XYChart.Series series = new XYChart.Series(); series.setName(valueName); seriesList.add(series); barChart.getData().add(i, series); palette.put(valueName, settingsController.color(valueName)); } for (EpidemicReport report : timeReports) { String label = (multipleDatasets ? report.getDataSet() + " - " : "") + report.getLocationFullName(); for (int i = 0; i < valuesNames.size(); i++) { XYChart.Series series = seriesList.get(i); String valueName = valuesNames.get(i); Number value = report.value(valueName); if (value == null) { continue; } double coordinateValue = FxmlControl.coordinateValue(chartCoordinate, value.doubleValue()); XYChart.Data item = new XYChart.Data(label, coordinateValue); series.getData().add(item); if (labelType == LabelType.Pop) { FxmlControl.setTooltip(item.getNode(), label + " " + value); } } } FxmlControl.setBarChartColors(barChart, palette, legendSide != null); } catch (Exception e) { logger.debug(e.toString()); } } protected void drawValuesBarsHorizontal(List<EpidemicReport> timeReports) { try { if (valuesNames.size() <= 1 || timeReports == null || timeReports.isEmpty()) { return; } LabeledHorizontalBarChart barChart = addHorizontalBarChart(); Map<String, String> palette = new HashMap(); List<XYChart.Series> seriesList = new ArrayList<>(); for (int i = 0; i < valuesNames.size(); i++) { String valueName = valuesNames.get(i); XYChart.Series series = new XYChart.Series(); series.setName(valueName); seriesList.add(series); barChart.getData().add(i, series); palette.put(valueName, settingsController.color(valueName)); } for (int i = timeReports.size() - 1; i >= 0; i--) { EpidemicReport report = timeReports.get(i); String label = (multipleDatasets ? report.getDataSet() + " - " : "") + report.getLocationFullName(); for (int j = 0; j < valuesNames.size(); j++) { XYChart.Series series = seriesList.get(j); String valueName = valuesNames.get(j); Number value = report.value(valueName); if (value == null) { continue; } double coordinateValue = FxmlControl.coordinateValue(chartCoordinate, value.doubleValue()); XYChart.Data item = new XYChart.Data(coordinateValue, label); series.getData().add(item); if (labelType == LabelType.Pop) { FxmlControl.setTooltip(item.getNode(), label + " " + value); } } } FxmlControl.setBarChartColors(barChart, palette, legendSide != null); } catch (Exception e) { logger.debug(e.toString()); } } protected void drawValueBarsVertical(List<EpidemicReport> timeReports) { if (valuesNames.size() != 1 || timeReports == null || timeReports.isEmpty()) { return; } String valueName = orderNames.get(0); LabeledBarChart barChart = addVerticalBarChart(); Map<String, String> palette = new HashMap(); for (int i = 0; i < timeReports.size(); i++) { EpidemicReport report = timeReports.get(i); String location = report.getLocationFullName(); String label = (multipleDatasets ? report.getDataSet() + " - " : "") + location; Number value = report.value(valueName); if (value == null) { continue; } double coordinateValue = FxmlControl.coordinateValue(chartCoordinate, value.doubleValue()); XYChart.Series series = new XYChart.Series(); series.setName(label); palette.put(label, settingsController.locationColors.get(location)); XYChart.Data item = new XYChart.Data(label, coordinateValue); series.getData().add(item); barChart.getData().add(series); if (labelType == LabelType.Pop) { FxmlControl.setTooltip(item.getNode(), label + " " + value); } } FxmlControl.setBarChartColors(barChart, palette, legendSide != null); } protected void drawValueBarsHorizontal(List<EpidemicReport> timeReports) { if (valuesNames.size() != 1 || timeReports == null || timeReports.isEmpty()) { return; } String valueName = orderNames.get(0); LabeledHorizontalBarChart barChart = addHorizontalBarChart(); Map<String, String> palette = new HashMap(); for (int i = timeReports.size() - 1; i >= 0; i--) { EpidemicReport report = timeReports.get(i); String location = report.getLocationFullName(); String label = (multipleDatasets ? report.getDataSet() + " - " : "") + location; palette.put(label, settingsController.locationColors.get(location)); Number value = report.value(valueName); if (value == null) { continue; } double coordinateValue = FxmlControl.coordinateValue(chartCoordinate, value.doubleValue()); XYChart.Series series = new XYChart.Series(); series.setName(label); XYChart.Data item = new XYChart.Data(coordinateValue, label); series.getData().add(item); barChart.getData().add(series); if (labelType == LabelType.Pop) { FxmlControl.setTooltip(item.getNode(), label + " " + value); } } FxmlControl.setBarChartColors(barChart, palette, legendSide != null); } protected void drawMap(List<EpidemicReport> timeReports) { try { if (timeReports == null || timeReports.isEmpty()) { return; } webEngine.executeScript("clearMap();"); for (EpidemicReport report : timeReports) { GeographyCode location = report.getLocation(); if (!location.validCoordinate()) { continue; } if (!mapCentered) { webEngine.executeScript("setCenter(" + location.getLongitude() + "," + location.getLatitude() + ");"); mapCentered = true; } String name = "<font color=\"black\">" + (multipleDatasets ? report.getDataSet() + " - " : "") + location.getFullName() + "</font>"; String value = ""; for (String valueName : valuesNames) { value += " <font color=\"" + settingsController.color(valueName) + "\">" + report.value(valueName) + "</font> "; } String label; switch (labelType) { case Name: label = "<div>" + name + "</div>"; break; case NameAndValue: label = "<div>" + name + value + "</div>"; break; case NotDisplay: label = ""; break; case Value: default: label = value; break; } String info = popInfoCheck.isSelected() || labelType == LabelType.Pop ? "<div>" + name + value + "</div></br>" + location.info("</br>") : ""; markerSize = markSize(report.value(orderNames.get(0)).doubleValue()); LocationTools.addMarkerInGaoDeMap(webEngine, location.getLongitude(), location.getLatitude(), label, info, locationImage(), true, -99, markerSize, textSize); } } catch (Exception e) { logger.debug(e.toString()); } } // maximum marker size of GaoDe Map is 64 protected int markSize(double value) { if (maxValue == 0) { markerSize = 20; return markerSize; } double d, m; switch (chartCoordinate) { case LogarithmicE: d = Math.log(value); m = Math.log(maxValue); break; case Logarithmic10: d = Math.log10(value); m = Math.log10(maxValue); break; case SquareRoot: d = Math.sqrt(value); m = Math.sqrt(maxValue); break; default: d = value; m = maxValue; } markerSize = Math.min(60, Math.max(6, (int) (d * 60 / m))); return markerSize; } @FXML protected void snapAction() { try { snapWidth = settingsController.snapWidth; chartLoadTime = settingsController.chartLoadTime; dpi = settingsController.dpi; final SnapshotParameters snapPara; final double scale; double scalev = dpi / Screen.getPrimary().getDpi(); scale = scalev > 1 ? scalev : 1; snapPara = new SnapshotParameters(); snapPara.setFill(Color.WHITE); snapPara.setTransform(Transform.scale(scale, scale)); if (timer == null) { openImageViewer(snapWidth(snapPara, scale, Integer.MAX_VALUE, chartSnapBox)); return; } timer.cancel(); timer = null; List<File> snapshots = new ArrayList<>(); loading = openHandlingStage(Modality.WINDOW_MODAL); byte[] lock = new byte[0]; int speed = chartLoadTime + 100; timer = new Timer(); timer.schedule(new TimerTask() { private int index = 0; @Override public void run() { Platform.runLater(() -> { if (loading == null || loading.isIsCanceled()) { timer.cancel(); timer = null; drawChart(); return; } if (index > chartTimes.size() - 1) { timer.cancel(); timer = null; if (snapshots.isEmpty()) { return; } if (snapshots.size() == 1) { openImageViewer(snapshots.get(0)); } else { File file = FileTools.getTempFile(".gif"); if (loading != null) { loading.setInfo(message("Saving") + ": " + file); } ImageGifFile.writeImageFiles(snapshots, file, interval, true); if (file.exists()) { ImageGifViewerController controller = (ImageGifViewerController) openStage(CommonValues.ImageGifViewerFxml); controller.sourceFileChanged(file); } } if (loading != null) { loading.closeStage(); loading = null; } drawChart(); return; } if (loading != null) { loading.setInfo(message("Snapping") + ": " + (index + 1) + "/" + chartTimes.size()); } synchronized (lock) { drawFrame(index); try { Thread.sleep(100); } catch (Exception e) { } snapshots.add(snapWidth(snapPara, scale, snapWidth, chartSnapBox)); ++index; } }); } }, 0, speed + 100); } catch (Exception e) { logger.debug(e.toString()); } } protected File snapWidth(SnapshotParameters snapPara, double scale, int width, Region chart) { try { Bounds bounds = chart.getLayoutBounds(); int imageWidth = (int) Math.round(bounds.getWidth() * scale); int imageHeight = (int) Math.round(bounds.getHeight() * scale); Image snap = chart.snapshot(snapPara, new WritableImage(imageWidth, imageHeight)); BufferedImage bufferedImage = SwingFXUtils.fromFXImage(snap, null); if (bufferedImage.getWidth() > width) { bufferedImage = ImageManufacture.scaleImageWidthKeep(bufferedImage, snapWidth); } File tmpfile = FileTools.getTempFile(".png"); ImageFileWriters.writeImageFile(bufferedImage, "png", tmpfile.getAbsolutePath()); return tmpfile; } catch (Exception e) { logger.debug(e.toString()); return null; } } @FXML @Override public void clearAction() { webEngine.executeScript("clearMap();"); if (timer != null) { timer.cancel(); timer = null; } } @Override public String locationImage() { String path = "/" + ControlStyle.getIconPath(); return FxmlControl.getInternalFile(path + "iconCircle.png", "map", AppVariables.ControlColor.name() + "Circle.png").getAbsolutePath(); } protected void setPause(boolean setAsPaused) { if (setAsPaused) { ControlStyle.setIcon(pauseButton, ControlStyle.getIcon("iconPlay.png")); FxmlControl.setTooltip(pauseButton, new Tooltip(message("Continue"))); previousButton.setDisable(false); nextButton.setDisable(false); pauseButton.setUserData("paused"); } else { ControlStyle.setIcon(pauseButton, ControlStyle.getIcon("iconPause.png")); FxmlControl.setTooltip(pauseButton, new Tooltip(message("Pause"))); previousButton.setDisable(true); nextButton.setDisable(true); pauseButton.setUserData("playing"); } pauseButton.applyCss(); } @FXML public void pauseAction() { Platform.runLater(() -> { if (pauseButton.getUserData() != null && "paused".equals(pauseButton.getUserData())) { drawChart(); } else { setPause(true); if (timer != null) { timer.cancel(); timer = null; } chartIndex--; } }); } @FXML @Override public void previousAction() { if (timer != null) { timer.cancel(); timer = null; } drawFrame(chartIndex - 1); setPause(true); } @FXML @Override public void nextAction() { if (timer != null) { timer.cancel(); timer = null; } drawFrame(chartIndex + 1); setPause(true); } /* get/set */ public EpidemicReportsController getReportsController() { return reportsController; } public void setReportsController(EpidemicReportsController reportsController) { this.reportsController = reportsController; } public EpidemicReportsSettingsController getSettingsController() { return settingsController; } public void setSettingsController(EpidemicReportsSettingsController settingsController) { this.settingsController = settingsController; } }