|
1 /** |
|
2 * SQL-DK |
|
3 * Copyright © 2015 František Kučera (frantovo.cz) |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, either version 3 of the License, or |
|
8 * (at your option) any later version. |
|
9 * |
|
10 * This program is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 * GNU General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License |
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 */ |
|
18 package info.globalcode.sql.dk.formatting; |
|
19 |
|
20 import info.globalcode.sql.dk.Functions; |
|
21 import info.globalcode.sql.dk.configuration.PropertyDeclaration; |
|
22 import info.globalcode.sql.dk.logging.LoggerProducer; |
|
23 import java.math.BigDecimal; |
|
24 import java.math.MathContext; |
|
25 import java.math.RoundingMode; |
|
26 import java.util.List; |
|
27 import java.util.logging.Level; |
|
28 import java.util.logging.Logger; |
|
29 |
|
30 /** |
|
31 * TODO: min/max values – range for case that no value is 100 % |
|
32 * |
|
33 * TODO: multiple barcharts in same table (last column is still default) + multiple resultsets |
|
34 * |
|
35 * TODO: negative values - bar starting from the middle, not always from the left |
|
36 * |
|
37 * @author Ing. František Kučera (frantovo.cz) |
|
38 */ |
|
39 @PropertyDeclaration(name = BarChartFormatter.PROPERTY_PRECISION, type = Integer.class, defaultValue = BarChartFormatter.PROPERTY_PRECISION_DEFAULT, description = "number of characters representing 100 % in the bar chart") |
|
40 public class BarChartFormatter extends TabularPrefetchingFormatter { |
|
41 |
|
42 public static final String NAME = "barchart"; // bash-completion:formatter |
|
43 public static final String PROPERTY_PRECISION = "precision"; |
|
44 protected static final String PROPERTY_PRECISION_DEFAULT = "100"; |
|
45 private static final MathContext mathContext = MathContext.DECIMAL128; |
|
46 public static final Logger log = LoggerProducer.getLogger(); |
|
47 private final BigDecimal chartPrecision; |
|
48 private final char chartFull; |
|
49 private final char chartEmpty; |
|
50 |
|
51 public BarChartFormatter(FormatterContext formatterContext) { |
|
52 super(formatterContext); |
|
53 chartPrecision = BigDecimal.valueOf(formatterContext.getProperties().getInteger(PROPERTY_PRECISION, Integer.parseInt(PROPERTY_PRECISION_DEFAULT))); |
|
54 chartFull = isAsciiNostalgia() ? '#' : '█'; |
|
55 chartEmpty = isAsciiNostalgia() ? '~' : '░'; |
|
56 // TODO: consider using partial blocks for more precision: https://en.wikipedia.org/wiki/Block_Elements |
|
57 } |
|
58 |
|
59 @Override |
|
60 protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) { |
|
61 super.postprocessPrefetchedResultSet(currentHeader, currentResultSet); |
|
62 |
|
63 updateColumnWidth(currentHeader.getColumnCount(), chartPrecision.intValue()); |
|
64 |
|
65 BigDecimal maximum = BigDecimal.ZERO; |
|
66 BigDecimal minimum = BigDecimal.ZERO; |
|
67 int lastIndex = currentHeader.getColumnCount() - 1; |
|
68 |
|
69 Object valueObject = null; |
|
70 try { |
|
71 for (Object[] row : currentResultSet) { |
|
72 valueObject = row[lastIndex]; |
|
73 if (valueObject != null) { |
|
74 BigDecimal value = new BigDecimal(valueObject.toString()); |
|
75 maximum = maximum.max(value); |
|
76 minimum = minimum.min(value); |
|
77 } |
|
78 } |
|
79 |
|
80 BigDecimal range = maximum.subtract(minimum); |
|
81 |
|
82 for (Object[] row : currentResultSet) { |
|
83 valueObject = row[lastIndex]; |
|
84 if (valueObject == null) { |
|
85 row[lastIndex] = ""; |
|
86 } else { |
|
87 BigDecimal value = new BigDecimal(valueObject.toString()); |
|
88 BigDecimal valueFromMinimum = value.subtract(minimum); |
|
89 |
|
90 BigDecimal points = chartPrecision.divide(range, mathContext).multiply(valueFromMinimum, mathContext); |
|
91 int pointsRounded = points.setScale(0, RoundingMode.HALF_UP).intValue(); |
|
92 row[lastIndex] = Functions.repeat(chartFull, pointsRounded) + Functions.repeat(chartEmpty, chartPrecision.intValue() - pointsRounded); |
|
93 } |
|
94 } |
|
95 |
|
96 } catch (NumberFormatException e) { |
|
97 // https://en.wiktionary.org/wiki/parsable |
|
98 log.log(Level.SEVERE, "Last column must be number or an object with toString() value parsable to a number. But was „{0}“", valueObject); |
|
99 // FIXME: throw FormatterException |
|
100 throw e; |
|
101 } |
|
102 } |
|
103 |
|
104 } |