/* 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.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ComposeShader; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.Shader; import android.util.AttributeSet; /** * Inherits from GraphView and draws a graph with a shaded area under a line. * Layout parameters are read in from XML. */ public class AreaGraphView extends GraphView { protected Paint mLinePaint = new Paint(); protected int[] bitmapColours; protected Bitmap mBitmap; protected Shader bandShader; protected Shader gradientShader; protected Shader compositeShader; protected Path mLinePath = new Path(); protected Path mGradientPath = new Path(); /** * Creates a new ShaderAreaGraphView and reads in layout parameters from * XML. * * @param context * @param attributes */ public AreaGraphView(Context context, AttributeSet attributes) { super(context, attributes); mLinePaint.setColor(attributes.getAttributeIntValue(customSchemaLocation, "graph_line_colour", 0xFF007ba1)); mLinePaint.setStrokeWidth(attributes.getAttributeFloatValue(customSchemaLocation, "graph_line_width", 3.0f)); mLinePaint.setAntiAlias(attributes.getAttributeBooleanValue(customSchemaLocation, "graph_line_anti_alias", true)); mLinePaint.setStyle(Paint.Style.STROKE); // Ensure you can see the gridlines behind the graph area, but only just mBackgroundPaint.setAlpha(200); String bitmapColoursString = attributes.getAttributeValue(customSchemaLocation, "graph_shaded_area_colours"); if (bitmapColoursString == null) bitmapColoursString = "FF007ba1; FF007ba1; FF007ba1; 00007ba1; 00007ba1; 00007ba1"; String[] bitmapColoursStringArray = bitmapColoursString.split("; "); int[] bitmapColoursIntArray = new int[bitmapColoursStringArray.length]; for (int i = 0; i < bitmapColoursIntArray.length; i++) { bitmapColoursIntArray[i] = (int) (Long.parseLong(bitmapColoursStringArray[i], 16) & 0xffffffffL); } bitmapColours = bitmapColoursIntArray; mBitmap = Bitmap.createBitmap(bitmapColours, 0, 1, 1, 6, Bitmap.Config.ARGB_8888); bandShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.REPEAT); } @Override protected void drawPlot(Canvas canvas) { for (int set = 0; set < mData.length; set++) { try { int setColour = mDataSetColours[set]; mLinePaint.setColor(setColour); int[] barBitmapColours = {setColour, setColour, setColour, 00000000, 00000000, 00000000}; Bitmap barShaderBitmap = Bitmap.createBitmap(barBitmapColours, 0, 1, 1, 6, Bitmap.Config.ARGB_8888); bandShader = new BitmapShader(barShaderBitmap, Shader.TileMode.CLAMP, Shader.TileMode.REPEAT); gradientShader = new LinearGradient(0, 0, 0, graphHeight, 0xCCffffff, 0x11ffffff, Shader.TileMode.CLAMP); compositeShader = new ComposeShader(bandShader, gradientShader, PorterDuff.Mode.MULTIPLY); mAscendingPaint.setShader(compositeShader); } catch (IndexOutOfBoundsException e) { // Just carry on using the last colour } float[] startCoordinates = calcCoordinates(set, 0); canvas.save(); canvas.clipRect(mLeftPadding + mLinePaint.getStrokeWidth() / 2 - 0.5f, mTopPadding - mLinePaint.getStrokeWidth(), mLeftPadding + graphWidth, mTopPadding + graphHeight - mAxesPaint.getStrokeWidth() / 2); mLinePath = new Path(); mGradientPath = new Path(); mLinePath.moveTo(startCoordinates[0], startCoordinates[1]); mGradientPath.moveTo(startCoordinates[0], startCoordinates[1]); for (int i = 0; i < mData[set][0].length - 1; i++) { float[] coords = calcCoordinates(set, i); if (Float.isNaN(mData[set][1][i + 1])) { mLinePath.moveTo(coords[0], mTopPadding + graphHeight); mGradientPath.lineTo(coords[0], mTopPadding + graphHeight); } else if (Float.isNaN(mData[set][1][i]) && Float.isNaN(mData[set][1][i + 1]) == false) { mLinePath.moveTo(coords[2], coords[3]); mGradientPath.lineTo(coords[2], mTopPadding + graphHeight); mGradientPath.lineTo(coords[2], coords[3]); } else if (coords[3] < coords[1]) { // If the data is increasing, draw in the ascending style... drawAscendingSection(canvas, coords); } else { // ...otherwise, draw in the descending style drawDescendingSection(canvas, coords); } } float[] endCoordinates = calcCoordinates(set, mData[set][0].length - 2); mGradientPath.lineTo(endCoordinates[2], mTopPadding + graphHeight); mGradientPath.lineTo(startCoordinates[0], mTopPadding + graphHeight); mGradientPath.lineTo(startCoordinates[0], startCoordinates[1]); gradientShader = new LinearGradient(0, 0, 0, graphHeight, 0xCCffffff, 0x11ffffff, Shader.TileMode.CLAMP); compositeShader = new ComposeShader(bandShader, gradientShader, PorterDuff.Mode.MULTIPLY); mAscendingPaint.setShader(compositeShader); canvas.drawPath(mGradientPath, mBackgroundPaint); canvas.drawPath(mGradientPath, mAscendingPaint); canvas.drawPath(mLinePath, mLinePaint); canvas.restore(); } } @Override protected void drawAscendingSection(Canvas canvas, float[] coords) { mLinePath.lineTo(coords[2], coords[3]); mGradientPath.lineTo(coords[2], coords[3]); } @Override protected void drawDescendingSection(Canvas canvas, float[] coords) { drawAscendingSection(canvas, coords); } }