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