diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java Mon Mar 04 20:15:24 2019 +0100 @@ -0,0 +1,104 @@ +/** + * SQL-DK + * Copyright © 2015 František Kučera (frantovo.cz) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package info.globalcode.sql.dk.formatting; + +import info.globalcode.sql.dk.Functions; +import info.globalcode.sql.dk.configuration.PropertyDeclaration; +import info.globalcode.sql.dk.logging.LoggerProducer; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * TODO: min/max values – range for case that no value is 100 % + * + * TODO: multiple barcharts in same table (last column is still default) + multiple resultsets + * + * TODO: negative values - bar starting from the middle, not always from the left + * + * @author Ing. František Kučera (frantovo.cz) + */ +@PropertyDeclaration(name = BarChartFormatter.PROPERTY_PRECISION, type = Integer.class, defaultValue = BarChartFormatter.PROPERTY_PRECISION_DEFAULT, description = "number of characters representing 100 % in the bar chart") +public class BarChartFormatter extends TabularPrefetchingFormatter { + + public static final String NAME = "barchart"; // bash-completion:formatter + public static final String PROPERTY_PRECISION = "precision"; + protected static final String PROPERTY_PRECISION_DEFAULT = "100"; + private static final MathContext mathContext = MathContext.DECIMAL128; + public static final Logger log = LoggerProducer.getLogger(); + private final BigDecimal chartPrecision; + private final char chartFull; + private final char chartEmpty; + + public BarChartFormatter(FormatterContext formatterContext) { + super(formatterContext); + chartPrecision = BigDecimal.valueOf(formatterContext.getProperties().getInteger(PROPERTY_PRECISION, Integer.parseInt(PROPERTY_PRECISION_DEFAULT))); + chartFull = isAsciiNostalgia() ? '#' : '█'; + chartEmpty = isAsciiNostalgia() ? '~' : '░'; + // TODO: consider using partial blocks for more precision: https://en.wikipedia.org/wiki/Block_Elements + } + + @Override + protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List currentResultSet) { + super.postprocessPrefetchedResultSet(currentHeader, currentResultSet); + + updateColumnWidth(currentHeader.getColumnCount(), chartPrecision.intValue()); + + BigDecimal maximum = BigDecimal.ZERO; + BigDecimal minimum = BigDecimal.ZERO; + int lastIndex = currentHeader.getColumnCount() - 1; + + Object valueObject = null; + try { + for (Object[] row : currentResultSet) { + valueObject = row[lastIndex]; + if (valueObject != null) { + BigDecimal value = new BigDecimal(valueObject.toString()); + maximum = maximum.max(value); + minimum = minimum.min(value); + } + } + + BigDecimal range = maximum.subtract(minimum); + + for (Object[] row : currentResultSet) { + valueObject = row[lastIndex]; + if (valueObject == null) { + row[lastIndex] = ""; + } else { + BigDecimal value = new BigDecimal(valueObject.toString()); + BigDecimal valueFromMinimum = value.subtract(minimum); + + BigDecimal points = chartPrecision.divide(range, mathContext).multiply(valueFromMinimum, mathContext); + int pointsRounded = points.setScale(0, RoundingMode.HALF_UP).intValue(); + row[lastIndex] = Functions.repeat(chartFull, pointsRounded) + Functions.repeat(chartEmpty, chartPrecision.intValue() - pointsRounded); + } + } + + } catch (NumberFormatException e) { + // https://en.wiktionary.org/wiki/parsable + log.log(Level.SEVERE, "Last column must be number or an object with toString() value parsable to a number. But was „{0}“", valueObject); + // FIXME: throw FormatterException + throw e; + } + } + +}