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
17package com.androidplot.demos;
18
19import java.text.DateFormatSymbols;
20import java.text.FieldPosition;
21import java.text.NumberFormat;
22import java.text.ParsePosition;
23import java.util.Arrays;
24import java.util.Iterator;
25
26import android.app.Activity;
27import android.graphics.Color;
28import android.graphics.Paint;
29import android.graphics.PointF;
30import android.os.Bundle;
31import android.util.Pair;
32import android.view.MotionEvent;
33import android.view.View;
34import android.widget.AdapterView;
35import android.widget.AdapterView.OnItemSelectedListener;
36import android.widget.ArrayAdapter;
37import android.widget.CheckBox;
38import android.widget.CompoundButton;
39import android.widget.SeekBar;
40import android.widget.Spinner;
41
42import com.androidplot.LineRegion;
43import com.androidplot.ui.AnchorPosition;
44import com.androidplot.ui.SeriesRenderer;
45import com.androidplot.ui.SizeLayoutType;
46import com.androidplot.ui.SizeMetrics;
47import com.androidplot.ui.TextOrientationType;
48import com.androidplot.ui.widget.TextLabelWidget;
49import com.androidplot.util.PixelUtils;
50import com.androidplot.xy.*;
51import com.androidplot.ui.XLayoutStyle;
52import com.androidplot.ui.YLayoutStyle;
53
54/**
55 * The simplest possible example of using AndroidPlot to plot some data.
56 */
57public 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