blob: 4c79dd8c50d6620c865c365257070c710b21c136 [file] [log] [blame]
/*
* Copyright 2012 AndroidPlot.com
*
* 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 com.androidplot.demos;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.Iterator;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.Spinner;
import com.androidplot.LineRegion;
import com.androidplot.ui.AnchorPosition;
import com.androidplot.ui.SeriesRenderer;
import com.androidplot.ui.SizeLayoutType;
import com.androidplot.ui.SizeMetrics;
import com.androidplot.ui.TextOrientationType;
import com.androidplot.ui.widget.TextLabelWidget;
import com.androidplot.util.PixelUtils;
import com.androidplot.xy.*;
import com.androidplot.ui.XLayoutStyle;
import com.androidplot.ui.YLayoutStyle;
/**
* The simplest possible example of using AndroidPlot to plot some data.
*/
public class BarPlotExampleActivity extends Activity
{
private static final String NO_SELECTION_TXT = "Touch bar to select.";
private XYPlot plot;
private CheckBox series1CheckBox;
private CheckBox series2CheckBox;
private Spinner spRenderStyle, spWidthStyle, spSeriesSize;
private SeekBar sbFixedWidth, sbVariableWidth;
private XYSeries series1;
private XYSeries series2;
private enum SeriesSize {
TEN,
TWENTY,
SIXTY
}
// Create a couple arrays of y-values to plot:
Number[] series1Numbers10 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5};
Number[] series2Numbers10 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4};
Number[] series1Numbers20 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3};
Number[] series2Numbers20 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9};
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};
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};
Number[] series1Numbers = series1Numbers10;
Number[] series2Numbers = series2Numbers10;
private MyBarFormatter formatter1 =
new MyBarFormatter(Color.argb(200, 100, 150, 100), Color.LTGRAY);
private MyBarFormatter formatter2 =
new MyBarFormatter(Color.argb(200, 100, 100, 150), Color.LTGRAY);
private MyBarFormatter selectionFormatter =
new MyBarFormatter(Color.YELLOW, Color.WHITE);
private TextLabelWidget selectionWidget;
private Pair<Integer, XYSeries> selection;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.bar_plot_example);
// initialize our XYPlot reference:
plot = (XYPlot) findViewById(R.id.mySimpleXYPlot);
selectionWidget = new TextLabelWidget(plot.getLayoutManager(), NO_SELECTION_TXT,
new SizeMetrics(
PixelUtils.dpToPix(100), SizeLayoutType.ABSOLUTE,
PixelUtils.dpToPix(100), SizeLayoutType.ABSOLUTE),
TextOrientationType.HORIZONTAL);
selectionWidget.getLabelPaint().setTextSize(PixelUtils.dpToPix(16));
// add a dark, semi-transparent background to the selection label widget:
Paint p = new Paint();
p.setARGB(100, 0, 0, 0);
selectionWidget.setBackgroundPaint(p);
selectionWidget.position(
0, XLayoutStyle.RELATIVE_TO_CENTER,
PixelUtils.dpToPix(45), YLayoutStyle.ABSOLUTE_FROM_TOP,
AnchorPosition.TOP_MIDDLE);
selectionWidget.pack();
// reduce the number of range labels
plot.setTicksPerRangeLabel(3);
plot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
plot.getGraphWidget().setGridPadding(30, 10, 30, 0);
plot.setTicksPerDomainLabel(2);
// setup checkbox listers:
series1CheckBox = (CheckBox) findViewById(R.id.s1CheckBox);
series1CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
onS1CheckBoxClicked(b);
}
});
series2CheckBox = (CheckBox) findViewById(R.id.s2CheckBox);
series2CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {onS2CheckBoxClicked(b);
}
});
plot.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
onPlotClicked(new PointF(motionEvent.getX(), motionEvent.getY()));
}
return true;
}
});
spRenderStyle = (Spinner) findViewById(R.id.spRenderStyle);
ArrayAdapter <BarRenderer.BarRenderStyle> adapter = new ArrayAdapter <BarRenderer.BarRenderStyle> (this, android.R.layout.simple_spinner_item, BarRenderer.BarRenderStyle.values() );
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spRenderStyle.setAdapter(adapter);
spRenderStyle.setSelection(BarRenderer.BarRenderStyle.OVERLAID.ordinal());
spRenderStyle.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
updatePlot();
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
spWidthStyle = (Spinner) findViewById(R.id.spWidthStyle);
ArrayAdapter <BarRenderer.BarWidthStyle> adapter1 = new ArrayAdapter <BarRenderer.BarWidthStyle> (this, android.R.layout.simple_spinner_item, BarRenderer.BarWidthStyle.values() );
adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spWidthStyle.setAdapter(adapter1);
spWidthStyle.setSelection(BarRenderer.BarWidthStyle.FIXED_WIDTH.ordinal());
spWidthStyle.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
if (BarRenderer.BarWidthStyle.FIXED_WIDTH.equals(spWidthStyle.getSelectedItem())) {
sbFixedWidth.setVisibility(View.VISIBLE);
sbVariableWidth.setVisibility(View.INVISIBLE);
} else {
sbFixedWidth.setVisibility(View.INVISIBLE);
sbVariableWidth.setVisibility(View.VISIBLE);
}
updatePlot();
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
spSeriesSize = (Spinner) findViewById(R.id.spSeriesSize);
ArrayAdapter <SeriesSize> adapter11 = new ArrayAdapter <SeriesSize> (this, android.R.layout.simple_spinner_item, SeriesSize.values() );
adapter11.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spSeriesSize.setAdapter(adapter11);
spSeriesSize.setSelection(SeriesSize.TEN.ordinal());
spSeriesSize.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
switch ((SeriesSize)arg0.getSelectedItem()) {
case TEN:
series1Numbers = series1Numbers10;
series2Numbers = series2Numbers10;
break;
case TWENTY:
series1Numbers = series1Numbers20;
series2Numbers = series2Numbers20;
break;
case SIXTY:
series1Numbers = series1Numbers60;
series2Numbers = series2Numbers60;
break;
default:
break;
}
updatePlot();
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
sbFixedWidth = (SeekBar) findViewById(R.id.sbFixed);
sbFixedWidth.setProgress(50);
sbFixedWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {updatePlot();}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
sbVariableWidth = (SeekBar) findViewById(R.id.sbVariable);
sbVariableWidth.setProgress(1);
sbVariableWidth.setVisibility(View.INVISIBLE);
sbVariableWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {updatePlot();}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
plot.setDomainValueFormat(new NumberFormat() {
@Override
public StringBuffer format(double value, StringBuffer buffer, FieldPosition field) {
int year = (int) (value + 0.5d) / 12;
int month = (int) ((value + 0.5d) % 12);
return new StringBuffer(DateFormatSymbols.getInstance().getShortMonths()[month] + " '0" + year);
}
@Override
public StringBuffer format(long value, StringBuffer buffer, FieldPosition field) {
throw new UnsupportedOperationException("Not yet implemented.");
}
@Override
public Number parse(String string, ParsePosition position) {
throw new UnsupportedOperationException("Not yet implemented.");
}
});
updatePlot();
}
private void updatePlot() {
// Remove all current series from each plot
Iterator<XYSeries> iterator1 = plot.getSeriesSet().iterator();
while(iterator1.hasNext()) {
XYSeries setElement = iterator1.next();
plot.removeSeries(setElement);
}
// Setup our Series with the selected number of elements
series1 = new SimpleXYSeries(Arrays.asList(series1Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Us");
series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Them");
// add a new series' to the xyplot:
if (series1CheckBox.isChecked()) plot.addSeries(series1, formatter1);
if (series2CheckBox.isChecked()) plot.addSeries(series2, formatter2);
// Setup the BarRenderer with our selected options
MyBarRenderer renderer = ((MyBarRenderer)plot.getRenderer(MyBarRenderer.class));
renderer.setBarRenderStyle((BarRenderer.BarRenderStyle)spRenderStyle.getSelectedItem());
renderer.setBarWidthStyle((BarRenderer.BarWidthStyle)spWidthStyle.getSelectedItem());
renderer.setBarWidth(sbFixedWidth.getProgress());
renderer.setBarGap(sbVariableWidth.getProgress());
if (BarRenderer.BarRenderStyle.STACKED.equals(spRenderStyle.getSelectedItem())) {
plot.setRangeTopMin(15);
} else {
plot.setRangeTopMin(0);
}
plot.redraw();
}
private void onPlotClicked(PointF point) {
// make sure the point lies within the graph area. we use gridrect
// because it accounts for margins and padding as well.
if (plot.getGraphWidget().getGridRect().contains(point.x, point.y)) {
Number x = plot.getXVal(point);
Number y = plot.getYVal(point);
selection = null;
double xDistance = 0;
double yDistance = 0;
// find the closest value to the selection:
for (XYSeries series : plot.getSeriesSet()) {
for (int i = 0; i < series.size(); i++) {
Number thisX = series.getX(i);
Number thisY = series.getY(i);
if (thisX != null && thisY != null) {
double thisXDistance =
LineRegion.measure(x, thisX).doubleValue();
double thisYDistance =
LineRegion.measure(y, thisY).doubleValue();
if (selection == null) {
selection = new Pair<Integer, XYSeries>(i, series);
xDistance = thisXDistance;
yDistance = thisYDistance;
} else if (thisXDistance < xDistance) {
selection = new Pair<Integer, XYSeries>(i, series);
xDistance = thisXDistance;
yDistance = thisYDistance;
} else if (thisXDistance == xDistance &&
thisYDistance < yDistance &&
thisY.doubleValue() >= y.doubleValue()) {
selection = new Pair<Integer, XYSeries>(i, series);
xDistance = thisXDistance;
yDistance = thisYDistance;
}
}
}
}
} else {
// if the press was outside the graph area, deselect:
selection = null;
}
if(selection == null) {
selectionWidget.setText(NO_SELECTION_TXT);
} else {
selectionWidget.setText("Selected: " + selection.second.getTitle() +
" Value: " + selection.second.getY(selection.first));
}
plot.redraw();
}
private void onS1CheckBoxClicked(boolean checked) {
if (checked) {
plot.addSeries(series1, formatter1);
} else {
plot.removeSeries(series1);
}
plot.redraw();
}
private void onS2CheckBoxClicked(boolean checked) {
if (checked) {
plot.addSeries(series2, formatter2);
} else {
plot.removeSeries(series2);
}
plot.redraw();
}
class MyBarFormatter extends BarFormatter {
public MyBarFormatter(int fillColor, int borderColor) {
super(fillColor, borderColor);
}
@Override
public Class<? extends SeriesRenderer> getRendererClass() {
return MyBarRenderer.class;
}
@Override
public SeriesRenderer getRendererInstance(XYPlot plot) {
return new MyBarRenderer(plot);
}
}
class MyBarRenderer extends BarRenderer<MyBarFormatter> {
public MyBarRenderer(XYPlot plot) {
super(plot);
}
/**
* Implementing this method to allow us to inject our
* special selection formatter.
* @param index index of the point being rendered.
* @param series XYSeries to which the point being rendered belongs.
* @return
*/
//@Override
// TODO: figure out why using @Override screws up the Maven builds
protected MyBarFormatter getFormatter(int index, XYSeries series) {
if(selection != null &&
selection.second == series &&
selection.first == index) {
return selectionFormatter;
} else {
return getFormatter(series);
}
}
}
}