Use special date format for x axis in achartengine - android

I'm using achartengine to visualize some data in time. The use of a TimeChart and TimeSeries is perfect but I would like following result which doesn't seem possible at the moment.
Currently I'm using this:
GraphicalView view = ChartFactory.getTimeChartView(this, dataset, renderer, "MMM");
This results in following output on x axis:
nov. apr. aug.
But I would like a marker for each month and only the first character of each month, like this:
N D J F M A M J J A S O
Ideas on how to accomplish this? Thanks!

I've found the solution myself where I override the drawXLabels method in a custom TimeChart class.
private class CustomTimeChart extends TimeChart {
public CustomTimeChart(XYMultipleSeriesDataset dataset,
XYMultipleSeriesRenderer renderer) {
super(dataset, renderer);
}
#Override
protected void drawXLabels(List<Double> xLabels,
Double[] xTextLabelLocations, Canvas canvas, Paint paint,
int left, int top, int bottom, double xPixelsPerUnit,
double minX, double maxX) {
int length = xLabels.size();
if (length > 0) {
boolean showLabels = mRenderer.isShowLabels();
boolean showGridY = mRenderer.isShowGridY();
DateFormat format = new SimpleDateFormat("MMMM");
for (int i = 0; i < length; i++) {
long label = Math.round(xLabels.get(i));
float xLabel = (float) (left + xPixelsPerUnit
* (label - minX));
if (showLabels) {
paint.setColor(mRenderer.getXLabelsColor());
canvas.drawLine(xLabel, bottom, xLabel, bottom
+ mRenderer.getLabelsTextSize() / 3, paint);
drawText(canvas, format.format(new Date(label)).substring(0, 1).toUpperCase(Locale.getDefault()),
xLabel, bottom + mRenderer.getLabelsTextSize()
* 4 / 3, paint,
mRenderer.getXLabelsAngle());
}
if (showGridY) {
paint.setColor(mRenderer.getGridColor());
canvas.drawLine(xLabel, bottom, xLabel, top, paint);
}
}
}
drawXTextLabels(xTextLabelLocations, canvas, paint, true, left,
top, bottom, xPixelsPerUnit, minX, maxX);
}
Also make sure to use the setXLabels(int i) on the XYMultipleSeriesRenderer instance to set how many months you want to show on x axis.

Related

Move a ball on Grid path randomly in Canvas Android

Requirement is to keep a ball moving on the Grid path generated in Canvas. I have generated a Grid in canvas but not able to understand how to move the ball randomly means starting point show be different on the path. I am sharing what I have done. I have also plotted the ball in the screen but not getting the point how to put the ball exactly on the grid line randomly and start moving it
public class PixelGridView extends View {
//number of row and column
int horizontalGridCount = 11;
private Drawable horiz;
private Drawable vert;
private final float width;
long mInterpolateTime;
PointF mImageSource = new PointF();
public PixelGridView(#NonNull Context context) {
this(context, null);
}
public PixelGridView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
horiz = new ColorDrawable(Color.WHITE);
horiz.setAlpha(160);
vert = new ColorDrawable(Color.WHITE);
vert.setAlpha(160);
width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, .9f, context.getResources().getDisplayMetrics());
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
horiz.setBounds(left, 0, right, (int) width);
vert.setBounds(0, top, (int) width, bottom);
}
private float getLinePosition(int lineNumber) {
int lineCount = horizontalGridCount;
return (1f / (lineCount + 1)) * (lineNumber + 1f);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//drawTask.start();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
canvas.drawCircle(120, 110, 10, paint);
int count = horizontalGridCount;
for (int n = 0; n < count; n++) {
float pos = getLinePosition(n);
// Draw horizontal line
canvas.translate(0, pos * getHeight());
Log.e("Position1", "" + pos * getHeight());
horiz.draw(canvas);
canvas.translate(0, -pos * getHeight());
// Draw vertical line
canvas.translate(pos * getHeight(), 0);
Log.e("Position2", "" + pos * getHeight());
vert.draw(canvas);
canvas.translate(-pos * getHeight(), 0);
}
}
}[![Canvas Image][1]][1]
//MainActivity
public class PathAnimationActivity extends AppCompatActivity {
LinearLayout rlLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_path);
rlLayout=findViewById(R.id.rlLayout);
PixelGridView pixelGrid = new PixelGridView(this);
rlLayout.addView(pixelGrid);
}
}
First thing i have noticed it that you haven't used invalidate (); at the end because thats critical in order to animate the canvas ( redraw the frames ) so please include that .
there may be several ways to achieve what you want follows is my idea
this canvas need to be divided into multiple x , y divided planes as follows and save them in array of points which you can randomize and give those points to ball to move ,
Step 1, get the canvas size
step 2, divide is in x and y coordinate depending on size of each device varies so you need to control that factor via Screen size
step 3, save the coordinates in matrix or array
step 4, set position of balls from those arrays values ( randomly can you define the random limits as per the max and min values of x and y from the coordinates division .
example , function move will take ball object and x, y are the positions move (ball, x, y ); and you can randomize the x and y based on max and min limits of your coordinates division example total y lines and total x lines values
in order to get an idea about how to move the ball on canvas you can see this code here : https://github.com/pintspin/ball_animation

How to get X and Y of detected circles?

I want to extract circles in and Image, So I extract them with below code:
Mat circles = new Mat();
Imgproc.HoughCircles(adaptiveThresh, circles, Imgproc.HOUGH_GRADIENT, 1.0, (double) adaptiveThresh.rows() / 40, 100.0, 30.0, 20, 30);
And then I iterate through them with below code:
for (int x = 0; x < circles.cols(); x++) {
double[] c = circles.get(0, x);
Point center = new Point(Math.round(c[0]), Math.round(c[1]));
int radius = (int) Math.round(c[2]);
Imgproc.circle(source, center, radius, new Scalar(0, 0, 255), 3);
}
But I want to sort them from topleft to bottom right, And the problem is I can not access the x and y of the circles!
How may I sort them based on row from top left to bottom right?
Your question is can be confusing. As it can be (1) sort distance to the circle to the top left of the image. (2)sort the distance from top left of each circle to top left of the image corner?
I assume you want to find the circle which is most close to top left case (1).
Here is my response.
From the C++ sample( i guess you are using android, not very familiar). You can convert using my sample code below.
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// circle center
circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );
// circle outline
circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
the center should be the point you want.
To sort them from top left to bottom right using city block distance you just have to
void sort_points (std::vector<Vec3f> &array)
{
std::cout<<"Elements in the array: "<<array.size()<<std::endl;
//comparisons will be done n times
for (int i = 0; i < array.size(); i++)
{
//compare elemet to the next element, and swap if condition is true
for(int j = 0; j < array.size() - 1; j++)
{
if ((array[j][0]+array[j][1]) > (array[j+1][0]+ array[j+1][1])
Swap(&array[j], &array[j+1]);
}
}
}
int main(argc,argv)
// ...................//do your stuff
vector<Vec3f> circles; //detected circle
// ... //do the detection
sort_points(circles);
//circles here is fully sorted from city block distance
std::cout<<circles<<std::endl; // print out all sorted circile
// ...................//do your stuff
}
if it is 2nd case just change if by
if ((array[j][0]+array[j][1]-2*array[j][2]) > (array[j+1][0]+ array[j+1][1]-2*array[j+1][2])
Here is what you need to do:
First, declare a Circle class (for encapsulation the circle properties for sorting purpose)
class Circle {
int cX;
int cY;
int radius;
double distance;
}
Now iterate over the HoughCircles result, and create a Circle instance and then add it to the List
List<Circle> circleList = new ArrayList<>();
//start point, it is used to calculate the distance
Point p1 = new Point(0, 0);
for (int x = 0; x < circles.cols(); x++) {
double[] c = circles.get(0, x);
Point center = new Point(Math.round(c[0]), Math.round(c[1]));
int radius = (int) Math.round(c[2]);
Imgproc.circle(source, center, radius, new Scalar(0, 0, 255), 3);
// here create the Circle instance
Circle circle = new Circle();
cricle.cX = center.x;
circle.cY = center.y;
circle.radius= radius;
double D = Math.sqrt(Math.pow(abs(p1.x - circles.x), 2) + Math.pow(abs(p1.y - circles.y), 2));
circle.distance = D;
// add the circle instance to the list
circleList.add(circle);
}
Now sort the circles in the list, use distance from small to bigger
circleList.sort(new Comparator<File>() {
#Override
public int compare(Circle c1, Circle c2) {
return Double.compare(c1.distance, c2.distance);
}
});
Now you can do what you want with circles list.
Hope it helps!!

How to avoid object creation inside the onDraw method

I know that creating objects inside the onDraw method is very costly. I want to draw a matrix of rounded rectangles, which coordinates are dynamic, and I can't cache all that rectangles, because I use a scroll view and there may be a lot of rectangles, there's no other overload for drawRoundRect method, which has primitive arguments, and I forced to create a Rectangle object in every iteration. Who can suggest an effective solution for that?
#Override
protected void onDraw(Canvas canvas) {
int h = getMeasuredHeight();
int tileSize = h / rows;
for(int i = 0; i < rows; ++i) {
for(int j = 0; j < columns; ++j) {
int x = j * tileSize;
int y = i * tileSize;
canvas.drawRoundRect(new RectF(x, y, x + tileSize, y + tileSize), 10, 10, tilePaint);
}
}
}
This is a just an example, rectangles can have arbitrary coordinates.
RectF has the set(left, top, right, bottom) method. You could allocate it on the constructor and it this method to change the Rectf's bounds.
mRect.set(x, y, x + tileSize, y + tileSize);
where mRect is RectF mRect = new RectF();

How to add ImageView instead Labels on Axis in AChartEngine?

I want to add Image instead of Labels on XAxis, so does anyone know how to do that ?
Thanks in advance.
Update
I want to do something like the below image.
For this you have to edit source code of Achartengine because there is not any direct method for this.
Draw circles instead of small lines . For different colors of the circles , you may have to change few other things .
Change drawXLabels() method in XYChart.java .
protected void drawXLabels(List<Double> xLabels, Double[] xTextLabelLocations, Canvas canvas,
Paint paint, int left, int top, int bottom, double xPixelsPerUnit, double minX, double maxX) {
int length = xLabels.size();
boolean showLabels = mRenderer.isShowLabels();
boolean showGridY = mRenderer.isShowGridY();
boolean showTickMarks = mRenderer.isShowTickMarks();
for (int i = 0; i < length; i++) {
double label = xLabels.get(i);
float xLabel = (float) (left + xPixelsPerUnit * (label - minX));
if (showLabels) {
paint.setColor(mRenderer.getXLabelsColor());
if (showTickMarks) {
// canvas.drawLine(xLabel, bottom, xLabel, bottom + mRenderer.getLabelsTextSize() / 3, paint);
canvas.drawCircle(xLabel, bottom + mRenderer.getLabelsTextSize() / 3,3, paint);
}
drawText(canvas, getLabel(mRenderer.getLabelFormat(), label), xLabel,
bottom + mRenderer.getLabelsTextSize() * 4 / 3 + mRenderer.getXLabelsPadding(), paint,
mRenderer.getXLabelsAngle());
}
if (showGridY) {
paint.setColor(mRenderer.getGridColor(0));
// canvas.drawLine(xLabel, bottom, xLabel, top, paint);
canvas.drawCircle(xLabel, top,3, paint);
}
}
drawXTextLabels(xTextLabelLocations, canvas, paint, showLabels, left, top, bottom,
xPixelsPerUnit, minX, maxX);
}

Rotate the actual values that are displayed in bar chart on Android using AchartEngine

How can I rotate the values that are displayed on top of the bars shown in bar chart in achartengine. I know we can rotate the Y-axis labels using setYLabelsAngle(float angle) but I want to rotate the actual values shown on top of the bars. i.e values shown when you say setDisplayChartValues(boolean display).
I have searched for this feature on AchartEngine, AndroidPlot and AFreeChart but could not find it. Could you provide me a solution or should I implement this feature on my own using Canvas and custom view.
PS I only need to display BarChart in my app.
You cant do this by using any function of renderer etc, If you have source code of achartengine library, you can edit it and achieve your goal.
To rotate text change the drawChartValuesText() inside Barchart class not in XYChart class.
Just put the angle of rotation inside drawText() function at both the places as the following
protected void drawChartValuesText(Canvas canvas, XYSeries series, SimpleSeriesRenderer renderer,
Paint paint, List<Float> points, int seriesIndex, int startIndex) {
int seriesNr = mDataset.getSeriesCount();
int length = points.size();
float halfDiffX = getHalfDiffX(points, length, seriesNr);
for (int i = 0; i < length; i += 2) {
int index = startIndex + i / 2;
double value = series.getY(index);
if (!isNullValue(value)) {
float x = points.get(i);
if (mType == Type.DEFAULT) {
x += seriesIndex * 2 * halfDiffX - (seriesNr - 1.5f) * halfDiffX;
}
if (value >= 0) {
drawText(canvas, getLabel(renderer.getChartValuesFormat(), value), x, points.get(i + 1)
- renderer.getChartValuesSpacing(), paint, 90);
//Initially 90 was 0 you can change it according to your requirement
} else {
drawText(canvas, getLabel(renderer.getChartValuesFormat(), value), x, points.get(i + 1)
+ renderer.getChartValuesTextSize() + renderer.getChartValuesSpacing() - 3, paint, 90);
//Initially 90 was 0 you can change it according to your requirement
}
}
}
}
See the changes made inside the drawText() at both places and see the comments.There is not another way to achieve your goal.If it is make me know.

Categories

Resources