package gui.views; import java.awt.Color; import java.util.LinkedList; import org.apache.commons.lang3.ArrayUtils; import controller.DataHub; import controller.ElementData; import controller.Feature; import controller.Group; import db.DatabaseAccessException; /** * Different utility methods for views. */ public final class ViewUtils { /** * Controls threshold of flips when sorting features. */ public static final float AUTOSORT_FLIP_THRESHOLD = 0.7f; /** * Private constructor to avoid construction. */ private ViewUtils() { // utility class, do not construct throw new AssertionError(); } /** * Calculate color of an element depending on groups and feature values. * * @param ed * element data. * @return color of the element in rgba notation. */ public static Color calcColor(ElementData ed) { Color c = new Color(0.f, 0.f, 0.f, 1.f); Group[] groups = ed.getGroups(); if (groups.length > 0) { int partsN = groups.length; float[] partsRed = new float[partsN]; float[] partsGreen = new float[partsN]; float[] partsBlue = new float[partsN]; float[] partsAlpha = new float[partsN]; for (int i = 0; i < partsN; i++) { int rgba = groups[i].getColor(); partsBlue[i] = (rgba & 0xff) / 255.f; partsGreen[i] = ((rgba >> 8) & 0xff) / 255.f; partsRed[i] = ((rgba >> 16) & 0xff) / 255.f; partsAlpha[i] = ((rgba >> 24) & 0xff) / 255.f; Feature feature = groups[i].getColorFeature(); if (feature != null) { float value = ed.getValue(feature); if (!Float.isNaN(value)) { float min = feature.getMinValue(); float max = feature.getMaxValue(); // scale alpha linear partsAlpha[i] *= (value - min) / (max - min); } } } float red = combineComponent(partsRed, partsAlpha, partsN); float green = combineComponent(partsGreen, partsAlpha, partsN); float blue = combineComponent(partsBlue, partsAlpha, partsN); float alpha = combineAlpha(partsAlpha, partsN); c = new Color(red, green, blue, alpha); } return c; } /** * Combine multiple values of an color component (red, green or blue). * * @param partsComponent * values of the component. * @param partsAlpha * alpha values. * @param partsN * number of values. * @return combined value */ public static float combineComponent(float[] partsComponent, float[] partsAlpha, int partsN) { float sumComponent = 0.f; float sumAlpha = 0.f; int count = 0; for (int i = 0; i < partsN; i++) { if ((!Float.isNaN(partsComponent[i])) && (!Float.isNaN(partsAlpha[i]))) { sumComponent += partsComponent[i] * partsAlpha[i]; sumAlpha += partsAlpha[i]; count++; } } // fall back to the simple method without partsAlpha if ((count <= 0) || (sumAlpha <= 0.f)) { count = 0; for (int i = 0; i < partsN; i++) { if (!Float.isNaN(partsComponent[i])) { sumComponent += partsComponent[i]; count++; } } sumAlpha = count; } if (count > 0) { return sumComponent / sumAlpha; } else { return 0.f; } } /** * Combine multiple alpha values. * * @param partsAlpha * alpha values. * @param partsN * number of values. * @return combined alpha */ public static float combineAlpha(float[] partsAlpha, int partsN) { float sumAlpha = 0.f; int count = 0; for (int i = 0; i < partsN; i++) { if (!Float.isNaN(partsAlpha[i])) { sumAlpha += partsAlpha[i]; count++; } } if (count > 0) { return sumAlpha / count; } else { return 0.f; } } /** * Sort all features by covariance. * * @param features * features to sort. * @param dataHub * DataHub for data access. * @return sorted feature list * * @throws DatabaseAccessException * thrown if there is an error on database access. */ public static Feature[] autoSort(Feature[] features, DataHub dataHub) throws DatabaseAccessException { if ((features == null) || (features.length < 3)) { return features; } int featureCount = features.length; ElementData[] data = dataHub.getData(); int elementCount = data.length; if (elementCount <= 1) { return features; } // E[X] = 1/n * Sum(i=1,n,x_i) float[] e = new float[featureCount]; for (int i = 0; i < featureCount; i++) { int count = 0; for (int j = 0; j < elementCount; j++) { float value = data[j].getValue(features[i]); if (!Float.isNaN(value)) { e[i] += value; count++; } } if (count > 0) { e[i] /= count; } else { e[i] = 0.f; } } // E[X*Y] = 1/n * Sum(i=1,n,x_i*y_i) float[][] eMatrix = new float[featureCount][featureCount]; for (int i = 0; i < featureCount; i++) { for (int j = 0; j < featureCount; j++) { int count = 0; for (int k = 0; k < elementCount; k++) { float value1 = data[k].getValue(features[i]); float value2 = data[k].getValue(features[j]); if ((!Float.isNaN(value1)) && (!Float.isNaN(value2))) { eMatrix[i][j] += value1 * value2; count++; } } if (count > 0) { eMatrix[i][j] /= count; } else { eMatrix[i][j] = 0.f; } } } // Cov(X,Y) = E[X*Y] - E[X] * E[Y] float[][] c = new float[featureCount][featureCount]; for (int i = 0; i < featureCount; i++) { for (int j = 0; j < featureCount; j++) { c[i][j] = eMatrix[i][j] - e[i] * e[j]; } } // Var(X) = 1/(n-1) * Sum(i=1,n,(x_i - E[X])^2) float[] v = new float[featureCount]; for (int i = 0; i < featureCount; i++) { int count = 0; for (int j = 0; j < elementCount; j++) { float value = data[j].getValue(features[i]); if (!Float.isNaN(value)) { float x = value - e[i]; v[i] += x * x; count++; } } if (count > 1) { v[i] /= elementCount - 1; } else { v[i] = 0.f; } } // p(x,y) = C(X,Y) / sqrt(V(X)*V(Y)) float[][] p = new float[featureCount][featureCount]; for (int i = 0; i < featureCount; i++) { for (int j = 0; j < featureCount; j++) { p[i][j] = (float) (c[i][j] / Math.sqrt(v[i] * v[j])); } } // sort, so that sum of p is very high int[] best = new int[featureCount]; float startMax = Float.NEGATIVE_INFINITY; for (int i = 0; i < featureCount; i++) { for (int j = 0; j < featureCount; j++) { if ((i != j) && (p[i][j] > startMax)) { best[0] = i; best[1] = j; startMax = p[i][j]; } } } for (int i = 2; i < featureCount; i++) { float max = Float.NEGATIVE_INFINITY; int last = best[i - 1]; for (int j = 0; j < featureCount; j++) { float x = p[last][j]; if (x < 0.f) { x *= -1.f * AUTOSORT_FLIP_THRESHOLD; } if (x > max) { boolean notInList = true; for (int k = 0; (k < i) && notInList; k++) { if (best[k] == j) { notInList = false; } } if (notInList) { max = x; best[i] = j; } } } } Feature[] sortedFeatures = new Feature[featureCount]; for (int i = 0; i < featureCount; i++) { sortedFeatures[i] = features[best[i]]; } return sortedFeatures; } /** * Convert a number into a nice number. * * @param x * the number to convert. * @param round * controls if the number should rounded. * @return the nice number */ public static float niceNum(float x, boolean round) { int exp = (int) Math.floor(Math.log10(x)); float f = (float) (x / Math.pow(10, exp)); float nf; if (round) { if (f < 1.5f) { nf = 1.f; } else if (f < 3.f) { nf = 2.f; } else if (f < 7.f) { nf = 5.f; } else { nf = 10.f; } } else { if (f < 1.f) { nf = 1.f; } else if (f < 2.f) { nf = 2.f; } else if (f < 5.f) { nf = 5.f; } else { nf = 10.f; } } return (float) (nf * Math.pow(10, exp)); } /** * Generate axis markers. * * @param a * minimum axis value. * @param b * maximum axis value. * @param length * axis length. * @param pixelsPerTick * markers per pixel. * @return marker values, first value in array is nfrac */ public static float[] calcAxisMarkers(float a, float b, float length, float pixelsPerTick) { // parse input params boolean reverse = false; float minVar = a; float maxVar = b; if (minVar > maxVar) { reverse = true; minVar = b; maxVar = a; } // calc ticks, borders, steps int ntick = (int) (length / pixelsPerTick); float range = niceNum(maxVar - minVar, false); float d = niceNum(range / (ntick - 1), true); float graphMin = (float) (Math.floor(minVar / d) * d); float graphMax = (float) (Math.ceil(maxVar / d) * d); int nfrac = Math.max(-(int) Math.floor(Math.log10(d)), 0); // generate markers LinkedList<Float> markers = new LinkedList<Float>(); markers.add((float) nfrac); for (float m = graphMin; m < graphMax + 0.5f * d; m += d) { float marker = m; if (marker < minVar) { marker = minVar; } else if (marker > maxVar) { marker = maxVar; } markers.add(marker); } // convert to result Float[] tmp = new Float[markers.size()]; markers.toArray(tmp); float[] result = ArrayUtils.toPrimitive(tmp); // reverse? if (reverse) { float[] r = result; result = new float[r.length]; result[0] = r[0]; for (int i = 1; i < r.length; i++) { result[i] = r[r.length - i]; } } return result; } }