All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tri.covid19.coda.history.HistoryPanelPlots.kt Maven / Gradle / Ivy

@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")

/*-
 * #%L
 * coda-app
 * --
 * Copyright (C) 2020 - 2021 Elisha Peterson
 * --
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package tri.covid19.coda.history

import com.sun.javafx.charts.Legend
import javafx.beans.binding.Bindings
import javafx.geometry.Pos
import tornadofx.*
import tri.covid19.CASES
import tri.covid19.DEATHS
import tri.covid19.coda.charts.*
import tri.covid19.coda.installStandardHoverAndTooltip
import tri.covid19.coda.utils.*
import kotlin.time.ExperimentalTime

/**
 * View with charts showing line plots for several selected regions.
 */
@ExperimentalTime
class HistoryPanelPlots constructor(val historyPanelModel: HistoryPanelModel, val hubbertPanelModel: HistoryHubbertModel) : View() {

    private lateinit var standardChart: TimeSeriesChart
    private lateinit var doublingTotalChart: DoublingTotalChart
    private lateinit var hubbertChart: HubbertChart
    private lateinit var deathCaseChart: DeathCaseChart
    private lateinit var legend: Legend

    override val root = borderpane {
        center = gridpane {
            row {
                standardChart = timeserieschart("Historical Data", "Day", "Count", historyPanelModel.logScale)
                doublingTotalChart = doublingtotalchart("Daily Count vs Doubling Time", "Doubling Time", "Daily Count")
            }
            row {
                hubbertChart = hubbertChart("Percent Growth vs Total", "Total", "Percent Growth")
                deathCaseChart = deathcasechart("Confirmed Cases vs Deaths", "Deaths", "Confirmed Cases")
            }
        }
        bottom = hbox(alignment = Pos.CENTER) {
            legend = Legend()
            legend.alignment = Pos.CENTER
            val chartLegend = standardChart.childrenUnmodifiable.first { it is Legend } as Legend
            Bindings.bindContent(legend.items, chartLegend.items)
            this += legend
        }
    }

    init {
        historyPanelModel.onChange = { updateAllCharts() }
        hubbertPanelModel.onChange = { updateAllCharts() }
        historyPanelModel._logScale.onChange { resetChartAxes() }
        updateAllCharts()
    }

    /** Resets positioning of chart, must do when axes change. */
    private fun resetChartAxes() {
        // TODO - this needs to reset and relink the legend
        val parent = standardChart.parent
        hubbertChart.removeFromParent()
        standardChart.removeFromParent()
        standardChart = timeserieschart("Historical Data", "Day", "Count", historyPanelModel.logScale)
        parent.add(hubbertChart)
        updateStandardChart()
    }

    /** Update all charts. */
    private fun updateAllCharts() {
        if (this::standardChart.isInitialized) {
            updateStandardChart()
            updateDoubleTotalChart()
            updateHubbertChart()
            updateDeathCaseChart()
        }
    }

    //region CHART UPDATERS

    private fun updateStandardChart() {
        val (domain, series) = historyPanelModel.historicalDataSeries()
        standardChart.setTimeSeries(domain, series)
        standardChart.lineWidth = lineChartWidthForCount(series.size)

        if (hubbertPanelModel.showPeakCurve.get() && historyPanelModel.perDay) {
            val peak = hubbertPanelModel.peakValue
            val label = hubbertPanelModel.peakLabel
            val peakSeries = domain.mapIndexed { i, _ -> xy(i, peak) }
            standardChart.series(label, peakSeries.asObservable()).also {
                it.nodeProperty().get().style = "-fx-stroke-width: 8px; -fx-stroke: #88888888"
            }
        }

        standardChart.installStandardHoverAndTooltip()
    }

    private fun updateHubbertChart() {
        val series = historyPanelModel.hubbertDataSeries()
        hubbertChart.dataSeries = series
        hubbertChart.lineWidth = lineChartWidthForCount(series.size)

        if (hubbertPanelModel.showPeakCurve.get()) {
            val max = series.mapNotNull { it.points.map { it.first.toDouble() }.maxOrNull() }.maxOrNull()
            if (max != null) {
                val peak = hubbertPanelModel.peakValue
                val label = hubbertPanelModel.peakLabel
                val min = peak / 0.3
                val peakSeries = (0..100).map { min + (max - min) * it / 100.0 }.map { xy(it, peak / it) }
                hubbertChart.series(label, peakSeries.asObservable()).also {
                    it.nodeProperty().get().style = "-fx-stroke-width: 8px; -fx-stroke: #88888888"
                }
            }
        }

        hubbertChart.installStandardHoverAndTooltip()
    }

    private fun updateDoubleTotalChart() {
        with (doublingTotalChart) {
            series = historyPanelModel.smoothedData()
            lineWidth = lineChartWidthForCount(data.size)
            installStandardHoverAndTooltip()
        }
    }

    private fun updateDeathCaseChart() {
        with (deathCaseChart) {
            val perDay = historyPanelModel.perDay
            val deaths = historyPanelModel.smoothedData(metric = DEATHS)
            val cases = historyPanelModel.smoothedData(metric = CASES)
            series = if (perDay) deaths.map { it.deltas() } to cases.map { it.deltas() } else deaths to cases
            lineWidth = lineChartWidthForCount(data.size)
            installStandardHoverAndTooltip()
        }
    }

    //endregion

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy