AChartEngine max x labels visible - android

My question is how to set in AChartEngine lots of x labels on x axis and set visible only for example 20 and when make zoom in more precision labels will appear.

At the beginning, just set the needed maximum number of labels for the initial view:
renderer.setXLabels(20);
Whenever you zoom in, AChartEngine will update the labels to have a maximum number of 20 labels. It automatically chooses the round values labels, so you won't have exactly 20, but you will have a decent number of quite round numbers.
On the other side, if you mean custom text labels, like the ones you add using renderer.setXTextLabel() then you have to listen for zoom events and update the labels accordingly:
mChartView.addZoomListener(new ZoomListener() {
public void zoomApplied(ZoomEvent e) {
double start = renderer.getXAxisMin();
double stop = renderer.getXAxisMax();
double step = (stop - start) / 20;
renderer.removeXTextLabels();
for (double i = start; i <= stop; i += step) {
renderer.addXTextLabel(i, "text");
}
}
public void zoomReset() {
}
}, true, true);

Related

Drawable Icon of entry will disappear if chart.setVisibleXRangeMaximum bigger than a specific value

I am using MPAndroidchart and I want to draw a red dot when I detect a trough in realtime.
I use
set.getEntryForIndex(set.getEntryCount()-1).setIcon(ContextCompat.getDrawable(this, R.drawable.red_dot));
If I set chart.setVisibleXRangeMaximum(90), it is fine, but if I set XRangeMaximum bigger than about 100, the red dot will disappear if I don't use finger to zoom in.
When I zoom in, I can see those red dot
Is there any solution to keep the red dot visible when XRangeMaximum bigger than 100 and no need to zoom in because I want to show about 300 data in that chart.
private void addEntry(double Pulse_Signal){
LineData data = chart.getData();
if (data != null) {
ILineDataSet set = data.getDataSetByIndex(0);
// set.addEntry(...); // can be called as well
if (set == null) {
set = createSet(false);
data.addDataSet(set);
}
if(DrawCircleFlag){
set.getEntryForIndex(set.getEntryCount()-1).setIcon(ContextCompat.getDrawable(this, R.drawable.red_dot));
}
else {
data.addEntry(new Entry(set.getEntryCount(), (float) Pulse_Signal), 0);
}
data.notifyDataChanged();
chart.notifyDataSetChanged();
chart.setVisibleXRangeMaximum(260);
chart.moveViewToX(data.getEntryCount());
}
}
XRangeMaximum 90 like this
XRangeMaximum 260 like this
XRangeMaximum 260 when I use finger to zoom in
I had this problem and found the answer. There is a maximum number of visible values on the chart, and it won't draw icons when you exceed this. The default seems to be 100. I used the following to make sure that icons are always drawn on my chart:
chart.setMaxVisibleValueCount(10000000);

Show XY Plot with only one value in Androidplot

I am using Androidplot to display real time data. The real time data is dynamic and the x axis is time. The domain is a fixed timespan. Because the values could be any double, rangeStepMode is set to subdivide the y-axis. The issue is that when all the values are the same, the plot does not draw correctly.
You can see in the image that there are no range grids or labels.
From this related question:
This happens because Androidplot does not have enough information to automatically calculate what a reasonable domain/range scale would be from a single point.
This makes sense, and the linked solution of setting a non-zero range boundary works but instead of an arbitrary range like miny - 1to maxy + 1, I'd like for there to be only one labeled range line in these cases across the middle of the screen.
My Activity:
public class SimpleXYPlotActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_xy_plot_example);
XYPlot plot = (XYPlot) findViewById(R.id.plot);
Number[] series1Numbers = new Number[]{
1,
1,
};
XYSeries series1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series1", series1Numbers);
LineAndPointFormatter series1Format = new LineAndPointFormatter(Color.RED, Color.GREEN, null, null);
plot.addSeries(series1, series1Format);
StepMode stepMode = StepMode.SUBDIVIDE;
int stepValue = 10;
plot.setRangeStepMode(stepMode);
plot.setRangeStepValue(stepValue);
}
}
My Layout:
<com.androidplot.xy.XYPlot
android:id="#+id/plot"
style="#style/APDefacto.Dark"
android:layout_width="match_parent"
android:layout_height="match_parent"
ap:rangeStep="5"
ap:rangeStepMode="subdivide"
ap:domainStep="1"
ap:domainStepMode="increment_by_val"
/>
What I've tried
The following works on the simple xy example, but when I try setting it within minmax in my time series class, the thread drawing the domain gridlines gets into a very long loop as it calculates xPix to be very very large. This also isn't a very solution for situations where I have multiple series. Any ideas on how I can get the results I want?
Double minY = null;
Double maxY = null;
for (Double aDouble : series1Numbers) {
if (minY == null) {
minY = aDouble;
} else {
minY = Math.min(minY, aDouble);
}
if (maxY == null) {
maxY = aDouble;
} else {
maxY = Math.max(maxY, aDouble);
}
}
if (minY.equals(maxY)) {
plot.setUserRangeOrigin(minY);
plot.setRangeBoundaries(0, 2 * maxY, BoundaryMode.FIXED);
plot.setRangeStepMode(StepMode.INCREMENT_BY_PIXELS);
plot.setRangeStepValue(Double.POSITIVE_INFINITY);
} else {
plot.setUserRangeOrigin(0);
plot.setRangeBoundaries(minY, maxY, BoundaryMode.FIXED);
plot.setRangeStepMode(StepMode.SUBDIVIDE);
plot.setRangeStepValue(5);
}
This wont get rid of the CPU overhead but you can use PlotUtils.minMax to find these values for you
If implementing and using FastXYSeries is a possibility for you then the overhead of using minMax drops away too, as the calculation only ever gets done when your XYSeries data changes.

mpandroidchart - How can I avoid the repeated values in Y-Axis?

I want to avoid the repeated values and the -0 values in Y-Axis, avoiding the image situation.
I have these ideas to solve this, but any solution:
Limit the zoom before having repeated values in YAxis, therefore stop the infinite zoom-in on the chart
Get the YAxis values and remove the duplicate values.
Though this is an older question I'd like to add to it for future reference. Newer versions of the library have a little known feature that resolves the duplicated labels, called granularity. This is way simpler to use than the older solutions (though to be fair, this wasn't available at the time those were posted).
You can always check the latest AxisBase Javadocs (3.0.0-beta1) for a more detailed explanation. Here are the relevant methods:
setGranularity(float granularity):
Set a minimum interval for the axis when zooming in. The axis is not
allowed to go below that limit. This can be used to avoid label
duplicating when zooming in.
setGranularityEnabled(boolean enabled):
Enabled/disable granularity control on axis value intervals. If
enabled, the axis interval is not allowed to go below a certain
granularity.
So in your case you'd need to set the granularity to 0.1f since you have one decimal point. The following snippet of code should avoid the repeated values on the axis:
YAxis yAxis = mChart.getAxisLeft();
yAxis.setGranularityEnabled(true);
yAxis.setGranularity(0.1f);
tl;dr You can do this by changing the label count in onChartScale.
First, you want your listener set up:
chart.setOnChartGestureListener(this); // set a listener ;)
You try to get the top/bottom drawn values and check what gets drawn on the screen. Apply some basic calculations, and you're set.
The following code will draw 2 labels if the zoom level gets (too) high and up to 9 otherwise:
#Override
public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
final YAxis yAxis = mChart.getAxisLeft();
final Transformer transformer = mChart.getTransformer(YAxis.AxisDependency.LEFT);
// ...minor dirty hack
final PointD top = transformer.getValuesByTouchPoint(0, 0);
final PointD bottom = transformer.getValuesByTouchPoint(0, mChart.getHeight());
final int diff = (int)(top.y - bottom.y);
// draw 2-9 axis labels
final int count = Math.min(9, Math.max(diff, 2));
Log.d("scale", String.format("scale %f: diff %d and count %d", scaleY, diff, count));
// "force" the count, for there are drawing issues where none get drawn on high zoom levels (just try it out)
yAxis.setLabelCount(count, true);
}
// todo implement other interface methods
The value formatter and everything else stays the same.
And some ugly screenshot to show that it works :D
code like this:
mchart.setAutoScaleMinMaxEnabled(false);
mchart.getAxisLeft().setAxisMaxValue(10);
mchart.getAxisLeft().setAxisMinValue(5);
mchart.getAxisRight().setAxisMaxValue(10);
mchart.getAxisRight().setAxisMinValue(5);
or:
boolean a = isReady;
mchart.getAxisLeft().setFormater(new format(float b){ return "" ;})
When u get data :
mchart.setAutoScaleMinMaxEnabled(true);
mchart.getAxisLeft().resetAxisMaxValue();
mchart.getAxisLeft().resetAxisMinValue();
mchart.getAxisRight().resetAxisMaxValue();
mchart.getAxisRight().resetAxisMinValue(5);
I have no code by my hand.
You can set granularity to your required value to prevent the repetition when zoomed.
mBarChart.getAxisLeft().setGranularity(1f); // y-axis scale will be 0,1,2,3,4....
mBarChart.getAxisLeft().setGranularityEnabled(true);
If you want to set granularity dynamically, then implement IAxisValueFormatter and compare the return value to get the difference and set Granularity to that difference.
private float yAxisScaleDifference = -1;
private boolean granularitySet = false;
//say the values returned by IAxisFormatter is in hundreds: 100, 200, 300...
mBarChart.getAxisLeft.setValueFormatter(new IAxisValueFormatter() {
#Override
public String getFormattedValue(float v, AxisBase axisBase) {
if(!granularitySet) {
if(yAxisScaleDifference == -1) {
yAxisScaleDifference = v; //10
}
else {
float diff = v - yAxisScaleDifference; //200 - 100 = 100
if(diff >= 1000) {
yAxisLeft.setGranularity(1000f);
}
else if(diff >= 100) {
yAxisLeft.setGranularity(100f); //set to 100
}
else if(diff >= 1f) {
yAxisLeft.setGranularity(1f);
}
granularitySet =true;
}
}
return val;
}
});
Another Example:
say Y-Axis returns [1200,3400,8000,9000....]
first time: 1200
second time: 3400 - 1200 = 2200
granularity set to 1000
If the difference is not uniform you have to use array to store the differences and take the average to get the right granularity.
If you are using IAxisValueFormatter the problem might be in the conversion of float to Int when trying to access the values array.
For me the solution was to
IAxisValueFormatter numAppointmentsXAxisFormatter = new IAxisValueFormatter() {
#Override
public String getFormattedValue(float value, AxisBase axis) {
int index = (int)Math.ceil(value);
if(index < 0){
index = 0;
}else if(index >= numAppointmentsLabels.size()){
index = numAppointmentsLabels.size()-1;
}
return numAppointmentsLabels.get(index);
}
};
I also added +1 to the label count
chart.getXAxis().setLabelCount(numValue+1, true);
Hope it helps
I have never used MPAndroidCharts before, but just a little search here and there got me this method which I think would be useful.
public void setLabelCount(int count, boolean force) {
if (count > 25)
count = 25;
if (count < 2)
count = 2;
mLabelCount = count;
mForceLabels = force;
}
The description says that "exact specified count of labels will be drawn and evenly distributed alongside the axis". If you can make it work in your favor, you might be able to limit the zoom.
Also, there is another method-
public int getLabelCount() {
return mLabelCount;
}
This returns the number of labels on the axis. Hope this helps.
https://github.com/PhilJay/MPAndroidChart/blob/5a15715b25991e3d61d27d552f9eba45975d65e7/MPChartLib/src/com/github/mikephil/charting/components/YAxis.java

How to display X and Y axis for XYPlot in AndroidPlot

Background
I'm developing an app for Android that plots data as a line graph using AndroidPlot. Because of the nature of the data, it's important that it be pannable and zoomable. I'm using AndroidPlot's sample code on bitbucket for panning and zooming, modified to allow panning and zooming in both X and Y directions.
Everything works as desired except that there are no X and Y axis lines. It is very disorienting to look at the data without them. The grid helps, but there's no guarantee that grid lines will actually fall on the axis.
To remedy this I have tried adding two series, one that falls on just the X axis and the other on the Y. The problem with this is that if one zooms out too far the axis simply end, and it becomes apparent that I have applied a 'hack'.
Question
Is it possible to add X and Y axis lines to AndroidPlot? Or will my sad hack have to do?
EDIT
Added tags
I figured it out. It wasn't trivial, took a joint effort with a collaborator, and sucked up many hours of our time.
Starting with the sample mentioned in my question, I had to extend XYPlot (which I called GraphView) and override the onPreInit method. Note that I have two PointF's, minXY and maxXY, that are defined in my overridden XYPlot and manipulated when I zoom or scroll.
#Override
protected void onPreInit() {
super.onPreInit();
final Paint axisPaint = new Paint();
axisPaint.setColor(getResources().getColor(R.color.MY_AXIS_COLOR));
axisPaint.setStrokeWidth(3); //or whatever stroke width you want
XYGraphWidget oldWidget = getGraphWidget();
XYGraphWidget widget = new XYGraphWidget(getLayoutManager(),
this,
new SizeMetrics(
oldWidget.getHeightMetric(),
oldWidget.getWidthMetric())) {
//We now override XYGraphWidget methods
RectF mGridRect;
#Override
protected void doOnDraw(Canvas canvas, RectF widgetRect)
throws PlotRenderException {
//In order to draw the x axis, we must obtain gridRect. I believe this is the only
//way to do so as the more convenient routes have private rather than protected access.
mGridRect = new RectF(widgetRect.left + ((isRangeAxisLeft())?getRangeLabelWidth():1),
widgetRect.top + ((isDomainAxisBottom())?1:getDomainLabelWidth()),
widgetRect.right - ((isRangeAxisLeft())?1:getRangeLabelWidth()),
widgetRect.bottom - ((isDomainAxisBottom())?getDomainLabelWidth():1));
super.doOnDraw(canvas, widgetRect);
}
#Override
protected void drawGrid(Canvas canvas) {
super.drawGrid(canvas);
if(mGridRect == null) return;
//minXY and maxXY are PointF's defined elsewhere. See my comment in the answer.
if(minXY.y <= 0 && maxXY.y >= 0) { //Draw the x axis
RectF paddedGridRect = getGridRect();
//Note: GraphView.this is the extended XYPlot instance.
XYStep rangeStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.RANGE,
paddedGridRect, getCalculatedMinY().doubleValue(),
getCalculatedMaxY().doubleValue());
double rangeOriginF = paddedGridRect.bottom;
float yPix = (float) (rangeOriginF + getRangeOrigin().doubleValue() * rangeStep.getStepPix() /
rangeStep.getStepVal());
//Keep things consistent with drawing y axis even though drawRangeTick is public
//drawRangeTick(canvas, yPix, 0, getRangeLabelPaint(), axisPaint, true);
canvas.drawLine(mGridRect.left, yPix, mGridRect.right, yPix, axisPaint);
}
if(minXY.x <= 0 && maxXY.x >= 0) { //Draw the y axis
RectF paddedGridRect = getGridRect();
XYStep domianStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.DOMAIN,
paddedGridRect, getCalculatedMinX().doubleValue(),
getCalculatedMaxX().doubleValue());
double domainOriginF = paddedGridRect.left;
float xPix = (float) (domainOriginF - getDomainOrigin().doubleValue() * domianStep.getStepPix() /
domianStep.getStepVal());
//Unfortunately, drawDomainTick has private access in XYGraphWidget
canvas.drawLine(xPix, mGridRect.top, xPix, mGridRect.bottom, axisPaint);
}
}
};
widget.setBackgroundPaint(oldWidget.getBackgroundPaint());
widget.setMarginTop(oldWidget.getMarginTop());
widget.setMarginRight(oldWidget.getMarginRight());
widget.setPositionMetrics(oldWidget.getPositionMetrics());
getLayoutManager().remove(oldWidget);
getLayoutManager().addToTop(widget);
setGraphWidget(widget);
//More customizations can go here
}
And that was that. I sure wish this was built into AndroidPlot; it'll be nasty trying to fix this when it breaks in an AndroidPlot update...

Android - How to keep default zoom/pan features with custom x-axis labels using AChartEngine

How do I apply custom x-axis labels to my AChartEngine BarGraph while keeping the same zoom/pan functionality as when default labels are used? The problem is that I have 100+ data points so I only want to draw 5 or 6 labels on the screen at a time.
The default functionality originally draws 3 labels (0, 50, and 100 in my case) and when zooming in, 4/5 labels are drawn. When panning left or right, the labels move along with the corresponding bars:
I have attempted two approaches however neither of them give me the same zoom and pan functionality as the default labels.
1) Generate 5 evenly spaced labels when the graph is first created.
The problem with this is that it only creates 5 labels so when you zoom in too far, no labels are shown.
//For 100 data points, add five labels every 20 datapoints
mRenderer.addXTextLabel(0, "label 1");
mRenderer.addXTextLabel(20, "label 2");
mRenderer.addXTextLabel(40, "label 3");
mRenderer.addXTextLabel(60, "label 4");
mRenderer.addXTextLabel(80, "label 5");
mRenderer.setXLabels(0);
2) Implement the ZoomListener and PanListener interfaces on my GraphicalView.
This solution almost works, however the labels are always in fixed locations and are just "updated" with a new value when the graph is panned left or right. I need them to move with their corresponding bars.
graphicalView.addZoomListener( new ZoomListener() {
#Override
public void zoomApplied(ZoomEvent e) {
double start = mRenderer.getXAxisMin();
double stop = (double)mRenderer.getXAxisMax();
double step = (double)(stop - start) / 5;
mRenderer.clearXTextLabels();
for (double i = start; i <= stop; i += step)
mRenderer.addXTextLabel(i+1, "label"+(int)i);
mRenderer.setXLabels(0);
}
#Override
public void zoomReset() { }
}, true, true);
graphicalView.addPanListener(new PanListener() {
#Override
public void panApplied() {
double start = mRenderer.getXAxisMin();
double stop = (double)mRenderer.getXAxisMax();
double step = (double)(stop - start) / 5;
mRenderer.clearXTextLabels();
for (double i = start; i <= stop; i += step)
mRenderer.addXTextLabel(i+1, "label"+(int)i);
mRenderer.setXLabels(0);
}
});
The custom labels that I want to implement are time in minutes. Can this be done using the
TimeSeries class with a bar chart?
Your solution is quite good, so I can only suggest an improvement for getting to the labels behavior you need: Use MathHelper.getLabels(start, stop, approxNumLabels) instead of your for loop between start and stop.
So your for loop would become something like this:
List<Double> labels = MathHelper.getLabels(start, stop, 10);
for (Double label : labels) {
mRenderer.addXTextLabel(label, "label " + label);
}

Categories

Resources