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.demos; 18 19 import java.text.DateFormatSymbols; 20 import java.text.FieldPosition; 21 import java.text.NumberFormat; 22 import java.text.ParsePosition; 23 import java.util.Arrays; 24 import java.util.Iterator; 25 26 import android.app.Activity; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.PointF; 30 import android.os.Bundle; 31 import android.util.Pair; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.widget.AdapterView; 35 import android.widget.AdapterView.OnItemSelectedListener; 36 import android.widget.ArrayAdapter; 37 import android.widget.CheckBox; 38 import android.widget.CompoundButton; 39 import android.widget.SeekBar; 40 import android.widget.Spinner; 41 42 import com.androidplot.LineRegion; 43 import com.androidplot.ui.AnchorPosition; 44 import com.androidplot.ui.SeriesRenderer; 45 import com.androidplot.ui.SizeLayoutType; 46 import com.androidplot.ui.SizeMetrics; 47 import com.androidplot.ui.TextOrientationType; 48 import com.androidplot.ui.widget.TextLabelWidget; 49 import com.androidplot.util.PixelUtils; 50 import com.androidplot.xy.*; 51 import com.androidplot.ui.XLayoutStyle; 52 import com.androidplot.ui.YLayoutStyle; 53 54 /** 55 * The simplest possible example of using AndroidPlot to plot some data. 56 */ 57 public class BarPlotExampleActivity extends Activity 58 { 59 60 private static final String NO_SELECTION_TXT = "Touch bar to select."; 61 private XYPlot plot; 62 63 private CheckBox series1CheckBox; 64 private CheckBox series2CheckBox; 65 private Spinner spRenderStyle, spWidthStyle, spSeriesSize; 66 private SeekBar sbFixedWidth, sbVariableWidth; 67 68 private XYSeries series1; 69 private XYSeries series2; 70 private enum SeriesSize { 71 TEN, 72 TWENTY, 73 SIXTY 74 } 75 76 // Create a couple arrays of y-values to plot: 77 Number[] series1Numbers10 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5}; 78 Number[] series2Numbers10 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4}; 79 Number[] series1Numbers20 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3}; 80 Number[] series2Numbers20 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9}; 81 Number[] series1Numbers60 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3, 2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3, 2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3}; 82 Number[] series2Numbers60 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9, 4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9, 4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9}; 83 Number[] series1Numbers = series1Numbers10; 84 Number[] series2Numbers = series2Numbers10; 85 86 private MyBarFormatter formatter1 = 87 new MyBarFormatter(Color.argb(200, 100, 150, 100), Color.LTGRAY); 88 89 private MyBarFormatter formatter2 = 90 new MyBarFormatter(Color.argb(200, 100, 100, 150), Color.LTGRAY); 91 92 private MyBarFormatter selectionFormatter = 93 new MyBarFormatter(Color.YELLOW, Color.WHITE); 94 95 private TextLabelWidget selectionWidget; 96 97 private Pair<Integer, XYSeries> selection; 98 99 @Override 100 public void onCreate(Bundle savedInstanceState) 101 { 102 103 super.onCreate(savedInstanceState); 104 setContentView(R.layout.bar_plot_example); 105 106 // initialize our XYPlot reference: 107 plot = (XYPlot) findViewById(R.id.mySimpleXYPlot); 108 109 selectionWidget = new TextLabelWidget(plot.getLayoutManager(), NO_SELECTION_TXT, 110 new SizeMetrics( 111 PixelUtils.dpToPix(100), SizeLayoutType.ABSOLUTE, 112 PixelUtils.dpToPix(100), SizeLayoutType.ABSOLUTE), 113 TextOrientationType.HORIZONTAL); 114 115 selectionWidget.getLabelPaint().setTextSize(PixelUtils.dpToPix(16)); 116 117 // add a dark, semi-transparent background to the selection label widget: 118 Paint p = new Paint(); 119 p.setARGB(100, 0, 0, 0); 120 selectionWidget.setBackgroundPaint(p); 121 122 selectionWidget.position( 123 0, XLayoutStyle.RELATIVE_TO_CENTER, 124 PixelUtils.dpToPix(45), YLayoutStyle.ABSOLUTE_FROM_TOP, 125 AnchorPosition.TOP_MIDDLE); 126 selectionWidget.pack(); 127 128 129 // reduce the number of range labels 130 plot.setTicksPerRangeLabel(3); 131 plot.setRangeLowerBoundary(0, BoundaryMode.FIXED); 132 plot.getGraphWidget().setGridPadding(30, 10, 30, 0); 133 134 plot.setTicksPerDomainLabel(2); 135 136 137 // setup checkbox listers: 138 series1CheckBox = (CheckBox) findViewById(R.id.s1CheckBox); 139 series1CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 140 @Override 141 public void onCheckedChanged(CompoundButton compoundButton, boolean b) { 142 onS1CheckBoxClicked(b); 143 } 144 }); 145 146 series2CheckBox = (CheckBox) findViewById(R.id.s2CheckBox); 147 series2CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 148 @Override 149 public void onCheckedChanged(CompoundButton compoundButton, boolean b) {onS2CheckBoxClicked(b); 150 } 151 }); 152 153 plot.setOnTouchListener(new View.OnTouchListener() { 154 @Override 155 public boolean onTouch(View view, MotionEvent motionEvent) { 156 if(motionEvent.getAction() == MotionEvent.ACTION_DOWN) { 157 onPlotClicked(new PointF(motionEvent.getX(), motionEvent.getY())); 158 } 159 return true; 160 } 161 }); 162 163 spRenderStyle = (Spinner) findViewById(R.id.spRenderStyle); 164 ArrayAdapter <BarRenderer.BarRenderStyle> adapter = new ArrayAdapter <BarRenderer.BarRenderStyle> (this, android.R.layout.simple_spinner_item, BarRenderer.BarRenderStyle.values() ); 165 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 166 spRenderStyle.setAdapter(adapter); 167 spRenderStyle.setSelection(BarRenderer.BarRenderStyle.OVERLAID.ordinal()); 168 spRenderStyle.setOnItemSelectedListener(new OnItemSelectedListener() { 169 public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) { 170 updatePlot(); 171 } 172 @Override 173 public void onNothingSelected(AdapterView<?> arg0) { 174 } 175 }); 176 177 spWidthStyle = (Spinner) findViewById(R.id.spWidthStyle); 178 ArrayAdapter <BarRenderer.BarWidthStyle> adapter1 = new ArrayAdapter <BarRenderer.BarWidthStyle> (this, android.R.layout.simple_spinner_item, BarRenderer.BarWidthStyle.values() ); 179 adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 180 spWidthStyle.setAdapter(adapter1); 181 spWidthStyle.setSelection(BarRenderer.BarWidthStyle.FIXED_WIDTH.ordinal()); 182 spWidthStyle.setOnItemSelectedListener(new OnItemSelectedListener() { 183 public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) { 184 if (BarRenderer.BarWidthStyle.FIXED_WIDTH.equals(spWidthStyle.getSelectedItem())) { 185 sbFixedWidth.setVisibility(View.VISIBLE); 186 sbVariableWidth.setVisibility(View.INVISIBLE); 187 } else { 188 sbFixedWidth.setVisibility(View.INVISIBLE); 189 sbVariableWidth.setVisibility(View.VISIBLE); 190 } 191 updatePlot(); 192 } 193 @Override 194 public void onNothingSelected(AdapterView<?> arg0) { 195 } 196 }); 197 198 spSeriesSize = (Spinner) findViewById(R.id.spSeriesSize); 199 ArrayAdapter <SeriesSize> adapter11 = new ArrayAdapter <SeriesSize> (this, android.R.layout.simple_spinner_item, SeriesSize.values() ); 200 adapter11.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 201 spSeriesSize.setAdapter(adapter11); 202 spSeriesSize.setSelection(SeriesSize.TEN.ordinal()); 203 spSeriesSize.setOnItemSelectedListener(new OnItemSelectedListener() { 204 public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) { 205 switch ((SeriesSize)arg0.getSelectedItem()) { 206 case TEN: 207 series1Numbers = series1Numbers10; 208 series2Numbers = series2Numbers10; 209 break; 210 case TWENTY: 211 series1Numbers = series1Numbers20; 212 series2Numbers = series2Numbers20; 213 break; 214 case SIXTY: 215 series1Numbers = series1Numbers60; 216 series2Numbers = series2Numbers60; 217 break; 218 default: 219 break; 220 } 221 updatePlot(); 222 } 223 @Override 224 public void onNothingSelected(AdapterView<?> arg0) { 225 } 226 }); 227 228 229 sbFixedWidth = (SeekBar) findViewById(R.id.sbFixed); 230 sbFixedWidth.setProgress(50); 231 sbFixedWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 232 @Override 233 public void onProgressChanged(SeekBar seekBar, int i, boolean b) {updatePlot();} 234 @Override 235 public void onStartTrackingTouch(SeekBar seekBar) {} 236 237 @Override 238 public void onStopTrackingTouch(SeekBar seekBar) {} 239 }); 240 241 242 sbVariableWidth = (SeekBar) findViewById(R.id.sbVariable); 243 sbVariableWidth.setProgress(1); 244 sbVariableWidth.setVisibility(View.INVISIBLE); 245 sbVariableWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 246 @Override 247 public void onProgressChanged(SeekBar seekBar, int i, boolean b) {updatePlot();} 248 @Override 249 public void onStartTrackingTouch(SeekBar seekBar) {} 250 @Override 251 public void onStopTrackingTouch(SeekBar seekBar) {} 252 }); 253 254 plot.setDomainValueFormat(new NumberFormat() { 255 @Override 256 public StringBuffer format(double value, StringBuffer buffer, FieldPosition field) { 257 int year = (int) (value + 0.5d) / 12; 258 int month = (int) ((value + 0.5d) % 12); 259 return new StringBuffer(DateFormatSymbols.getInstance().getShortMonths()[month] + " '0" + year); 260 } 261 262 @Override 263 public StringBuffer format(long value, StringBuffer buffer, FieldPosition field) { 264 throw new UnsupportedOperationException("Not yet implemented."); 265 } 266 267 @Override 268 public Number parse(String string, ParsePosition position) { 269 throw new UnsupportedOperationException("Not yet implemented."); 270 } 271 }); 272 updatePlot(); 273 274 } 275 276 private void updatePlot() { 277 278 // Remove all current series from each plot 279 Iterator<XYSeries> iterator1 = plot.getSeriesSet().iterator(); 280 while(iterator1.hasNext()) { 281 XYSeries setElement = iterator1.next(); 282 plot.removeSeries(setElement); 283 } 284 285 // Setup our Series with the selected number of elements 286 series1 = new SimpleXYSeries(Arrays.asList(series1Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Us"); 287 series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Them"); 288 289 // add a new series' to the xyplot: 290 if (series1CheckBox.isChecked()) plot.addSeries(series1, formatter1); 291 if (series2CheckBox.isChecked()) plot.addSeries(series2, formatter2); 292 293 // Setup the BarRenderer with our selected options 294 MyBarRenderer renderer = ((MyBarRenderer)plot.getRenderer(MyBarRenderer.class)); 295 renderer.setBarRenderStyle((BarRenderer.BarRenderStyle)spRenderStyle.getSelectedItem()); 296 renderer.setBarWidthStyle((BarRenderer.BarWidthStyle)spWidthStyle.getSelectedItem()); 297 renderer.setBarWidth(sbFixedWidth.getProgress()); 298 renderer.setBarGap(sbVariableWidth.getProgress()); 299 300 if (BarRenderer.BarRenderStyle.STACKED.equals(spRenderStyle.getSelectedItem())) { 301 plot.setRangeTopMin(15); 302 } else { 303 plot.setRangeTopMin(0); 304 } 305 306 plot.redraw(); 307 308 } 309 310 private void onPlotClicked(PointF point) { 311 312 // make sure the point lies within the graph area. we use gridrect 313 // because it accounts for margins and padding as well. 314 if (plot.getGraphWidget().getGridRect().contains(point.x, point.y)) { 315 Number x = plot.getXVal(point); 316 Number y = plot.getYVal(point); 317 318 319 selection = null; 320 double xDistance = 0; 321 double yDistance = 0; 322 323 // find the closest value to the selection: 324 for (XYSeries series : plot.getSeriesSet()) { 325 for (int i = 0; i < series.size(); i++) { 326 Number thisX = series.getX(i); 327 Number thisY = series.getY(i); 328 if (thisX != null && thisY != null) { 329 double thisXDistance = 330 LineRegion.measure(x, thisX).doubleValue(); 331 double thisYDistance = 332 LineRegion.measure(y, thisY).doubleValue(); 333 if (selection == null) { 334 selection = new Pair<Integer, XYSeries>(i, series); 335 xDistance = thisXDistance; 336 yDistance = thisYDistance; 337 } else if (thisXDistance < xDistance) { 338 selection = new Pair<Integer, XYSeries>(i, series); 339 xDistance = thisXDistance; 340 yDistance = thisYDistance; 341 } else if (thisXDistance == xDistance && 342 thisYDistance < yDistance && 343 thisY.doubleValue() >= y.doubleValue()) { 344 selection = new Pair<Integer, XYSeries>(i, series); 345 xDistance = thisXDistance; 346 yDistance = thisYDistance; 347 } 348 } 349 } 350 } 351 352 } else { 353 // if the press was outside the graph area, deselect: 354 selection = null; 355 } 356 357 if(selection == null) { 358 selectionWidget.setText(NO_SELECTION_TXT); 359 } else { 360 selectionWidget.setText("Selected: " + selection.second.getTitle() + 361 " Value: " + selection.second.getY(selection.first)); 362 } 363 plot.redraw(); 364 } 365 366 private void onS1CheckBoxClicked(boolean checked) { 367 if (checked) { 368 plot.addSeries(series1, formatter1); 369 } else { 370 plot.removeSeries(series1); 371 } 372 plot.redraw(); 373 } 374 375 private void onS2CheckBoxClicked(boolean checked) { 376 if (checked) { 377 plot.addSeries(series2, formatter2); 378 } else { 379 plot.removeSeries(series2); 380 } 381 plot.redraw(); 382 } 383 384 class MyBarFormatter extends BarFormatter { 385 public MyBarFormatter(int fillColor, int borderColor) { 386 super(fillColor, borderColor); 387 } 388 389 @Override 390 public Class<? extends SeriesRenderer> getRendererClass() { 391 return MyBarRenderer.class; 392 } 393 394 @Override 395 public SeriesRenderer getRendererInstance(XYPlot plot) { 396 return new MyBarRenderer(plot); 397 } 398 } 399 400 class MyBarRenderer extends BarRenderer<MyBarFormatter> { 401 402 public MyBarRenderer(XYPlot plot) { 403 super(plot); 404 } 405 406 /** 407 * Implementing this method to allow us to inject our 408 * special selection formatter. 409 * @param index index of the point being rendered. 410 * @param series XYSeries to which the point being rendered belongs. 411 * @return 412 */ 413 //@Override 414 // TODO: figure out why using @Override screws up the Maven builds 415 protected MyBarFormatter getFormatter(int index, XYSeries series) { 416 if(selection != null && 417 selection.second == series && 418 selection.first == index) { 419 return selectionFormatter; 420 } else { 421 return getFormatter(series); 422 } 423 } 424 } 425 } 426