/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package uk.ac.cam.cl.dtg.snowdon; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import kr.kdev.dg1s.biowiki.R; /** * Displays data as a heatmap. Colours, labels and other layout features are * read in from XML. */ public class HeatMapView extends View { protected Paint mBlockPaint = new Paint(); protected Paint mBlockGridPaint = new Paint(); protected int[] mColours; protected int[] mColourBandUpperBounds; protected float mTopPadding; protected float mBottomPadding; protected float mLeftPadding; protected float mRightPadding; protected float graphHeight; protected float graphWidth; protected float mTickLength; protected float mXLabelSpacing; protected float mYLabelSpacing; protected Paint mTextPaint = new Paint(); protected String[] mYAxisLabels; protected float[] mYAxisLabelPositions; protected String[] mXAxisLabels; protected float[] mXAxisLabelPositions; protected int mNaNColour; private float[][] mData = {{Float.NaN, Float.NaN}, {Float.NaN, Float.NaN}}; // Each element is a row of integer data /** * Creates a new HeatMapView and sets layout parameters as defined by XML. * * @param context * @param attributes */ public HeatMapView(Context context, AttributeSet attributes) { super(context, attributes); mBlockGridPaint.setColor(0xFF000000); mTextPaint.setColor(0xFF000000); mTextPaint.setTextSize(20); mTextPaint.setAntiAlias(true); String customSchemaLocation = getResources().getString(R.string.NS); mTopPadding = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_top_padding", 30.0f); mBottomPadding = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_bottom_padding", 0.0f); mLeftPadding = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_left_padding", 50.0f); mRightPadding = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_right_padding", 25.0f); mTickLength = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_tick_length", 5.0f); mXLabelSpacing = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_x_label_spacing", 10.0f); mYLabelSpacing = attributes.getAttributeFloatValue(customSchemaLocation, "heat_map_y_label_spacing", 5.0f); String yAxisLabels = attributes.getAttributeValue(customSchemaLocation, "heat_map_y_axis_labels"); if (yAxisLabels == null) yAxisLabels = "0%; 25%; 50%; 75%; 100%"; mYAxisLabels = yAxisLabels.split("; "); String yAxisLabelPositions = attributes.getAttributeValue(customSchemaLocation, "heat_map_y_axis_label_positions"); if (yAxisLabelPositions == null) yAxisLabelPositions = "0.00; 1.00; 2.00; 3.00; 4.00; 5.00; 6.00"; String[] yAxisLabelPositionsStringArray = yAxisLabelPositions.split("; "); float[] yAxisLabelPositionsFloatArray = new float[yAxisLabelPositionsStringArray.length]; for (int i = 0; i < yAxisLabelPositionsFloatArray.length; i++) { yAxisLabelPositionsFloatArray[i] = Float.parseFloat(yAxisLabelPositionsStringArray[i]); } mYAxisLabelPositions = yAxisLabelPositionsFloatArray; String xAxisLabels = attributes.getAttributeValue(customSchemaLocation, "heat_map_x_axis_labels"); if (xAxisLabels == null) xAxisLabels = "0%; 25%; 50%; 75%; 100%"; mXAxisLabels = xAxisLabels.split("; "); String xAxisLabelPositions = attributes.getAttributeValue(customSchemaLocation, "heat_map_x_axis_label_positions"); if (xAxisLabelPositions == null) xAxisLabelPositions = "0.00; 6.00; 12.00; 18.00; 24.00"; String[] xAxisLabelPositionsStringArray = xAxisLabelPositions.split("; "); float[] xAxisLabelPositionsFloatArray = new float[xAxisLabelPositionsStringArray.length]; for (int i = 0; i < xAxisLabelPositionsFloatArray.length; i++) { xAxisLabelPositionsFloatArray[i] = Float.parseFloat(xAxisLabelPositionsStringArray[i]); } mXAxisLabelPositions = xAxisLabelPositionsFloatArray; String colours = attributes.getAttributeValue(customSchemaLocation, "heat_map_colours"); if (colours == null) colours = "FFeefbff; FFdbf7ff; FFbff0ff; FF80e1ff; FF40d2ff; FF00c3ff; FF00ace0; FF0093bf; FF007ba1; FF006280"; String[] coloursStringArray = colours.split("; "); int[] coloursIntArray = new int[coloursStringArray.length]; for (int i = 0; i < coloursIntArray.length; i++) { coloursIntArray[i] = (int) (Long.parseLong(coloursStringArray[i], 16) & 0xffffffffL); } mColours = coloursIntArray; mNaNColour = attributes.getAttributeIntValue(customSchemaLocation, "heat_map_no_data_colour", 0xFFAEAEAE); String colourBandUpperBounds = attributes.getAttributeValue(customSchemaLocation, "heat_map_colour_upper_bounds"); if (colourBandUpperBounds == null) colourBandUpperBounds = "10; 20; 30; 40; 50; 60; 70; 80; 90"; String[] colourBandUpperBoundsStringArray = colourBandUpperBounds.split("; "); int[] colourBandUpperBoundsIntArray = new int[colourBandUpperBoundsStringArray.length]; for (int i = 0; i < colourBandUpperBoundsIntArray.length; i++) { colourBandUpperBoundsIntArray[i] = Integer.parseInt(colourBandUpperBoundsStringArray[i]); } mColourBandUpperBounds = colourBandUpperBoundsIntArray; } /** * Invalidates the view and causes it to be redrawn. */ public void redraw() { invalidate(); } /** * Sets the data to be plotted in the heat map. * * @param data A two-dimensional array of floats, with the x data located * in the first element and y in the second. */ public void setData(float[][] data) { if (data != null) { mData = data; } } /** * Sets the labels on the y-axis using the specified string array. * * @param yLabels A string array containing the text to be displayed at each * row. */ public void setYLabels(String[] yLabels) { mYAxisLabels = yLabels; } @Override protected void onDraw(Canvas canvas) { float blockWidth = (getWidth() - mLeftPadding - mRightPadding - mBlockPaint.getStrokeWidth()) / mData[0].length; float blockHeight = (getHeight() - mTopPadding - mBottomPadding - mBlockPaint.getStrokeWidth()) / mData.length; float textVertOffset = 0.25f * (2 * mTextPaint.getTextSize() - mTextPaint.getFontSpacing()); float textHoriOffset; for (int i = 0; i < mXAxisLabels.length; i++) { textHoriOffset = mTextPaint.measureText(mXAxisLabels[i]) / 2; canvas.drawText(mXAxisLabels[i], mLeftPadding - textHoriOffset + mXAxisLabelPositions[i] * blockWidth - 0.5f, mTopPadding - mXLabelSpacing, mTextPaint); canvas.drawLine(mLeftPadding + mXAxisLabelPositions[i] * blockWidth - 0.5f, mTopPadding - 0.5f, mLeftPadding + mXAxisLabelPositions[i] * blockWidth - 0.5f, mTopPadding - mTickLength - 0.5f, mBlockGridPaint); } for (int i = 0; i < mYAxisLabels.length; i++) { textHoriOffset = mTextPaint.measureText(mYAxisLabels[i]); canvas.drawText(mYAxisLabels[i], mLeftPadding - textHoriOffset - mYLabelSpacing, mTopPadding + textVertOffset + blockHeight * (0.5f + mYAxisLabelPositions[i]), mTextPaint); } for (int j = 0; j < mData.length; j++) { for (int i = 0; i < mData[j].length; i++) { int colourIndex = 0; if (Float.isNaN(mData[j][i]) == true) { mBlockPaint.setColor(mNaNColour); } else { for (int k = 0; k < mColourBandUpperBounds.length; k++) { if (mData[j][i] > mColourBandUpperBounds[k]) { colourIndex = k + 1; } } mBlockPaint.setColor(mColours[colourIndex]); } canvas.drawRect(mLeftPadding + i * blockWidth, mTopPadding + j * blockHeight, mLeftPadding + (i + 1) * blockWidth, mTopPadding + (j + 1) * blockHeight, mBlockPaint); canvas.drawLine(mLeftPadding + i * blockWidth - 0.5f, mTopPadding + j * blockHeight - 0.5f, mLeftPadding + (i + 1) * blockWidth - 0.5f, mTopPadding + j * blockHeight - 0.5f, mBlockGridPaint); canvas.drawLine(mLeftPadding + i * blockWidth - 0.5f, mTopPadding + (j + 1) * blockHeight - 0.5f, mLeftPadding + (i + 1) * blockWidth - 0.5f, mTopPadding + (j + 1) * blockHeight - 0.5f, mBlockGridPaint); canvas.drawLine(mLeftPadding + i * blockWidth - 0.5f, mTopPadding + j * blockHeight - 0.5f, mLeftPadding + (i) * blockWidth - 0.5f, mTopPadding + (j + 1) * blockHeight - 0.5f, mBlockGridPaint); canvas.drawLine(mLeftPadding + (i + 1) * blockWidth - 0.5f, mTopPadding + j * blockHeight - 0.5f, mLeftPadding + (i + 1) * blockWidth - 0.5f, mTopPadding + (j + 1) * blockHeight - 0.5f, mBlockGridPaint); } } } }