revert extracting graphview to aar
This commit is contained in:
parent
719ab09940
commit
ef4a15a4c5
|
@ -172,6 +172,7 @@ dependencies {
|
||||||
// in order to use internet's versions you'd need to enable Jetifier again
|
// in order to use internet's versions you'd need to enable Jetifier again
|
||||||
// https://github.com/nightscout/graphview.git
|
// https://github.com/nightscout/graphview.git
|
||||||
// https://github.com/nightscout/iconify.git
|
// https://github.com/nightscout/iconify.git
|
||||||
|
implementation project(':graphview')
|
||||||
implementation project(':libraries')
|
implementation project(':libraries')
|
||||||
implementation project(':shared')
|
implementation project(':shared')
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
|
|
|
@ -13,7 +13,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':libraries')
|
implementation project(':graphview')
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
implementation project(':database')
|
implementation project(':database')
|
||||||
implementation project(':shared')
|
implementation project(':shared')
|
||||||
|
|
|
@ -12,7 +12,7 @@ apply from: "${project.rootDir}/core/test_dependencies.gradle"
|
||||||
apply from: "${project.rootDir}/core/jacoco_global.gradle"
|
apply from: "${project.rootDir}/core/jacoco_global.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':libraries')
|
implementation project(':graphview')
|
||||||
implementation project(':shared')
|
implementation project(':shared')
|
||||||
implementation project(':database')
|
implementation project(':database')
|
||||||
}
|
}
|
||||||
|
|
1
graphview/.gitignore
vendored
Normal file
1
graphview/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
14
graphview/build.gradle
Normal file
14
graphview/build.gradle
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
|
apply from: "${project.rootDir}/core/android_dependencies.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
|
||||||
|
namespace 'com.jjoe64.graphview'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api "androidx.core:core-ktx:$core_version"
|
||||||
|
}
|
0
graphview/consumer-rules.pro
Normal file
0
graphview/consumer-rules.pro
Normal file
21
graphview/proguard-rules.pro
vendored
Normal file
21
graphview/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
4
graphview/src/main/AndroidManifest.xml
Normal file
4
graphview/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label formatter that will be used
|
||||||
|
* by default.
|
||||||
|
* It will use the NumberFormat from Android
|
||||||
|
* and sets the maximal fraction digits
|
||||||
|
* depending on the range between min and max
|
||||||
|
* value of the current viewport.
|
||||||
|
*
|
||||||
|
* It is recommended to use this label formatter
|
||||||
|
* as base class to implement a custom formatter.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class DefaultLabelFormatter implements LabelFormatter {
|
||||||
|
/**
|
||||||
|
* number formatter for x and y values
|
||||||
|
*/
|
||||||
|
protected NumberFormat[] mNumberFormatter = new NumberFormat[2];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference to the viewport of the
|
||||||
|
* graph.
|
||||||
|
* Will be used to calculate the current
|
||||||
|
* range of values.
|
||||||
|
*/
|
||||||
|
protected Viewport mViewport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uses the default number format for the labels
|
||||||
|
*/
|
||||||
|
public DefaultLabelFormatter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use custom number format
|
||||||
|
*
|
||||||
|
* @param xFormat the number format for the x labels
|
||||||
|
* @param yFormat the number format for the y labels
|
||||||
|
*/
|
||||||
|
public DefaultLabelFormatter(NumberFormat xFormat, NumberFormat yFormat) {
|
||||||
|
mNumberFormatter[0] = yFormat;
|
||||||
|
mNumberFormatter[1] = xFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param viewport the viewport of the graph
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setViewport(Viewport viewport) {
|
||||||
|
mViewport = viewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the raw value to a nice
|
||||||
|
* looking label, depending on the
|
||||||
|
* current range of the viewport.
|
||||||
|
*
|
||||||
|
* @param value raw value
|
||||||
|
* @param isValueX true if it's a x value, otherwise false
|
||||||
|
* @return the formatted value as string
|
||||||
|
*/
|
||||||
|
public String formatLabel(double value, boolean isValueX) {
|
||||||
|
int i = isValueX ? 1 : 0;
|
||||||
|
if (mNumberFormatter[i] == null) {
|
||||||
|
mNumberFormatter[i] = NumberFormat.getNumberInstance();
|
||||||
|
double highestvalue = isValueX ? mViewport.getMaxX(false) : mViewport.getMaxY(false);
|
||||||
|
double lowestvalue = isValueX ? mViewport.getMinX(false) : mViewport.getMinY(false);
|
||||||
|
if (highestvalue - lowestvalue < 0.1) {
|
||||||
|
mNumberFormatter[i].setMaximumFractionDigits(6);
|
||||||
|
} else if (highestvalue - lowestvalue < 1) {
|
||||||
|
mNumberFormatter[i].setMaximumFractionDigits(4);
|
||||||
|
} else if (highestvalue - lowestvalue < 20) {
|
||||||
|
mNumberFormatter[i].setMaximumFractionDigits(3);
|
||||||
|
} else if (highestvalue - lowestvalue < 100) {
|
||||||
|
mNumberFormatter[i].setMaximumFractionDigits(1);
|
||||||
|
} else {
|
||||||
|
mNumberFormatter[i].setMaximumFractionDigits(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mNumberFormatter[i].format(value);
|
||||||
|
}
|
||||||
|
}
|
548
graphview/src/main/java/com/jjoe64/graphview/GraphView.java
Normal file
548
graphview/src/main/java/com/jjoe64/graphview/GraphView.java
Normal file
|
@ -0,0 +1,548 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.series.Series;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jjoe64
|
||||||
|
* @version 4.0.0
|
||||||
|
*/
|
||||||
|
public class GraphView extends View {
|
||||||
|
/**
|
||||||
|
* Class to wrap style options that are general
|
||||||
|
* to graphs.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
private static final class Styles {
|
||||||
|
/**
|
||||||
|
* The font size of the title that can be displayed
|
||||||
|
* above the graph.
|
||||||
|
*
|
||||||
|
* @see GraphView#setTitle(String)
|
||||||
|
*/
|
||||||
|
float titleTextSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The font color of the title that can be displayed
|
||||||
|
* above the graph.
|
||||||
|
*
|
||||||
|
* @see GraphView#setTitle(String)
|
||||||
|
*/
|
||||||
|
int titleColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to detect tap events on the
|
||||||
|
* graph.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
private class TapDetector {
|
||||||
|
/**
|
||||||
|
* save the time of the last down event
|
||||||
|
*/
|
||||||
|
private long lastDown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* point of the tap down event
|
||||||
|
*/
|
||||||
|
private PointF lastPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to be called to process the events
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* @return true if there was a tap event. otherwise returns false.
|
||||||
|
*/
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
lastDown = System.currentTimeMillis();
|
||||||
|
lastPoint = new PointF(event.getX(), event.getY());
|
||||||
|
} else if (lastDown > 0 && event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
|
if (Math.abs(event.getX() - lastPoint.x) > 60
|
||||||
|
|| Math.abs(event.getY() - lastPoint.y) > 60) {
|
||||||
|
lastDown = 0;
|
||||||
|
}
|
||||||
|
} else if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
if (System.currentTimeMillis() - lastDown < 400) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* our series (this does not contain the series
|
||||||
|
* that can be displayed on the right side. The
|
||||||
|
* right side series is a special feature of
|
||||||
|
* the {@link SecondScale} feature.
|
||||||
|
*/
|
||||||
|
private List<Series> mSeries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the renderer for the grid and labels
|
||||||
|
*/
|
||||||
|
private GridLabelRenderer mGridLabelRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* viewport that holds the current bounds of
|
||||||
|
* view.
|
||||||
|
*/
|
||||||
|
private Viewport mViewport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* title of the graph that will be shown above
|
||||||
|
*/
|
||||||
|
private String mTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wraps the general styles
|
||||||
|
*/
|
||||||
|
private Styles mStyles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* feature to have a second scale e.g. on the
|
||||||
|
* right side
|
||||||
|
*/
|
||||||
|
protected SecondScale mSecondScale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tap detector
|
||||||
|
*/
|
||||||
|
private TapDetector mTapDetector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* renderer for the legend
|
||||||
|
*/
|
||||||
|
private LegendRenderer mLegendRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paint for the graph title
|
||||||
|
*/
|
||||||
|
private Paint mPaintTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paint for the preview (in the SDK)
|
||||||
|
*/
|
||||||
|
private Paint mPreviewPaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the GraphView view
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
public GraphView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the GraphView view.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param attrs
|
||||||
|
*/
|
||||||
|
public GraphView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the GraphView view
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param attrs
|
||||||
|
* @param defStyle
|
||||||
|
*/
|
||||||
|
public GraphView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize the internal objects.
|
||||||
|
* This method has to be called directly
|
||||||
|
* in the constructors.
|
||||||
|
*/
|
||||||
|
protected void init() {
|
||||||
|
mPreviewPaint = new Paint();
|
||||||
|
mPreviewPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
mPreviewPaint.setColor(Color.BLACK);
|
||||||
|
mPreviewPaint.setTextSize(50);
|
||||||
|
|
||||||
|
mStyles = new Styles();
|
||||||
|
mViewport = new Viewport(this);
|
||||||
|
mGridLabelRenderer = new GridLabelRenderer(this);
|
||||||
|
mLegendRenderer = new LegendRenderer(this);
|
||||||
|
|
||||||
|
mSeries = new ArrayList<Series>();
|
||||||
|
mPaintTitle = new Paint();
|
||||||
|
|
||||||
|
mTapDetector = new TapDetector();
|
||||||
|
|
||||||
|
loadStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loads the font
|
||||||
|
*/
|
||||||
|
protected void loadStyles() {
|
||||||
|
mStyles.titleColor = mGridLabelRenderer.getHorizontalLabelsColor();
|
||||||
|
mStyles.titleTextSize = mGridLabelRenderer.getTextSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the renderer for the grid and labels
|
||||||
|
*/
|
||||||
|
public GridLabelRenderer getGridLabelRenderer() {
|
||||||
|
return mGridLabelRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new series to the graph. This will
|
||||||
|
* automatically redraw the graph.
|
||||||
|
* @param s the series to be added
|
||||||
|
*/
|
||||||
|
public void addSeries(Series s) {
|
||||||
|
s.onGraphViewAttached(this);
|
||||||
|
mSeries.add(s);
|
||||||
|
onDataChanged(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* important: do not do modifications on the list
|
||||||
|
* object that will be returned.
|
||||||
|
* Use {@link #removeSeries(com.jjoe64.graphview.series.Series)} and {@link #addSeries(com.jjoe64.graphview.series.Series)}
|
||||||
|
*
|
||||||
|
* @return all series
|
||||||
|
*/
|
||||||
|
public List<Series> getSeries() {
|
||||||
|
// TODO immutable array
|
||||||
|
return mSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call this to let the graph redraw and
|
||||||
|
* recalculate the viewport.
|
||||||
|
* This will be called when a new series
|
||||||
|
* was added or removed and when data
|
||||||
|
* was appended via {@link com.jjoe64.graphview.series.BaseSeries#appendData(com.jjoe64.graphview.series.DataPointInterface, boolean, int)}
|
||||||
|
* or {@link com.jjoe64.graphview.series.BaseSeries#resetData(com.jjoe64.graphview.series.DataPointInterface[])}.
|
||||||
|
*
|
||||||
|
* @param keepLabelsSize true if you don't want
|
||||||
|
* to recalculate the size of
|
||||||
|
* the labels. It is recommended
|
||||||
|
* to use "true" because this will
|
||||||
|
* improve performance and prevent
|
||||||
|
* a flickering.
|
||||||
|
* @param keepViewport true if you don't want that
|
||||||
|
* the viewport will be recalculated.
|
||||||
|
* It is recommended to use "true" for
|
||||||
|
* performance.
|
||||||
|
*/
|
||||||
|
public void onDataChanged(boolean keepLabelsSize, boolean keepViewport) {
|
||||||
|
// adjust grid system
|
||||||
|
mViewport.calcCompleteRange();
|
||||||
|
mGridLabelRenderer.invalidate(keepLabelsSize, keepViewport);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* will be called from Android system.
|
||||||
|
*
|
||||||
|
* @param canvas Canvas
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
if (isInEditMode()) {
|
||||||
|
canvas.drawColor(Color.rgb(200, 200, 200));
|
||||||
|
canvas.drawText("GraphView: No Preview available", canvas.getWidth()/2, canvas.getHeight()/2, mPreviewPaint);
|
||||||
|
} else {
|
||||||
|
drawTitle(canvas);
|
||||||
|
mViewport.drawFirst(canvas);
|
||||||
|
mGridLabelRenderer.draw(canvas);
|
||||||
|
for (Series s : mSeries) {
|
||||||
|
s.draw(this, canvas, false);
|
||||||
|
}
|
||||||
|
if (mSecondScale != null) {
|
||||||
|
for (Series s : mSecondScale.getSeries()) {
|
||||||
|
s.draw(this, canvas, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mViewport.draw(canvas);
|
||||||
|
mLegendRenderer.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the Graphs title that will be
|
||||||
|
* shown above the viewport.
|
||||||
|
* Will be called by GraphView.
|
||||||
|
*
|
||||||
|
* @param canvas Canvas
|
||||||
|
*/
|
||||||
|
protected void drawTitle(Canvas canvas) {
|
||||||
|
if (mTitle != null && mTitle.length()>0) {
|
||||||
|
mPaintTitle.setColor(mStyles.titleColor);
|
||||||
|
mPaintTitle.setTextSize(mStyles.titleTextSize);
|
||||||
|
mPaintTitle.setTextAlign(Paint.Align.CENTER);
|
||||||
|
float x = canvas.getWidth()/2;
|
||||||
|
float y = mPaintTitle.getTextSize();
|
||||||
|
canvas.drawText(mTitle, x, y, mPaintTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the height of the title.
|
||||||
|
*
|
||||||
|
* @return the actual size of the title.
|
||||||
|
* if there is no title, 0 will be
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
protected int getTitleHeight() {
|
||||||
|
if (mTitle != null && mTitle.length()>0) {
|
||||||
|
return (int) mPaintTitle.getTextSize();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the viewport of the Graph.
|
||||||
|
* @see com.jjoe64.graphview.Viewport
|
||||||
|
*/
|
||||||
|
public Viewport getViewport() {
|
||||||
|
return mViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by Android system if the size
|
||||||
|
* of the view was changed. Will recalculate
|
||||||
|
* the viewport and labels.
|
||||||
|
*
|
||||||
|
* @param w
|
||||||
|
* @param h
|
||||||
|
* @param oldw
|
||||||
|
* @param oldh
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
onDataChanged(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the space on the left side of the
|
||||||
|
* view from the left border to the
|
||||||
|
* beginning of the graph viewport.
|
||||||
|
*/
|
||||||
|
public int getGraphContentLeft() {
|
||||||
|
int border = getGridLabelRenderer().getStyles().padding;
|
||||||
|
return border + getGridLabelRenderer().getLabelVerticalWidth() + getGridLabelRenderer().getVerticalAxisTitleWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the space on the top of the
|
||||||
|
* view from the top border to the
|
||||||
|
* beginning of the graph viewport.
|
||||||
|
*/
|
||||||
|
public int getGraphContentTop() {
|
||||||
|
int border = getGridLabelRenderer().getStyles().padding + getTitleHeight();
|
||||||
|
return border;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the height of the graph viewport.
|
||||||
|
*/
|
||||||
|
public int getGraphContentHeight() {
|
||||||
|
int border = getGridLabelRenderer().getStyles().padding;
|
||||||
|
int graphheight = getHeight() - (2 * border) - getGridLabelRenderer().getLabelHorizontalHeight() - getTitleHeight();
|
||||||
|
graphheight -= getGridLabelRenderer().getHorizontalAxisTitleHeight();
|
||||||
|
return graphheight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the width of the graph viewport.
|
||||||
|
*/
|
||||||
|
public int getGraphContentWidth() {
|
||||||
|
int border = getGridLabelRenderer().getStyles().padding;
|
||||||
|
int graphwidth = getWidth() - (2 * border) - getGridLabelRenderer().getLabelVerticalWidth();
|
||||||
|
if (mSecondScale != null) {
|
||||||
|
graphwidth -= getGridLabelRenderer().getLabelVerticalSecondScaleWidth();
|
||||||
|
}
|
||||||
|
return graphwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* will be called from Android system.
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
boolean b = mViewport.onTouchEvent(event);
|
||||||
|
boolean a = super.onTouchEvent(event);
|
||||||
|
|
||||||
|
// is it a click?
|
||||||
|
if (mTapDetector.onTouchEvent(event)) {
|
||||||
|
for (Series s : mSeries) {
|
||||||
|
s.onTap(event.getX(), event.getY());
|
||||||
|
}
|
||||||
|
if (mSecondScale != null) {
|
||||||
|
for (Series s : mSecondScale.getSeries()) {
|
||||||
|
s.onTap(event.getX(), event.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b || a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void computeScroll() {
|
||||||
|
super.computeScroll();
|
||||||
|
mViewport.computeScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the legend renderer.
|
||||||
|
* @see com.jjoe64.graphview.LegendRenderer
|
||||||
|
*/
|
||||||
|
public LegendRenderer getLegendRenderer() {
|
||||||
|
return mLegendRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use a specific legend renderer
|
||||||
|
*
|
||||||
|
* @param mLegendRenderer the new legend renderer
|
||||||
|
*/
|
||||||
|
public void setLegendRenderer(LegendRenderer mLegendRenderer) {
|
||||||
|
this.mLegendRenderer = mLegendRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the title that will be shown
|
||||||
|
* above the graph.
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title of the graph that will
|
||||||
|
* be shown above the graph's viewport.
|
||||||
|
*
|
||||||
|
* @param mTitle the title
|
||||||
|
* @see #setTitleColor(int) to set the font color
|
||||||
|
* @see #setTitleTextSize(float) to set the font size
|
||||||
|
*/
|
||||||
|
public void setTitle(String mTitle) {
|
||||||
|
this.mTitle = mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the title font size
|
||||||
|
*/
|
||||||
|
public float getTitleTextSize() {
|
||||||
|
return mStyles.titleTextSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title's font size
|
||||||
|
*
|
||||||
|
* @param titleTextSize font size
|
||||||
|
* @see #setTitle(String)
|
||||||
|
*/
|
||||||
|
public void setTitleTextSize(float titleTextSize) {
|
||||||
|
mStyles.titleTextSize = titleTextSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return font color of the title
|
||||||
|
*/
|
||||||
|
public int getTitleColor() {
|
||||||
|
return mStyles.titleColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title's font color
|
||||||
|
*
|
||||||
|
* @param titleColor font color of the title
|
||||||
|
* @see #setTitle(String)
|
||||||
|
*/
|
||||||
|
public void setTitleColor(int titleColor) {
|
||||||
|
mStyles.titleColor = titleColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public SecondScale getSecondScale() {
|
||||||
|
if (mSecondScale == null) {
|
||||||
|
mSecondScale = new SecondScale(mViewport);
|
||||||
|
}
|
||||||
|
return mSecondScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all series of the graph.
|
||||||
|
*/
|
||||||
|
public void removeAllSeries() {
|
||||||
|
mSeries.clear();
|
||||||
|
onDataChanged(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific series of the graph.
|
||||||
|
* This will also re-render the graph, but
|
||||||
|
* without recalculating the viewport and
|
||||||
|
* label sizes.
|
||||||
|
* If you want this, you have to call {@link #onDataChanged(boolean, boolean)}
|
||||||
|
* manually.
|
||||||
|
*
|
||||||
|
* @param series
|
||||||
|
*/
|
||||||
|
public void removeSeries(Series<?> series) {
|
||||||
|
mSeries.remove(series);
|
||||||
|
onDataChanged(false, false);
|
||||||
|
}
|
||||||
|
}
|
1468
graphview/src/main/java/com/jjoe64/graphview/GridLabelRenderer.java
Normal file
1468
graphview/src/main/java/com/jjoe64/graphview/GridLabelRenderer.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to use as label formatter.
|
||||||
|
* Implement this in order to generate
|
||||||
|
* your own labels format.
|
||||||
|
* It is recommended to override {@link com.jjoe64.graphview.DefaultLabelFormatter}.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public interface LabelFormatter {
|
||||||
|
/**
|
||||||
|
* converts a raw number as input to
|
||||||
|
* a formatted string for the label.
|
||||||
|
*
|
||||||
|
* @param value raw input number
|
||||||
|
* @param isValueX true if it is a value for the x axis
|
||||||
|
* false if it is a value for the y axis
|
||||||
|
* @return the formatted number as string
|
||||||
|
*/
|
||||||
|
public String formatLabel(double value, boolean isValueX);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* will be called in order to have a
|
||||||
|
* reference to the current viewport.
|
||||||
|
* This is useful if you need the bounds
|
||||||
|
* to generate your labels.
|
||||||
|
* You store this viewport in as member variable
|
||||||
|
* and access it e.g. in the {@link #formatLabel(double, boolean)}
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* @param viewport the used viewport
|
||||||
|
*/
|
||||||
|
public void setViewport(Viewport viewport);
|
||||||
|
}
|
394
graphview/src/main/java/com/jjoe64/graphview/LegendRenderer.java
Normal file
394
graphview/src/main/java/com/jjoe64/graphview/LegendRenderer.java
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview;
|
||||||
|
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.series.Series;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default renderer for the legend box
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class LegendRenderer {
|
||||||
|
/**
|
||||||
|
* wrapped styles regarding to the
|
||||||
|
* legend
|
||||||
|
*/
|
||||||
|
private final class Styles {
|
||||||
|
float textSize;
|
||||||
|
int spacing;
|
||||||
|
int padding;
|
||||||
|
int width;
|
||||||
|
int backgroundColor;
|
||||||
|
int textColor;
|
||||||
|
int margin;
|
||||||
|
LegendAlign align;
|
||||||
|
Point fixedPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alignment of the legend
|
||||||
|
*/
|
||||||
|
public enum LegendAlign {
|
||||||
|
/**
|
||||||
|
* top right corner
|
||||||
|
*/
|
||||||
|
TOP,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* middle right
|
||||||
|
*/
|
||||||
|
MIDDLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bottom right corner
|
||||||
|
*/
|
||||||
|
BOTTOM
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles
|
||||||
|
*/
|
||||||
|
private Styles mStyles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference to the graphview
|
||||||
|
*/
|
||||||
|
private final GraphView mGraphView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether legend will be
|
||||||
|
* drawn
|
||||||
|
*/
|
||||||
|
private boolean mIsVisible;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paint for the drawing
|
||||||
|
*/
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cached legend width
|
||||||
|
* this will be filled in the drawing.
|
||||||
|
* Can be cleared via {@link #resetStyles()}
|
||||||
|
*/
|
||||||
|
private int cachedLegendWidth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates legend renderer
|
||||||
|
*
|
||||||
|
* @param graphView regarding graphview
|
||||||
|
*/
|
||||||
|
public LegendRenderer(GraphView graphView) {
|
||||||
|
mGraphView = graphView;
|
||||||
|
mIsVisible = false;
|
||||||
|
mPaint = new Paint();
|
||||||
|
mPaint.setTextAlign(Paint.Align.LEFT);
|
||||||
|
mStyles = new Styles();
|
||||||
|
cachedLegendWidth = 0;
|
||||||
|
resetStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resets the styles to the defaults
|
||||||
|
* and clears the legend width cache
|
||||||
|
*/
|
||||||
|
public void resetStyles() {
|
||||||
|
mStyles.align = LegendAlign.MIDDLE;
|
||||||
|
mStyles.textSize = mGraphView.getGridLabelRenderer().getTextSize();
|
||||||
|
mStyles.spacing = (int) (mStyles.textSize / 5);
|
||||||
|
mStyles.padding = (int) (mStyles.textSize / 2);
|
||||||
|
mStyles.width = 0;
|
||||||
|
mStyles.backgroundColor = Color.argb(180, 100, 100, 100);
|
||||||
|
mStyles.margin = (int) (mStyles.textSize / 5);
|
||||||
|
|
||||||
|
// get matching styles from theme
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
mGraphView.getContext().getTheme().resolveAttribute(android.R.attr.textAppearanceSmall, typedValue, true);
|
||||||
|
|
||||||
|
int color1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TypedArray array = mGraphView.getContext().obtainStyledAttributes(typedValue.data, new int[]{
|
||||||
|
android.R.attr.textColorPrimary});
|
||||||
|
color1 = array.getColor(0, Color.BLACK);
|
||||||
|
array.recycle();
|
||||||
|
} catch (Exception e) {
|
||||||
|
color1 = Color.BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
mStyles.textColor = color1;
|
||||||
|
|
||||||
|
cachedLegendWidth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draws the legend if it is visible
|
||||||
|
*
|
||||||
|
* @param canvas canvas
|
||||||
|
* @see #setVisible(boolean)
|
||||||
|
*/
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
if (!mIsVisible) return;
|
||||||
|
|
||||||
|
mPaint.setTextSize(mStyles.textSize);
|
||||||
|
|
||||||
|
int shapeSize = (int) (mStyles.textSize*0.8d);
|
||||||
|
|
||||||
|
List<Series> allSeries = new ArrayList<Series>();
|
||||||
|
allSeries.addAll(mGraphView.getSeries());
|
||||||
|
if (mGraphView.mSecondScale != null) {
|
||||||
|
allSeries.addAll(mGraphView.getSecondScale().getSeries());
|
||||||
|
}
|
||||||
|
|
||||||
|
// width
|
||||||
|
int legendWidth = mStyles.width;
|
||||||
|
if (legendWidth == 0) {
|
||||||
|
// auto
|
||||||
|
legendWidth = cachedLegendWidth;
|
||||||
|
|
||||||
|
if (legendWidth == 0) {
|
||||||
|
Rect textBounds = new Rect();
|
||||||
|
for (Series s : allSeries) {
|
||||||
|
if (s.getTitle() != null) {
|
||||||
|
mPaint.getTextBounds(s.getTitle(), 0, s.getTitle().length(), textBounds);
|
||||||
|
legendWidth = Math.max(legendWidth, textBounds.width());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (legendWidth == 0) legendWidth = 1;
|
||||||
|
|
||||||
|
// add shape size
|
||||||
|
legendWidth += shapeSize+mStyles.padding*2 + mStyles.spacing;
|
||||||
|
cachedLegendWidth = legendWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rect
|
||||||
|
float legendHeight = (mStyles.textSize+mStyles.spacing)*allSeries.size() -mStyles.spacing;
|
||||||
|
float lLeft;
|
||||||
|
float lTop;
|
||||||
|
if (mStyles.fixedPosition != null) {
|
||||||
|
// use fied position
|
||||||
|
lLeft = mGraphView.getGraphContentLeft() + mStyles.margin + mStyles.fixedPosition.x;
|
||||||
|
lTop = mGraphView.getGraphContentTop() + mStyles.margin + mStyles.fixedPosition.y;
|
||||||
|
} else {
|
||||||
|
lLeft = mGraphView.getGraphContentLeft() + mGraphView.getGraphContentWidth() - legendWidth - mStyles.margin;
|
||||||
|
switch (mStyles.align) {
|
||||||
|
case TOP:
|
||||||
|
lTop = mGraphView.getGraphContentTop() + mStyles.margin;
|
||||||
|
break;
|
||||||
|
case MIDDLE:
|
||||||
|
lTop = mGraphView.getHeight() / 2 - legendHeight / 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lTop = mGraphView.getGraphContentTop() + mGraphView.getGraphContentHeight() - mStyles.margin - legendHeight - 2*mStyles.padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float lRight = lLeft+legendWidth;
|
||||||
|
float lBottom = lTop+legendHeight+2*mStyles.padding;
|
||||||
|
mPaint.setColor(mStyles.backgroundColor);
|
||||||
|
canvas.drawRoundRect(new RectF(lLeft, lTop, lRight, lBottom), 8, 8, mPaint);
|
||||||
|
|
||||||
|
int i=0;
|
||||||
|
for (Series series : allSeries) {
|
||||||
|
mPaint.setColor(series.getColor());
|
||||||
|
canvas.drawRect(new RectF(lLeft+mStyles.padding, lTop+mStyles.padding+(i*(mStyles.textSize+mStyles.spacing)), lLeft+mStyles.padding+shapeSize, lTop+mStyles.padding+(i*(mStyles.textSize+mStyles.spacing))+shapeSize), mPaint);
|
||||||
|
if (series.getTitle() != null) {
|
||||||
|
mPaint.setColor(mStyles.textColor);
|
||||||
|
canvas.drawText(series.getTitle(), lLeft+mStyles.padding+shapeSize+mStyles.spacing, lTop+mStyles.padding+mStyles.textSize+(i*(mStyles.textSize+mStyles.spacing)), mPaint);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the flag whether the legend will be drawn
|
||||||
|
*/
|
||||||
|
public boolean isVisible() {
|
||||||
|
return mIsVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the flag whether the legend will be drawn
|
||||||
|
*
|
||||||
|
* @param mIsVisible visible flag
|
||||||
|
*/
|
||||||
|
public void setVisible(boolean mIsVisible) {
|
||||||
|
this.mIsVisible = mIsVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return font size
|
||||||
|
*/
|
||||||
|
public float getTextSize() {
|
||||||
|
return mStyles.textSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the font size. this will clear
|
||||||
|
* the internal legend width cache
|
||||||
|
*
|
||||||
|
* @param textSize font size
|
||||||
|
*/
|
||||||
|
public void setTextSize(float textSize) {
|
||||||
|
mStyles.textSize = textSize;
|
||||||
|
cachedLegendWidth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the spacing between the text lines
|
||||||
|
*/
|
||||||
|
public int getSpacing() {
|
||||||
|
return mStyles.spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the spacing between the text lines
|
||||||
|
*
|
||||||
|
* @param spacing the spacing between the text lines
|
||||||
|
*/
|
||||||
|
public void setSpacing(int spacing) {
|
||||||
|
mStyles.spacing = spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* padding is the space between the edge of the box
|
||||||
|
* and the beginning of the text
|
||||||
|
*
|
||||||
|
* @return padding from edge to text
|
||||||
|
*/
|
||||||
|
public int getPadding() {
|
||||||
|
return mStyles.padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* padding is the space between the edge of the box
|
||||||
|
* and the beginning of the text
|
||||||
|
*
|
||||||
|
* @param padding padding from edge to text
|
||||||
|
*/
|
||||||
|
public void setPadding(int padding) {
|
||||||
|
mStyles.padding = padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the width of the box exclusive padding
|
||||||
|
*
|
||||||
|
* @return the width of the box
|
||||||
|
* 0 => auto
|
||||||
|
*/
|
||||||
|
public int getWidth() {
|
||||||
|
return mStyles.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the width of the box exclusive padding
|
||||||
|
* @param width the width of the box exclusive padding
|
||||||
|
* 0 => auto
|
||||||
|
*/
|
||||||
|
public void setWidth(int width) {
|
||||||
|
mStyles.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return background color of the box
|
||||||
|
* it is recommended to use semi-transparent
|
||||||
|
* color.
|
||||||
|
*/
|
||||||
|
public int getBackgroundColor() {
|
||||||
|
return mStyles.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param backgroundColor background color of the box
|
||||||
|
* it is recommended to use semi-transparent
|
||||||
|
* color.
|
||||||
|
*/
|
||||||
|
public void setBackgroundColor(int backgroundColor) {
|
||||||
|
mStyles.backgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return margin from the edge of the box
|
||||||
|
* to the corner of the graphview
|
||||||
|
*/
|
||||||
|
public int getMargin() {
|
||||||
|
return mStyles.margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param margin margin from the edge of the box
|
||||||
|
* to the corner of the graphview
|
||||||
|
*/
|
||||||
|
public void setMargin(int margin) {
|
||||||
|
mStyles.margin = margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the vertical alignment of the box
|
||||||
|
*/
|
||||||
|
public LegendAlign getAlign() {
|
||||||
|
return mStyles.align;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param align the vertical alignment of the box
|
||||||
|
*/
|
||||||
|
public void setAlign(LegendAlign align) {
|
||||||
|
mStyles.align = align;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return font color
|
||||||
|
*/
|
||||||
|
public int getTextColor() {
|
||||||
|
return mStyles.textColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param textColor font color
|
||||||
|
*/
|
||||||
|
public void setTextColor(int textColor) {
|
||||||
|
mStyles.textColor = textColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use fixed coordinates to position the legend.
|
||||||
|
* This will override the align setting.
|
||||||
|
*
|
||||||
|
* @param x x coordinates in pixel
|
||||||
|
* @param y y coordinates in pixel
|
||||||
|
*/
|
||||||
|
public void setFixedPosition(int x, int y) {
|
||||||
|
mStyles.fixedPosition = new Point(x, y);
|
||||||
|
}
|
||||||
|
}
|
165
graphview/src/main/java/com/jjoe64/graphview/SecondScale.java
Normal file
165
graphview/src/main/java/com/jjoe64/graphview/SecondScale.java
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.series.Series;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used to plot a second scale
|
||||||
|
* on the graph.
|
||||||
|
* The second scale has always to have
|
||||||
|
* manual bounds.
|
||||||
|
* Use {@link #setMinY(double)} and {@link #setMaxY(double)}
|
||||||
|
* to set them.
|
||||||
|
* The second scale has it's own array of series.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class SecondScale {
|
||||||
|
/**
|
||||||
|
* reference to the viewport of the graph
|
||||||
|
*/
|
||||||
|
protected final Viewport mViewport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* array of series for the second
|
||||||
|
* scale
|
||||||
|
*/
|
||||||
|
protected List<Series> mSeries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the y axis bounds
|
||||||
|
* are manual.
|
||||||
|
* For the current version this is always
|
||||||
|
* true.
|
||||||
|
*/
|
||||||
|
private boolean mYAxisBoundsManual = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* min y value for the y axis bounds
|
||||||
|
*/
|
||||||
|
private double mMinY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* max y value for the y axis bounds
|
||||||
|
*/
|
||||||
|
private double mMaxY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* label formatter for the y labels
|
||||||
|
* on the right side
|
||||||
|
*/
|
||||||
|
protected LabelFormatter mLabelFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the second scale.
|
||||||
|
* normally you do not call this contructor.
|
||||||
|
* Use {@link com.jjoe64.graphview.GraphView#getSecondScale()}
|
||||||
|
* in order to get the instance.
|
||||||
|
*/
|
||||||
|
SecondScale(Viewport viewport) {
|
||||||
|
mViewport = viewport;
|
||||||
|
mSeries = new ArrayList<Series>();
|
||||||
|
mLabelFormatter = new DefaultLabelFormatter();
|
||||||
|
mLabelFormatter.setViewport(mViewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a series to the second scale.
|
||||||
|
* Don't add this series also to the GraphView
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* @param s the series
|
||||||
|
*/
|
||||||
|
public void addSeries(Series s) {
|
||||||
|
mSeries.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
//public void setYAxisBoundsManual(boolean mYAxisBoundsManual) {
|
||||||
|
// this.mYAxisBoundsManual = mYAxisBoundsManual;
|
||||||
|
//}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the min y bounds
|
||||||
|
*
|
||||||
|
* @param d min y value
|
||||||
|
*/
|
||||||
|
public void setMinY(double d) {
|
||||||
|
mMinY = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the max y bounds
|
||||||
|
*
|
||||||
|
* @param d max y value
|
||||||
|
*/
|
||||||
|
public void setMaxY(double d) {
|
||||||
|
mMaxY = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the series of the second scale
|
||||||
|
*/
|
||||||
|
public List<Series> getSeries() {
|
||||||
|
return mSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return min y bound
|
||||||
|
*/
|
||||||
|
public double getMinY() {
|
||||||
|
return mMinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return max y bound
|
||||||
|
*/
|
||||||
|
public double getMaxY() {
|
||||||
|
return mMaxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return always true for the current implementation
|
||||||
|
*/
|
||||||
|
public boolean isYAxisBoundsManual() {
|
||||||
|
return mYAxisBoundsManual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return label formatter for the y labels on the right side
|
||||||
|
*/
|
||||||
|
public LabelFormatter getLabelFormatter() {
|
||||||
|
return mLabelFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a custom label formatter that is used
|
||||||
|
* for the y labels on the right side.
|
||||||
|
*
|
||||||
|
* @param formatter label formatter for the y labels
|
||||||
|
*/
|
||||||
|
public void setLabelFormatter(LabelFormatter formatter) {
|
||||||
|
mLabelFormatter = formatter;
|
||||||
|
mLabelFormatter.setViewport(mViewport);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jjoe64.graphview;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.series.DataPointInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* you can change the color depending on the value.
|
||||||
|
* takes only effect for BarGraphSeries.
|
||||||
|
*
|
||||||
|
* @see com.jjoe64.graphview.series.BarGraphSeries#setValueDependentColor(ValueDependentColor)
|
||||||
|
*/
|
||||||
|
public interface ValueDependentColor<T extends DataPointInterface> {
|
||||||
|
/**
|
||||||
|
* this is called when a bar is about to draw
|
||||||
|
* and the color is be loaded.
|
||||||
|
*
|
||||||
|
* @param data the current input value
|
||||||
|
* @return the color that the bar should be drawn with
|
||||||
|
* Generate the int via the android.graphics.Color class.
|
||||||
|
*/
|
||||||
|
public int get(T data);
|
||||||
|
}
|
1005
graphview/src/main/java/com/jjoe64/graphview/Viewport.java
Normal file
1005
graphview/src/main/java/com/jjoe64/graphview/Viewport.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.compat;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.widget.OverScroller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for using {@link android.widget.OverScroller} in a backward-compatible fashion.
|
||||||
|
*/
|
||||||
|
public class OverScrollerCompat {
|
||||||
|
/**
|
||||||
|
* Disallow instantiation.
|
||||||
|
*/
|
||||||
|
private OverScrollerCompat() {
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @see android.view.ScaleGestureDetector#getCurrentSpanY()
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||||
|
public static float getCurrVelocity(OverScroller overScroller) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||||
|
return overScroller.getCurrVelocity();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.helper;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.DefaultLabelFormatter;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to use date objects as x-values.
|
||||||
|
* This will use your own Date Format or by default
|
||||||
|
* the Android default date format to convert
|
||||||
|
* the x-values (that has to be millis from
|
||||||
|
* 01-01-1970) into a formatted date string.
|
||||||
|
*
|
||||||
|
* See the DateAsXAxis example in the GraphView-Demos project
|
||||||
|
* to see a working example.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class DateAsXAxisLabelFormatter extends DefaultLabelFormatter {
|
||||||
|
/**
|
||||||
|
* the date format that will convert
|
||||||
|
* the unix timestamp to string
|
||||||
|
*/
|
||||||
|
protected final DateFormat mDateFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calendar to avoid creating new date objects
|
||||||
|
*/
|
||||||
|
protected final Calendar mCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the formatter with the Android default date format to convert
|
||||||
|
* the x-values.
|
||||||
|
*
|
||||||
|
* @param context the application context
|
||||||
|
*/
|
||||||
|
public DateAsXAxisLabelFormatter(Context context) {
|
||||||
|
mDateFormat = android.text.format.DateFormat.getDateFormat(context);
|
||||||
|
mCalendar = Calendar.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the formatter with your own custom
|
||||||
|
* date format to convert the x-values.
|
||||||
|
*
|
||||||
|
* @param context the application context
|
||||||
|
* @param dateFormat custom date format
|
||||||
|
*/
|
||||||
|
public DateAsXAxisLabelFormatter(Context context, DateFormat dateFormat) {
|
||||||
|
mDateFormat = dateFormat;
|
||||||
|
mCalendar = Calendar.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formats the x-values as date string.
|
||||||
|
*
|
||||||
|
* @param value raw value
|
||||||
|
* @param isValueX true if it's a x value, otherwise false
|
||||||
|
* @return value converted to string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String formatLabel(double value, boolean isValueX) {
|
||||||
|
if (isValueX) {
|
||||||
|
// format as date
|
||||||
|
mCalendar.setTimeInMillis((long) value);
|
||||||
|
return mDateFormat.format(mCalendar.getTimeInMillis());
|
||||||
|
} else {
|
||||||
|
return super.formatLabel(value, isValueX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.helper;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
import com.jjoe64.graphview.R;
|
||||||
|
import com.jjoe64.graphview.series.BarGraphSeries;
|
||||||
|
import com.jjoe64.graphview.series.BaseSeries;
|
||||||
|
import com.jjoe64.graphview.series.DataPoint;
|
||||||
|
import com.jjoe64.graphview.series.DataPointInterface;
|
||||||
|
import com.jjoe64.graphview.series.LineGraphSeries;
|
||||||
|
import com.jjoe64.graphview.series.PointsGraphSeries;
|
||||||
|
import com.jjoe64.graphview.series.Series;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper class to use GraphView directly
|
||||||
|
* in a XML layout file.
|
||||||
|
*
|
||||||
|
* You can set the data via attribute <b>app:seriesData</b>
|
||||||
|
* in the format: "X=Y;X=Y;..." e.g. "0=5.0;1=5;2=4;3=9"
|
||||||
|
*
|
||||||
|
* Other styling options:
|
||||||
|
* <li>app:seriesType="line|bar|points"</li>
|
||||||
|
* <li>app:seriesColor="#ff0000"</li>
|
||||||
|
* <li>app:seriesTitle="foobar" - if this is set, the legend will be drawn</li>
|
||||||
|
* <li>android:title="foobar"</li>
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* <com.jjoe64.graphview.helper.GraphViewXML
|
||||||
|
* android:layout_width="match_parent"
|
||||||
|
* android:layout_height="100dip"
|
||||||
|
* app:seriesData="0=5;2=5;3=0;4=2"
|
||||||
|
* app:seriesType="line"
|
||||||
|
* app:seriesColor="#ee0000" />
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class GraphViewXML extends GraphView {
|
||||||
|
/**
|
||||||
|
* creates the graphview object with data and
|
||||||
|
* other options from xml attributes.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param attrs
|
||||||
|
*/
|
||||||
|
public GraphViewXML(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
// get attributes
|
||||||
|
TypedArray a=context.obtainStyledAttributes(
|
||||||
|
attrs,
|
||||||
|
R.styleable.GraphViewXML);
|
||||||
|
|
||||||
|
String dataStr = a.getString(R.styleable.GraphViewXML_seriesData);
|
||||||
|
int color = a.getColor(R.styleable.GraphViewXML_seriesColor, Color.TRANSPARENT);
|
||||||
|
String type = a.getString(R.styleable.GraphViewXML_seriesType);
|
||||||
|
String seriesTitle = a.getString(R.styleable.GraphViewXML_seriesTitle);
|
||||||
|
String title = a.getString(R.styleable.GraphViewXML_android_title);
|
||||||
|
|
||||||
|
a.recycle();
|
||||||
|
|
||||||
|
// decode data
|
||||||
|
DataPoint[] data;
|
||||||
|
if (dataStr == null || dataStr.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Attribute seriesData is required in the format: 0=5.0;1=5;2=4;3=9");
|
||||||
|
} else {
|
||||||
|
String[] d = dataStr.split(";");
|
||||||
|
try {
|
||||||
|
data = new DataPoint[d.length];
|
||||||
|
int i = 0;
|
||||||
|
for (String dd : d) {
|
||||||
|
String[] xy = dd.split("=");
|
||||||
|
data[i] = new DataPoint(Double.parseDouble(xy[0]), Double.parseDouble(xy[1]));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("GraphViewXML", e.toString());
|
||||||
|
throw new IllegalArgumentException("Attribute seriesData is broken. Use this format: 0=5.0;1=5;2=4;3=9");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create series
|
||||||
|
BaseSeries<DataPoint> series;
|
||||||
|
if (type == null || type.isEmpty()) {
|
||||||
|
type = "line";
|
||||||
|
}
|
||||||
|
if (type.equals("line")) {
|
||||||
|
series = new LineGraphSeries<DataPoint>(data);
|
||||||
|
} else if (type.equals("bar")) {
|
||||||
|
series = new BarGraphSeries<DataPoint>(data);
|
||||||
|
} else if (type.equals("points")) {
|
||||||
|
series = new PointsGraphSeries<DataPoint>(data);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unknown graph type: "+type+". Possible is line|bar|points");
|
||||||
|
}
|
||||||
|
if (color != Color.TRANSPARENT) {
|
||||||
|
series.setColor(color);
|
||||||
|
}
|
||||||
|
addSeries(series);
|
||||||
|
|
||||||
|
if (seriesTitle != null && !seriesTitle.isEmpty()) {
|
||||||
|
series.setTitle(seriesTitle);
|
||||||
|
getLegendRenderer().setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title != null && !title.isEmpty()) {
|
||||||
|
setTitle(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
package com.jjoe64.graphview.helper;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.DefaultLabelFormatter;
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
import com.jjoe64.graphview.LabelFormatter;
|
||||||
|
import com.jjoe64.graphview.Viewport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this label formatter to show static labels.
|
||||||
|
* Static labels are not bound to the data. It is typical used
|
||||||
|
* for show text like "low", "middle", "high".
|
||||||
|
*
|
||||||
|
* You can set the static labels for vertical or horizontal
|
||||||
|
* individually and you can define a label formatter that
|
||||||
|
* is to be used if you don't define static labels.
|
||||||
|
*
|
||||||
|
* For example if you only use static labels for horizontal labels,
|
||||||
|
* graphview will use the dynamicLabelFormatter for the vertical labels.
|
||||||
|
*/
|
||||||
|
public class StaticLabelsFormatter implements LabelFormatter {
|
||||||
|
/**
|
||||||
|
* reference to the viewport
|
||||||
|
*/
|
||||||
|
protected Viewport mViewport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the vertical labels, ordered from bottom to the top
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
*/
|
||||||
|
protected String[] mVerticalLabels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the horizontal labels, ordered form the left to the right
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
*/
|
||||||
|
protected String[] mHorizontalLabels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the label formatter that will format the labels
|
||||||
|
* for that there are no static labels defined.
|
||||||
|
*/
|
||||||
|
protected LabelFormatter mDynamicLabelFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference to the graphview
|
||||||
|
*/
|
||||||
|
protected final GraphView mGraphView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the formatter without any static labels
|
||||||
|
* define your static labels via {@link #setHorizontalLabels(String[])} and {@link #setVerticalLabels(String[])}
|
||||||
|
*
|
||||||
|
* @param graphView reference to the graphview
|
||||||
|
*/
|
||||||
|
public StaticLabelsFormatter(GraphView graphView) {
|
||||||
|
mGraphView = graphView;
|
||||||
|
init(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the formatter without any static labels.
|
||||||
|
* define your static labels via {@link #setHorizontalLabels(String[])} and {@link #setVerticalLabels(String[])}
|
||||||
|
*
|
||||||
|
* @param graphView reference to the graphview
|
||||||
|
* @param dynamicLabelFormatter the label formatter that will format the labels
|
||||||
|
* for that there are no static labels defined.
|
||||||
|
*/
|
||||||
|
public StaticLabelsFormatter(GraphView graphView, LabelFormatter dynamicLabelFormatter) {
|
||||||
|
mGraphView = graphView;
|
||||||
|
init(null, null, dynamicLabelFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the formatter with static labels defined.
|
||||||
|
*
|
||||||
|
* @param graphView reference to the graphview
|
||||||
|
* @param horizontalLabels the horizontal labels, ordered form the left to the right
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
* @param verticalLabels the vertical labels, ordered from bottom to the top
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
*/
|
||||||
|
public StaticLabelsFormatter(GraphView graphView, String[] horizontalLabels, String[] verticalLabels) {
|
||||||
|
mGraphView = graphView;
|
||||||
|
init(horizontalLabels, verticalLabels, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the formatter with static labels defined.
|
||||||
|
*
|
||||||
|
* @param graphView reference to the graphview
|
||||||
|
* @param horizontalLabels the horizontal labels, ordered form the left to the right
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
* @param verticalLabels the vertical labels, ordered from bottom to the top
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
* @param dynamicLabelFormatter the label formatter that will format the labels
|
||||||
|
* for that there are no static labels defined.
|
||||||
|
*/
|
||||||
|
public StaticLabelsFormatter(GraphView graphView, String[] horizontalLabels, String[] verticalLabels, LabelFormatter dynamicLabelFormatter) {
|
||||||
|
mGraphView = graphView;
|
||||||
|
init(horizontalLabels, verticalLabels, dynamicLabelFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param horizontalLabels the horizontal labels, ordered form the left to the right
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
* @param verticalLabels the vertical labels, ordered from bottom to the top
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
* @param dynamicLabelFormatter the label formatter that will format the labels
|
||||||
|
* for that there are no static labels defined.
|
||||||
|
*/
|
||||||
|
protected void init(String[] horizontalLabels, String[] verticalLabels, LabelFormatter dynamicLabelFormatter) {
|
||||||
|
mDynamicLabelFormatter = dynamicLabelFormatter;
|
||||||
|
if (mDynamicLabelFormatter == null) {
|
||||||
|
mDynamicLabelFormatter = new DefaultLabelFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
|
mHorizontalLabels = horizontalLabels;
|
||||||
|
mVerticalLabels = verticalLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a label formatter that will be used for the labels
|
||||||
|
* that don't have static labels.
|
||||||
|
*
|
||||||
|
* For example if you only use static labels for horizontal labels,
|
||||||
|
* graphview will use the dynamicLabelFormatter for the vertical labels.
|
||||||
|
*
|
||||||
|
* @param dynamicLabelFormatter the label formatter that will format the labels
|
||||||
|
* for that there are no static labels defined.
|
||||||
|
*/
|
||||||
|
public void setDynamicLabelFormatter(LabelFormatter dynamicLabelFormatter) {
|
||||||
|
this.mDynamicLabelFormatter = dynamicLabelFormatter;
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param horizontalLabels the horizontal labels, ordered form the left to the right
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
*/
|
||||||
|
public void setHorizontalLabels(String[] horizontalLabels) {
|
||||||
|
this.mHorizontalLabels = horizontalLabels;
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param verticalLabels the vertical labels, ordered from bottom to the top
|
||||||
|
* if it is null, the labels will be generated via the #dynamicLabelFormatter
|
||||||
|
*/
|
||||||
|
public void setVerticalLabels(String[] verticalLabels) {
|
||||||
|
this.mVerticalLabels = verticalLabels;
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value raw input number
|
||||||
|
* @param isValueX true if it is a value for the x axis
|
||||||
|
* false if it is a value for the y axis
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String formatLabel(double value, boolean isValueX) {
|
||||||
|
if (isValueX && mHorizontalLabels != null) {
|
||||||
|
double minX = mViewport.getMinX(false);
|
||||||
|
double maxX = mViewport.getMaxX(false);
|
||||||
|
double range = maxX - minX;
|
||||||
|
value = value-minX;
|
||||||
|
int idx = (int)((value/range) * (mHorizontalLabels.length-1));
|
||||||
|
return mHorizontalLabels[idx];
|
||||||
|
} else if (!isValueX && mVerticalLabels != null) {
|
||||||
|
double minY = mViewport.getMinY(false);
|
||||||
|
double maxY = mViewport.getMaxY(false);
|
||||||
|
double range = maxY - minY;
|
||||||
|
value = value-minY;
|
||||||
|
int idx = (int)((value/range) * (mVerticalLabels.length-1));
|
||||||
|
return mVerticalLabels[idx];
|
||||||
|
} else {
|
||||||
|
return mDynamicLabelFormatter.formatLabel(value, isValueX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param viewport the used viewport
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setViewport(Viewport viewport) {
|
||||||
|
mViewport = viewport;
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adjusts the number of vertical/horizontal labels
|
||||||
|
*/
|
||||||
|
protected void adjust() {
|
||||||
|
mDynamicLabelFormatter.setViewport(mViewport);
|
||||||
|
if (mVerticalLabels != null) {
|
||||||
|
if (mVerticalLabels.length < 2) {
|
||||||
|
throw new IllegalStateException("You need at least 2 vertical labels if you use static label formatter.");
|
||||||
|
}
|
||||||
|
mGraphView.getGridLabelRenderer().setNumVerticalLabels(mVerticalLabels.length);
|
||||||
|
}
|
||||||
|
if (mHorizontalLabels != null) {
|
||||||
|
if (mHorizontalLabels.length < 2) {
|
||||||
|
throw new IllegalStateException("You need at least 2 horizontal labels if you use static label formatter.");
|
||||||
|
}
|
||||||
|
mGraphView.getGridLabelRenderer().setNumHorizontalLabels(mHorizontalLabels.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,380 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
import com.jjoe64.graphview.ValueDependentColor;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series with Bars to visualize the data.
|
||||||
|
* The Bars are always vertical.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class BarGraphSeries<E extends DataPointInterface> extends BaseSeries<E> {
|
||||||
|
/**
|
||||||
|
* paint to do drawing on canvas
|
||||||
|
*/
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spacing between the bars in percentage.
|
||||||
|
* 0 => no spacing
|
||||||
|
* 100 => the space bewetten the bars is as big as the bars itself
|
||||||
|
*/
|
||||||
|
private int mSpacing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback to generate value-dependent colors
|
||||||
|
* of the bars
|
||||||
|
*/
|
||||||
|
private ValueDependentColor<E> mValueDependentColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the values should drawn
|
||||||
|
* above the bars as text
|
||||||
|
*/
|
||||||
|
private boolean mDrawValuesOnTop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* color of the text above the bars.
|
||||||
|
*
|
||||||
|
* @see #mDrawValuesOnTop
|
||||||
|
*/
|
||||||
|
private int mValuesOnTopColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* font size of the text above the bars.
|
||||||
|
*
|
||||||
|
* @see #mDrawValuesOnTop
|
||||||
|
*/
|
||||||
|
private float mValuesOnTopSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stores the coordinates of the bars to
|
||||||
|
* trigger tap on series events.
|
||||||
|
*/
|
||||||
|
private Map<RectF, E> mDataPoints = new HashMap<RectF, E>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates bar series without any data
|
||||||
|
*/
|
||||||
|
public BarGraphSeries() {
|
||||||
|
mPaint = new Paint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates bar series with data
|
||||||
|
*
|
||||||
|
* @param data values
|
||||||
|
*/
|
||||||
|
public BarGraphSeries(E[] data) {
|
||||||
|
super(data);
|
||||||
|
mPaint = new Paint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draws the bars on the canvas
|
||||||
|
*
|
||||||
|
* @param graphView corresponding graphview
|
||||||
|
* @param canvas canvas
|
||||||
|
* @param isSecondScale whether we are plotting the second scale or not
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
|
||||||
|
mPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
if (mValuesOnTopSize == 0) {
|
||||||
|
mValuesOnTopSize = graphView.getGridLabelRenderer().getTextSize();
|
||||||
|
}
|
||||||
|
mPaint.setTextSize(mValuesOnTopSize);
|
||||||
|
|
||||||
|
// get data
|
||||||
|
double maxX = graphView.getViewport().getMaxX(false);
|
||||||
|
double minX = graphView.getViewport().getMinX(false);
|
||||||
|
|
||||||
|
double maxY;
|
||||||
|
double minY;
|
||||||
|
if (isSecondScale) {
|
||||||
|
maxY = graphView.getSecondScale().getMaxY();
|
||||||
|
minY = graphView.getSecondScale().getMinY();
|
||||||
|
} else {
|
||||||
|
maxY = graphView.getViewport().getMaxY(false);
|
||||||
|
minY = graphView.getViewport().getMinY(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all bar graph series
|
||||||
|
// so we know how wide to make our bar,
|
||||||
|
// and in what position to put it in
|
||||||
|
int numBarSeries = 0;
|
||||||
|
int currentSeriesOrder = 0;
|
||||||
|
int numValues = 0;
|
||||||
|
boolean isCurrentSeries;
|
||||||
|
SortedSet<Double> xVals = new TreeSet<Double>();
|
||||||
|
for(Series inspectedSeries: graphView.getSeries()) {
|
||||||
|
if(inspectedSeries instanceof BarGraphSeries) {
|
||||||
|
isCurrentSeries = (inspectedSeries == this);
|
||||||
|
if(isCurrentSeries) {
|
||||||
|
currentSeriesOrder = numBarSeries;
|
||||||
|
}
|
||||||
|
numBarSeries++;
|
||||||
|
|
||||||
|
// calculate the number of slots for bars based on the minimum distance between
|
||||||
|
// x coordinates in the series. This is divided into the range to find
|
||||||
|
// the placement and width of bar slots
|
||||||
|
// (sections of the x axis for each bar or set of bars)
|
||||||
|
// TODO: Move this somewhere more general and cache it, so we don't recalculate it for each series
|
||||||
|
Iterator<E> curValues = inspectedSeries.getValues(minX, maxX);
|
||||||
|
if (curValues.hasNext()) {
|
||||||
|
xVals.add(curValues.next().getX());
|
||||||
|
if(isCurrentSeries) { numValues++; }
|
||||||
|
while (curValues.hasNext()) {
|
||||||
|
xVals.add(curValues.next().getX());
|
||||||
|
if(isCurrentSeries) { numValues++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numValues == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Double lastVal = null;
|
||||||
|
double minGap = 0;
|
||||||
|
for(Double curVal: xVals) {
|
||||||
|
if(lastVal != null) {
|
||||||
|
double curGap = Math.abs(curVal - lastVal);
|
||||||
|
if (minGap == 0 || (curGap > 0 && curGap < minGap)) {
|
||||||
|
minGap = curGap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastVal = curVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numBarSlots = (minGap == 0) ? 1 : (int)Math.round((maxX - minX)/minGap) + 1;
|
||||||
|
|
||||||
|
Iterator<E> values = getValues(minX, maxX);
|
||||||
|
|
||||||
|
// Calculate the overall bar slot width - this includes all bars across
|
||||||
|
// all series, and any spacing between sets of bars
|
||||||
|
float barSlotWidth = numBarSlots == 1
|
||||||
|
? graphView.getGraphContentWidth()
|
||||||
|
: graphView.getGraphContentWidth() / (numBarSlots-1);
|
||||||
|
Log.d("BarGraphSeries", "numBars=" + numBarSlots);
|
||||||
|
|
||||||
|
// Total spacing (both sides) between sets of bars
|
||||||
|
float spacing = Math.min((float) barSlotWidth*mSpacing/100, barSlotWidth*0.98f);
|
||||||
|
// Width of an individual bar
|
||||||
|
float barWidth = (barSlotWidth - spacing) / numBarSeries;
|
||||||
|
// Offset from the center of a given bar to start drawing
|
||||||
|
float offset = barSlotWidth/2;
|
||||||
|
|
||||||
|
double diffY = maxY - minY;
|
||||||
|
double diffX = maxX - minX;
|
||||||
|
float contentHeight = graphView.getGraphContentHeight();
|
||||||
|
float contentWidth = graphView.getGraphContentWidth();
|
||||||
|
float contentLeft = graphView.getGraphContentLeft();
|
||||||
|
float contentTop = graphView.getGraphContentTop();
|
||||||
|
|
||||||
|
// draw data
|
||||||
|
int i=0;
|
||||||
|
while (values.hasNext()) {
|
||||||
|
E value = values.next();
|
||||||
|
|
||||||
|
double valY = value.getY() - minY;
|
||||||
|
double ratY = valY / diffY;
|
||||||
|
double y = contentHeight * ratY;
|
||||||
|
|
||||||
|
double valY0 = 0 - minY;
|
||||||
|
double ratY0 = valY0 / diffY;
|
||||||
|
double y0 = contentHeight * ratY0;
|
||||||
|
|
||||||
|
double valX = value.getX() - minX;
|
||||||
|
double ratX = valX / diffX;
|
||||||
|
double x = contentWidth * ratX;
|
||||||
|
|
||||||
|
// hook for value dependent color
|
||||||
|
if (getValueDependentColor() != null) {
|
||||||
|
mPaint.setColor(getValueDependentColor().get(value));
|
||||||
|
} else {
|
||||||
|
mPaint.setColor(getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
float left = (float)x + contentLeft - offset + spacing/2 + currentSeriesOrder*barWidth;
|
||||||
|
float top = (contentTop - (float)y) + contentHeight;
|
||||||
|
float right = left + barWidth;
|
||||||
|
float bottom = (contentTop - (float)y0) + contentHeight - (graphView.getGridLabelRenderer().isHighlightZeroLines()?4:1);
|
||||||
|
|
||||||
|
boolean reverse = top > bottom;
|
||||||
|
if (reverse) {
|
||||||
|
float tmp = top;
|
||||||
|
top = bottom + (graphView.getGridLabelRenderer().isHighlightZeroLines()?4:1);
|
||||||
|
bottom = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// overdraw
|
||||||
|
left = Math.max(left, contentLeft);
|
||||||
|
right = Math.min(right, contentLeft+contentWidth);
|
||||||
|
bottom = Math.min(bottom, contentTop+contentHeight);
|
||||||
|
top = Math.max(top, contentTop);
|
||||||
|
|
||||||
|
mDataPoints.put(new RectF(left, top, right, bottom), value);
|
||||||
|
|
||||||
|
canvas.drawRect(left, top, right, bottom, mPaint);
|
||||||
|
|
||||||
|
// set values on top of graph
|
||||||
|
if (mDrawValuesOnTop) {
|
||||||
|
if (reverse) {
|
||||||
|
top = bottom + mValuesOnTopSize + 4;
|
||||||
|
if (top > contentTop+contentHeight) top = contentTop + contentHeight;
|
||||||
|
} else {
|
||||||
|
top -= 4;
|
||||||
|
if (top<=contentTop) top+=contentTop+4;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPaint.setColor(mValuesOnTopColor);
|
||||||
|
canvas.drawText(
|
||||||
|
graphView.getGridLabelRenderer().getLabelFormatter().formatLabel(value.getY(), false)
|
||||||
|
, (left+right)/2, top, mPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the hook to generate value-dependent color. default null
|
||||||
|
*/
|
||||||
|
public ValueDependentColor<E> getValueDependentColor() {
|
||||||
|
return mValueDependentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set a hook to make the color of the bars depending
|
||||||
|
* on the actually value/data.
|
||||||
|
*
|
||||||
|
* @param mValueDependentColor hook
|
||||||
|
* null to disable
|
||||||
|
*/
|
||||||
|
public void setValueDependentColor(ValueDependentColor<E> mValueDependentColor) {
|
||||||
|
this.mValueDependentColor = mValueDependentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the spacing between the bars in percentage
|
||||||
|
*/
|
||||||
|
public int getSpacing() {
|
||||||
|
return mSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mSpacing spacing between the bars in percentage.
|
||||||
|
* 0 => no spacing
|
||||||
|
* 100 => the space between the bars is as big as the bars itself
|
||||||
|
*/
|
||||||
|
public void setSpacing(int mSpacing) {
|
||||||
|
this.mSpacing = mSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the values should be drawn above the bars
|
||||||
|
*/
|
||||||
|
public boolean isDrawValuesOnTop() {
|
||||||
|
return mDrawValuesOnTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mDrawValuesOnTop flag whether the values should drawn
|
||||||
|
* above the bars as text
|
||||||
|
*/
|
||||||
|
public void setDrawValuesOnTop(boolean mDrawValuesOnTop) {
|
||||||
|
this.mDrawValuesOnTop = mDrawValuesOnTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return font color of the values on top of the bars
|
||||||
|
* @see #setDrawValuesOnTop(boolean)
|
||||||
|
*/
|
||||||
|
public int getValuesOnTopColor() {
|
||||||
|
return mValuesOnTopColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mValuesOnTopColor the font color of the values on top of the bars
|
||||||
|
* @see #setDrawValuesOnTop(boolean)
|
||||||
|
*/
|
||||||
|
public void setValuesOnTopColor(int mValuesOnTopColor) {
|
||||||
|
this.mValuesOnTopColor = mValuesOnTopColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return font size of the values above the bars
|
||||||
|
* @see #setDrawValuesOnTop(boolean)
|
||||||
|
*/
|
||||||
|
public float getValuesOnTopSize() {
|
||||||
|
return mValuesOnTopSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mValuesOnTopSize font size of the values above the bars
|
||||||
|
* @see #setDrawValuesOnTop(boolean)
|
||||||
|
*/
|
||||||
|
public void setValuesOnTopSize(float mValuesOnTopSize) {
|
||||||
|
this.mValuesOnTopSize = mValuesOnTopSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resets the cached coordinates of the bars
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void resetDataPoints() {
|
||||||
|
mDataPoints.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find the corresponding data point by
|
||||||
|
* coordinates.
|
||||||
|
*
|
||||||
|
* @param x pixels
|
||||||
|
* @param y pixels
|
||||||
|
* @return datapoint or null
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected E findDataPoint(float x, float y) {
|
||||||
|
for (Map.Entry<RectF, E> entry : mDataPoints.entrySet()) {
|
||||||
|
if (x >= entry.getKey().left && x <= entry.getKey().right
|
||||||
|
&& y >= entry.getKey().top && y <= entry.getKey().bottom) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,448 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basis implementation for series.
|
||||||
|
* Used for series that are plotted on
|
||||||
|
* a default x/y 2d viewport.
|
||||||
|
*
|
||||||
|
* Extend this class to implement your own custom
|
||||||
|
* graph type.
|
||||||
|
*
|
||||||
|
* This implementation uses a internal Array to store
|
||||||
|
* the data. If you want to implement a custom data provider
|
||||||
|
* you may want to implement {@link com.jjoe64.graphview.series.Series}.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public abstract class BaseSeries<E extends DataPointInterface> implements Series<E> {
|
||||||
|
/**
|
||||||
|
* holds the data
|
||||||
|
*/
|
||||||
|
final private List<E> mData = new ArrayList<E>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stores the used coordinates to find the
|
||||||
|
* corresponding data point on a tap
|
||||||
|
*
|
||||||
|
* Key => x/y pixel
|
||||||
|
* Value => Plotted Datapoint
|
||||||
|
*
|
||||||
|
* will be filled while drawing via {@link #registerDataPoint(float, float, DataPointInterface)}
|
||||||
|
*/
|
||||||
|
private Map<PointF, E> mDataPoints = new HashMap<PointF, E>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* title for this series that can be displayed
|
||||||
|
* in the legend.
|
||||||
|
*/
|
||||||
|
private String mTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base color for this series. will be used also in
|
||||||
|
* the legend
|
||||||
|
*/
|
||||||
|
private int mColor = 0xff0077cc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* listener to handle tap events on a data point
|
||||||
|
*/
|
||||||
|
protected OnDataPointTapListener mOnDataPointTapListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stores the graphviews where this series is used.
|
||||||
|
* Can be more than one.
|
||||||
|
*/
|
||||||
|
private List<GraphView> mGraphViews;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates series without data
|
||||||
|
*/
|
||||||
|
public BaseSeries() {
|
||||||
|
mGraphViews = new ArrayList<GraphView>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates series with data
|
||||||
|
*
|
||||||
|
* @param data data points
|
||||||
|
* important: array has to be sorted from lowest x-value to the highest
|
||||||
|
*/
|
||||||
|
public BaseSeries(E[] data) {
|
||||||
|
mGraphViews = new ArrayList<GraphView>();
|
||||||
|
for (E d : data) {
|
||||||
|
mData.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the lowest x value, or 0 if there is no data
|
||||||
|
*/
|
||||||
|
public double getLowestValueX() {
|
||||||
|
if (mData.isEmpty()) return 0d;
|
||||||
|
return mData.get(0).getX();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the highest x value, or 0 if there is no data
|
||||||
|
*/
|
||||||
|
public double getHighestValueX() {
|
||||||
|
if (mData.isEmpty()) return 0d;
|
||||||
|
return mData.get(mData.size()-1).getX();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the lowest y value, or 0 if there is no data
|
||||||
|
*/
|
||||||
|
public double getLowestValueY() {
|
||||||
|
if (mData.isEmpty()) return 0d;
|
||||||
|
double l = mData.get(0).getY();
|
||||||
|
for (int i = 1; i < mData.size(); i++) {
|
||||||
|
double c = mData.get(i).getY();
|
||||||
|
if (l > c) {
|
||||||
|
l = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the highest y value, or 0 if there is no data
|
||||||
|
*/
|
||||||
|
public double getHighestValueY() {
|
||||||
|
if (mData.isEmpty()) return 0d;
|
||||||
|
double h = mData.get(0).getY();
|
||||||
|
for (int i = 1; i < mData.size(); i++) {
|
||||||
|
double c = mData.get(i).getY();
|
||||||
|
if (h < c) {
|
||||||
|
h = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the values for a given x range. if from and until are bigger or equal than
|
||||||
|
* all the data, the original data is returned.
|
||||||
|
* If it is only a part of the data, the range is returned plus one datapoint
|
||||||
|
* before and after to get a nice scrolling.
|
||||||
|
*
|
||||||
|
* @param from minimal x-value
|
||||||
|
* @param until maximal x-value
|
||||||
|
* @return data for the range +/- 1 datapoint
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterator<E> getValues(final double from, final double until) {
|
||||||
|
if (from <= getLowestValueX() && until >= getHighestValueX()) {
|
||||||
|
return mData.iterator();
|
||||||
|
} else {
|
||||||
|
return new Iterator<E>() {
|
||||||
|
Iterator<E> org = mData.iterator();
|
||||||
|
E nextValue = null;
|
||||||
|
E nextNextValue = null;
|
||||||
|
boolean plusOne = true;
|
||||||
|
|
||||||
|
{
|
||||||
|
// go to first
|
||||||
|
boolean found = false;
|
||||||
|
E prevValue = null;
|
||||||
|
if (org.hasNext()) {
|
||||||
|
prevValue = org.next();
|
||||||
|
}
|
||||||
|
if (prevValue.getX() >= from) {
|
||||||
|
nextValue = prevValue;
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
while (org.hasNext()) {
|
||||||
|
nextValue = org.next();
|
||||||
|
if (nextValue.getX() >= from) {
|
||||||
|
found = true;
|
||||||
|
nextNextValue = nextValue;
|
||||||
|
nextValue = prevValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevValue = nextValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
nextValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E next() {
|
||||||
|
if (hasNext()) {
|
||||||
|
E r = nextValue;
|
||||||
|
if (r.getX() > until) {
|
||||||
|
plusOne = false;
|
||||||
|
}
|
||||||
|
if (nextNextValue != null) {
|
||||||
|
nextValue = nextNextValue;
|
||||||
|
nextNextValue = null;
|
||||||
|
} else if (org.hasNext()) nextValue = org.next();
|
||||||
|
else nextValue = null;
|
||||||
|
return r;
|
||||||
|
} else {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return nextValue != null && (nextValue.getX() <= until || plusOne);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the title of the series
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the title of the series. This will be used in
|
||||||
|
* the legend.
|
||||||
|
*
|
||||||
|
* @param mTitle title of the series
|
||||||
|
*/
|
||||||
|
public void setTitle(String mTitle) {
|
||||||
|
this.mTitle = mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return color of the series
|
||||||
|
*/
|
||||||
|
public int getColor() {
|
||||||
|
return mColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the color of the series. This will be used in
|
||||||
|
* plotting (depends on the series implementation) and
|
||||||
|
* is used in the legend.
|
||||||
|
*
|
||||||
|
* @param mColor
|
||||||
|
*/
|
||||||
|
public void setColor(int mColor) {
|
||||||
|
this.mColor = mColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set a listener for tap on a data point.
|
||||||
|
*
|
||||||
|
* @param l listener
|
||||||
|
*/
|
||||||
|
public void setOnDataPointTapListener(OnDataPointTapListener l) {
|
||||||
|
this.mOnDataPointTapListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called by the tap detector in order to trigger
|
||||||
|
* the on tap on datapoint event.
|
||||||
|
*
|
||||||
|
* @param x pixel
|
||||||
|
* @param y pixel
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onTap(float x, float y) {
|
||||||
|
if (mOnDataPointTapListener != null) {
|
||||||
|
E p = findDataPoint(x, y);
|
||||||
|
if (p != null) {
|
||||||
|
mOnDataPointTapListener.onTap(this, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find the data point which is next to the
|
||||||
|
* coordinates
|
||||||
|
*
|
||||||
|
* @param x pixel
|
||||||
|
* @param y pixel
|
||||||
|
* @return the data point or null if nothing was found
|
||||||
|
*/
|
||||||
|
protected E findDataPoint(float x, float y) {
|
||||||
|
float shortestDistance = Float.NaN;
|
||||||
|
E shortest = null;
|
||||||
|
for (Map.Entry<PointF, E> entry : mDataPoints.entrySet()) {
|
||||||
|
float x1 = entry.getKey().x;
|
||||||
|
float y1 = entry.getKey().y;
|
||||||
|
float x2 = x;
|
||||||
|
float y2 = y;
|
||||||
|
|
||||||
|
float distance = (float) Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
|
||||||
|
if (shortest == null || distance < shortestDistance) {
|
||||||
|
shortestDistance = distance;
|
||||||
|
shortest = entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shortest != null) {
|
||||||
|
if (shortestDistance < 120) {
|
||||||
|
return shortest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* register the datapoint to find it at a tap
|
||||||
|
*
|
||||||
|
* @param x pixel
|
||||||
|
* @param y pixel
|
||||||
|
* @param dp the data point to save
|
||||||
|
*/
|
||||||
|
protected void registerDataPoint(float x, float y, E dp) {
|
||||||
|
mDataPoints.put(new PointF(x, y), dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clears the cached data point coordinates
|
||||||
|
*/
|
||||||
|
protected void resetDataPoints() {
|
||||||
|
mDataPoints.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clears the data of this series and sets new.
|
||||||
|
* will redraw the graph
|
||||||
|
*
|
||||||
|
* @param data the values must be in the correct order!
|
||||||
|
* x-value has to be ASC. First the lowest x value and at least the highest x value.
|
||||||
|
*/
|
||||||
|
public void resetData(E[] data) {
|
||||||
|
mData.clear();
|
||||||
|
for (E d : data) {
|
||||||
|
mData.add(d);
|
||||||
|
}
|
||||||
|
checkValueOrder(null);
|
||||||
|
|
||||||
|
// update graphview
|
||||||
|
for (GraphView gv : mGraphViews) {
|
||||||
|
gv.onDataChanged(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when the series was added to a graph
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onGraphViewAttached(GraphView graphView) {
|
||||||
|
mGraphViews.add(graphView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param dataPoint values the values must be in the correct order!
|
||||||
|
* x-value has to be ASC. First the lowest x value and at least the highest x value.
|
||||||
|
* @param scrollToEnd true => graphview will scroll to the end (maxX)
|
||||||
|
* @param maxDataPoints if max data count is reached, the oldest data
|
||||||
|
* value will be lost to avoid memory leaks
|
||||||
|
*/
|
||||||
|
public void appendData(E dataPoint, boolean scrollToEnd, int maxDataPoints) {
|
||||||
|
checkValueOrder(dataPoint);
|
||||||
|
|
||||||
|
if (!mData.isEmpty() && dataPoint.getX() < mData.get(mData.size()-1).getX()) {
|
||||||
|
throw new IllegalArgumentException("new x-value must be greater then the last value. x-values has to be ordered in ASC.");
|
||||||
|
}
|
||||||
|
synchronized (mData) {
|
||||||
|
int curDataCount = mData.size();
|
||||||
|
if (curDataCount < maxDataPoints) {
|
||||||
|
// enough space
|
||||||
|
mData.add(dataPoint);
|
||||||
|
} else {
|
||||||
|
// we have to trim one data
|
||||||
|
mData.remove(0);
|
||||||
|
mData.add(dataPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalc the labels when it was the first data
|
||||||
|
boolean keepLabels = mData.size() != 1;
|
||||||
|
|
||||||
|
// update linked graph views
|
||||||
|
// update graphview
|
||||||
|
for (GraphView gv : mGraphViews) {
|
||||||
|
gv.onDataChanged(keepLabels, scrollToEnd);
|
||||||
|
if (scrollToEnd) {
|
||||||
|
gv.getViewport().scrollToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether there are data points
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return mData.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks that the data is in the correct order
|
||||||
|
*
|
||||||
|
* @param onlyLast if not null, it will only check that this
|
||||||
|
* datapoint is after the last point.
|
||||||
|
*/
|
||||||
|
protected void checkValueOrder(DataPointInterface onlyLast) {
|
||||||
|
if (mData.size()>1) {
|
||||||
|
if (onlyLast != null) {
|
||||||
|
// only check last
|
||||||
|
if (onlyLast.getX() < mData.get(mData.size()-1).getX()) {
|
||||||
|
throw new IllegalArgumentException("new x-value must be greater then the last value. x-values has to be ordered in ASC.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
double lx = mData.get(0).getX();
|
||||||
|
|
||||||
|
for (int i = 1; i < mData.size(); i++) {
|
||||||
|
if (mData.get(i).getX() != Double.NaN) {
|
||||||
|
if (lx > mData.get(i).getX()) {
|
||||||
|
throw new IllegalArgumentException("The order of the values is not correct. X-Values have to be ordered ASC. First the lowest x value and at least the highest x value.");
|
||||||
|
}
|
||||||
|
lx = mData.get(i).getX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default data point implementation.
|
||||||
|
* This stores the x and y values.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class DataPoint implements DataPointInterface, Serializable {
|
||||||
|
private static final long serialVersionUID=1428263322645L;
|
||||||
|
|
||||||
|
private double x;
|
||||||
|
private double y;
|
||||||
|
|
||||||
|
public DataPoint(double x, double y) {
|
||||||
|
this.x=x;
|
||||||
|
this.y=y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataPoint(Date x, double y) {
|
||||||
|
this.x = x.getTime();
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "["+x+"/"+y+"]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interface of data points. Implement this in order
|
||||||
|
* to use your class in {@link com.jjoe64.graphview.series.Series}.
|
||||||
|
*
|
||||||
|
* You can also use the default implementation {@link com.jjoe64.graphview.series.DataPoint} so
|
||||||
|
* you do not have to implement it for yourself.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public interface DataPointInterface {
|
||||||
|
/**
|
||||||
|
* @return the x value
|
||||||
|
*/
|
||||||
|
public double getX();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the y value
|
||||||
|
*/
|
||||||
|
public double getY();
|
||||||
|
}
|
|
@ -0,0 +1,409 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series to plot the data as line.
|
||||||
|
* The line can be styled with many options.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class LineGraphSeries<E extends DataPointInterface> extends BaseSeries<E> {
|
||||||
|
/**
|
||||||
|
* wrapped styles regarding the line
|
||||||
|
*/
|
||||||
|
private final class Styles {
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
|
||||||
|
*/
|
||||||
|
private int thickness = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @see #backgroundColor
|
||||||
|
*/
|
||||||
|
private boolean drawBackground = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @see #dataPointsRadius
|
||||||
|
*/
|
||||||
|
private boolean drawDataPoints = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the radius for the data points.
|
||||||
|
*
|
||||||
|
* @see #drawDataPoints
|
||||||
|
*/
|
||||||
|
private float dataPointsRadius = 10f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
*
|
||||||
|
* @see #drawBackground
|
||||||
|
*/
|
||||||
|
private int backgroundColor = Color.argb(100, 172, 218, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles
|
||||||
|
*/
|
||||||
|
private Styles mStyles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal paint object
|
||||||
|
*/
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paint for the background
|
||||||
|
*/
|
||||||
|
private Paint mPaintBackground;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* path for the background filling
|
||||||
|
*/
|
||||||
|
private Path mPathBackground;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* path to the line
|
||||||
|
*/
|
||||||
|
private Path mPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom paint that can be used.
|
||||||
|
* this will ignore the thickness and color styles.
|
||||||
|
*/
|
||||||
|
private Paint mCustomPaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a series without data
|
||||||
|
*/
|
||||||
|
public LineGraphSeries() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a series with data
|
||||||
|
*
|
||||||
|
* @param data data points
|
||||||
|
*/
|
||||||
|
public LineGraphSeries(E[] data) {
|
||||||
|
super(data);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* do the initialization
|
||||||
|
* creates internal objects
|
||||||
|
*/
|
||||||
|
protected void init() {
|
||||||
|
mStyles = new Styles();
|
||||||
|
mPaint = new Paint();
|
||||||
|
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mPaintBackground = new Paint();
|
||||||
|
|
||||||
|
mPathBackground = new Path();
|
||||||
|
mPath = new Path();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plots the series
|
||||||
|
* draws the line and the background
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
* @param canvas canvas
|
||||||
|
* @param isSecondScale flag if it is the second scale
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
|
||||||
|
resetDataPoints();
|
||||||
|
|
||||||
|
// get data
|
||||||
|
double maxX = graphView.getViewport().getMaxX(false);
|
||||||
|
double minX = graphView.getViewport().getMinX(false);
|
||||||
|
|
||||||
|
double maxY;
|
||||||
|
double minY;
|
||||||
|
if (isSecondScale) {
|
||||||
|
maxY = graphView.getSecondScale().getMaxY();
|
||||||
|
minY = graphView.getSecondScale().getMinY();
|
||||||
|
} else {
|
||||||
|
maxY = graphView.getViewport().getMaxY(false);
|
||||||
|
minY = graphView.getViewport().getMinY(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<E> values = getValues(minX, maxX);
|
||||||
|
|
||||||
|
// draw background
|
||||||
|
double lastEndY = 0;
|
||||||
|
double lastEndX = 0;
|
||||||
|
|
||||||
|
// draw data
|
||||||
|
mPaint.setStrokeWidth(mStyles.thickness);
|
||||||
|
mPaint.setColor(getColor());
|
||||||
|
mPaintBackground.setColor(mStyles.backgroundColor);
|
||||||
|
|
||||||
|
Paint paint;
|
||||||
|
if (mCustomPaint != null) {
|
||||||
|
paint = mCustomPaint;
|
||||||
|
} else {
|
||||||
|
paint = mPaint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
mPathBackground.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
double diffY = maxY - minY;
|
||||||
|
double diffX = maxX - minX;
|
||||||
|
|
||||||
|
float graphHeight = graphView.getGraphContentHeight();
|
||||||
|
float graphWidth = graphView.getGraphContentWidth();
|
||||||
|
float graphLeft = graphView.getGraphContentLeft();
|
||||||
|
float graphTop = graphView.getGraphContentTop();
|
||||||
|
|
||||||
|
lastEndY = 0;
|
||||||
|
lastEndX = 0;
|
||||||
|
double lastUsedEndX = 0;
|
||||||
|
float firstX = 0;
|
||||||
|
int i=0;
|
||||||
|
while (values.hasNext()) {
|
||||||
|
E value = values.next();
|
||||||
|
|
||||||
|
double valY = value.getY() - minY;
|
||||||
|
double ratY = valY / diffY;
|
||||||
|
double y = graphHeight * ratY;
|
||||||
|
|
||||||
|
double valX = value.getX() - minX;
|
||||||
|
double ratX = valX / diffX;
|
||||||
|
double x = graphWidth * ratX;
|
||||||
|
|
||||||
|
double orgX = x;
|
||||||
|
double orgY = y;
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
// overdraw
|
||||||
|
if (x > graphWidth) { // end right
|
||||||
|
double b = ((graphWidth - lastEndX) * (y - lastEndY)/(x - lastEndX));
|
||||||
|
y = lastEndY+b;
|
||||||
|
x = graphWidth;
|
||||||
|
}
|
||||||
|
if (y < 0) { // end bottom
|
||||||
|
double b = ((0 - lastEndY) * (x - lastEndX)/(y - lastEndY));
|
||||||
|
x = lastEndX+b;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
if (y > graphHeight) { // end top
|
||||||
|
double b = ((graphHeight - lastEndY) * (x - lastEndX)/(y - lastEndY));
|
||||||
|
x = lastEndX+b;
|
||||||
|
y = graphHeight;
|
||||||
|
}
|
||||||
|
if (lastEndY < 0) { // start bottom
|
||||||
|
double b = ((0 - y) * (x - lastEndX)/(lastEndY - y));
|
||||||
|
lastEndX = x-b;
|
||||||
|
lastEndY = 0;
|
||||||
|
}
|
||||||
|
if (lastEndX < 0) { // start left
|
||||||
|
double b = ((0 - x) * (y - lastEndY)/(lastEndX - x));
|
||||||
|
lastEndY = y-b;
|
||||||
|
lastEndX = 0;
|
||||||
|
}
|
||||||
|
if (lastEndY > graphHeight) { // start top
|
||||||
|
double b = ((graphHeight - y) * (x - lastEndX)/(lastEndY - y));
|
||||||
|
lastEndX = x-b;
|
||||||
|
lastEndY = graphHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
float startX = (float) lastEndX + (graphLeft + 1);
|
||||||
|
float startY = (float) (graphTop - lastEndY) + graphHeight;
|
||||||
|
float endX = (float) x + (graphLeft + 1);
|
||||||
|
float endY = (float) (graphTop - y) + graphHeight;
|
||||||
|
|
||||||
|
// draw data point
|
||||||
|
if (mStyles.drawDataPoints) {
|
||||||
|
//fix: last value was not drawn. Draw here now the end values
|
||||||
|
canvas.drawCircle(endX, endY, mStyles.dataPointsRadius, mPaint);
|
||||||
|
}
|
||||||
|
registerDataPoint(endX, endY, value);
|
||||||
|
|
||||||
|
mPath.reset();
|
||||||
|
mPath.moveTo(startX, startY);
|
||||||
|
mPath.lineTo(endX, endY);
|
||||||
|
canvas.drawPath(mPath, paint);
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
if (i==1) {
|
||||||
|
firstX = startX;
|
||||||
|
mPathBackground.moveTo(startX, startY);
|
||||||
|
}
|
||||||
|
mPathBackground.lineTo(endX, endY);
|
||||||
|
}
|
||||||
|
lastUsedEndX = endX;
|
||||||
|
} else if (mStyles.drawDataPoints) {
|
||||||
|
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
|
||||||
|
float first_X = (float) x + (graphLeft + 1);
|
||||||
|
float first_Y = (float) (graphTop - y) + graphHeight;
|
||||||
|
//TODO canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
|
||||||
|
}
|
||||||
|
lastEndY = orgY;
|
||||||
|
lastEndX = orgX;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
// end / close path
|
||||||
|
mPathBackground.lineTo((float) lastUsedEndX, graphHeight + graphTop);
|
||||||
|
mPathBackground.lineTo(firstX, graphHeight + graphTop);
|
||||||
|
mPathBackground.close();
|
||||||
|
canvas.drawPath(mPathBackground, mPaintBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
|
||||||
|
*
|
||||||
|
* @return the thickness of the line
|
||||||
|
*/
|
||||||
|
public int getThickness() {
|
||||||
|
return mStyles.thickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
|
||||||
|
*
|
||||||
|
* @param thickness thickness of the line
|
||||||
|
*/
|
||||||
|
public void setThickness(int thickness) {
|
||||||
|
mStyles.thickness = thickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @return whether the background will be drawn
|
||||||
|
* @see #getBackgroundColor()
|
||||||
|
*/
|
||||||
|
public boolean isDrawBackground() {
|
||||||
|
return mStyles.drawBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @param drawBackground whether the background will be drawn
|
||||||
|
* @see #setBackgroundColor(int)
|
||||||
|
*/
|
||||||
|
public void setDrawBackground(boolean drawBackground) {
|
||||||
|
mStyles.drawBackground = drawBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @return flag whether the data points are highlighted
|
||||||
|
* @see #setDataPointsRadius(float)
|
||||||
|
*/
|
||||||
|
public boolean isDrawDataPoints() {
|
||||||
|
return mStyles.drawDataPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @param drawDataPoints flag whether the data points are highlighted
|
||||||
|
* @see #setDataPointsRadius(float)
|
||||||
|
*/
|
||||||
|
public void setDrawDataPoints(boolean drawDataPoints) {
|
||||||
|
mStyles.drawDataPoints = drawDataPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the radius for the data points.
|
||||||
|
* @see #setDrawDataPoints(boolean)
|
||||||
|
*/
|
||||||
|
public float getDataPointsRadius() {
|
||||||
|
return mStyles.dataPointsRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dataPointsRadius the radius for the data points.
|
||||||
|
* @see #setDrawDataPoints(boolean)
|
||||||
|
*/
|
||||||
|
public void setDataPointsRadius(float dataPointsRadius) {
|
||||||
|
mStyles.dataPointsRadius = dataPointsRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
* @see #setDrawBackground(boolean)
|
||||||
|
*/
|
||||||
|
public int getBackgroundColor() {
|
||||||
|
return mStyles.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param backgroundColor the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
* @see #setDrawBackground(boolean)
|
||||||
|
*/
|
||||||
|
public void setBackgroundColor(int backgroundColor) {
|
||||||
|
mStyles.backgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom paint that can be used.
|
||||||
|
* this will ignore the thickness and color styles.
|
||||||
|
*
|
||||||
|
* @param customPaint the custom paint to be used for rendering the line
|
||||||
|
*/
|
||||||
|
public void setCustomPaint(Paint customPaint) {
|
||||||
|
this.mCustomPaint = customPaint;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for the tap event which will be
|
||||||
|
* triggered when the user touches on a datapoint.
|
||||||
|
*
|
||||||
|
* Use this in {@link com.jjoe64.graphview.series.BaseSeries#setOnDataPointTapListener(OnDataPointTapListener)}
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public interface OnDataPointTapListener {
|
||||||
|
/**
|
||||||
|
* gets called when the user touches on a datapoint.
|
||||||
|
*
|
||||||
|
* @param series the corresponding series
|
||||||
|
* @param dataPoint the data point that was tapped on
|
||||||
|
*/
|
||||||
|
void onTap(Series series, DataPointInterface dataPoint);
|
||||||
|
}
|
|
@ -0,0 +1,312 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Point;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series that plots the data as points.
|
||||||
|
* The points can be different shapes or a
|
||||||
|
* complete custom drawing.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public class PointsGraphSeries<E extends DataPointInterface> extends BaseSeries<E> {
|
||||||
|
/**
|
||||||
|
* interface to implement a custom
|
||||||
|
* drawing for the data points.
|
||||||
|
*/
|
||||||
|
public static interface CustomShape {
|
||||||
|
/**
|
||||||
|
* called when drawing a single data point.
|
||||||
|
* use the x and y coordinates to render your
|
||||||
|
* drawing at this point.
|
||||||
|
*
|
||||||
|
* @param canvas canvas to draw on
|
||||||
|
* @param paint internal paint object. this has the correct color.
|
||||||
|
* But you can use your own paint.
|
||||||
|
* @param x x-coordinate the point has to be drawn to
|
||||||
|
* @param y y-coordinate the point has to be drawn to
|
||||||
|
* @param dataPoint the related data point
|
||||||
|
*/
|
||||||
|
void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* choose a predefined shape to render for
|
||||||
|
* each data point.
|
||||||
|
* You can also render a custom drawing via {@link com.jjoe64.graphview.series.PointsGraphSeries.CustomShape}
|
||||||
|
*/
|
||||||
|
public enum Shape {
|
||||||
|
/**
|
||||||
|
* draws a point / circle
|
||||||
|
*/
|
||||||
|
POINT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draws a triangle
|
||||||
|
*/
|
||||||
|
TRIANGLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draws a rectangle
|
||||||
|
*/
|
||||||
|
RECTANGLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles for this series
|
||||||
|
*/
|
||||||
|
private final class Styles {
|
||||||
|
/**
|
||||||
|
* this is used for the size of the shape that
|
||||||
|
* will be drawn.
|
||||||
|
* This is useless if you are using a custom shape.
|
||||||
|
*/
|
||||||
|
float size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the shape that will be drawn for each point.
|
||||||
|
*/
|
||||||
|
Shape shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles
|
||||||
|
*/
|
||||||
|
private Styles mStyles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal paint object
|
||||||
|
*/
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handler to use a custom drawing
|
||||||
|
*/
|
||||||
|
private CustomShape mCustomShape;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the series without data
|
||||||
|
*/
|
||||||
|
public PointsGraphSeries() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the series with data
|
||||||
|
*
|
||||||
|
* @param data datapoints
|
||||||
|
*/
|
||||||
|
public PointsGraphSeries(E[] data) {
|
||||||
|
super(data);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* inits the internal objects
|
||||||
|
* set the defaults
|
||||||
|
*/
|
||||||
|
protected void init() {
|
||||||
|
mStyles = new Styles();
|
||||||
|
mStyles.size = 20f;
|
||||||
|
mPaint = new Paint();
|
||||||
|
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
setShape(Shape.POINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plot the data to the viewport
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
* @param canvas canvas to draw on
|
||||||
|
* @param isSecondScale whether it is the second scale
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
|
||||||
|
resetDataPoints();
|
||||||
|
|
||||||
|
// get data
|
||||||
|
double maxX = graphView.getViewport().getMaxX(false);
|
||||||
|
double minX = graphView.getViewport().getMinX(false);
|
||||||
|
|
||||||
|
double maxY;
|
||||||
|
double minY;
|
||||||
|
if (isSecondScale) {
|
||||||
|
maxY = graphView.getSecondScale().getMaxY();
|
||||||
|
minY = graphView.getSecondScale().getMinY();
|
||||||
|
} else {
|
||||||
|
maxY = graphView.getViewport().getMaxY(false);
|
||||||
|
minY = graphView.getViewport().getMinY(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<E> values = getValues(minX, maxX);
|
||||||
|
|
||||||
|
// draw background
|
||||||
|
double lastEndY = 0;
|
||||||
|
double lastEndX = 0;
|
||||||
|
|
||||||
|
// draw data
|
||||||
|
mPaint.setColor(getColor());
|
||||||
|
|
||||||
|
double diffY = maxY - minY;
|
||||||
|
double diffX = maxX - minX;
|
||||||
|
|
||||||
|
float graphHeight = graphView.getGraphContentHeight();
|
||||||
|
float graphWidth = graphView.getGraphContentWidth();
|
||||||
|
float graphLeft = graphView.getGraphContentLeft();
|
||||||
|
float graphTop = graphView.getGraphContentTop();
|
||||||
|
|
||||||
|
lastEndY = 0;
|
||||||
|
lastEndX = 0;
|
||||||
|
float firstX = 0;
|
||||||
|
int i=0;
|
||||||
|
while (values.hasNext()) {
|
||||||
|
E value = values.next();
|
||||||
|
|
||||||
|
double valY = value.getY() - minY;
|
||||||
|
double ratY = valY / diffY;
|
||||||
|
double y = graphHeight * ratY;
|
||||||
|
|
||||||
|
double valX = value.getX() - minX;
|
||||||
|
double ratX = valX / diffX;
|
||||||
|
double x = graphWidth * ratX;
|
||||||
|
|
||||||
|
double orgX = x;
|
||||||
|
double orgY = y;
|
||||||
|
|
||||||
|
// overdraw
|
||||||
|
boolean overdraw = false;
|
||||||
|
if (x > graphWidth) { // end right
|
||||||
|
overdraw = true;
|
||||||
|
}
|
||||||
|
if (y < 0) { // end bottom
|
||||||
|
overdraw = true;
|
||||||
|
}
|
||||||
|
if (y > graphHeight) { // end top
|
||||||
|
overdraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float endX = (float) x + (graphLeft + 1);
|
||||||
|
float endY = (float) (graphTop - y) + graphHeight;
|
||||||
|
registerDataPoint(endX, endY, value);
|
||||||
|
|
||||||
|
// draw data point
|
||||||
|
if (!overdraw) {
|
||||||
|
if (mCustomShape != null) {
|
||||||
|
mCustomShape.draw(canvas, mPaint, endX, endY, value);
|
||||||
|
} else if (mStyles.shape == Shape.POINT) {
|
||||||
|
canvas.drawCircle(endX, endY, mStyles.size, mPaint);
|
||||||
|
} else if (mStyles.shape == Shape.RECTANGLE) {
|
||||||
|
canvas.drawRect(endX-mStyles.size, endY-mStyles.size, endX+mStyles.size, endY+mStyles.size, mPaint);
|
||||||
|
} else if (mStyles.shape == Shape.TRIANGLE) {
|
||||||
|
Point[] points = new Point[3];
|
||||||
|
points[0] = new Point((int)endX, (int)(endY-getSize()));
|
||||||
|
points[1] = new Point((int)(endX+getSize()), (int)(endY+getSize()*0.67));
|
||||||
|
points[2] = new Point((int)(endX-getSize()), (int)(endY+getSize()*0.67));
|
||||||
|
drawArrows(points, canvas, mPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper to render triangle
|
||||||
|
*
|
||||||
|
* @param point array with 3 coordinates
|
||||||
|
* @param canvas canvas to draw on
|
||||||
|
* @param paint paint object
|
||||||
|
*/
|
||||||
|
private void drawArrows(Point[] point, Canvas canvas, Paint paint) {
|
||||||
|
float [] points = new float[8];
|
||||||
|
points[0] = point[0].x;
|
||||||
|
points[1] = point[0].y;
|
||||||
|
points[2] = point[1].x;
|
||||||
|
points[3] = point[1].y;
|
||||||
|
points[4] = point[2].x;
|
||||||
|
points[5] = point[2].y;
|
||||||
|
points[6] = point[0].x;
|
||||||
|
points[7] = point[0].y;
|
||||||
|
|
||||||
|
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, 8, points, 0, null, 0, null, 0, null, 0, 0, paint);
|
||||||
|
Path path = new Path();
|
||||||
|
path.moveTo(point[0].x , point[0].y);
|
||||||
|
path.lineTo(point[1].x,point[1].y);
|
||||||
|
path.lineTo(point[2].x,point[2].y);
|
||||||
|
canvas.drawPath(path,paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used for the size of the shape that
|
||||||
|
* will be drawn.
|
||||||
|
* This is useless if you are using a custom shape.
|
||||||
|
*
|
||||||
|
* @return the size of the shape
|
||||||
|
*/
|
||||||
|
public float getSize() {
|
||||||
|
return mStyles.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used for the size of the shape that
|
||||||
|
* will be drawn.
|
||||||
|
* This is useless if you are using a custom shape.
|
||||||
|
*
|
||||||
|
* @param radius the size of the shape
|
||||||
|
*/
|
||||||
|
public void setSize(float radius) {
|
||||||
|
mStyles.size = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the shape that will be drawn for each point
|
||||||
|
*/
|
||||||
|
public Shape getShape() {
|
||||||
|
return mStyles.shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param s the shape that will be drawn for each point
|
||||||
|
*/
|
||||||
|
public void setShape(Shape s) {
|
||||||
|
mStyles.shape = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a custom handler to render your own
|
||||||
|
* drawing for each data point.
|
||||||
|
*
|
||||||
|
* @param shape handler to use a custom drawing
|
||||||
|
*/
|
||||||
|
public void setCustomShape(CustomShape shape) {
|
||||||
|
mCustomShape = shape;
|
||||||
|
}
|
||||||
|
}
|
125
graphview/src/main/java/com/jjoe64/graphview/series/Series.java
Normal file
125
graphview/src/main/java/com/jjoe64/graphview/series/Series.java
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
||||||
|
*/
|
||||||
|
package com.jjoe64.graphview.series;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
|
||||||
|
import com.jjoe64.graphview.GraphView;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basis interface for series that can be plotted
|
||||||
|
* on the graph.
|
||||||
|
* You can implement this in order to create a completely
|
||||||
|
* custom series type.
|
||||||
|
* But it is recommended to extend {@link com.jjoe64.graphview.series.BaseSeries} or another
|
||||||
|
* implemented Series class to save time.
|
||||||
|
* Anyway this interface can make sense if you want to implement
|
||||||
|
* a custom data provider, because BaseSeries uses a internal Array to store
|
||||||
|
* the data.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
public interface Series<E extends DataPointInterface> {
|
||||||
|
/**
|
||||||
|
* @return the lowest x-value of the data
|
||||||
|
*/
|
||||||
|
public double getLowestValueX();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the highest x-value of the data
|
||||||
|
*/
|
||||||
|
public double getHighestValueX();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the lowest y-value of the data
|
||||||
|
*/
|
||||||
|
public double getLowestValueY();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the highest y-value of the data
|
||||||
|
*/
|
||||||
|
public double getHighestValueY();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the values for a specific range. It is
|
||||||
|
* important that the data comes in the sorted order
|
||||||
|
* (from lowest to highest x-value).
|
||||||
|
*
|
||||||
|
* @param from the minimal x-value
|
||||||
|
* @param until the maximal x-value
|
||||||
|
* @return all datapoints between the from and until x-value
|
||||||
|
* including the from and until data points.
|
||||||
|
*/
|
||||||
|
public Iterator<E> getValues(double from, double until);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plots the series to the viewport.
|
||||||
|
* You have to care about overdrawing.
|
||||||
|
* This method may be called 2 times: one for
|
||||||
|
* the default scale and one time for the
|
||||||
|
* second scale.
|
||||||
|
*
|
||||||
|
* @param graphView corresponding graphview
|
||||||
|
* @param canvas canvas to draw on
|
||||||
|
* @param isSecondScale true if the drawing is for the second scale
|
||||||
|
*/
|
||||||
|
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the title of the series. Used in the legend
|
||||||
|
*/
|
||||||
|
public String getTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the color of the series. Used in the legend and should
|
||||||
|
* be used for the plotted points or lines.
|
||||||
|
*/
|
||||||
|
public int getColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set a listener for tap on a data point.
|
||||||
|
*
|
||||||
|
* @param l listener
|
||||||
|
*/
|
||||||
|
public void setOnDataPointTapListener(OnDataPointTapListener l);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called by the tap detector in order to trigger
|
||||||
|
* the on tap on datapoint event.
|
||||||
|
*
|
||||||
|
* @param x pixel
|
||||||
|
* @param y pixel
|
||||||
|
*/
|
||||||
|
void onTap(float x, float y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when the series was added to a graph
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
*/
|
||||||
|
void onGraphViewAttached(GraphView graphView);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether there are data points
|
||||||
|
*/
|
||||||
|
boolean isEmpty();
|
||||||
|
}
|
10
graphview/src/main/res/values/attr.xml
Normal file
10
graphview/src/main/res/values/attr.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="GraphViewXML">
|
||||||
|
<attr name="seriesData" format="string" />
|
||||||
|
<attr name="seriesType" format="string" />
|
||||||
|
<attr name="seriesTitle" format="string" />
|
||||||
|
<attr name="android:title" />
|
||||||
|
<attr name="seriesColor" format="color" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
|
@ -1,6 +1,4 @@
|
||||||
// in order to use internet's versions you'd need to enable Jetifier again
|
// in order to use internet's versions you'd need to enable Jetifier again
|
||||||
// https://github.com/nightscout/graphview.git
|
|
||||||
// https://github.com/nightscout/iconify.git
|
// https://github.com/nightscout/iconify.git
|
||||||
configurations.create("default")
|
configurations.create("default")
|
||||||
artifacts.add("default", file('libs/graphview.aar'))
|
|
||||||
artifacts.add("default", file('libs/iconify.aar'))
|
artifacts.add("default", file('libs/iconify.aar'))
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,25 +0,0 @@
|
||||||
package info.nightscout.libraries
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("info.nightscout.libraries.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package info.nightscout.libraries
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,4 +17,5 @@ include ':omnipod-dash'
|
||||||
include ':diaconn'
|
include ':diaconn'
|
||||||
include ':openhumans'
|
include ':openhumans'
|
||||||
include ':shared'
|
include ':shared'
|
||||||
|
include ':graphview'
|
||||||
include ':libraries'
|
include ':libraries'
|
Loading…
Reference in a new issue