replace graphview module by aar
This commit is contained in:
parent
5f8abca2ca
commit
eb9646cc7e
37 changed files with 11 additions and 6364 deletions
|
@ -169,8 +169,11 @@ allprojects {
|
|||
dependencies {
|
||||
wearApp project(':wear')
|
||||
|
||||
// https://github.com/nightscout/graphview.git
|
||||
// in order to use internet's version you'd need to enable Jetifier again
|
||||
implementation(files("${rootProject.rootDir}/libs/graphview.aar"))
|
||||
|
||||
implementation project(':iconify')
|
||||
implementation project(':graphview')
|
||||
implementation project(':shared')
|
||||
implementation project(':core')
|
||||
implementation project(':automation')
|
||||
|
|
|
@ -12,10 +12,10 @@ android {
|
|||
namespace 'info.nightscout.androidaps.automation'
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(files("${rootProject.rootDir}/libs/graphview.aar"))
|
||||
|
||||
implementation project(':core')
|
||||
implementation project(':database')
|
||||
implementation project(':shared')
|
||||
implementation project(':graphview')
|
||||
}
|
|
@ -12,9 +12,10 @@ apply from: "${project.rootDir}/core/test_dependencies.gradle"
|
|||
apply from: "${project.rootDir}/core/jacoco_global.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(files("${rootProject.rootDir}/libs/graphview.aar"))
|
||||
|
||||
implementation project(':shared')
|
||||
implementation project(':database')
|
||||
implementation project(':graphview')
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
1
graphview/.gitignore
vendored
1
graphview/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,20 +0,0 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-allopen'
|
||||
apply plugin: 'com.hiya.jacoco-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
apply from: "${project.rootDir}/core/android_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/android_module_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/test_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/jacoco_global.gradle"
|
||||
|
||||
android {
|
||||
|
||||
namespace 'com.jjoe64.graphview'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api "androidx.core:core-ktx:$core_version"
|
||||
}
|
21
graphview/proguard-rules.pro
vendored
21
graphview/proguard-rules.pro
vendored
|
@ -1,21 +0,0 @@
|
|||
# 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
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
|
@ -1,105 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -1,548 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
|
@ -1,394 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
|
@ -1,996 +0,0 @@
|
|||
/**
|
||||
* 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.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.widget.EdgeEffectCompat;
|
||||
|
||||
import com.jjoe64.graphview.compat.OverScrollerCompat;
|
||||
import com.jjoe64.graphview.series.DataPointInterface;
|
||||
import com.jjoe64.graphview.series.Series;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the default implementation for the viewport.
|
||||
* This implementation so for a normal viewport
|
||||
* where there is a horizontal x-axis and a
|
||||
* vertical y-axis.
|
||||
* This viewport is compatible with
|
||||
* - {@link com.jjoe64.graphview.series.BarGraphSeries}
|
||||
* - {@link com.jjoe64.graphview.series.LineGraphSeries}
|
||||
* - {@link com.jjoe64.graphview.series.PointsGraphSeries}
|
||||
*
|
||||
* @author jjoe64
|
||||
*/
|
||||
public class Viewport {
|
||||
/**
|
||||
* listener for the scale gesture
|
||||
*/
|
||||
private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
|
||||
= new ScaleGestureDetector.OnScaleGestureListener() {
|
||||
/**
|
||||
* called by android
|
||||
* @param detector detector
|
||||
* @return always true
|
||||
*/
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
float viewportWidth = mCurrentViewport.width();
|
||||
float center = mCurrentViewport.left + viewportWidth / 2;
|
||||
viewportWidth /= detector.getScaleFactor();
|
||||
mCurrentViewport.left = center - viewportWidth / 2;
|
||||
mCurrentViewport.right = mCurrentViewport.left+viewportWidth;
|
||||
|
||||
// viewportStart must not be < minX
|
||||
float minX = (float) getMinX(true);
|
||||
if (mCurrentViewport.left < minX) {
|
||||
mCurrentViewport.left = minX;
|
||||
mCurrentViewport.right = mCurrentViewport.left+viewportWidth;
|
||||
}
|
||||
|
||||
// viewportStart + viewportSize must not be > maxX
|
||||
float maxX = (float) getMaxX(true);
|
||||
if (viewportWidth == 0) {
|
||||
mCurrentViewport.right = maxX;
|
||||
}
|
||||
double overlap = mCurrentViewport.left + viewportWidth - maxX;
|
||||
if (overlap > 0) {
|
||||
// scroll left
|
||||
if (mCurrentViewport.left-overlap > minX) {
|
||||
mCurrentViewport.left -= overlap;
|
||||
mCurrentViewport.right = mCurrentViewport.left+viewportWidth;
|
||||
} else {
|
||||
// maximal scale
|
||||
mCurrentViewport.left = minX;
|
||||
mCurrentViewport.right = maxX;
|
||||
}
|
||||
}
|
||||
|
||||
// adjust viewport, labels, etc.
|
||||
mGraphView.onDataChanged(true, false);
|
||||
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* called when scaling begins
|
||||
*
|
||||
* @param detector detector
|
||||
* @return true if it is scalable
|
||||
*/
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
if (mIsScalable) {
|
||||
mScalingBeginWidth = mCurrentViewport.width();
|
||||
mScalingBeginLeft = mCurrentViewport.left;
|
||||
mScalingActive = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called when sacling ends
|
||||
* This will re-adjust the viewport.
|
||||
*
|
||||
* @param detector detector
|
||||
*/
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
mScalingActive = false;
|
||||
|
||||
// re-adjust
|
||||
mXAxisBoundsStatus = AxisBoundsStatus.READJUST_AFTER_SCALE;
|
||||
|
||||
mScrollingReferenceX = Float.NaN;
|
||||
|
||||
// adjust viewport, labels, etc.
|
||||
mGraphView.onDataChanged(true, false);
|
||||
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* simple gesture listener to track scroll events
|
||||
*/
|
||||
private final GestureDetector.SimpleOnGestureListener mGestureListener
|
||||
= new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
if (!mIsScrollable || mScalingActive) return false;
|
||||
|
||||
// Initiates the decay phase of any active edge effects.
|
||||
releaseEdgeEffects();
|
||||
mScrollerStartViewport.set(mCurrentViewport);
|
||||
// Aborts any active scroll animations and invalidates.
|
||||
mScroller.forceFinished(true);
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (!mIsScrollable || mScalingActive) return false;
|
||||
|
||||
if (Float.isNaN(mScrollingReferenceX)) {
|
||||
mScrollingReferenceX = mCurrentViewport.left;
|
||||
}
|
||||
|
||||
// Scrolling uses math based on the viewport (as opposed to math using pixels).
|
||||
/**
|
||||
* Pixel offset is the offset in screen pixels, while viewport offset is the
|
||||
* offset within the current viewport. For additional information on surface sizes
|
||||
* and pixel offsets, see the docs for {@link computeScrollSurfaceSize()}. For
|
||||
* additional information about the viewport, see the comments for
|
||||
* {@link mCurrentViewport}.
|
||||
*/
|
||||
float viewportOffsetX = distanceX * mCurrentViewport.width() / mGraphView.getGraphContentWidth();
|
||||
float viewportOffsetY = -distanceY * mCurrentViewport.height() / mGraphView.getGraphContentHeight();
|
||||
|
||||
int completeWidth = (int)((mCompleteRange.width()/mCurrentViewport.width()) * (float) mGraphView.getGraphContentWidth());
|
||||
int completeHeight = (int)((mCompleteRange.height()/mCurrentViewport.height()) * (float) mGraphView.getGraphContentHeight());
|
||||
|
||||
int scrolledX = (int) (completeWidth
|
||||
* (mCurrentViewport.left + viewportOffsetX - mCompleteRange.left)
|
||||
/ mCompleteRange.width());
|
||||
int scrolledY = (int) (completeHeight
|
||||
* (mCompleteRange.bottom - mCurrentViewport.bottom - viewportOffsetY)
|
||||
/ mCompleteRange.height());
|
||||
boolean canScrollX = mCurrentViewport.left > mCompleteRange.left
|
||||
|| mCurrentViewport.right < mCompleteRange.right;
|
||||
boolean canScrollY = mCurrentViewport.bottom > mCompleteRange.bottom
|
||||
|| mCurrentViewport.top < mCompleteRange.top;
|
||||
|
||||
if (canScrollX) {
|
||||
if (viewportOffsetX < 0) {
|
||||
float tooMuch = mCurrentViewport.left+viewportOffsetX - mCompleteRange.left;
|
||||
if (tooMuch < 0) {
|
||||
viewportOffsetX -= tooMuch;
|
||||
}
|
||||
} else {
|
||||
float tooMuch = mCurrentViewport.right+viewportOffsetX - mCompleteRange.right;
|
||||
if (tooMuch > 0) {
|
||||
viewportOffsetX -= tooMuch;
|
||||
}
|
||||
}
|
||||
mCurrentViewport.left += viewportOffsetX;
|
||||
mCurrentViewport.right += viewportOffsetX;
|
||||
}
|
||||
if (canScrollY) {
|
||||
//mCurrentViewport.top += viewportOffsetX;
|
||||
//mCurrentViewport.bottom -= viewportOffsetX;
|
||||
}
|
||||
|
||||
if (canScrollX && scrolledX < 0) {
|
||||
mEdgeEffectLeft.onPull(scrolledX / (float) mGraphView.getGraphContentWidth());
|
||||
mEdgeEffectLeftActive = true;
|
||||
}
|
||||
if (canScrollY && scrolledY < 0) {
|
||||
mEdgeEffectBottom.onPull(scrolledY / (float) mGraphView.getGraphContentHeight());
|
||||
mEdgeEffectBottomActive = true;
|
||||
}
|
||||
if (canScrollX && scrolledX > completeWidth - mGraphView.getGraphContentWidth()) {
|
||||
mEdgeEffectRight.onPull((scrolledX - completeWidth + mGraphView.getGraphContentWidth())
|
||||
/ (float) mGraphView.getGraphContentWidth());
|
||||
mEdgeEffectRightActive = true;
|
||||
}
|
||||
//if (canScrollY && scrolledY > mSurfaceSizeBuffer.y - mContentRect.height()) {
|
||||
// mEdgeEffectTop.onPull((scrolledY - mSurfaceSizeBuffer.y + mContentRect.height())
|
||||
// / (float) mContentRect.height());
|
||||
// mEdgeEffectTopActive = true;
|
||||
//}
|
||||
|
||||
// adjust viewport, labels, etc.
|
||||
mGraphView.onDataChanged(true, false);
|
||||
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2,
|
||||
float velocityX, float velocityY) {
|
||||
//fling((int) -velocityX, (int) -velocityY);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* the state of the axis bounds
|
||||
*/
|
||||
public enum AxisBoundsStatus {
|
||||
/**
|
||||
* initial means that the bounds gets
|
||||
* auto adjusted if they are not manual.
|
||||
* After adjusting the status comes to
|
||||
* #AUTO_ADJUSTED.
|
||||
*/
|
||||
INITIAL,
|
||||
|
||||
/**
|
||||
* after the bounds got auto-adjusted,
|
||||
* this status will set.
|
||||
*/
|
||||
AUTO_ADJUSTED,
|
||||
|
||||
/**
|
||||
* this flags the status that a scale was
|
||||
* done and the bounds has to be auto-adjusted
|
||||
* afterwards.
|
||||
*/
|
||||
READJUST_AFTER_SCALE,
|
||||
|
||||
/**
|
||||
* means that the bounds are fix (manually) and
|
||||
* are not to be auto-adjusted.
|
||||
*/
|
||||
FIX
|
||||
}
|
||||
|
||||
/**
|
||||
* paint to draw background
|
||||
*/
|
||||
private Paint mPaint;
|
||||
|
||||
/**
|
||||
* reference to the graphview
|
||||
*/
|
||||
private final GraphView mGraphView;
|
||||
|
||||
/**
|
||||
* this holds the current visible viewport
|
||||
* left = minX, right = maxX
|
||||
* bottom = minY, top = maxY
|
||||
*/
|
||||
protected RectF mCurrentViewport = new RectF();
|
||||
|
||||
/**
|
||||
* this holds the whole range of the data
|
||||
* left = minX, right = maxX
|
||||
* bottom = minY, top = maxY
|
||||
*/
|
||||
protected RectF mCompleteRange = new RectF();
|
||||
|
||||
/**
|
||||
* flag whether scaling is currently active
|
||||
*/
|
||||
protected boolean mScalingActive;
|
||||
|
||||
/**
|
||||
* stores the width of the viewport at the time
|
||||
* of beginning of the scaling.
|
||||
*/
|
||||
protected float mScalingBeginWidth;
|
||||
|
||||
/**
|
||||
* stores the viewport left at the time of
|
||||
* beginning of the scaling.
|
||||
*/
|
||||
protected float mScalingBeginLeft;
|
||||
|
||||
/**
|
||||
* flag whether the viewport is scrollable
|
||||
*/
|
||||
private boolean mIsScrollable;
|
||||
|
||||
/**
|
||||
* flag whether the viewport is scalable
|
||||
*/
|
||||
private boolean mIsScalable;
|
||||
|
||||
/**
|
||||
* gesture detector to detect scrolling
|
||||
*/
|
||||
protected GestureDetector mGestureDetector;
|
||||
|
||||
/**
|
||||
* detect scaling
|
||||
*/
|
||||
protected ScaleGestureDetector mScaleGestureDetector;
|
||||
|
||||
/**
|
||||
* not used - for fling
|
||||
*/
|
||||
protected OverScroller mScroller;
|
||||
|
||||
/**
|
||||
* not used
|
||||
*/
|
||||
private EdgeEffectCompat mEdgeEffectTop;
|
||||
|
||||
/**
|
||||
* not used
|
||||
*/
|
||||
private EdgeEffectCompat mEdgeEffectBottom;
|
||||
|
||||
/**
|
||||
* glow effect when scrolling left
|
||||
*/
|
||||
private EdgeEffectCompat mEdgeEffectLeft;
|
||||
|
||||
/**
|
||||
* glow effect when scrolling right
|
||||
*/
|
||||
private EdgeEffectCompat mEdgeEffectRight;
|
||||
|
||||
/**
|
||||
* not used
|
||||
*/
|
||||
private boolean mEdgeEffectTopActive;
|
||||
|
||||
/**
|
||||
* not used
|
||||
*/
|
||||
private boolean mEdgeEffectBottomActive;
|
||||
|
||||
/**
|
||||
* glow effect when scrolling left
|
||||
*/
|
||||
private boolean mEdgeEffectLeftActive;
|
||||
|
||||
/**
|
||||
* glow effect when scrolling right
|
||||
*/
|
||||
private boolean mEdgeEffectRightActive;
|
||||
|
||||
/**
|
||||
* stores the viewport at the time of
|
||||
* the beginning of scaling
|
||||
*/
|
||||
private RectF mScrollerStartViewport = new RectF();
|
||||
|
||||
/**
|
||||
* stores the viewport left value at the
|
||||
* time of beginning of the scrolling
|
||||
*/
|
||||
protected float mScrollingReferenceX = Float.NaN;
|
||||
|
||||
/**
|
||||
* state of the x axis
|
||||
*/
|
||||
private AxisBoundsStatus mXAxisBoundsStatus;
|
||||
|
||||
/**
|
||||
* state of the y axis
|
||||
*/
|
||||
private AxisBoundsStatus mYAxisBoundsStatus;
|
||||
|
||||
/**
|
||||
* flag whether the x axis bounds are manual
|
||||
*/
|
||||
private boolean mXAxisBoundsManual;
|
||||
|
||||
/**
|
||||
* flag whether the y axis bounds are manual
|
||||
*/
|
||||
private boolean mYAxisBoundsManual;
|
||||
|
||||
/**
|
||||
* background color of the viewport area
|
||||
* it is recommended to use a semi-transparent color
|
||||
*/
|
||||
private int mBackgroundColor;
|
||||
|
||||
/**
|
||||
* creates the viewport
|
||||
*
|
||||
* @param graphView graphview
|
||||
*/
|
||||
Viewport(GraphView graphView) {
|
||||
mScroller = new OverScroller(graphView.getContext());
|
||||
mEdgeEffectTop = new EdgeEffectCompat(graphView.getContext());
|
||||
mEdgeEffectBottom = new EdgeEffectCompat(graphView.getContext());
|
||||
mEdgeEffectLeft = new EdgeEffectCompat(graphView.getContext());
|
||||
mEdgeEffectRight = new EdgeEffectCompat(graphView.getContext());
|
||||
mGestureDetector = new GestureDetector(graphView.getContext(), mGestureListener);
|
||||
mScaleGestureDetector = new ScaleGestureDetector(graphView.getContext(), mScaleGestureListener);
|
||||
|
||||
mGraphView = graphView;
|
||||
mXAxisBoundsStatus = AxisBoundsStatus.INITIAL;
|
||||
mYAxisBoundsStatus = AxisBoundsStatus.INITIAL;
|
||||
mBackgroundColor = Color.TRANSPARENT;
|
||||
mPaint = new Paint();
|
||||
}
|
||||
|
||||
/**
|
||||
* will be called on a touch event.
|
||||
* needed to use scaling and scrolling
|
||||
*
|
||||
* @param event
|
||||
* @return true if it was consumed
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean b = mScaleGestureDetector.onTouchEvent(event);
|
||||
b |= mGestureDetector.onTouchEvent(event);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* change the state of the x axis.
|
||||
* normally you do not call this method.
|
||||
* If you want to set manual axis use
|
||||
* {@link #setXAxisBoundsManual(boolean)} and {@link #setYAxisBoundsManual(boolean)}
|
||||
*
|
||||
* @param s state
|
||||
*/
|
||||
public void setXAxisBoundsStatus(AxisBoundsStatus s) {
|
||||
mXAxisBoundsStatus = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* change the state of the y axis.
|
||||
* normally you do not call this method.
|
||||
* If you want to set manual axis use
|
||||
* {@link #setXAxisBoundsManual(boolean)} and {@link #setYAxisBoundsManual(boolean)}
|
||||
*
|
||||
* @param s state
|
||||
*/
|
||||
public void setYAxisBoundsStatus(AxisBoundsStatus s) {
|
||||
mYAxisBoundsStatus = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the viewport is scrollable
|
||||
*/
|
||||
public boolean isScrollable() {
|
||||
return mIsScrollable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mIsScrollable whether is viewport is scrollable
|
||||
*/
|
||||
public void setScrollable(boolean mIsScrollable) {
|
||||
this.mIsScrollable = mIsScrollable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the x axis state
|
||||
*/
|
||||
public AxisBoundsStatus getXAxisBoundsStatus() {
|
||||
return mXAxisBoundsStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the y axis state
|
||||
*/
|
||||
public AxisBoundsStatus getYAxisBoundsStatus() {
|
||||
return mYAxisBoundsStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* caches the complete range (minX, maxX, minY, maxY)
|
||||
* by iterating all series and all datapoints and
|
||||
* stores it into #mCompleteRange
|
||||
*/
|
||||
public void calcCompleteRange() {
|
||||
List<Series> series = mGraphView.getSeries();
|
||||
mCompleteRange.set(0, 0, 0, 0);
|
||||
if (!series.isEmpty() && !series.get(0).isEmpty()) {
|
||||
double d = series.get(0).getLowestValueX();
|
||||
for (Series s : series) {
|
||||
if (!s.isEmpty() && d > s.getLowestValueX()) {
|
||||
d = s.getLowestValueX();
|
||||
}
|
||||
}
|
||||
mCompleteRange.left = (float) d;
|
||||
|
||||
d = series.get(0).getHighestValueX();
|
||||
for (Series s : series) {
|
||||
if (!s.isEmpty() && d < s.getHighestValueX()) {
|
||||
d = s.getHighestValueX();
|
||||
}
|
||||
}
|
||||
mCompleteRange.right = (float) d;
|
||||
|
||||
d = series.get(0).getLowestValueY();
|
||||
for (Series s : series) {
|
||||
if (!s.isEmpty() && d > s.getLowestValueY()) {
|
||||
d = s.getLowestValueY();
|
||||
}
|
||||
}
|
||||
mCompleteRange.bottom = (float) d;
|
||||
|
||||
d = series.get(0).getHighestValueY();
|
||||
for (Series s : series) {
|
||||
if (!s.isEmpty() && d < s.getHighestValueY()) {
|
||||
d = s.getHighestValueY();
|
||||
}
|
||||
}
|
||||
mCompleteRange.top = (float) d;
|
||||
}
|
||||
|
||||
// calc current viewport bounds
|
||||
if (mYAxisBoundsStatus == AxisBoundsStatus.AUTO_ADJUSTED) {
|
||||
mYAxisBoundsStatus = AxisBoundsStatus.INITIAL;
|
||||
}
|
||||
if (mYAxisBoundsStatus == AxisBoundsStatus.INITIAL) {
|
||||
mCurrentViewport.top = mCompleteRange.top;
|
||||
mCurrentViewport.bottom = mCompleteRange.bottom;
|
||||
}
|
||||
|
||||
if (mXAxisBoundsStatus == AxisBoundsStatus.AUTO_ADJUSTED) {
|
||||
mXAxisBoundsStatus = AxisBoundsStatus.INITIAL;
|
||||
}
|
||||
if (mXAxisBoundsStatus == AxisBoundsStatus.INITIAL) {
|
||||
mCurrentViewport.left = mCompleteRange.left;
|
||||
mCurrentViewport.right = mCompleteRange.right;
|
||||
} else if (mXAxisBoundsManual && !mYAxisBoundsManual && mCompleteRange.width() != 0) {
|
||||
// get highest/lowest of current viewport
|
||||
// lowest
|
||||
double d = Double.MAX_VALUE;
|
||||
for (Series s : series) {
|
||||
Iterator<DataPointInterface> values = s.getValues(mCurrentViewport.left, mCurrentViewport.right);
|
||||
while (values.hasNext()) {
|
||||
double v = values.next().getY();
|
||||
if (d > v) {
|
||||
d = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentViewport.bottom = (float) d;
|
||||
|
||||
// highest
|
||||
d = Double.MIN_VALUE;
|
||||
for (Series s : series) {
|
||||
Iterator<DataPointInterface> values = s.getValues(mCurrentViewport.left, mCurrentViewport.right);
|
||||
while (values.hasNext()) {
|
||||
double v = values.next().getY();
|
||||
if (d < v) {
|
||||
d = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
mCurrentViewport.top = (float) d;
|
||||
}
|
||||
|
||||
// fixes blank screen when range is zero
|
||||
if (mCurrentViewport.left == mCurrentViewport.right) mCurrentViewport.right++;
|
||||
if (mCurrentViewport.top == mCurrentViewport.bottom) mCurrentViewport.top++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param completeRange if true => minX of the complete range of all series
|
||||
* if false => minX of the current visible viewport
|
||||
* @return the min x value
|
||||
*/
|
||||
public double getMinX(boolean completeRange) {
|
||||
if (completeRange) {
|
||||
return (double) mCompleteRange.left;
|
||||
} else {
|
||||
return (double) mCurrentViewport.left;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param completeRange if true => maxX of the complete range of all series
|
||||
* if false => maxX of the current visible viewport
|
||||
* @return the max x value
|
||||
*/
|
||||
public double getMaxX(boolean completeRange) {
|
||||
if (completeRange) {
|
||||
return (double) mCompleteRange.right;
|
||||
} else {
|
||||
return mCurrentViewport.right;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param completeRange if true => minY of the complete range of all series
|
||||
* if false => minY of the current visible viewport
|
||||
* @return the min y value
|
||||
*/
|
||||
public double getMinY(boolean completeRange) {
|
||||
if (completeRange) {
|
||||
return (double) mCompleteRange.bottom;
|
||||
} else {
|
||||
return mCurrentViewport.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param completeRange if true => maxY of the complete range of all series
|
||||
* if false => maxY of the current visible viewport
|
||||
* @return the max y value
|
||||
*/
|
||||
public double getMaxY(boolean completeRange) {
|
||||
if (completeRange) {
|
||||
return (double) mCompleteRange.top;
|
||||
} else {
|
||||
return mCurrentViewport.top;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set the maximal y value for the current viewport.
|
||||
* Make sure to set the y bounds to manual via
|
||||
* {@link #setYAxisBoundsManual(boolean)}
|
||||
* @param y max / highest value
|
||||
*/
|
||||
public void setMaxY(double y) {
|
||||
mCurrentViewport.top = (float) y;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the minimal y value for the current viewport.
|
||||
* Make sure to set the y bounds to manual via
|
||||
* {@link #setYAxisBoundsManual(boolean)}
|
||||
* @param y min / lowest value
|
||||
*/
|
||||
public void setMinY(double y) {
|
||||
mCurrentViewport.bottom = (float) y;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the maximal x value for the current viewport.
|
||||
* Make sure to set the x bounds to manual via
|
||||
* {@link #setXAxisBoundsManual(boolean)}
|
||||
* @param x max / highest value
|
||||
*/
|
||||
public void setMaxX(double x) {
|
||||
mCurrentViewport.right = (float) x;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the minimal x value for the current viewport.
|
||||
* Make sure to set the x bounds to manual via
|
||||
* {@link #setXAxisBoundsManual(boolean)}
|
||||
* @param x min / lowest value
|
||||
*/
|
||||
public void setMinX(double x) {
|
||||
mCurrentViewport.left = (float) x;
|
||||
}
|
||||
|
||||
/**
|
||||
* release the glowing effects
|
||||
*/
|
||||
private void releaseEdgeEffects() {
|
||||
mEdgeEffectLeftActive
|
||||
= mEdgeEffectRightActive
|
||||
= false;
|
||||
mEdgeEffectLeft.onRelease();
|
||||
mEdgeEffectRight.onRelease();
|
||||
}
|
||||
|
||||
/**
|
||||
* not used currently
|
||||
*
|
||||
* @param velocityX
|
||||
* @param velocityY
|
||||
*/
|
||||
private void fling(int velocityX, int velocityY) {
|
||||
velocityY = 0;
|
||||
releaseEdgeEffects();
|
||||
// Flings use math in pixels (as opposed to math based on the viewport).
|
||||
mScrollerStartViewport.set(mCurrentViewport);
|
||||
int maxX = (int)((mCurrentViewport.width()/mCompleteRange.width())*(float)mGraphView.getGraphContentWidth()) - mGraphView.getGraphContentWidth();
|
||||
int maxY = (int)((mCurrentViewport.height()/mCompleteRange.height())*(float)mGraphView.getGraphContentHeight()) - mGraphView.getGraphContentHeight();
|
||||
int startX = (int)((mCurrentViewport.left - mCompleteRange.left)/mCompleteRange.width())*maxX;
|
||||
int startY = (int)((mCurrentViewport.top - mCompleteRange.top)/mCompleteRange.height())*maxY;
|
||||
mScroller.forceFinished(true);
|
||||
mScroller.fling(
|
||||
startX,
|
||||
startY,
|
||||
velocityX,
|
||||
velocityY,
|
||||
0, maxX,
|
||||
0, maxY,
|
||||
mGraphView.getGraphContentWidth() / 2,
|
||||
mGraphView.getGraphContentHeight() / 2);
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
}
|
||||
|
||||
/**
|
||||
* not used currently
|
||||
*/
|
||||
public void computeScroll() {
|
||||
if (true) return;
|
||||
|
||||
boolean needsInvalidate = false;
|
||||
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
// The scroller isn't finished, meaning a fling or programmatic pan operation is
|
||||
// currently active.
|
||||
|
||||
int completeWidth = (int)((mCompleteRange.width()/mCurrentViewport.width()) * (float) mGraphView.getGraphContentWidth());
|
||||
int completeHeight = (int)((mCompleteRange.height()/mCurrentViewport.height()) * (float) mGraphView.getGraphContentHeight());
|
||||
|
||||
int currX = mScroller.getCurrX();
|
||||
int currY = mScroller.getCurrY();
|
||||
|
||||
boolean canScrollX = mCurrentViewport.left > mCompleteRange.left
|
||||
|| mCurrentViewport.right < mCompleteRange.right;
|
||||
boolean canScrollY = mCurrentViewport.bottom > mCompleteRange.bottom
|
||||
|| mCurrentViewport.top < mCompleteRange.top;
|
||||
|
||||
if (canScrollX
|
||||
&& currX < 0
|
||||
&& mEdgeEffectLeft.isFinished()
|
||||
&& !mEdgeEffectLeftActive) {
|
||||
mEdgeEffectLeft.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));
|
||||
mEdgeEffectLeftActive = true;
|
||||
needsInvalidate = true;
|
||||
} else if (canScrollX
|
||||
&& currX > (completeWidth - mGraphView.getGraphContentWidth())
|
||||
&& mEdgeEffectRight.isFinished()
|
||||
&& !mEdgeEffectRightActive) {
|
||||
mEdgeEffectRight.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));
|
||||
mEdgeEffectRightActive = true;
|
||||
needsInvalidate = true;
|
||||
}
|
||||
|
||||
if (canScrollY
|
||||
&& currY < 0
|
||||
&& mEdgeEffectTop.isFinished()
|
||||
&& !mEdgeEffectTopActive) {
|
||||
mEdgeEffectTop.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));
|
||||
mEdgeEffectTopActive = true;
|
||||
needsInvalidate = true;
|
||||
} else if (canScrollY
|
||||
&& currY > (completeHeight - mGraphView.getGraphContentHeight())
|
||||
&& mEdgeEffectBottom.isFinished()
|
||||
&& !mEdgeEffectBottomActive) {
|
||||
mEdgeEffectBottom.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));
|
||||
mEdgeEffectBottomActive = true;
|
||||
needsInvalidate = true;
|
||||
}
|
||||
|
||||
float currXRange = mCompleteRange.left + mCompleteRange.width()
|
||||
* currX / completeWidth;
|
||||
float currYRange = mCompleteRange.top - mCompleteRange.height()
|
||||
* currY / completeHeight;
|
||||
|
||||
float currWidth = mCurrentViewport.width();
|
||||
float currHeight = mCurrentViewport.height();
|
||||
mCurrentViewport.left = currXRange;
|
||||
mCurrentViewport.right = currXRange + currWidth;
|
||||
//mCurrentViewport.bottom = currYRange;
|
||||
//mCurrentViewport.top = currYRange + currHeight;
|
||||
}
|
||||
|
||||
if (needsInvalidate) {
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the overscroll "glow" at the four edges of the chart region, if necessary.
|
||||
*
|
||||
* @see EdgeEffectCompat
|
||||
*/
|
||||
private void drawEdgeEffectsUnclipped(Canvas canvas) {
|
||||
// The methods below rotate and translate the canvas as needed before drawing the glow,
|
||||
// since EdgeEffectCompat always draws a top-glow at 0,0.
|
||||
|
||||
boolean needsInvalidate = false;
|
||||
|
||||
if (!mEdgeEffectTop.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
canvas.translate(mGraphView.getGraphContentLeft(), mGraphView.getGraphContentTop());
|
||||
mEdgeEffectTop.setSize(mGraphView.getGraphContentWidth(), mGraphView.getGraphContentHeight());
|
||||
if (mEdgeEffectTop.draw(canvas)) {
|
||||
needsInvalidate = true;
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
|
||||
//if (!mEdgeEffectBottom.isFinished()) {
|
||||
// final int restoreCount = canvas.save();
|
||||
// canvas.translate(2 * mContentRect.left - mContentRect.right, mContentRect.bottom);
|
||||
// canvas.rotate(180, mContentRect.width(), 0);
|
||||
// mEdgeEffectBottom.setSize(mContentRect.width(), mContentRect.height());
|
||||
// if (mEdgeEffectBottom.draw(canvas)) {
|
||||
// needsInvalidate = true;
|
||||
// }
|
||||
// canvas.restoreToCount(restoreCount);
|
||||
//}
|
||||
|
||||
if (!mEdgeEffectLeft.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
canvas.translate(mGraphView.getGraphContentLeft(), mGraphView.getGraphContentTop()+ mGraphView.getGraphContentHeight());
|
||||
canvas.rotate(-90, 0, 0);
|
||||
mEdgeEffectLeft.setSize(mGraphView.getGraphContentHeight(), mGraphView.getGraphContentWidth());
|
||||
if (mEdgeEffectLeft.draw(canvas)) {
|
||||
needsInvalidate = true;
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
|
||||
if (!mEdgeEffectRight.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
canvas.translate(mGraphView.getGraphContentLeft()+ mGraphView.getGraphContentWidth(), mGraphView.getGraphContentTop());
|
||||
canvas.rotate(90, 0, 0);
|
||||
mEdgeEffectRight.setSize(mGraphView.getGraphContentHeight(), mGraphView.getGraphContentWidth());
|
||||
if (mEdgeEffectRight.draw(canvas)) {
|
||||
needsInvalidate = true;
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
|
||||
if (needsInvalidate) {
|
||||
ViewCompat.postInvalidateOnAnimation(mGraphView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* will be first called in order to draw
|
||||
* the canvas
|
||||
* Used to draw the background
|
||||
*
|
||||
* @param c canvas.
|
||||
*/
|
||||
public void drawFirst(Canvas c) {
|
||||
// draw background
|
||||
if (mBackgroundColor != Color.TRANSPARENT) {
|
||||
mPaint.setColor(mBackgroundColor);
|
||||
c.drawRect(
|
||||
mGraphView.getGraphContentLeft(),
|
||||
mGraphView.getGraphContentTop(),
|
||||
mGraphView.getGraphContentLeft()+mGraphView.getGraphContentWidth(),
|
||||
mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight(),
|
||||
mPaint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* draws the glowing edge effect
|
||||
*
|
||||
* @param c canvas
|
||||
*/
|
||||
public void draw(Canvas c) {
|
||||
drawEdgeEffectsUnclipped(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return background of the viewport area
|
||||
*/
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mBackgroundColor background of the viewport area
|
||||
* use transparent to have no background
|
||||
*/
|
||||
public void setBackgroundColor(int mBackgroundColor) {
|
||||
this.mBackgroundColor = mBackgroundColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the viewport is scalable
|
||||
*/
|
||||
public boolean isScalable() {
|
||||
return mIsScalable;
|
||||
}
|
||||
|
||||
/**
|
||||
* active the scaling/zooming feature
|
||||
* notice: sets the x axis bounds to manual
|
||||
*
|
||||
* @param mIsScalable whether the viewport is scalable
|
||||
*/
|
||||
public void setScalable(boolean mIsScalable) {
|
||||
this.mIsScalable = mIsScalable;
|
||||
if (mIsScalable) {
|
||||
mIsScrollable = true;
|
||||
|
||||
// set viewport to manual
|
||||
setXAxisBoundsManual(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the x axis bounds are manual.
|
||||
* @see #setMinX(double)
|
||||
* @see #setMaxX(double)
|
||||
*/
|
||||
public boolean isXAxisBoundsManual() {
|
||||
return mXAxisBoundsManual;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mXAxisBoundsManual whether the x axis bounds are manual.
|
||||
* @see #setMinX(double)
|
||||
* @see #setMaxX(double)
|
||||
*/
|
||||
public void setXAxisBoundsManual(boolean mXAxisBoundsManual) {
|
||||
this.mXAxisBoundsManual = mXAxisBoundsManual;
|
||||
if (mXAxisBoundsManual) {
|
||||
mXAxisBoundsStatus = AxisBoundsStatus.FIX;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the y axis bound are manual
|
||||
*/
|
||||
public boolean isYAxisBoundsManual() {
|
||||
return mYAxisBoundsManual;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mYAxisBoundsManual whether the y axis bounds are manual
|
||||
* @see #setMaxY(double)
|
||||
* @see #setMinY(double)
|
||||
*/
|
||||
public void setYAxisBoundsManual(boolean mYAxisBoundsManual) {
|
||||
this.mYAxisBoundsManual = mYAxisBoundsManual;
|
||||
if (mYAxisBoundsManual) {
|
||||
mYAxisBoundsStatus = AxisBoundsStatus.FIX;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* forces the viewport to scroll to the end
|
||||
* of the range by keeping the current viewport size.
|
||||
*
|
||||
* Important: Only takes effect if x axis bounds are manual.
|
||||
*
|
||||
* @see #setXAxisBoundsManual(boolean)
|
||||
*/
|
||||
public void scrollToEnd() {
|
||||
if (mXAxisBoundsManual) {
|
||||
float size = mCurrentViewport.width();
|
||||
mCurrentViewport.right = mCompleteRange.right;
|
||||
mCurrentViewport.left = mCompleteRange.right - size;
|
||||
mScrollingReferenceX = Float.NaN;
|
||||
mGraphView.onDataChanged(true, false);
|
||||
} else {
|
||||
Log.w("GraphView", "scrollToEnd works only with manual x axis bounds");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,379 +0,0 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,448 +0,0 @@
|
|||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
* 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+"]";
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* 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();
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
|
@ -1,312 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
/**
|
||||
* 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();
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?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,229 +0,0 @@
|
|||
package com.joanzapata.iconify;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.*;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextPaint;
|
||||
import android.util.TypedValue;
|
||||
import com.joanzapata.iconify.internal.IconFontDescriptorWrapper;
|
||||
|
||||
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
|
||||
|
||||
/**
|
||||
* Embed an icon into a Drawable that can be used as TextView icons, or ActionBar icons.
|
||||
* <pre>
|
||||
* new IconDrawable(context, IconValue.icon_star)
|
||||
* .colorRes(R.color.white)
|
||||
* .actionBarSize();
|
||||
* </pre>
|
||||
* If you don't set the size of the drawable, it will use the size
|
||||
* that is given to him. Note that in an ActionBar, if you don't
|
||||
* set the size explicitly it uses 0, so please use actionBarSize().
|
||||
*/
|
||||
public class IconDrawable extends Drawable {
|
||||
|
||||
public static final int ANDROID_ACTIONBAR_ICON_SIZE_DP = 24;
|
||||
|
||||
private Context context;
|
||||
|
||||
private Icon icon;
|
||||
|
||||
private TextPaint paint;
|
||||
|
||||
private int size = -1;
|
||||
|
||||
private int alpha = 255;
|
||||
|
||||
/**
|
||||
* Create an IconDrawable.
|
||||
* @param context Your activity or application context.
|
||||
* @param iconKey The icon key you want this drawable to display.
|
||||
* @throws IllegalArgumentException if the key doesn't match any icon.
|
||||
*/
|
||||
public IconDrawable(Context context, String iconKey) {
|
||||
Icon icon = Iconify.findIconForKey(iconKey);
|
||||
if (icon == null) {
|
||||
throw new IllegalArgumentException("No icon with that key \"" + iconKey + "\".");
|
||||
}
|
||||
init(context, icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IconDrawable.
|
||||
* @param context Your activity or application context.
|
||||
* @param icon The icon you want this drawable to display.
|
||||
*/
|
||||
public IconDrawable(Context context, Icon icon) {
|
||||
init(context, icon);
|
||||
}
|
||||
|
||||
private void init(Context context, Icon icon) {
|
||||
this.context = context;
|
||||
this.icon = icon;
|
||||
paint = new TextPaint();
|
||||
IconFontDescriptorWrapper descriptor = Iconify.findTypefaceOf(icon);
|
||||
if (descriptor == null) {
|
||||
throw new IllegalStateException("Unable to find the module associated " +
|
||||
"with icon " + icon.key() + ", have you registered the module " +
|
||||
"you are trying to use with Iconify.with(...) in your Application?");
|
||||
}
|
||||
paint.setTypeface(descriptor.getTypeface(context));
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setUnderlineText(false);
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of this icon to the standard Android ActionBar.
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable actionBarSize() {
|
||||
return sizeDp(ANDROID_ACTIONBAR_ICON_SIZE_DP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the drawable.
|
||||
* @param dimenRes The dimension resource.
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable sizeRes(int dimenRes) {
|
||||
return sizePx(context.getResources().getDimensionPixelSize(dimenRes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the drawable.
|
||||
* @param size The size in density-independent pixels (dp).
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable sizeDp(int size) {
|
||||
return sizePx(convertDpToPx(context, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the drawable.
|
||||
* @param size The size in pixels (px).
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable sizePx(int size) {
|
||||
this.size = size;
|
||||
setBounds(0, 0, size, size);
|
||||
invalidateSelf();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color of the drawable.
|
||||
* @param color The color, usually from android.graphics.Color or 0xFF012345.
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable color(int color) {
|
||||
paint.setColor(color);
|
||||
invalidateSelf();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color of the drawable.
|
||||
* @param colorRes The color resource, from your R file.
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable colorRes(int colorRes) {
|
||||
paint.setColor(context.getColor(colorRes));
|
||||
invalidateSelf();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha of this drawable.
|
||||
* @param alpha The alpha, between 0 (transparent) and 255 (opaque).
|
||||
* @return The current IconDrawable for chaining.
|
||||
*/
|
||||
public IconDrawable alpha(int alpha) {
|
||||
setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Rect bounds = getBounds();
|
||||
int height = bounds.height();
|
||||
paint.setTextSize(height);
|
||||
Rect textBounds = new Rect();
|
||||
String textValue = String.valueOf(icon.character());
|
||||
paint.getTextBounds(textValue, 0, 1, textBounds);
|
||||
int textHeight = textBounds.height();
|
||||
float textBottom = bounds.top + (height - textHeight) / 2f + textHeight - textBounds.bottom;
|
||||
canvas.drawText(textValue, bounds.exactCenterX(), textBottom, paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setState(int[] stateSet) {
|
||||
int oldValue = paint.getAlpha();
|
||||
int newValue = isEnabled(stateSet) ? alpha : alpha / 2;
|
||||
paint.setAlpha(newValue);
|
||||
return oldValue != newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
this.alpha = alpha;
|
||||
paint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
paint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColorFilter() {
|
||||
paint.setColorFilter(null);
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant") @Override
|
||||
public int getOpacity() {
|
||||
return this.alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets paint style.
|
||||
* @param style to be applied
|
||||
*/
|
||||
public void setStyle(Paint.Style style) {
|
||||
paint.setStyle(style);
|
||||
}
|
||||
|
||||
// Util
|
||||
private boolean isEnabled(int[] stateSet) {
|
||||
for (int state : stateSet)
|
||||
if (state == android.R.attr.state_enabled)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Util
|
||||
private int convertDpToPx(Context context, float dp) {
|
||||
return (int) TypedValue.applyDimension(
|
||||
COMPLEX_UNIT_DIP, dp,
|
||||
context.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
BIN
libs/graphview.aar
Normal file
BIN
libs/graphview.aar
Normal file
Binary file not shown.
|
@ -19,4 +19,3 @@ include ':diaconn'
|
|||
include ':openhumans'
|
||||
include ':shared'
|
||||
include ':iconify'
|
||||
include ':graphview'
|
||||
|
|
|
@ -107,8 +107,8 @@ dependencies {
|
|||
compileOnly "com.google.android.wearable:wearable:$wearable_version"
|
||||
implementation "com.google.android.support:wearable:$wearable_version"
|
||||
implementation "com.google.android.gms:play-services-wearable:$play_services_wearable_version"
|
||||
implementation(files('libs/ustwo-clockwise-debug.aar'))
|
||||
implementation(files('libs/wearpreferenceactivity-0.5.0.aar'))
|
||||
implementation(files("${rootProject.rootDir}/libs/ustwo-clockwise-debug.aar"))
|
||||
implementation(files("${rootProject.rootDir}/libs/wearpreferenceactivity-0.5.0.aar"))
|
||||
implementation('com.github.lecho:hellocharts-library:1.5.8@aar')
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue