Commit 907f8f6a authored by Nico Mack's avatar Nico Mack

Implementation of ColourScaleRenderer class

parent e8dff83a
......@@ -3,6 +3,7 @@ BELOW_ELEMENT=below
BLINK_ON_OUT_OF_RANGE_NODE=blinkOnOutOfRange
CAPPED_DISPLAY_NODE=cappedDisplay
COLOUR_SCALE_NODE=colourScale
DECIMALS_NODE=decimals
FACE_IS_TOUCHABLE_NODE=faceIsTouchable
ITEMS_VARIABLE_NODE=itemsVariable
HTML_TEMPLATE_NODE=htmlTemplate
......
......@@ -22,6 +22,7 @@ abstract public class Executor {
protected static final String RESULT_TEMPLATE_PLAIN = "Result <- {} = {}"; //$NON-NLS-1$
protected static final String RESULT_TEMPLATE_EQUATE = "Result <- {}'{'{}'}' = {}"; //$NON-NLS-1$
protected static final String RESULT_TEMPLATE_INDEX = "Result <- {}'[{}]' = {}"; //$NON-NLS-1$
protected static final String RESULT_TEMPLATE_SCALAR_INDEX = "Result <- {} = [{}] = {}"; //$NON-NLS-1$
public Executor() {
executionErrors = new CharArrayWriter();
......
......@@ -38,20 +38,19 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
private static final Map<Integer, String> SCALAR_MAPPING = new HashMap<>();
static {
SCALAR_MAPPING.put(REXP.XT_BOOL, "asBool");
SCALAR_MAPPING.put(REXP.XT_DOUBLE, "asDouble");
SCALAR_MAPPING.put(REXP.XT_INT, "asInt");
SCALAR_MAPPING.put(REXP.XT_STR, "asString");
SCALAR_MAPPING.put(REXP.XT_BOOL, "asBool"); //$NON-NLS-1$
SCALAR_MAPPING.put(REXP.XT_DOUBLE, "asDouble"); //$NON-NLS-1$
SCALAR_MAPPING.put(REXP.XT_INT, "asInt"); //$NON-NLS-1$
SCALAR_MAPPING.put(REXP.XT_STR, "asString"); //$NON-NLS-1$
}
private static final Map<Integer, String> ARRAY_MAPPING = new HashMap<>();
static {
ARRAY_MAPPING.put(REXP.XT_ARRAY_DOUBLE, "asDoubleArray");
ARRAY_MAPPING.put(REXP.XT_ARRAY_INT, "asIntArray");
ARRAY_MAPPING.put(REXP.XT_ARRAY_STR, "asStringArray");
ARRAY_MAPPING.put(REXP.XT_ARRAY_DOUBLE, "asDoubleArray"); //$NON-NLS-1$
ARRAY_MAPPING.put(REXP.XT_ARRAY_INT, "asIntArray"); //$NON-NLS-1$
ARRAY_MAPPING.put(REXP.XT_ARRAY_STR, "asStringArray"); //$NON-NLS-1$
}
private static final String[] R_ARGS = new String[] {"--vanilla"}; //$NON-NLS-1$
private static final String CLEAR_WORKSPACE_CMD = "rm(list = ls())"; //$NON-NLS-1$
private static final String JRI_PATH = "r.jri.path"; //$NON-NLS-1$
......@@ -69,17 +68,20 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
super(properties);
addLibraryPath(properties.getProperty(JRI_PATH));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("JRI Path => {}", properties.getProperty(JRI_PATH)); //$NON-NLS-1$
}
StringBuilder workingDir = new StringBuilder("setwd(\"").append(properties.getProperty(R_WORKING_DIR)).append("\")");
StringBuilder workingDir = new StringBuilder("setwd(\"").append(properties.getProperty(R_WORKING_DIR)).append("\")"); //$NON-NLS-1$ //$NON-NLS-2$
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("R Working Dir => {}", properties.getProperty(R_WORKING_DIR)); //$NON-NLS-1$
}
engine = new Rengine(R_ARGS, false, this);
if (!engine.waitForR()) {
LOGGER.error("Failed to load R Engine"); //$NON-NLS-1$
LOGGER.error("Failed to load R Engine!"); //$NON-NLS-1$
}
engine.eval(workingDir.toString());
if (LOGGER.isInfoEnabled()) {
LOGGER.info(workingDir.toString());
}
}
// ---------------------------------------------------------------------------
......@@ -87,6 +89,16 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
// * Primitive(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Assigns the specified parameter array to a symbol in R identified by the specified identifier.
*
* @param identifier
* specifies the name of the R symbol to assign the array to.
* @param parameter
* specifies the array of values to assign to the R symbol.
* @return <code>true</code> if assigning was successful, <code>false</code> otherwise.
*/
// ---------------------------------------------------------------------------
private <T> boolean assign(String identifier, T[] parameter) {
boolean success = false;
......@@ -109,6 +121,14 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
}
// ---------------------------------------------------------------------------
/**
* Retrieves the value of the R symbol specified by identifier.
*
* @param identifier
* specifies the name of the R symbol to retrieve the value of
* @return a array holding the retrieved value
*/
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
private <T> T[] retrieve(String identifier) {
......@@ -123,7 +143,6 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
array = (T[]) Array.newInstance(value.getClass(), 1);
array[0] = value;
} else if (ARRAY_MAPPING.containsKey(result.getType())) {
Object[] arguments = new Object[1];
Class<?>[] argumentTypes = new Class<?>[1];
......@@ -140,11 +159,20 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
return array;
}
// ---------------------------------------------------------------------------
/**
* Converts scalar values into R single value arrays.
*
* @param clazz
* specifies the class of the array to create.
* @param value
* specifies the value of the scalar to be placed into single value array.
* @return A single value array with the specified value at position 0.
*/
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
private <T> T[] rify(Class<T> clazz, T value) {
private <T> T[] rifyScalar(Class<T> clazz, T value) {
T[] array = (T[]) Array.newInstance(clazz, 1);
array[0] = value;
return array;
......@@ -160,7 +188,7 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
T value = scalar.getValue();
Class<T> clazz = scalar.getContentClass();
T[] array = this.rify(clazz, value);
T[] array = this.rifyScalar(clazz, value);
success = this.assign(identifier, array);
return success;
......@@ -184,15 +212,17 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
if (index >= 0) {
if (index < array.length) {
scalar.setValue(array[index]);
if (LOGGER.isInfoEnabled()) {
LOGGER.info(RESULT_TEMPLATE_SCALAR_INDEX, scalar.getName(), index, scalar.getValue()); // $NON-NLS-1$
}
} else {
LOGGER.error("Index {} is out of range! {} >= {}!", index, index, array.length);
LOGGER.error("Index {} is out of range! {} >= {}!", index, index, array.length); //$NON-NLS-1$
}
} else {
scalar.setValue(array[0]);
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info(this.RESULT_TEMPLATE_PLAIN, scalar.getName(), scalar.getValue()); // $NON-NLS-1$
if (LOGGER.isInfoEnabled()) {
LOGGER.info(RESULT_TEMPLATE_PLAIN, scalar.getName(), scalar.getValue()); // $NON-NLS-1$
}
}
return scalar;
......@@ -211,7 +241,7 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
if (!vector.isEmpty()) {
Class<T> clazz = (Class<T>) vector.getClassOfValues();
if (index >= 0) {
array = this.rify(clazz, vector.get(index));
array = this.rifyScalar(clazz, vector.get(index));
success = this.assign(identifier, array);
} else {
List<T> values = vector.getValue();
......@@ -243,7 +273,7 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
if (index >= 0) {
vector.set(index, array[0]);
if (LOGGER.isInfoEnabled()) {
LOGGER.info(this.RESULT_TEMPLATE_INDEX, vector.getName(), index, array[0]); // $NON-NLS-1$
LOGGER.info(RESULT_TEMPLATE_INDEX, vector.getName(), index, array[0]); // $NON-NLS-1$
}
} else {
vector.suspendListenerNotification(true);
......@@ -256,10 +286,8 @@ public class RExecutor extends Executor implements RMainLoopCallbacks {
vector.notifyInputChangeListeners();
if (LOGGER.isInfoEnabled()) {
LOGGER.info(this.RESULT_TEMPLATE_PLAIN, vector.getName(), array); // $NON-NLS-1$
LOGGER.info(RESULT_TEMPLATE_PLAIN, vector.getName(), array); // $NON-NLS-1$
}
}
return vector;
......
......@@ -34,6 +34,8 @@ public class CpsNamespace extends NLS {
public static String COLOUR_SCALE_NODE;
public static String FACE_IS_TOUCHABLE_NODE;
public static String DECIMALS_NODE;
public static String HTML_TEMPLATE_NODE;
public static String ITEMS_VARIABLE_NODE;
......
......@@ -33,6 +33,7 @@ import java.awt.Color;
public class ColourMapping<T extends Comparable<T>> {
private Color colour;
private ValueRange<T> range;
private String label;
// ***************************************************************************
// * Constants *
......@@ -131,6 +132,31 @@ public class ColourMapping<T extends Comparable<T>> {
return inRange;
}
// ---------------------------------------------------------------------------
/**
* Simple getter method for label.
*
* @return The value of label.
*/
// ---------------------------------------------------------------------------
public String getLabel() {
return label;
}
// ---------------------------------------------------------------------------
/**
* Simple setter method for label.
*
* @param label
* The value to set label to.
*/
// ---------------------------------------------------------------------------
public void setLabel(String label) {
this.label = label;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of class *
......
......@@ -22,11 +22,15 @@ import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.StringUtils;
import lu.list.itis.dkd.tui.utility.ValueRange;
import com.jgoodies.common.base.Strings;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
......@@ -45,6 +49,8 @@ public class ColourScale<T extends Comparable<T>> {
private String name;
private List<ColourMapping<T>> mappings;
private Color defaultColour;
private String unit;
private DecimalFormat format;
// ***************************************************************************
// * Constants *
......@@ -63,12 +69,31 @@ public class ColourScale<T extends Comparable<T>> {
public ColourScale() {
this.mappings = new ArrayList<>();
this.defaultColour = Color.WHITE;
format = new DecimalFormat();
format.setDecimalSeparatorAlwaysShown(false);
format.setMaximumFractionDigits(2);
format.setGroupingUsed(true);
DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
symbols.setGroupingSeparator('.');
symbols.setDecimalSeparator(',');
format.setDecimalFormatSymbols(symbols);
}
// ---------------------------------------------------------------------------
public ColourScale(Element rootElement) throws BuildException {
this.mappings = new ArrayList<>();
format = new DecimalFormat();
format.setDecimalSeparatorAlwaysShown(false);
format.setMaximumFractionDigits(2);
format.setGroupingUsed(true);
DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
symbols.setGroupingSeparator('.');
symbols.setDecimalSeparator(',');
format.setDecimalFormatSymbols(symbols);
this.buildFromBootstrap(rootElement);
}
......@@ -81,6 +106,13 @@ public class ColourScale<T extends Comparable<T>> {
private void buildFromBootstrap(@Nullable Element rootElement) throws BuildException {
name = BootstrappingUtils.getAttributeAsString(rootElement, Externalization.NAME_NODE, BootstrappingUtils.MANDATORY, null);
unit = BootstrappingUtils.getAttributeAsString(rootElement, Externalization.UNIT_NODE, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING);
int decimals = BootstrappingUtils.getAttributeAsInteger(rootElement, CpsNamespace.DECIMALS_NODE, BootstrappingUtils.OPTIONAL, -1);
if (decimals >= 0) {
this.format.setMinimumFractionDigits(decimals);
this.format.setMaximumFractionDigits(decimals);
}
defaultColour = new Color(StringUtils.getIntegerValue(BootstrappingUtils.getAttributeAsString(rootElement, Externalization.DEFAULT_NODE, BootstrappingUtils.OPTIONAL, "0xFFFFFF")));
List<Element> mappingElements = rootElement.getChildren(CpsNamespace.MAPPING_NODE);
......@@ -114,6 +146,11 @@ public class ColourScale<T extends Comparable<T>> {
ValueRange<Double> range = new ValueRange<>(lowerBound, upperBound);
mapping = (ColourMapping<T>) new ColourMapping<>(range, colour);
String label = BootstrappingUtils.getAttributeAsString(rootElement, CpsNamespace.LABEL_NODE, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING);
if (Strings.isNotBlank(label)) {
mapping.setLabel(label);
}
return mapping;
}
......@@ -129,6 +166,11 @@ public class ColourScale<T extends Comparable<T>> {
// ---------------------------------------------------------------------------
public String getUnit() {
return this.unit;
}
// ---------------------------------------------------------------------------
public boolean addMapping(ColourMapping<T> mapping) {
return this.mappings.add(mapping);
}
......@@ -177,6 +219,42 @@ public class ColourScale<T extends Comparable<T>> {
}
return this.defaultColour;
}
// ---------------------------------------------------------------------------
/**
* Returns a list of all mappings present in this colour scale.
*
* @return
*/
// ---------------------------------------------------------------------------
public List<ColourMapping<T>> getColourMappings() {
List<ColourMapping<T>> cloned = new ArrayList<>(mappings);
return cloned;
}
// ---------------------------------------------------------------------------
/**
*
* @param mapping
* @return
*/
// ---------------------------------------------------------------------------
public String getLabelFor(ColourMapping<T> mapping) {
StringBuilder labelBuilder = new StringBuilder();
if (Strings.isNotBlank(mapping.getLabel())) {
labelBuilder.append(mapping.getLabel());
} else {
ValueRange<T> range = mapping.getRange();
labelBuilder.append(format.format(range.getLowerValue()));
labelBuilder.append(" - "); //$NON-NLS-1$
labelBuilder.append(format.format(range.getUpperValue()));
}
return labelBuilder.toString();
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class *
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2018. All rights reserved. If you wish
* to use this code for any purpose, please contact the author(s).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package lu.list.itis.dkd.tui.utility.scales;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
/**
* @author Nico Mack [nico.mack@list.lu]
* @since 2.5
* @version 2.5.0
* @param <T>
* @param <T>
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ColourScaleRenderer {
private ColourScale<?> scale;
private Shape mappingShape;
private Color fontColour;
private Font mappingFont;
private int horizontalGap = 5;
private int verticalGap = 5;
private double lineHeight;
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final RenderingHints renderingHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
public ColourScaleRenderer(ColourScale<?> scale, Shape shape, Font font) {
this.scale = scale;
this.mappingShape = shape;
this.mappingFont = font;
this.fontColour = Color.BLACK;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitive(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
private <T extends Comparable<T>> Dimension determineRequiredDimensions(ColourScale<T> scale) {
List<ColourMapping<T>> mappings = scale.getColourMappings();
int widest = 0;
int highest = 0;
FontRenderContext frc = new FontRenderContext(null, false, false);
Rectangle2D shapeBounds = mappingShape.getBounds2D();
highest = (int) shapeBounds.getHeight();
for (ColourMapping<T> mapping : mappings) {
String label = scale.getLabelFor(mapping);
GlyphVector glyphVector = mappingFont.createGlyphVector(frc, label);
Rectangle2D bounds = glyphVector.getLogicalBounds();
int width = (int) bounds.getWidth();
int height = (int) bounds.getHeight();
highest = Math.max(height, highest);
widest = Math.max(width, widest);
}
lineHeight = highest;
int height = (highest + verticalGap) * mappings.size();
int width = (int) shapeBounds.getWidth() + horizontalGap + widest;
return new Dimension(width, height);
}
// ---------------------------------------------------------------------------
public <T extends Comparable<T>> BufferedImage renderScale(ColourScale<T> scale) {
BufferedImage rendered;
Dimension required = this.determineRequiredDimensions(scale);
rendered = new BufferedImage(required.width, required.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D offscreen = rendered.createGraphics();
offscreen.setRenderingHints(renderingHints);
FontMetrics metrics = offscreen.getFontMetrics(mappingFont);
Rectangle2D shapeBounds = mappingShape.getBounds2D();
List<ColourMapping<T>> mappings = scale.getColourMappings();
double offset = (double) required.height / mappings.size();
double fontBaseLineOffset = metrics.getAscent() / 2;
offscreen.translate(-shapeBounds.getX(), (offset / 2));
offscreen.setFont(mappingFont);
for (ColourMapping<T> mapping : mappings) {
offscreen.setPaint(mapping.getColour());
offscreen.fill(mappingShape);
offscreen.setPaint(fontColour);
offscreen.drawString(scale.getLabelFor(mapping), (int) (shapeBounds.getX() + shapeBounds.getWidth() + horizontalGap), (int) fontBaseLineOffset);
offscreen.translate(0, offset);
}
File outputfile = new File("/Users/mack/Desktop/dump.png");
try {
ImageIO.write(rendered, "png", outputfile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return rendered;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment