I'm trying to draw a pie chart with rounded corners using MpAndroidChart library.
Expected output is something similar to this.
Both ends need to be outer round. There is a method pieChart.setDrawRoundedSlices(true), but the issue is start point of the pie chart getting inner round.
This is the actual output.
// initialise pie chart UI
fun initChart(mChart: PieChart) {
mChart.description.isEnabled = false
mChart.holeRadius = 75f
mChart.transparentCircleRadius = 60f
mChart.setHoleColor(Color.TRANSPARENT)
mChart.legend.isEnabled = false
mChart.isRotationEnabled = false
mChart.setTouchEnabled(false)
mChart.maxAngle = 270f
mChart.rotation = -135f
mChart.animateX(400)
mChart.setDrawRoundedSlices(true)
}
I was faced with the same challenge recently, this is the code of the renderer in case anyone may need it:
public class RoundedSlicesPieChartRenderer extends PieChartRenderer {
public RoundedSlicesPieChartRenderer(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
chart.setDrawRoundedSlices(true);
}
#Override
protected void drawDataSet(Canvas c, IPieDataSet dataSet) {
float angle = 0;
float rotationAngle = mChart.getRotationAngle();
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
final RectF circleBox = mChart.getCircleBox();
final int entryCount = dataSet.getEntryCount();
final float[] drawAngles = mChart.getDrawAngles();
final MPPointF center = mChart.getCenterCircleBox();
final float radius = mChart.getRadius();
final boolean drawInnerArc = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled();
final float userInnerRadius = drawInnerArc
? radius * (mChart.getHoleRadius() / 100.f)
: 0.f;
final float roundedRadius = (radius - (radius * mChart.getHoleRadius() / 100f)) / 2f;
final RectF roundedCircleBox = new RectF();
int visibleAngleCount = 0;
for (int j = 0; j < entryCount; j++) {
// draw only if the value is greater than zero
if ((Math.abs(dataSet.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) {
visibleAngleCount++;
}
}
final float sliceSpace = visibleAngleCount <= 1 ? 0.f : getSliceSpace(dataSet);
final Path pathBuffer = new Path();
final RectF mInnerRectBuffer = new RectF();
for (int j = 0; j < entryCount; j++) {
float sliceAngle = drawAngles[j];
float innerRadius = userInnerRadius;
Entry e = dataSet.getEntryForIndex(j);
// draw only if the value is greater than zero
if (!(Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
angle += sliceAngle * phaseX;
continue;
}
// Don't draw if it's highlighted, unless the chart uses rounded slices
if (mChart.needsHighlight(j) && !drawInnerArc) {
angle += sliceAngle * phaseX;
continue;
}
final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f;
mRenderPaint.setColor(dataSet.getColor(j));
final float sliceSpaceAngleOuter = visibleAngleCount == 1 ?
0.f :
sliceSpace / (Utils.FDEG2RAD * radius);
final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY;
float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY;
if (sweepAngleOuter < 0.f) {
sweepAngleOuter = 0.f;
}
pathBuffer.reset();
float arcStartPointX = center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD);
float arcStartPointY = center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD);
if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
// Android is doing "mod 360"
pathBuffer.addCircle(center.x, center.y, radius, Path.Direction.CW);
} else {
if (drawInnerArc) {
float x = center.x + (radius - roundedRadius) * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD);
float y = center.y + (radius - roundedRadius) * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD);
roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius);
pathBuffer.arcTo(roundedCircleBox, startAngleOuter - 180, 180);
}
pathBuffer.arcTo(
circleBox,
startAngleOuter,
sweepAngleOuter
);
}
// API < 21 does not receive floats in addArc, but a RectF
mInnerRectBuffer.set(
center.x - innerRadius,
center.y - innerRadius,
center.x + innerRadius,
center.y + innerRadius);
if (drawInnerArc && (innerRadius > 0.f || accountForSliceSpacing)) {
if (accountForSliceSpacing) {
float minSpacedRadius =
calculateMinimumRadiusForSpacedSlice(
center, radius,
sliceAngle * phaseY,
arcStartPointX, arcStartPointY,
startAngleOuter,
sweepAngleOuter);
if (minSpacedRadius < 0.f)
minSpacedRadius = -minSpacedRadius;
innerRadius = Math.max(innerRadius, minSpacedRadius);
}
final float sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.f ?
0.f :
sliceSpace / (Utils.FDEG2RAD * innerRadius);
final float startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.f) * phaseY;
float sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY;
if (sweepAngleInner < 0.f) {
sweepAngleInner = 0.f;
}
final float endAngleInner = startAngleInner + sweepAngleInner;
if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
// Android is doing "mod 360"
pathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW);
} else {
float x = center.x + (radius - roundedRadius) * (float) Math.cos(endAngleInner * Utils.FDEG2RAD);
float y = center.y + (radius - roundedRadius) * (float) Math.sin(endAngleInner * Utils.FDEG2RAD);
roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius);
pathBuffer.arcTo(roundedCircleBox, endAngleInner, 180);
pathBuffer.arcTo(mInnerRectBuffer, endAngleInner, -sweepAngleInner);
}
} else {
if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) {
if (accountForSliceSpacing) {
float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f;
float sliceSpaceOffset =
calculateMinimumRadiusForSpacedSlice(
center,
radius,
sliceAngle * phaseY,
arcStartPointX,
arcStartPointY,
startAngleOuter,
sweepAngleOuter);
float arcEndPointX = center.x +
sliceSpaceOffset * (float) Math.cos(angleMiddle * Utils.FDEG2RAD);
float arcEndPointY = center.y +
sliceSpaceOffset * (float) Math.sin(angleMiddle * Utils.FDEG2RAD);
pathBuffer.lineTo(
arcEndPointX,
arcEndPointY);
} else {
pathBuffer.lineTo(
center.x,
center.y);
}
}
}
pathBuffer.close();
mBitmapCanvas.drawPath(pathBuffer, mRenderPaint);
angle += sliceAngle * phaseX;
}
MPPointF.recycleInstance(center);
}
}
And then you use it like this:
mChart.setRenderer(new RoundedSlicesPieChartRenderer(pieChart, pieChart.getAnimator(), pieChart.getViewPortHandler()));
Related
Hi i am creating indicator for doughnut chart but it is covering only three side. Here i added onDraw method.The selected chart index arc should be highlighted with indicator.The indicator should be cover all the four sides.
Code:
#Override
public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) {
paint.setAntiAlias(true);
paint.setStyle(Style.FILL);
int legendSize = getLegendSize(mRenderer, height / 5, 0);
int left = x;
int top = y;
int right = x + width;
int sLength = mDataset.getItemCount();
double total = 0;
String[] titles = new String[sLength];
for (int i = 0; i < sLength; i++) {
total += mDataset.getValue(i);
titles[i] = mDataset.getCategory(i);
}
if (mRenderer.isFitLegend()) {
legendSize = drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, true);
}
int bottom = y + height - legendSize;
drawBackground(mRenderer, canvas, x, y, width, height, paint, false, Renderer.NO_COLOR);
float currentAngle = mRenderer.getStartAngle();
float labelCurrentAngle = mRenderer.getStartAngle();
// int mRadius = Math.min(Math.abs(right - left), Math.abs(bottom - top));
// int radius = (int) (500 * 0.35) + 50;
//Log.i("radius++", "" + radius);
int mRadius = Math.min(Math.abs(right - left), Math.abs(bottom - top));
double rCoef = 0.35 * mRenderer.getScale();
double decCoef = 0.2 / sLength;
int radius = (int) (mRadius * rCoef);
if (mCenterX == 0) {
mCenterX = (left + right) / 2;
}
if (mCenterY == 0) {
mCenterY = (bottom + top) / 2;
}
// Hook in clip detection after center has been calculated
mPieMap.setDimensions(radius, mCenterX, mCenterY);
boolean loadPieCfg = !mPieMap.areAllSegmentPresent(sLength);
if (loadPieCfg) {
mPieMap.clearPieSegments();
}
float shortRadius = radius * 0.9f;
float longRadius = radius * 1.1f;
RectF oval = new RectF(mCenterX - radius, mCenterY - radius, mCenterX + radius, mCenterY + radius);
for (int i = 0; i < sLength; i++) {
float value = (float) mDataset.getValue(i);
float angle = (float) (value / total * 360);
int color = mRenderer.getRenderers().get(i).getColor();
if (mRenderer.getRenderers().get(i).isClicked()) {
ClickedArc.CANVAS = canvas;
ClickedArc.CURRENT_ANGLE = currentAngle;
ClickedArc.ANGLE = angle;
ClickedArc.COLOR = color;
ClickedArc.INDEX = mRenderer.getRenderers().get(i).getDataIndex();
} else {
Paint paint3 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint3.setStrokeCap(Paint.Cap.ROUND);
paint3.setStyle(Style.FILL);
int[] colors = {color, color};
RadialGradient gradient3 = new RadialGradient(mCenterX, mCenterY, radius, colors, null, android.graphics.Shader.TileMode.CLAMP);
paint3.setShader(gradient3);
paint3.setAntiAlias(true);
canvas.drawArc(oval, currentAngle, angle, true, paint3);
}
try {
if (mRenderer.getRenderers().get(ClickedArc.INDEX).isClicked()) {
Paint paint3 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint3.setStyle(Style.FILL);
paint3.setColor(Color.WHITE);
Paint shadow = new Paint(Paint.ANTI_ALIAS_FLAG);
int[] colors = {Color.BLACK, Color.BLACK};
RadialGradient gradient3 = new RadialGradient(mCenterX, mCenterY, radius, colors, null, android.graphics.Shader.TileMode.CLAMP);
shadow.setShader(gradient3);
shadow.setAntiAlias(true);
shadow.setColor(Color.BLACK);
shadow.setStrokeWidth(8);
shadow.setDither(true);
shadow.setStyle(Paint.Style.STROKE);
shadow.setStrokeCap(Paint.Cap.BUTT);
shadow.setAntiAlias(true);
int shadowRadius = radius + 2;
RectF shadowOval = new RectF(mCenterX - shadowRadius, mCenterY - shadowRadius, mCenterX + shadowRadius, mCenterY + shadowRadius);
canvas.drawArc(shadowOval, ClickedArc.CURRENT_ANGLE - 1, ClickedArc.ANGLE + 2, true, shadow);
canvas.drawArc(oval, ClickedArc.CURRENT_ANGLE, ClickedArc.ANGLE, true, paint3);
}
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
if (loadPieCfg) {
mRenderer.getRenderers().get(i).setDataIndex(i);
mPieMap.addPieSegment(i, value, currentAngle, angle + currentAngle);
}
currentAngle += angle;
}
radius -= (int) mRadius * decCoef;
shortRadius -= mRadius * decCoef - 2;
List<RectF> prevLabelsBounds = new ArrayList<RectF>();
for (int i = 0; i < sLength; i++) {
float value = (float) mDataset.getValue(i);
float angle = (float) (value / total * 360);
drawLabel(canvas, mDataset.getCategory(i), mRenderer, prevLabelsBounds, mCenterX, mCenterY, shortRadius / 2 + 50, longRadius / 2 + 50,
labelCurrentAngle, angle, left, right, mRenderer.getLabelsColor(), paint, true, mRenderer.getRenderers().get(i));
Point sPoint = mRenderer.getRenderers().get(i).getCenterPoint();
Point ePoint = new Point((int) mRenderer.getRenderers().get(i).getTextWidth(), (int) mRenderer.getRenderers().get(i).getTextHeight());
mPieMap.addLabelSegment(i, value, sPoint, ePoint);
labelCurrentAngle += angle;
}
prevLabelsBounds.clear();
}
I am currently developing a tree shaped structure using Canvas Circles and I have almost brought about the layouts using Canvas but facing problem while implementing Drag and Drop in Canvas. The code is as follows for better understanding :
private HashSet<CircleArea> mCircles = new HashSet<CircleArea>(CIRCLES_LIMIT);
private SparseArray<CircleArea> mCirclePointer = new SparseArray<CircleArea>(CIRCLES_LIMIT);
private static class CircleArea {
int radius;
int centerX;
int centerY;
CircleArea(int centerX, int centerY, int radius) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
}
public int getCenterX()
{
return centerX;
}
public int getCenterY()
{
return centerY;
}
public int getRadius()
{
return radius;
}
#Override
public String toString() {
return "Circle[" + centerX + ", " + centerY + ", " + radius + "]";
}
}
private void init(final Context ct) {
Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
System.out.println("Width is " + width + "Height is " + height);
scrWidth = width;
scrHeight = height;
}
#Override
public void onDraw(final Canvas canv) {
// setWillNotDraw(false);
for (int i = 0; i < name.length; i++) {
if (i == 0) {
for (int j = 0; j < latitude_0.length; j++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_0[j]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_0[j]));
mCircles.add(new CircleArea(x,y,RADIUS_LIMIT));
}
} else if (i == 1) {
for (int k = 0; k < latitude_1.length; k++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_1[k]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_1[k]));
mCircles.add(new CircleArea(x,y,RADIUS_LIMIT));
}
} else if (i == 2) {
for (int l = 0; l < latitude_2.length; l++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_2[l]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_2[l]));
mCircles.add(new CircleArea(x,y,RADIUS_LIMIT));
}
} else if (i == 3) {
for (int l = 0; l < latitude_3.length; l++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_3[l]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_3[l]));
mCircles.add(new CircleArea(x, y, RADIUS_LIMIT));
}
} else if (i == 4) {
for (int l = 0; l < latitude_4.length; l++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_4[l]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_4[l]));
mCircles.add(new CircleArea(x,y,RADIUS_LIMIT));
}
}else if (i == 5) {
for (int l = 0; l < latitude_5.length; l++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_5[l]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_5[l]));
mCircles.add(new CircleArea(x,y,RADIUS_LIMIT));
}
}else if (i == 6) {
for (int l = 0; l < latitude_6.length; l++) {
x = (int) ((scrWidth / 360.0) * (90 + longitude_6[l]));
y = (int) ((scrHeight / 180.0) * (90 - latitude_6[l]));
mCircles.add(new CircleArea(x,y,RADIUS_LIMIT));
}
}
}
for (CircleArea circle : mCircles) {
canv.drawCircle(circle.getCenterX(),circle.getCenterY(),RADIUS_LIMIT, mCirclePaint);
}
// invalidate();;
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
boolean handled = false;
CircleArea touchedCircle;
int xTouch;
int yTouch;
int pointerId;
int actionIndex = event.getActionIndex();
// get touch event coordinates and make transparent circle from it
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
xTouch = (int) event.getX(0);
yTouch = (int) event.getY(0);
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
mCirclePointer.put(event.getPointerId(0), touchedCircle);
invalidate();
handled = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.w(TAG, "Pointer down");
// It secondary pointers, so obtain their ids and check circles
pointerId = event.getPointerId(actionIndex);
xTouch = (int) event.getX(actionIndex);
yTouch = (int) event.getY(actionIndex);
// check if we've touched inside some circle
for(CircleArea circle: mCircles)
{
touchedCircle = obtainTouchedCircle(circle.getCenterX(), circle.getCenterY());
mCirclePointer.put(pointerId, touchedCircle);
touchedCircle.centerX = circle.getCenterX();
touchedCircle.centerY = circle.getCenterY();
}
invalidate();
handled = true;
break;
case MotionEvent.ACTION_MOVE:
final int pointerCount = event.getPointerCount();
Log.w(TAG, "Move");
for (actionIndex = 0; actionIndex < pointerCount; actionIndex++) {
// Some pointer has moved, search it by pointer id
pointerId = event.getPointerId(actionIndex);
for(CircleArea circle: mCircles)
{
xTouch = (int) event.getX(actionIndex);
yTouch = (int) event.getY(actionIndex);
float dx = xTouch - circle.getCenterX();
float dy = yTouch - circle.getCenterY();
float r = FloatMath.sqrt((dx * dx) + (dy * dy));
touchedCircle = mCirclePointer.get(pointerId);
if (null != touchedCircle) {
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
}
}
}
invalidate();
handled = true;
break;
case MotionEvent.ACTION_UP:
//clearCirclePointer();
invalidate();
handled = true;
break;
case MotionEvent.ACTION_POINTER_UP:
// not general pointer was up
pointerId = event.getPointerId(actionIndex);
mCirclePointer.remove(pointerId);
// invalidate();
handled = true;
break;
case MotionEvent.ACTION_CANCEL:
handled = true;
break;
default:
// do nothing
break;
}
return super.onTouchEvent(event) || handled;
}
private CircleArea obtainTouchedCircle(final int xTouch, final int yTouch) {
CircleArea touchedCircle = getTouchedCircle(xTouch, yTouch);
if (null == touchedCircle) {
touchedCircle = new CircleArea(xTouch, yTouch, RADIUS_LIMIT);
}
return touchedCircle;
}
private CircleArea getTouchedCircle(final int xTouch, final int yTouch) {
CircleArea touched = null;
for (CircleArea circle : mCircles) {
if ((circle.centerX - xTouch) * (circle.centerX - xTouch) + (circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) {
touched = circle;
break;
}
}
return touched;
}
I am well aware of the shabby code that I've written but highly helpless to generate a mathematical calculation on how to place the dragged circle to the nearest available canvas circle. Below depicted is a screenshot which helps in more understanding.
Points to be noted :
The function obtainTouchedCircle and getTouchedCircle are vital.
The MotionEvent.ACTION_MOVE helps me in moving a circle but what it does is that it generates a new circle with the same dimensions every time which is totally fine but when a user drags and drops in somewhere, it should get placed in the nearest available circle.
Consider the below image where the yellow circles need to be moved around and placed in the blue circles according to the user's wish. That is my objective.
If someone can help me with the calculation on how I should figure out the nearest available circle while dragging would be of great help !!!! Thanks in advance.
I have this constants defined, but I'm not sure how to find the normal index.
public static final int LEFT = 0;
public static final int RIGHT = 1;
public static final int UP = 2;
public static final int DOWN = 3;
public float[] normals = {-1, 0, 1, 0, 0, 1, 0, -1};
public float4 getNormal(int index)
{
return new float4(normals[index * 2], normals[index * 2 + 1], 0);
}
public static int normal(float x, float y, RectF rect)
{
// need to find the normal index here
return normal_index;
}
I think this should work for inside and outside points (radius centers):
public static int normal_index(float x, float y, RectF rect)
{
float cx = rect.left + rect.width() * 0.5f;
float cy = rect.top + rect.height() * 0.5f;
boolean a = x - rect.left < rect.bottom - y;
boolean b = x - rect.left < y - rect.top;
boolean c = x - rect.right < y - rect.bottom;
boolean d = x - rect.right < rect.top - y;
if (a && b && x < cx ) return LEFT;
else if (!c && !d && x >= cx ) return RIGHT;
else if (!a && c && y >= cy ) return DOWN;
else if (!b && d && y < cy ) return UP;
return -1;
}
The question seems related to this question Given a point inside a rectangle, determine the side that's closest to the point
For the lines a, b, c, d, I've got similar picture:
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I understand how to draw lines using a canvas, but how can I use the same lines using a canvas to draw a graph?
The problem is with the coordinates. (0,0) starts right at the top left corner of the device. How can I set (0,0) as the margin and draw the particular line with respect to the margin?
1)Create an activity.
public class GraphView1 extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
float[] values = new float[] { "your values"};
String[] verlabels = new String[] { "your values" };
String[] horlabels = new String[] { "your values"
GraphView graphView = new GraphView(this, values,"GraphView",horlabels, verlabels, GraphView.BAR);
setContentView(graphView);
}
}
2)Then create another class extends View:
public class GraphView2 extends View{
public static boolean LINE = true;
private Paint paint;
private float[] values;
private String[] str;
private String[] verlabels;
private String title;
private boolean type;
Context context;
public GraphView(Context context, float[] values, String title, String[] str,String[] verlabels, boolean type) {
super(context);
if (values == null)
values = new float[0];
else
this.values = values;
if (title == null)
title = "";
else
this.title = title;
if (str == null)
this.str = new String[0];
else
this.str = str;
if (verlabels == null)
this.verlabels = new String[0];
else
this.verlabels = verlabels;
this.type = type;
paint = new Paint();
}
#Override
protected void onDraw(final Canvas canvas) {
context=getContext();
float border = 15;
float horstart = border * 2;
float height = getHeight();
float width = getWidth();
float max = getMax();
Log.w("max", ""+max);
float min = getMin();
Log.w("min", ""+min);
float diff = max - min;
float graphheight = height - (2 * border);
float graphwidth = width - (2 * border);
paint.setTextAlign(Align.LEFT);
int vers = verlabels.length;
for (int i = 0; i < verlabels.length; i++) {
paint.setColor(Color.DKGRAY);
float y = ((graphheight / vers) * i) + border;
canvas.drawLine(horstart, y, width, y, paint);
paint.setColor(Color.WHITE);
paint.setTextSize(10);
canvas.drawText(verlabels[i], 0, y, paint);
}
int hors = values.length;
for (int i = 0; i < str.length; i++) {
paint.setColor(Color.DKGRAY);
float x = ((graphwidth / hors) * i) + horstart;
canvas.drawLine(x, height - border, x, border, paint);
paint.setTextAlign(Align.LEFT);
if (i==str.length)
paint.setTextAlign(Align.RIGHT);
if (i==0)
paint.setTextAlign(Align.LEFT);
paint.setColor(Color.WHITE);
paint.setTextSize(9);
canvas.drawText( str[i], x, height - 4, paint);
}
paint.setTextAlign(Align.CENTER);
canvas.drawText(title, (graphwidth / 2) + horstart, border - 4, paint);
if (max != min) {
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.FILL);
if (type == BAR) {
float datalength = values.length;
float colwidth = (width - (2 * border)) / datalength;
for (int i = 0; i < values.length; i++) {
// float val = values[i] - min;
// float rat = val / diff;
// float h = graphheight * rat;
// canvas.drawRect((i * colwidth) + horstart, (border - h) + graphheight, ((i * colwidth) + horstart) + (colwidth - 1), height - (border - 1), paint);
float graph_h = getHeight()-(border*2);
// Log.e("", "graph_h > "+graph_h);
float ind_h = graph_h/7;
//Log.e("", "ind_h > "+ind_h);
float t = values[i]/5;
float top = (graph_h - ind_h*(t));
// Log.e("", " > "+i+1);
// Log.e("", "top > "+top);
//for values between 0 and 5 ,vice versa
//Log.e("", " values[i] > "+values[i]);
float acc = ind_h/5;
acc = acc * (values[i]%5);
// Log.e("", " acc > "+acc);
canvas.drawRect((i * colwidth) + horstart, top+border-acc , ((i * colwidth) + horstart) + (colwidth - 1), graph_h+border, paint);
}
} else {
float datalength = values.length;
float colwidth = (width - (2 * border)) / datalength;
float halfcol = colwidth / 2;
float lasth = 0;
for (int i = 0; i < values.length; i++) {
float val = values[i] - min;
float rat = val / diff;
float h = graphheight * rat;
if (i > 0)
canvas.drawLine(((i - 1) * colwidth) + (horstart + 1) + halfcol, (border - lasth) + graphheight, (i * colwidth) + (horstart + 1) + halfcol, (border - h) + graphheight, paint);
lasth = h;
}
}
}
}
private float getMax() {
float largest = Integer.MIN_VALUE;
for (int i = 0; i < values.length; i++)
if (values[i] > largest)
largest = values[i];
return largest;
}
private float getMin() {
float smallest = Integer.MAX_VALUE;
for (int i = 0; i < values.length; i++)
if (values[i] < smallest)
smallest = values[i];
return smallest;
}
}
I have an imageView with multitouch roughly based on this tutorial. One of the commenters there put together a semi-dirty method of limiting the image drag to the boundaries of the image, so that the image edge cannot be dragged beyond its edge. This method sorta works, but not entirely. It only limits drag of two edges.
Does anyone know a less messy and actually functional method for limiting image drag?
This is a highly important concept for android app development that is not adequately addressed....
I was thinking of the following ideas:
1) setScaleType(scaleType.fitXY) when zoom = 1.0F (i.e. min zoom), and drag only enabled when zoom > 1.0f
2) when zoom > 1.0f, setScaleType(scaleType.MATRIX), then you determine image bounds and screen dimensions, and in some way that is too smart for me, using an if statement you only allow drag when the image edge is not on the screen. I don't know how to declare that, is the thing.
anyways, for completeness, here is the limit pan code from that link. This seems to be the most popular suggestion on stackoverflow, but I think we can do better:
// limit pan
matrix.getValues(matrixValues);
float currentY = matrixValues[Matrix.MTRANS_Y];
float currentX = matrixValues[Matrix.MTRANS_X];
float currentScale = matrixValues[Matrix.MSCALE_X];
float currentHeight = height * currentScale;
float currentWidth = width * currentScale;
float dx = event.getX() - start.x;
float dy = event.getY() - start.y;
float newX = currentX+dx;
float newY = currentY+dy;
RectF drawingRect = new RectF(newX, newY, newX+currentWidth, newY+currentHeight);
float diffUp = Math.min(viewRect.bottom-drawingRect.bottom, viewRect.top-drawingRect.top);
float diffDown = Math.max(viewRect.bottom-drawingRect.bottom, viewRect.top-drawingRect.top);
float diffLeft = Math.min(viewRect.left-drawingRect.left, viewRect.right-drawingRect.right);
float diffRight = Math.max(viewRect.left-drawingRect.left, viewRect.right-drawingRect.right);
if(diffUp > 0 ){
dy +=diffUp;
}
if(diffDown < 0){
dy +=diffDown;
}
if( diffLeft> 0){
dx += diffLeft;
}
if(diffRight < 0){
dx += diffRight;
}
matrix.postTranslate(dx, dy);
private void limitDrag(Matrix m, ImageView view) {
float[] values = new float[9];
m.getValues(values);
float transX = values[Matrix.MTRANS_X];
float transY = values[Matrix.MTRANS_Y];
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
Rect bounds = view.getDrawable().getBounds();
int viewWidth = getResources().getDisplayMetrics().widthPixels;
int viewHeight = getResources().getDisplayMetrics().heightPixels;
if(viewHeight<=480)
{
_y_up=0;
}
if(viewHeight>480&&viewHeight<980)
{
_y_up=140;
}
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
int __width=width;
int __height=height;
width = viewWidth / 2;
height = viewHeight / 2;
//height = 200 ;
float minX = (-width) ;//* scaleX;
float minY = (-height) ;//* scaleY;
if ((transX) > (viewWidth)) {
//_x_left
transX = viewWidth;
} else if (transX < minX) {
transX = minX;
}
if ((-transX) > (viewWidth)) {
// _x_right
transX = -(viewWidth);
} else if (-transX < minX) {
transX = -(minX+30);
}
if ((transY) > (viewHeight)) {
// _y_up
transY =( viewHeight);
} else if (transY < minY) {
transY = (minY+_y_up);
}
if ((-transY) > (viewHeight)) {
// _y_down
transY = -(viewHeight);
} else if (-transY < minY) {
transY = -(minY+170);
}
values[Matrix.MTRANS_X] = transX;
values[Matrix.MTRANS_Y] = transY;
m.setValues(values);
}
call this above your view.setImageMatrix(matrix) ;
I realize this is rather old now, but try this. imageWidth and imageHeight are unscaled values.
private void limitDrag(Matrix m, ImageView view, int imageWidth, int imageHeight) {
float[] values = new float[9];
m.getValues(values);
float[] orig = new float[] {0,0, imageWidth, imageHeight};
float[] trans = new float[4];
m.mapPoints(trans, orig);
float transLeft = trans[0];
float transTop = trans[1];
float transRight = trans[2];
float transBottom = trans[3];
float transWidth = transRight - transLeft;
float transHeight = transBottom - transTop;
float xOffset = 0;
if (transWidth > view.getWidth()) {
if (transLeft > 0) {
xOffset = -transLeft;
} else if (transRight < view.getWidth()) {
xOffset = view.getWidth() - transRight;
}
} else {
if (transLeft < 0) {
xOffset = -transLeft;
} else if (transRight > view.getWidth()) {
xOffset = -(transRight - view.getWidth());
}
}
float yOffset = 0;
if (transHeight > view.getHeight()) {
if (transTop > 0) {
yOffset = -transTop;
} else if (transBottom < view.getHeight()) {
yOffset = view.getHeight() - transBottom;
}
} else {
if (transTop < 0) {
yOffset = -transTop;
} else if (transBottom > view.getHeight()) {
yOffset = -(transBottom - view.getHeight());
}
}
float transX = values[Matrix.MTRANS_X];
float transY = values[Matrix.MTRANS_Y];
values[Matrix.MTRANS_X] = transX + xOffset;
values[Matrix.MTRANS_Y] = transY + yOffset;
m.setValues(values);
}