view : mGraphViews) {
551 | if (view != null && view.get() != null && view.get() == graphView) {
552 | mGraphViews.remove(view);
553 | break;
554 | }
555 | }
556 | }
557 | }
558 |
--------------------------------------------------------------------------------
/src/main/java/com/jjoe64/graphview/series/DataPoint.java:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphView
3 | * Copyright 2016 Jonas Gehring
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.jjoe64.graphview.series;
18 |
19 | import android.provider.ContactsContract;
20 |
21 | import java.io.Serializable;
22 | import java.util.Date;
23 |
24 | /**
25 | * default data point implementation.
26 | * This stores the x and y values.
27 | *
28 | * @author jjoe64
29 | */
30 | public class DataPoint implements DataPointInterface, Serializable {
31 | private static final long serialVersionUID=1428263322645L;
32 |
33 | private double x;
34 | private double y;
35 |
36 | public DataPoint(double x, double y) {
37 | this.x=x;
38 | this.y=y;
39 | }
40 |
41 | public DataPoint(Date x, double y) {
42 | this.x = x.getTime();
43 | this.y = y;
44 | }
45 |
46 | @Override
47 | public double getX() {
48 | return x;
49 | }
50 |
51 | @Override
52 | public double getY() {
53 | return y;
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return "["+x+"/"+y+"]";
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/jjoe64/graphview/series/DataPointInterface.java:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphView
3 | * Copyright 2016 Jonas Gehring
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.jjoe64.graphview.series;
18 |
19 | /**
20 | * interface of data points. Implement this in order
21 | * to use your class in {@link com.jjoe64.graphview.series.Series}.
22 | *
23 | * You can also use the default implementation {@link com.jjoe64.graphview.series.DataPoint} so
24 | * you do not have to implement it for yourself.
25 | *
26 | * @author jjoe64
27 | */
28 | public interface DataPointInterface {
29 | /**
30 | * @return the x value
31 | */
32 | public double getX();
33 |
34 | /**
35 | * @return the y value
36 | */
37 | public double getY();
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/jjoe64/graphview/series/LineGraphSeries.java:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphView
3 | * Copyright 2016 Jonas Gehring
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.jjoe64.graphview.series;
18 |
19 | import android.graphics.Canvas;
20 | import android.graphics.Color;
21 | import android.graphics.Paint;
22 | import android.graphics.Path;
23 | import androidx.core.view.ViewCompat;
24 | import android.view.animation.AccelerateInterpolator;
25 |
26 | import com.jjoe64.graphview.GraphView;
27 |
28 | import java.util.Iterator;
29 |
30 | /**
31 | * Series to plot the data as line.
32 | * The line can be styled with many options.
33 | *
34 | * @author jjoe64
35 | */
36 | public class LineGraphSeries extends BaseSeries {
37 | private static final long ANIMATION_DURATION = 333;
38 |
39 | /**
40 | * wrapped styles regarding the line
41 | */
42 | private final class Styles {
43 | /**
44 | * the thickness of the line.
45 | * This option will be ignored if you are
46 | * using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
47 | */
48 | private int thickness = 5;
49 |
50 | /**
51 | * flag whether the area under the line to the bottom
52 | * of the viewport will be filled with a
53 | * specific background color.
54 | *
55 | * @see #backgroundColor
56 | */
57 | private boolean drawBackground = false;
58 |
59 | /**
60 | * flag whether the data points are highlighted as
61 | * a visible point.
62 | *
63 | * @see #dataPointsRadius
64 | */
65 | private boolean drawDataPoints = false;
66 |
67 | /**
68 | * the radius for the data points.
69 | *
70 | * @see #drawDataPoints
71 | */
72 | private float dataPointsRadius = 10f;
73 |
74 | /**
75 | * the background color for the filling under
76 | * the line.
77 | *
78 | * @see #drawBackground
79 | */
80 | private int backgroundColor = Color.argb(100, 172, 218, 255);
81 | }
82 |
83 | /**
84 | * wrapped styles
85 | */
86 | private Styles mStyles;
87 |
88 | private Paint mSelectionPaint;
89 |
90 | /**
91 | * internal paint object
92 | */
93 | private Paint mPaint;
94 |
95 | /**
96 | * paint for the background
97 | */
98 | private Paint mPaintBackground;
99 |
100 | /**
101 | * path for the background filling
102 | */
103 | private Path mPathBackground;
104 |
105 | /**
106 | * path to the line
107 | */
108 | private Path mPath;
109 |
110 | /**
111 | * custom paint that can be used.
112 | * this will ignore the thickness and color styles.
113 | */
114 | private Paint mCustomPaint;
115 |
116 | /**
117 | * rendering is animated
118 | */
119 | private boolean mAnimated;
120 |
121 | /**
122 | * last animated value
123 | */
124 | private double mLastAnimatedValue = Double.NaN;
125 |
126 | /**
127 | * time of animation start
128 | */
129 | private long mAnimationStart;
130 |
131 | /**
132 | * animation interpolator
133 | */
134 | private AccelerateInterpolator mAnimationInterpolator;
135 |
136 | /**
137 | * number of animation frame to avoid lagging
138 | */
139 | private int mAnimationStartFrameNo;
140 |
141 | /**
142 | * flag whether the line should be drawn as a path
143 | * or with single drawLine commands (more performance)
144 | * By default we use drawLine because it has much more peformance.
145 | * For some styling reasons it can make sense to draw as path.
146 | */
147 | private boolean mDrawAsPath = false;
148 |
149 | /**
150 | * creates a series without data
151 | */
152 | public LineGraphSeries() {
153 | init();
154 | }
155 |
156 | /**
157 | * creates a series with data
158 | *
159 | * @param data data points
160 | * important: array has to be sorted from lowest x-value to the highest
161 | */
162 | public LineGraphSeries(E[] data) {
163 | super(data);
164 | init();
165 | }
166 |
167 | /**
168 | * do the initialization
169 | * creates internal objects
170 | */
171 | protected void init() {
172 | mStyles = new Styles();
173 | mPaint = new Paint();
174 | mPaint.setStrokeCap(Paint.Cap.ROUND);
175 | mPaint.setStyle(Paint.Style.STROKE);
176 | mPaintBackground = new Paint();
177 |
178 | mSelectionPaint = new Paint();
179 | mSelectionPaint.setColor(Color.argb(80, 0, 0, 0));
180 | mSelectionPaint.setStyle(Paint.Style.FILL);
181 |
182 | mPathBackground = new Path();
183 | mPath = new Path();
184 |
185 | mAnimationInterpolator = new AccelerateInterpolator(2f);
186 | }
187 |
188 | /**
189 | * plots the series
190 | * draws the line and the background
191 | *
192 | * @param graphView graphview
193 | * @param canvas canvas
194 | * @param isSecondScale flag if it is the second scale
195 | */
196 | @Override
197 | public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
198 | resetDataPoints();
199 |
200 | // get data
201 | double maxX = graphView.getViewport().getMaxX(false);
202 | double minX = graphView.getViewport().getMinX(false);
203 |
204 | double maxY;
205 | double minY;
206 | if (isSecondScale) {
207 | maxY = graphView.getSecondScale().getMaxY(false);
208 | minY = graphView.getSecondScale().getMinY(false);
209 | } else {
210 | maxY = graphView.getViewport().getMaxY(false);
211 | minY = graphView.getViewport().getMinY(false);
212 | }
213 |
214 | Iterator values = getValues(minX, maxX);
215 |
216 | // draw background
217 | double lastEndY = 0;
218 | double lastEndX = 0;
219 |
220 | // draw data
221 | mPaint.setStrokeWidth(mStyles.thickness);
222 | mPaint.setColor(getColor());
223 | mPaintBackground.setColor(mStyles.backgroundColor);
224 |
225 | Paint paint;
226 | if (mCustomPaint != null) {
227 | paint = mCustomPaint;
228 | } else {
229 | paint = mPaint;
230 | }
231 |
232 | mPath.reset();
233 |
234 | if (mStyles.drawBackground) {
235 | mPathBackground.reset();
236 | }
237 |
238 | double diffY = maxY - minY;
239 | double diffX = maxX - minX;
240 |
241 | float graphHeight = graphView.getGraphContentHeight();
242 | float graphWidth = graphView.getGraphContentWidth();
243 | float graphLeft = graphView.getGraphContentLeft();
244 | float graphTop = graphView.getGraphContentTop();
245 |
246 | lastEndY = 0;
247 | lastEndX = 0;
248 |
249 | // needed to end the path for background
250 | double lastUsedEndX = 0;
251 | double lastUsedEndY = 0;
252 | float firstX = -1;
253 | float firstY = -1;
254 | float lastRenderedX = Float.NaN;
255 | int i = 0;
256 | float lastAnimationReferenceX = graphLeft;
257 |
258 | boolean sameXSkip = false;
259 | float minYOnSameX = 0f;
260 | float maxYOnSameX = 0f;
261 |
262 | while (values.hasNext()) {
263 | E value = values.next();
264 |
265 | double valY = value.getY() - minY;
266 | double ratY = valY / diffY;
267 | double y = graphHeight * ratY;
268 |
269 | double valueX = value.getX();
270 | double valX = valueX - minX;
271 | double ratX = valX / diffX;
272 | double x = graphWidth * ratX;
273 |
274 | double orgX = x;
275 | double orgY = y;
276 |
277 | if (i > 0) {
278 | // overdraw
279 | boolean isOverdrawY = false;
280 | boolean isOverdrawEndPoint = false;
281 | boolean skipDraw = false;
282 |
283 | if (x > graphWidth) { // end right
284 | double b = ((graphWidth - lastEndX) * (y - lastEndY) / (x - lastEndX));
285 | y = lastEndY + b;
286 | x = graphWidth;
287 | isOverdrawEndPoint = true;
288 | }
289 | if (y < 0) { // end bottom
290 | // skip when previous and this point is out of bound
291 | if (lastEndY < 0) {
292 | skipDraw = true;
293 | } else {
294 | double b = ((0 - lastEndY) * (x - lastEndX) / (y - lastEndY));
295 | x = lastEndX + b;
296 | }
297 | y = 0;
298 | isOverdrawY = isOverdrawEndPoint = true;
299 | }
300 | if (y > graphHeight) { // end top
301 | // skip when previous and this point is out of bound
302 | if (lastEndY > graphHeight) {
303 | skipDraw = true;
304 | } else {
305 | double b = ((graphHeight - lastEndY) * (x - lastEndX) / (y - lastEndY));
306 | x = lastEndX + b;
307 | }
308 | y = graphHeight;
309 | isOverdrawY = isOverdrawEndPoint = true;
310 | }
311 | if (lastEndX < 0) { // start left
312 | double b = ((0 - x) * (y - lastEndY) / (lastEndX - x));
313 | lastEndY = y - b;
314 | lastEndX = 0;
315 | }
316 |
317 | // we need to save the X before it will be corrected when overdraw y
318 | float orgStartX = (float) lastEndX + (graphLeft + 1);
319 |
320 | if (lastEndY < 0) { // start bottom
321 | if (!skipDraw) {
322 | double b = ((0 - y) * (x - lastEndX) / (lastEndY - y));
323 | lastEndX = x - b;
324 | }
325 | lastEndY = 0;
326 | isOverdrawY = true;
327 | }
328 | if (lastEndY > graphHeight) { // start top
329 | // skip when previous and this point is out of bound
330 | if (!skipDraw) {
331 | double b = ((graphHeight - y) * (x - lastEndX) / (lastEndY - y));
332 | lastEndX = x - b;
333 | }
334 | lastEndY = graphHeight;
335 | isOverdrawY = true;
336 | }
337 |
338 | float startX = (float) lastEndX + (graphLeft + 1);
339 | float startY = (float) (graphTop - lastEndY) + graphHeight;
340 | float endX = (float) x + (graphLeft + 1);
341 | float endY = (float) (graphTop - y) + graphHeight;
342 | float startXAnimated = startX;
343 | float endXAnimated = endX;
344 |
345 | if (endX < startX) {
346 | // dont draw from right to left
347 | skipDraw = true;
348 | }
349 |
350 | // NaN can happen when previous and current value is out of y bounds
351 | if (!skipDraw && !Float.isNaN(startY) && !Float.isNaN(endY)) {
352 | // animation
353 | if (mAnimated) {
354 | if ((Double.isNaN(mLastAnimatedValue) || mLastAnimatedValue < valueX)) {
355 | long currentTime = System.currentTimeMillis();
356 | if (mAnimationStart == 0) {
357 | // start animation
358 | mAnimationStart = currentTime;
359 | mAnimationStartFrameNo = 0;
360 | } else {
361 | // anti-lag: wait a few frames
362 | if (mAnimationStartFrameNo < 15) {
363 | // second time
364 | mAnimationStart = currentTime;
365 | mAnimationStartFrameNo++;
366 | }
367 | }
368 | float timeFactor = (float) (currentTime - mAnimationStart) / ANIMATION_DURATION;
369 | float factor = mAnimationInterpolator.getInterpolation(timeFactor);
370 | if (timeFactor <= 1.0) {
371 | startXAnimated = (startX - lastAnimationReferenceX) * factor + lastAnimationReferenceX;
372 | startXAnimated = Math.max(startXAnimated, lastAnimationReferenceX);
373 | endXAnimated = (endX - lastAnimationReferenceX) * factor + lastAnimationReferenceX;
374 | ViewCompat.postInvalidateOnAnimation(graphView);
375 | } else {
376 | // animation finished
377 | mLastAnimatedValue = valueX;
378 | }
379 | } else {
380 | lastAnimationReferenceX = endX;
381 | }
382 | }
383 |
384 | // draw data point
385 | if (!isOverdrawEndPoint) {
386 | if (mStyles.drawDataPoints) {
387 | // draw first datapoint
388 | Paint.Style prevStyle = paint.getStyle();
389 | paint.setStyle(Paint.Style.FILL);
390 | canvas.drawCircle(endXAnimated, endY, mStyles.dataPointsRadius, paint);
391 | paint.setStyle(prevStyle);
392 | }
393 | registerDataPoint(endX, endY, value);
394 | }
395 |
396 | if (mDrawAsPath) {
397 | mPath.moveTo(startXAnimated, startY);
398 | }
399 | // performance opt.
400 | if (Float.isNaN(lastRenderedX) || Math.abs(endX - lastRenderedX) > .3f) {
401 | if (mDrawAsPath) {
402 | mPath.lineTo(endXAnimated, endY);
403 | } else {
404 | // draw vertical lines that were skipped
405 | if (sameXSkip) {
406 | sameXSkip = false;
407 | renderLine(canvas, new float[]{lastRenderedX, minYOnSameX, lastRenderedX, maxYOnSameX}, paint);
408 | }
409 | renderLine(canvas, new float[]{startXAnimated, startY, endXAnimated, endY}, paint);
410 | }
411 | lastRenderedX = endX;
412 | } else {
413 | // rendering on same x position
414 | // save min+max y position and draw it as line
415 | if (sameXSkip) {
416 | minYOnSameX = Math.min(minYOnSameX, endY);
417 | maxYOnSameX = Math.max(maxYOnSameX, endY);
418 | } else {
419 | // first
420 | sameXSkip = true;
421 | minYOnSameX = Math.min(startY, endY);
422 | maxYOnSameX = Math.max(startY, endY);
423 | }
424 | }
425 |
426 | }
427 |
428 | if (mStyles.drawBackground) {
429 | if (isOverdrawY) {
430 | // start draw original x
431 | if (firstX == -1) {
432 | firstX = orgStartX;
433 | firstY = startY;
434 | mPathBackground.moveTo(orgStartX, startY);
435 | }
436 | // from original start to new start
437 | mPathBackground.lineTo(startXAnimated, startY);
438 | }
439 | if (firstX == -1) {
440 | firstX = startXAnimated;
441 | firstY = startY;
442 | mPathBackground.moveTo(startXAnimated, startY);
443 | }
444 | mPathBackground.lineTo(startXAnimated, startY);
445 | mPathBackground.lineTo(endXAnimated, endY);
446 | }
447 |
448 | lastUsedEndX = endXAnimated;
449 | lastUsedEndY = endY;
450 | } else if (mStyles.drawDataPoints) {
451 | //fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
452 | float first_X = (float) x + (graphLeft + 1);
453 | float first_Y = (float) (graphTop - y) + graphHeight;
454 |
455 | if (first_X >= graphLeft && first_Y <= (graphTop + graphHeight)) {
456 | if (mAnimated && (Double.isNaN(mLastAnimatedValue) || mLastAnimatedValue < valueX)) {
457 | long currentTime = System.currentTimeMillis();
458 | if (mAnimationStart == 0) {
459 | // start animation
460 | mAnimationStart = currentTime;
461 | }
462 | float timeFactor = (float) (currentTime - mAnimationStart) / ANIMATION_DURATION;
463 | float factor = mAnimationInterpolator.getInterpolation(timeFactor);
464 | if (timeFactor <= 1.0) {
465 | first_X = (first_X - lastAnimationReferenceX) * factor + lastAnimationReferenceX;
466 | ViewCompat.postInvalidateOnAnimation(graphView);
467 | } else {
468 | // animation finished
469 | mLastAnimatedValue = valueX;
470 | }
471 | }
472 |
473 |
474 | Paint.Style prevStyle = paint.getStyle();
475 | paint.setStyle(Paint.Style.FILL);
476 | canvas.drawCircle(first_X, first_Y, mStyles.dataPointsRadius, paint);
477 | paint.setStyle(prevStyle);
478 | registerDataPoint(first_X, first_Y, value);
479 | }
480 | }
481 | lastEndY = orgY;
482 | lastEndX = orgX;
483 | i++;
484 | }
485 |
486 | if (mDrawAsPath) {
487 | // draw at the end
488 | canvas.drawPath(mPath, paint);
489 | }
490 |
491 | if (mStyles.drawBackground && firstX != -1) {
492 | // end / close path
493 | if (lastUsedEndY != graphHeight + graphTop) {
494 | // dont draw line to same point, otherwise the path is completely broken
495 | mPathBackground.lineTo((float) lastUsedEndX, graphHeight + graphTop);
496 | }
497 | mPathBackground.lineTo(firstX, graphHeight + graphTop);
498 | if (firstY != graphHeight + graphTop) {
499 | // dont draw line to same point, otherwise the path is completely broken
500 | mPathBackground.lineTo(firstX, firstY);
501 | }
502 | //mPathBackground.close();
503 | canvas.drawPath(mPathBackground, mPaintBackground);
504 | }
505 | }
506 |
507 | /**
508 | * just a wrapper to draw lines on canvas
509 | *
510 | * @param canvas
511 | * @param pts
512 | * @param paint
513 | */
514 | private void renderLine(Canvas canvas, float[] pts, Paint paint) {
515 | if (pts.length == 4 && pts[0] == pts[2] && pts[1] == pts[3]) {
516 | // avoid zero length lines, to makes troubles on some devices
517 | // see https://github.com/appsthatmatter/GraphView/issues/499
518 | return;
519 | }
520 | canvas.drawLines(pts, paint);
521 | }
522 |
523 | /**
524 | * the thickness of the line.
525 | * This option will be ignored if you are
526 | * using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
527 | *
528 | * @return the thickness of the line
529 | */
530 | public int getThickness() {
531 | return mStyles.thickness;
532 | }
533 |
534 | /**
535 | * the thickness of the line.
536 | * This option will be ignored if you are
537 | * using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
538 | *
539 | * @param thickness thickness of the line
540 | */
541 | public void setThickness(int thickness) {
542 | mStyles.thickness = thickness;
543 | }
544 |
545 | /**
546 | * flag whether the area under the line to the bottom
547 | * of the viewport will be filled with a
548 | * specific background color.
549 | *
550 | * @return whether the background will be drawn
551 | * @see #getBackgroundColor()
552 | */
553 | public boolean isDrawBackground() {
554 | return mStyles.drawBackground;
555 | }
556 |
557 | /**
558 | * flag whether the area under the line to the bottom
559 | * of the viewport will be filled with a
560 | * specific background color.
561 | *
562 | * @param drawBackground whether the background will be drawn
563 | * @see #setBackgroundColor(int)
564 | */
565 | public void setDrawBackground(boolean drawBackground) {
566 | mStyles.drawBackground = drawBackground;
567 | }
568 |
569 | /**
570 | * flag whether the data points are highlighted as
571 | * a visible point.
572 | *
573 | * @return flag whether the data points are highlighted
574 | * @see #setDataPointsRadius(float)
575 | */
576 | public boolean isDrawDataPoints() {
577 | return mStyles.drawDataPoints;
578 | }
579 |
580 | /**
581 | * flag whether the data points are highlighted as
582 | * a visible point.
583 | *
584 | * @param drawDataPoints flag whether the data points are highlighted
585 | * @see #setDataPointsRadius(float)
586 | */
587 | public void setDrawDataPoints(boolean drawDataPoints) {
588 | mStyles.drawDataPoints = drawDataPoints;
589 | }
590 |
591 | /**
592 | * @return the radius for the data points.
593 | * @see #setDrawDataPoints(boolean)
594 | */
595 | public float getDataPointsRadius() {
596 | return mStyles.dataPointsRadius;
597 | }
598 |
599 | /**
600 | * @param dataPointsRadius the radius for the data points.
601 | * @see #setDrawDataPoints(boolean)
602 | */
603 | public void setDataPointsRadius(float dataPointsRadius) {
604 | mStyles.dataPointsRadius = dataPointsRadius;
605 | }
606 |
607 | /**
608 | * @return the background color for the filling under
609 | * the line.
610 | * @see #setDrawBackground(boolean)
611 | */
612 | public int getBackgroundColor() {
613 | return mStyles.backgroundColor;
614 | }
615 |
616 | /**
617 | * @param backgroundColor the background color for the filling under
618 | * the line.
619 | * @see #setDrawBackground(boolean)
620 | */
621 | public void setBackgroundColor(int backgroundColor) {
622 | mStyles.backgroundColor = backgroundColor;
623 | }
624 |
625 | /**
626 | * custom paint that can be used.
627 | * this will ignore the thickness and color styles.
628 | *
629 | * @param customPaint the custom paint to be used for rendering the line
630 | */
631 | public void setCustomPaint(Paint customPaint) {
632 | this.mCustomPaint = customPaint;
633 | }
634 |
635 | /**
636 | * @param animated activate the animated rendering
637 | */
638 | public void setAnimated(boolean animated) {
639 | this.mAnimated = animated;
640 | }
641 |
642 | /**
643 | * flag whether the line should be drawn as a path
644 | * or with single drawLine commands (more performance)
645 | * By default we use drawLine because it has much more peformance.
646 | * For some styling reasons it can make sense to draw as path.
647 | */
648 | public boolean isDrawAsPath() {
649 | return mDrawAsPath;
650 | }
651 |
652 | /**
653 | * flag whether the line should be drawn as a path
654 | * or with single drawLine commands (more performance)
655 | * By default we use drawLine because it has much more peformance.
656 | * For some styling reasons it can make sense to draw as path.
657 | *
658 | * @param mDrawAsPath true to draw as path
659 | */
660 | public void setDrawAsPath(boolean mDrawAsPath) {
661 | this.mDrawAsPath = mDrawAsPath;
662 | }
663 |
664 | /**
665 | *
666 | * @param dataPoint values the values must be in the correct order!
667 | * x-value has to be ASC. First the lowest x value and at least the highest x value.
668 | * @param scrollToEnd true => graphview will scroll to the end (maxX)
669 | * @param maxDataPoints if max data count is reached, the oldest data
670 | * value will be lost to avoid memory leaks
671 | * @param silent set true to avoid rerender the graph
672 | */
673 | public void appendData(E dataPoint, boolean scrollToEnd, int maxDataPoints, boolean silent) {
674 | if (!isAnimationActive()) {
675 | mAnimationStart = 0;
676 | }
677 | super.appendData(dataPoint, scrollToEnd, maxDataPoints, silent);
678 | }
679 |
680 | /**
681 | * @return currently animation is active
682 | */
683 | private boolean isAnimationActive() {
684 | if (mAnimated) {
685 | long curr = System.currentTimeMillis();
686 | return curr - mAnimationStart <= ANIMATION_DURATION;
687 | }
688 | return false;
689 | }
690 |
691 | @Override
692 | public void drawSelection(GraphView graphView, Canvas canvas, boolean b, DataPointInterface value) {
693 | double spanX = graphView.getViewport().getMaxX(false) - graphView.getViewport().getMinX(false);
694 | double spanXPixel = graphView.getGraphContentWidth();
695 |
696 | double spanY = graphView.getViewport().getMaxY(false) - graphView.getViewport().getMinY(false);
697 | double spanYPixel = graphView.getGraphContentHeight();
698 |
699 | double pointX = (value.getX() - graphView.getViewport().getMinX(false)) * spanXPixel / spanX;
700 | pointX += graphView.getGraphContentLeft();
701 |
702 | double pointY = (value.getY() - graphView.getViewport().getMinY(false)) * spanYPixel / spanY;
703 | pointY = graphView.getGraphContentTop() + spanYPixel - pointY;
704 |
705 | // border
706 | canvas.drawCircle((float) pointX, (float) pointY, 30f, mSelectionPaint);
707 |
708 | // fill
709 | Paint.Style prevStyle = mPaint.getStyle();
710 | mPaint.setStyle(Paint.Style.FILL);
711 | canvas.drawCircle((float) pointX, (float) pointY, 23f, mPaint);
712 | mPaint.setStyle(prevStyle);
713 | }
714 | }
715 |
--------------------------------------------------------------------------------
/src/main/java/com/jjoe64/graphview/series/OnDataPointTapListener.java:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphView
3 | * Copyright 2016 Jonas Gehring
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.jjoe64.graphview.series;
18 |
19 | /**
20 | * Listener for the tap event which will be
21 | * triggered when the user touches on a datapoint.
22 | *
23 | * Use this in {@link com.jjoe64.graphview.series.BaseSeries#setOnDataPointTapListener(OnDataPointTapListener)}
24 | *
25 | * @author jjoe64
26 | */
27 | public interface OnDataPointTapListener {
28 | /**
29 | * gets called when the user touches on a datapoint.
30 | *
31 | * @param series the corresponding series
32 | * @param dataPoint the data point that was tapped on
33 | */
34 | void onTap(Series series, DataPointInterface dataPoint);
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/jjoe64/graphview/series/PointsGraphSeries.java:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphView
3 | * Copyright 2016 Jonas Gehring
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.jjoe64.graphview.series;
18 |
19 | import android.graphics.Canvas;
20 | import android.graphics.Color;
21 | import android.graphics.Paint;
22 | import android.graphics.Path;
23 | import android.graphics.Point;
24 |
25 | import com.jjoe64.graphview.GraphView;
26 |
27 | import java.util.Iterator;
28 |
29 | /**
30 | * Series that plots the data as points.
31 | * The points can be different shapes or a
32 | * complete custom drawing.
33 | *
34 | * @author jjoe64
35 | */
36 | public class PointsGraphSeries extends BaseSeries {
37 | /**
38 | * interface to implement a custom
39 | * drawing for the data points.
40 | */
41 | public static interface CustomShape {
42 | /**
43 | * called when drawing a single data point.
44 | * use the x and y coordinates to draw your
45 | * drawing at this point.
46 | *
47 | * @param canvas canvas to draw on
48 | * @param paint internal paint object. this has the correct color.
49 | * But you can use your own paint.
50 | * @param x x-coordinate the point has to be drawn to
51 | * @param y y-coordinate the point has to be drawn to
52 | * @param dataPoint the related data point
53 | */
54 | void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint);
55 | }
56 |
57 | /**
58 | * choose a predefined shape to draw for
59 | * each data point.
60 | * You can also draw a custom drawing via {@link com.jjoe64.graphview.series.PointsGraphSeries.CustomShape}
61 | */
62 | public enum Shape {
63 | /**
64 | * draws a point / circle
65 | */
66 | POINT,
67 |
68 | /**
69 | * draws a triangle
70 | */
71 | TRIANGLE,
72 |
73 | /**
74 | * draws a rectangle
75 | */
76 | RECTANGLE
77 | }
78 |
79 | /**
80 | * wrapped styles for this series
81 | */
82 | private final class Styles {
83 | /**
84 | * this is used for the size of the shape that
85 | * will be drawn.
86 | * This is useless if you are using a custom shape.
87 | */
88 | float size;
89 |
90 | /**
91 | * the shape that will be drawn for each point.
92 | */
93 | Shape shape;
94 | }
95 |
96 | /**
97 | * wrapped styles
98 | */
99 | private Styles mStyles;
100 |
101 | /**
102 | * internal paint object
103 | */
104 | private Paint mPaint;
105 |
106 | /**
107 | * handler to use a custom drawing
108 | */
109 | private CustomShape mCustomShape;
110 |
111 | /**
112 | * creates the series without data
113 | */
114 | public PointsGraphSeries() {
115 | init();
116 | }
117 |
118 | /**
119 | * creates the series with data
120 | *
121 | * @param data datapoints
122 | */
123 | public PointsGraphSeries(E[] data) {
124 | super(data);
125 | init();
126 | }
127 |
128 | /**
129 | * inits the internal objects
130 | * set the defaults
131 | */
132 | protected void init() {
133 | mStyles = new Styles();
134 | mStyles.size = 20f;
135 | mPaint = new Paint();
136 | mPaint.setStrokeCap(Paint.Cap.ROUND);
137 | setShape(Shape.POINT);
138 | }
139 |
140 | /**
141 | * plot the data to the viewport
142 | *
143 | * @param graphView graphview
144 | * @param canvas canvas to draw on
145 | * @param isSecondScale whether it is the second scale
146 | */
147 | @Override
148 | public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
149 | resetDataPoints();
150 |
151 | // get data
152 | double maxX = graphView.getViewport().getMaxX(false);
153 | double minX = graphView.getViewport().getMinX(false);
154 |
155 | double maxY;
156 | double minY;
157 | if (isSecondScale) {
158 | maxY = graphView.getSecondScale().getMaxY(false);
159 | minY = graphView.getSecondScale().getMinY(false);
160 | } else {
161 | maxY = graphView.getViewport().getMaxY(false);
162 | minY = graphView.getViewport().getMinY(false);
163 | }
164 |
165 | Iterator values = getValues(minX, maxX);
166 |
167 | // draw background
168 | double lastEndY = 0;
169 | double lastEndX = 0;
170 |
171 | // draw data
172 | mPaint.setColor(getColor());
173 |
174 | double diffY = maxY - minY;
175 | double diffX = maxX - minX;
176 |
177 | float graphHeight = graphView.getGraphContentHeight();
178 | float graphWidth = graphView.getGraphContentWidth();
179 | float graphLeft = graphView.getGraphContentLeft();
180 | float graphTop = graphView.getGraphContentTop();
181 |
182 | lastEndY = 0;
183 | lastEndX = 0;
184 | float firstX = 0;
185 | int i=0;
186 | while (values.hasNext()) {
187 | E value = values.next();
188 |
189 | double valY = value.getY() - minY;
190 | double ratY = valY / diffY;
191 | double y = graphHeight * ratY;
192 |
193 | double valX = value.getX() - minX;
194 | double ratX = valX / diffX;
195 | double x = graphWidth * ratX;
196 |
197 | double orgX = x;
198 | double orgY = y;
199 |
200 | // overdraw
201 | boolean overdraw = false;
202 | if (x > graphWidth) { // end right
203 | overdraw = true;
204 | }
205 | if (y < 0) { // end bottom
206 | overdraw = true;
207 | }
208 | if (y > graphHeight) { // end top
209 | overdraw = true;
210 | }
211 | /* Fix a bug that continue to show the DOT after Y axis */
212 | if(x < 0) {
213 | overdraw = true;
214 | }
215 |
216 | float endX = (float) x + (graphLeft + 1);
217 | float endY = (float) (graphTop - y) + graphHeight;
218 | registerDataPoint(endX, endY, value);
219 |
220 | // draw data point
221 | if (!overdraw) {
222 | if (mCustomShape != null) {
223 | mCustomShape.draw(canvas, mPaint, endX, endY, value);
224 | } else if (mStyles.shape == Shape.POINT) {
225 | canvas.drawCircle(endX, endY, mStyles.size, mPaint);
226 | } else if (mStyles.shape == Shape.RECTANGLE) {
227 | canvas.drawRect(endX-mStyles.size, endY-mStyles.size, endX+mStyles.size, endY+mStyles.size, mPaint);
228 | } else if (mStyles.shape == Shape.TRIANGLE) {
229 | Point[] points = new Point[3];
230 | points[0] = new Point((int)endX, (int)(endY-getSize()));
231 | points[1] = new Point((int)(endX+getSize()), (int)(endY+getSize()*0.67));
232 | points[2] = new Point((int)(endX-getSize()), (int)(endY+getSize()*0.67));
233 | drawArrows(points, canvas, mPaint);
234 | }
235 | }
236 |
237 | i++;
238 | }
239 |
240 | }
241 |
242 | /**
243 | * helper to draw triangle
244 | *
245 | * @param point array with 3 coordinates
246 | * @param canvas canvas to draw on
247 | * @param paint paint object
248 | */
249 | private void drawArrows(Point[] point, Canvas canvas, Paint paint) {
250 | float [] points = new float[8];
251 | points[0] = point[0].x;
252 | points[1] = point[0].y;
253 | points[2] = point[1].x;
254 | points[3] = point[1].y;
255 | points[4] = point[2].x;
256 | points[5] = point[2].y;
257 | points[6] = point[0].x;
258 | points[7] = point[0].y;
259 |
260 | canvas.drawVertices(Canvas.VertexMode.TRIANGLES, 8, points, 0, null, 0, null, 0, null, 0, 0, paint);
261 | Path path = new Path();
262 | path.moveTo(point[0].x , point[0].y);
263 | path.lineTo(point[1].x,point[1].y);
264 | path.lineTo(point[2].x,point[2].y);
265 | canvas.drawPath(path,paint);
266 | }
267 |
268 | /**
269 | * This is used for the size of the shape that
270 | * will be drawn.
271 | * This is useless if you are using a custom shape.
272 | *
273 | * @return the size of the shape
274 | */
275 | public float getSize() {
276 | return mStyles.size;
277 | }
278 |
279 | /**
280 | * This is used for the size of the shape that
281 | * will be drawn.
282 | * This is useless if you are using a custom shape.
283 | *
284 | * @param radius the size of the shape
285 | */
286 | public void setSize(float radius) {
287 | mStyles.size = radius;
288 | }
289 |
290 | /**
291 | * @return the shape that will be drawn for each point
292 | */
293 | public Shape getShape() {
294 | return mStyles.shape;
295 | }
296 |
297 | /**
298 | * @param s the shape that will be drawn for each point
299 | */
300 | public void setShape(Shape s) {
301 | mStyles.shape = s;
302 | }
303 |
304 | /**
305 | * Use a custom handler to draw your own
306 | * drawing for each data point.
307 | *
308 | * @param shape handler to use a custom drawing
309 | */
310 | public void setCustomShape(CustomShape shape) {
311 | mCustomShape = shape;
312 | }
313 |
314 | @Override
315 | public void drawSelection(GraphView mGraphView, Canvas canvas, boolean b, DataPointInterface value) {
316 | // TODO
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/src/main/java/com/jjoe64/graphview/series/Series.java:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphView
3 | * Copyright 2016 Jonas Gehring
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.jjoe64.graphview.series;
18 |
19 | import android.graphics.Canvas;
20 |
21 | import com.jjoe64.graphview.GraphView;
22 |
23 | import java.util.Iterator;
24 |
25 | /**
26 | * Basis interface for series that can be plotted
27 | * on the graph.
28 | * You can implement this in order to create a completely
29 | * custom series type.
30 | * But it is recommended to extend {@link com.jjoe64.graphview.series.BaseSeries} or another
31 | * implemented Series class to save time.
32 | * Anyway this interface can make sense if you want to implement
33 | * a custom data provider, because BaseSeries uses a internal Array to store
34 | * the data.
35 | *
36 | * @author jjoe64
37 | */
38 | public interface Series {
39 | /**
40 | * @return the lowest x-value of the data
41 | */
42 | public double getLowestValueX();
43 |
44 | /**
45 | * @return the highest x-value of the data
46 | */
47 | public double getHighestValueX();
48 |
49 | /**
50 | * @return the lowest y-value of the data
51 | */
52 | public double getLowestValueY();
53 |
54 | /**
55 | * @return the highest y-value of the data
56 | */
57 | public double getHighestValueY();
58 |
59 | /**
60 | * get the values for a specific range. It is
61 | * important that the data comes in the sorted order
62 | * (from lowest to highest x-value).
63 | *
64 | * @param from the minimal x-value
65 | * @param until the maximal x-value
66 | * @return all datapoints between the from and until x-value
67 | * including the from and until data points.
68 | */
69 | public Iterator getValues(double from, double until);
70 |
71 | /**
72 | * Plots the series to the viewport.
73 | * You have to care about overdrawing.
74 | * This method may be called 2 times: one for
75 | * the default scale and one time for the
76 | * second scale.
77 | *
78 | * @param graphView corresponding graphview
79 | * @param canvas canvas to draw on
80 | * @param isSecondScale true if the drawing is for the second scale
81 | */
82 | public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale);
83 |
84 | /**
85 | * @return the title of the series. Used in the legend
86 | */
87 | public String getTitle();
88 |
89 | /**
90 | * @return the color of the series. Used in the legend and should
91 | * be used for the plotted points or lines.
92 | */
93 | public int getColor();
94 |
95 | /**
96 | * set a listener for tap on a data point.
97 | *
98 | * @param l listener
99 | */
100 | public void setOnDataPointTapListener(OnDataPointTapListener l);
101 |
102 | /**
103 | * called by the tap detector in order to trigger
104 | * the on tap on datapoint event.
105 | *
106 | * @param x pixel
107 | * @param y pixel
108 | */
109 | void onTap(float x, float y);
110 |
111 | /**
112 | * called when the series was added to a graph
113 | *
114 | * @param graphView graphview
115 | */
116 | void onGraphViewAttached(GraphView graphView);
117 |
118 | /**
119 | * @return whether there are data points
120 | */
121 | boolean isEmpty();
122 |
123 | /**
124 | * clear reference to view and activity
125 | *
126 | * @param graphView
127 | */
128 | void clearReference(GraphView graphView);
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/res/values/attr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/zooming.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jjoe64/GraphView/047769ac3fbf06055fef3ab49ae515a0ec8b5322/zooming.gif
--------------------------------------------------------------------------------