Home | History | Annotate | Download | only in xy
      1 /*
      2  * Copyright 2012 AndroidPlot.com
      3  *
      4  *    Licensed under the Apache License, Version 2.0 (the "License");
      5  *    you may not use this file except in compliance with the License.
      6  *    You may obtain a copy of the License at
      7  *
      8  *        http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  *    Unless required by applicable law or agreed to in writing, software
     11  *    distributed under the License is distributed on an "AS IS" BASIS,
     12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  *    See the License for the specific language governing permissions and
     14  *    limitations under the License.
     15  */
     16 
     17 package com.androidplot.xy;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Collections;
     21 import java.util.Comparator;
     22 import java.util.List;
     23 import java.util.Map.Entry;
     24 import java.util.TreeMap;
     25 
     26 import android.graphics.Canvas;
     27 import android.graphics.RectF;
     28 
     29 import com.androidplot.exception.PlotRenderException;
     30 import com.androidplot.util.ValPixConverter;
     31 
     32 /**
     33  * Renders a point as a Bar
     34  */
     35 public class BarRenderer<T extends BarFormatter> extends XYSeriesRenderer<T> {
     36 
     37     private BarRenderStyle renderStyle = BarRenderStyle.OVERLAID;  // default Render Style
     38 	private BarWidthStyle widthStyle = BarWidthStyle.FIXED_WIDTH;  // default Width Style
     39     private float barWidth = 5;
     40     private float barGap = 1;
     41 
     42     public enum BarRenderStyle {
     43         OVERLAID,           // bars are overlaid in descending y-val order (largest val in back)
     44         STACKED,            // bars are drawn stacked vertically on top of each other
     45         SIDE_BY_SIDE        // bars are drawn horizontally next to each-other
     46     }
     47 
     48     public enum BarWidthStyle {
     49         FIXED_WIDTH,        // bar width is always barWidth
     50         VARIABLE_WIDTH      // bar width is calculated so that there is only barGap between each bar
     51     }
     52 
     53     public BarRenderer(XYPlot plot) {
     54         super(plot);
     55     }
     56 
     57     /**
     58      * Sets the width of the bars when using the FIXED_WIDTH render style
     59      * @param barWidth
     60      */
     61     public void setBarWidth(float barWidth) {
     62         this.barWidth = barWidth;
     63     }
     64 
     65     /**
     66      * Sets the size of the gap between the bar (or bar groups) when using the VARIABLE_WIDTH render style
     67      * @param barGap
     68      */
     69     public void setBarGap(float barGap) {
     70         this.barGap = barGap;
     71     }
     72 
     73     public void setBarRenderStyle(BarRenderStyle renderStyle) {
     74         this.renderStyle = renderStyle;
     75     }
     76 
     77     public void setBarWidthStyle(BarWidthStyle widthStyle) {
     78         this.widthStyle = widthStyle;
     79     }
     80 
     81     public void setBarWidthStyle(BarWidthStyle style, float value) {
     82     	setBarWidthStyle(style);
     83         switch (style) {
     84         	case FIXED_WIDTH:
     85         		setBarWidth(value);
     86                 break;
     87         	case VARIABLE_WIDTH:
     88         		setBarGap(value);
     89         		break;
     90 		default:
     91 			break;
     92         }
     93     }
     94 
     95     @Override
     96     public void doDrawLegendIcon(Canvas canvas, RectF rect, BarFormatter formatter) {
     97         canvas.drawRect(rect, formatter.getFillPaint());
     98         canvas.drawRect(rect, formatter.getBorderPaint());
     99     }
    100 
    101     /**
    102      * Retrieves the BarFormatter instance that corresponds with the series passed in.
    103      * Can be overridden to return other BarFormatters as a result of touch events etc.
    104      * @param index index of the point being rendered.
    105      * @param series XYSeries to which the point being rendered belongs.
    106      * @return
    107      */
    108     @SuppressWarnings("UnusedParameters")
    109     protected T getFormatter(int index, XYSeries series) {
    110         return getFormatter(series);
    111     }
    112 
    113     public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
    114 
    115     	List<XYSeries> sl = getPlot().getSeriesListForRenderer(this.getClass());
    116 
    117     	TreeMap<Number, BarGroup> axisMap = new TreeMap<Number, BarGroup>();
    118 
    119         // dont try to render anything if there's nothing to render.
    120         if(sl == null) return;
    121 
    122         /*
    123          * Build the axisMap (yVal,BarGroup)... a TreeMap of BarGroups
    124          * BarGroups represent a point on the X axis where a single or group of bars need to be drawn.
    125          */
    126 
    127         // For each Series
    128         for(XYSeries series : sl) {
    129         	BarGroup barGroup;
    130 
    131             // For each value in the series
    132             for(int i = 0; i < series.size(); i++) {
    133 
    134                	if (series.getX(i) != null) {
    135 
    136             		// get a new bar object
    137             		Bar b = new Bar(series,i,plotArea);
    138 
    139             		// Find or create the barGroup
    140 	            	if (axisMap.containsKey(b.intX)) {
    141 	            		barGroup = axisMap.get(b.intX);
    142 	            	} else {
    143 	            		barGroup = new BarGroup(b.intX,plotArea);
    144 	            		axisMap.put(b.intX, barGroup);
    145 	            	}
    146 	            	barGroup.addBar(b);
    147             	}
    148 
    149             }
    150         }
    151 
    152 		// Loop through the axisMap linking up prev pointers
    153 		BarGroup prev, current;
    154 		prev = null;
    155 		for(Entry<Number, BarGroup> mapEntry : axisMap.entrySet()) {
    156 			current = mapEntry.getValue();
    157     		current.prev = prev;
    158     		prev = current;
    159 		}
    160 
    161 
    162 		// The default gap between each bar section
    163 		int gap  = (int) barGap;
    164 
    165 		// Determine roughly how wide (rough_width) this bar should be. This is then used as a default width
    166 		// when there are gaps in the data or for the first/last bars.
    167 		float f_rough_width = ((plotArea.width() - ((axisMap.size() - 1) * gap)) / (axisMap.size() - 1));
    168 		int rough_width = (int) f_rough_width;
    169 		if (rough_width < 0) rough_width = 0;
    170 		if (gap > rough_width) {
    171 			gap = rough_width / 2;
    172 		}
    173 
    174 		//Log.d("PARAMTER","PLOT_WIDTH=" + plotArea.width());
    175 		//Log.d("PARAMTER","BAR_GROUPS=" + axisMap.size());
    176 		//Log.d("PARAMTER","ROUGH_WIDTH=" + rough_width);
    177 		//Log.d("PARAMTER","GAP=" + gap);
    178 
    179 		/*
    180 		 * Calculate the dimensions of each barGroup and then draw each bar within it according to
    181 		 * the Render Style and Width Style.
    182 		 */
    183 
    184 		for(Number key : axisMap.keySet()) {
    185 
    186 			BarGroup barGroup = axisMap.get(key);
    187 
    188 			// Determine the exact left and right X for the Bar Group
    189 			switch (widthStyle) {
    190 			case FIXED_WIDTH:
    191     			// use intX and go halfwidth either side.
    192     			barGroup.leftX = barGroup.intX - (int) (barWidth / 2);
    193     			barGroup.width = (int) barWidth;
    194     			barGroup.rightX = barGroup.leftX + barGroup.width;
    195 				break;
    196 			case VARIABLE_WIDTH:
    197 	    		if (barGroup.prev != null) {
    198 	    			if (barGroup.intX - barGroup.prev.intX - gap - 1 > (int)(rough_width * 1.5)) {
    199 	    				// use intX and go halfwidth either side.
    200 	        			barGroup.leftX = barGroup.intX - (rough_width / 2);
    201 	        			barGroup.width = rough_width;
    202 	        			barGroup.rightX = barGroup.leftX + barGroup.width;
    203 	    			} else {
    204 	    				// base left off prev right to get the gap correct.
    205 	    				barGroup.leftX = barGroup.prev.rightX + gap + 1;
    206 	    				if (barGroup.leftX > barGroup.intX) barGroup.leftX = barGroup.intX;
    207 	    				// base right off intX + halfwidth.
    208 	    				barGroup.rightX = barGroup.intX + (rough_width / 2);
    209 	    				// calculate the width
    210 	    				barGroup.width = barGroup.rightX - barGroup.leftX;
    211 	    			}
    212 	    		} else {
    213 	    			// use intX and go halfwidth either side.
    214 	    			barGroup.leftX = barGroup.intX - (rough_width / 2);
    215 	    			barGroup.width = rough_width;
    216 	    			barGroup.rightX = barGroup.leftX + barGroup.width;
    217 	    		}
    218 				break;
    219 			default:
    220 				break;
    221 			}
    222 
    223     		//Log.d("BAR_GROUP", "rough_width=" + rough_width + " width=" + barGroup.width + " <" + barGroup.leftX + "|" + barGroup.intX + "|" + barGroup.rightX + ">");
    224 
    225     		/*
    226     		 * Draw the bars within the barGroup area.
    227     		 */
    228 			switch (renderStyle) {
    229 			case OVERLAID:
    230 				Collections.sort(barGroup.bars, new BarComparator());
    231 				for (Bar b : barGroup.bars) {
    232 					BarFormatter formatter = b.formatter();
    233 			        PointLabelFormatter plf = formatter.getPointLabelFormatter();
    234 			        PointLabeler pointLabeler = null;
    235                 	if (formatter != null) {
    236                 		pointLabeler = formatter.getPointLabeler();
    237                 	}
    238 	        		//Log.d("BAR", b.series.getTitle() + " <" + b.barGroup.leftX + "|" + b.barGroup.intX + "|" + b.barGroup.rightX + "> " + b.intY);
    239 	    			if (b.barGroup.width >= 2) {
    240 	        			canvas.drawRect(b.barGroup.leftX, b.intY, b.barGroup.rightX, b.barGroup.plotArea.bottom, formatter.getFillPaint());
    241 	        		}
    242 	        		canvas.drawRect(b.barGroup.leftX, b.intY, b.barGroup.rightX, b.barGroup.plotArea.bottom, formatter.getBorderPaint());
    243 	        		if(plf != null && pointLabeler != null) {
    244 	                    canvas.drawText(pointLabeler.getLabel(b.series, b.seriesIndex), b.intX + plf.hOffset, b.intY + plf.vOffset, plf.getTextPaint());
    245 	                }
    246 	        	}
    247 				break;
    248 			case SIDE_BY_SIDE:
    249 				int width = (int) barGroup.width / barGroup.bars.size();
    250 				int leftX = barGroup.leftX;
    251 				Collections.sort(barGroup.bars, new BarComparator());
    252 				for (Bar b : barGroup.bars) {
    253 					BarFormatter formatter = b.formatter();
    254 			        PointLabelFormatter plf = formatter.getPointLabelFormatter();
    255 			        PointLabeler pointLabeler = null;
    256                 	if (formatter != null) {
    257                 		pointLabeler = formatter.getPointLabeler();
    258                 	}
    259 	        		//Log.d("BAR", "width=" + width + " <" + leftX + "|" + b.intX + "|" + (leftX + width) + "> " + b.intY);
    260 	        		if (b.barGroup.width >= 2) {
    261 	        			canvas.drawRect(leftX, b.intY, leftX + width, b.barGroup.plotArea.bottom, formatter.getFillPaint());
    262 	        		}
    263 	        		canvas.drawRect(leftX, b.intY, leftX + width, b.barGroup.plotArea.bottom, formatter.getBorderPaint());
    264 	        		if(plf != null && pointLabeler != null) {
    265 	                    canvas.drawText(pointLabeler.getLabel(b.series, b.seriesIndex), leftX + width/2 + plf.hOffset, b.intY + plf.vOffset, plf.getTextPaint());
    266 	                }
    267 	        		leftX = leftX + width;
    268 	        	}
    269 				break;
    270 			case STACKED:
    271 				int bottom = (int) barGroup.plotArea.bottom;
    272 				Collections.sort(barGroup.bars, new BarComparator());
    273 				for (Bar b : barGroup.bars) {
    274 					BarFormatter formatter = b.formatter();
    275 			        PointLabelFormatter plf = formatter.getPointLabelFormatter();
    276 			        PointLabeler pointLabeler = null;
    277                 	if (formatter != null) {
    278                 		pointLabeler = formatter.getPointLabeler();
    279                 	}
    280 	        		int height = (int) b.barGroup.plotArea.bottom - b.intY;
    281 	        		int top = bottom - height;
    282 	        		//Log.d("BAR", "top=" + top + " bottom=" + bottom + " height=" + height);
    283 	    			if (b.barGroup.width >= 2) {
    284 	        			canvas.drawRect(b.barGroup.leftX, top, b.barGroup.rightX, bottom, formatter.getFillPaint());
    285 	        		}
    286 	        		canvas.drawRect(b.barGroup.leftX, top, b.barGroup.rightX, bottom, formatter.getBorderPaint());
    287 	        		if(plf != null && pointLabeler != null) {
    288 	                    canvas.drawText(pointLabeler.getLabel(b.series, b.seriesIndex), b.intX + plf.hOffset, b.intY + plf.vOffset, plf.getTextPaint());
    289 	                }
    290 		        	bottom = top;
    291 	        	}
    292 				break;
    293 			default:
    294 				break;
    295 			}
    296 
    297 		}
    298 
    299     }
    300 
    301     private class Bar {
    302 		public XYSeries series;
    303 		public int seriesIndex;
    304 		public double yVal, xVal;
    305 		public int intX, intY;
    306 		public float pixX, pixY;
    307 		public BarGroup barGroup;
    308 
    309     	public Bar(XYSeries series, int seriesIndex, RectF plotArea) {
    310 			this.series = series;
    311 			this.seriesIndex = seriesIndex;
    312 
    313 			this.xVal = series.getX(seriesIndex).doubleValue();
    314 			this.pixX = ValPixConverter.valToPix(xVal, getPlot().getCalculatedMinX().doubleValue(), getPlot().getCalculatedMaxX().doubleValue(), plotArea.width(), false) + (plotArea.left);
    315 			this.intX = (int) pixX;
    316 
    317 			if (series.getY(seriesIndex) != null) {
    318 				this.yVal = series.getY(seriesIndex).doubleValue();
    319 				this.pixY = ValPixConverter.valToPix(yVal, getPlot().getCalculatedMinY().doubleValue(), getPlot().getCalculatedMaxY().doubleValue(), plotArea.height(), true) + plotArea.top;
    320 				this.intY = (int) pixY;
    321 			} else {
    322 				this.yVal = 0;
    323 				this.pixY = plotArea.bottom;
    324 				this.intY = (int) pixY;
    325 			}
    326 		}
    327     	public BarFormatter formatter() {
    328     		return getFormatter(seriesIndex, series);
    329     	}
    330     }
    331 
    332     private class BarGroup {
    333     	public ArrayList<Bar> bars;
    334     	public int intX;
    335     	public int width, leftX, rightX;
    336     	public RectF plotArea;
    337     	public BarGroup prev;
    338 
    339     	public BarGroup(int intX, RectF plotArea) {
    340     		// Setup the TreeMap with the required comparator
    341    			this.bars = new ArrayList<Bar>(); // create a comparator that compares series title given the index.
    342     		this.intX = intX;
    343 			this.plotArea = plotArea;
    344 		}
    345 
    346     	public void addBar(Bar bar) {
    347     		bar.barGroup = this;
    348    			this.bars.add(bar);
    349     	}
    350     }
    351 
    352     @SuppressWarnings("WeakerAccess")
    353     public class BarComparator implements Comparator<Bar>{
    354         @Override
    355         public int compare(Bar bar1, Bar bar2) {
    356 			switch (renderStyle) {
    357 			case OVERLAID:
    358 				return Integer.valueOf(bar1.intY).compareTo(bar2.intY);
    359 			case SIDE_BY_SIDE:
    360 				return bar1.series.getTitle().compareToIgnoreCase(bar2.series.getTitle());
    361 			case STACKED:
    362 				return bar1.series.getTitle().compareToIgnoreCase(bar2.series.getTitle());
    363 			default:
    364 	            return 0;
    365 			}
    366         }
    367     }
    368 
    369 }
    370