//图表库
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
X-axis: XAxis
Y-axis: YAxis
Legend: Legend
Description: Description
Limit line: LimitLine
Displayed view: MarkerView (it is displayed when selected)
The most important core function of MPAndroidChart
Many different chart types: LineChart (line chart), BarChart (bar chart, vertical, horizontal, stacked, grouped), PieChart (pie chart), ScatterChart (scatter chart), CandleStickChart (K-line chart, candle chart) , RadarChart (radar chart, spider chart), BubbleChart (bubble chart)
combined chart (eg, a line and a bar)
zoom on two axes (with touch gestures, individual axes or pinch zoom)
drag / pan (with touch gestures)
Separate (dual) y-axes
Highlight values (with customizable popup view)
Save chart to SD card (as image)
Predefined color templates
Legend (autogenerated, customizable)
Customizable axes (x and y)
animations (build animations on x and y)
limit lines (provide additional info, max values, etc.)
listeners for touch, gesture and selection callbacks
fully customizable (drawing , fonts, legends, colors, backgrounds, dashed lines, etc.)
Supports Realm.io mobile database via MPAndroidChart-Realm library
Smooth rendering of up to 10.000 data points in Line-Chart and BarChart (tested on a 2014 OnePlus One running Android 6.0)
Lightweight (method count ~1.4K)
Available as a .jar file (only 500kb in size)
Available as a gradle dependency and
Google-PlayStore demo application via Maven
Widely used, great support for both GitHub and stackoverflow - mpandroidchart
is also available for iOS: Charts (the API works the same way)
is also available for Xamarin: MPAndroidChart. Xamarin has
limited support for dynamic and real-time data
horizontal histogram
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="@+id/chart1"
android:layout_width="444px"
android:layout_height="444px"
android:background="@android:color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="500px"
android:orientation="horizontal">
<Button
android:id="@+id/actionToggleBarBorders"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="边框"></Button>
<Button
android:id="@+id/actionToggleValues"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示条形框的值"></Button>
<Button
android:id="@+id/actionToggleIcons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示图标"></Button>
<Button
android:id="@+id/actionToggleHighlight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="高亮显示"></Button>
<Button
android:id="@+id/actionTogglePinch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="手势"></Button>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="590px"
android:orientation="horizontal">
<Button
android:id="@+id/actionToggleAutoScaleMinMax"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="比例"></Button>
<Button
android:id="@+id/animateX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="X轴动画"></Button>
<Button
android:id="@+id/animateY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Y轴动画"></Button>
<Button
android:id="@+id/animateXY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="XY轴动画"></Button>
<Button
android:id="@+id/actionSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="保存"></Button>
</LinearLayout>
<SeekBar
android:id="@+id/seekBar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_margin="8dp"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/tvYMax"
android:max="200"
android:paddingBottom="12dp" />
<SeekBar
android:id="@+id/seekBar1"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_above="@+id/seekBar2"
android:layout_margin="8dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="35dp"
android:layout_toLeftOf="@+id/tvXMax"
android:max="500"
android:paddingBottom="12dp" />
<TextView
android:id="@+id/tvXMax"
android:layout_width="50dp"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/seekBar1"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginBottom="15dp"
android:gravity="right"
android:text="@string/dash"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/tvYMax"
android:visibility="gone"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/seekBar2"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginBottom="15dp"
android:gravity="right"
android:text="@string/dash"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
package com.xxmassdeveloper.mpchartexample;import com.sbas.xueliapplication.R;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.XAxis.XAxisPosition;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import com.github.mikephil.charting.utils.MPPointF;
import com.xxmassdeveloper.mpchartexample.notimportant.DemoBase;
import java.util.ArrayList;
import java.util.List;
public class HorizontalBarChartActivity extends DemoBase implements
OnChartValueSelectedListener, View.OnClickListener {
private HorizontalBarChart chart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_horizontalbarchart);
setTitle("HorizontalBarChartActivity");
chart = findViewById(R.id.chart1);
chart.setOnChartValueSelectedListener(this);
// chart.setHighlightEnabled(false);
chart.setDrawBarShadow(false);
chart.setDrawValueAboveBar(true);
chart.getDescription().setEnabled(false);
// if more than 60 entries are displayed in the chart, no values will be
// drawn
chart.setMaxVisibleValueCount(60);
// scaling can now only be done on x- and y-axis separately
chart.setPinchZoom(false);
// draw shadows for each bar that show the maximum value
// chart.setDrawBarShadow(true);
chart.setDrawGridBackground(false);
XAxis xl = chart.getXAxis();
xl.setPosition(XAxisPosition.BOTTOM);
xl.setTypeface(tfLight);
xl.setDrawAxisLine(true);
xl.setDrawGridLines(false);
xl.setGranularity(10f);
YAxis yl = chart.getAxisLeft();
yl.setTypeface(tfLight);
yl.setDrawAxisLine(true);
yl.setDrawGridLines(true);
yl.setAxisMinimum(0f); // this replaces setStartAtZero(true)
// yl.setInverted(true);
YAxis yr = chart.getAxisRight();
yr.setTypeface(tfLight);
yr.setDrawAxisLine(true);
yr.setDrawGridLines(false);
yr.setAxisMinimum(0f); // this replaces setStartAtZero(true)
// yr.setInverted(true);
chart.setFitBars(true);
chart.animateY(2500);
// setting data
setDataHorizontalBarChar(5,50);
Legend l = chart.getLegend();
l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM);
l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT);
l.setOrientation(Legend.LegendOrientation.HORIZONTAL);
l.setDrawInside(false);
l.setFormSize(8f);
l.setXEntrySpace(4f);
initButton();
}
private void initButton() {
Button actionToggleBarBorders = findViewById(R.id.actionToggleBarBorders);
actionToggleBarBorders.setOnClickListener(this::onClick);
Button actionToggleValues = findViewById(R.id.actionToggleValues);
actionToggleValues.setOnClickListener(this::onClick);
Button actionToggleIcons = findViewById(R.id.actionToggleIcons);
actionToggleIcons.setOnClickListener(this::onClick);
Button actionToggleHighlight = findViewById(R.id.actionToggleHighlight);
actionToggleHighlight.setOnClickListener(this::onClick);
Button actionTogglePinch = findViewById(R.id.actionTogglePinch);
actionTogglePinch.setOnClickListener(this::onClick);
Button actionToggleAutoScaleMinMax = findViewById(R.id.actionToggleAutoScaleMinMax);
actionToggleAutoScaleMinMax.setOnClickListener(this::onClick);
Button animateX = findViewById(R.id.animateX);
animateX.setOnClickListener(this::onClick);
Button animateY = findViewById(R.id.animateY);
animateY.setOnClickListener(this::onClick);
Button animateXY = findViewById(R.id.animateXY);
animateXY.setOnClickListener(this::onClick);
Button actionSave = findViewById(R.id.actionSave);
actionSave.setOnClickListener(this::onClick);
}
private void setDataHorizontalBarChar(int count, float range) {
float barWidth = 9f;
float spaceForBar = 10f;
ArrayList<BarEntry> values = new ArrayList<>();
for (int i = 0; i < count; i++) {
float val = (float) (Math.random() * range);
values.add(new BarEntry(i * spaceForBar, val,
getResources().getDrawable(R.drawable.star)));
}
BarDataSet set1;
if (chart.getData() != null &&
chart.getData().getDataSetCount() > 0) {
set1 = (BarDataSet) chart.getData().getDataSetByIndex(0);
set1.setValues(values);
chart.getData().notifyDataChanged();
chart.notifyDataSetChanged();
} else {
set1 = new BarDataSet(values, "DataSet 1");
set1.setDrawIcons(false);
ArrayList<IBarDataSet> dataSets = new ArrayList<>();
dataSets.add(set1);
BarData data = new BarData(dataSets);
data.setValueTextSize(10f);
data.setValueTypeface(tfLight);
data.setBarWidth(barWidth);
chart.setData(data);
}
}
@Override
protected void saveToGallery() {
saveToGallery(chart, "HorizontalBarChartActivity");
}
private final RectF mOnValueSelectedRectF = new RectF();
@Override
public void onValueSelected(Entry e, Highlight h) {
if (e == null)
return;
RectF bounds = mOnValueSelectedRectF;
chart.getBarBounds((BarEntry) e, bounds);
MPPointF position = chart.getPosition(e, chart.getData().getDataSetByIndex(h.getDataSetIndex())
.getAxisDependency());
Log.i("bounds", bounds.toString());
Log.i("position", position.toString());
MPPointF.recycleInstance(position);
}
@Override
public void onNothingSelected() {}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.viewGithub: {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://github.com/PhilJay/MPAndroidChart/blob/master/MPChartExample/src/com/xxmassdeveloper/mpchartexample/HorizontalBarChartActivity.java"));
startActivity(i);
break;
}
case R.id.actionToggleValues: {
List<IBarDataSet> sets = chart.getData()
.getDataSets();
for (IBarDataSet iSet : sets) {
iSet.setDrawValues(!iSet.isDrawValuesEnabled());
}
chart.invalidate();
break;
}
case R.id.actionToggleIcons: {
List<IBarDataSet> sets = chart.getData()
.getDataSets();
for (IBarDataSet iSet : sets) {
iSet.setDrawIcons(!iSet.isDrawIconsEnabled());
}
chart.invalidate();
break;
}
case R.id.actionToggleHighlight: {
if(chart.getData() != null) {
chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled());
chart.invalidate();
}
break;
}
case R.id.actionTogglePinch: {
if (chart.isPinchZoomEnabled())
chart.setPinchZoom(false);
else
chart.setPinchZoom(true);
chart.invalidate();
break;
}
case R.id.actionToggleAutoScaleMinMax: {
chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled());
chart.notifyDataSetChanged();
break;
}
case R.id.actionToggleBarBorders: {
for (IBarDataSet set : chart.getData().getDataSets())
((BarDataSet)set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f);
chart.invalidate();
break;
}
case R.id.animateX: {
chart.animateX(2000);
break;
}
case R.id.animateY: {
chart.animateY(2000);
break;
}
case R.id.animateXY: {
chart.animateXY(2000, 2000);
break;
}
case R.id.actionSave: {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
saveToGallery();
} else {
requestStoragePermission(chart);
}
break;
}
}
}
}
package com.xxmassdeveloper.mpchartexample.notimportant;
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.view.View;
import android.widget.Toast;
import com.github.mikephil.charting.charts.Chart;
import com.sbas.xueliapplication.R;
/**
* Base class of all Activities of the Demo Application.
*
* @author Philipp Jahoda
*/
public abstract class DemoBase extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
protected final String[] months = new String[] {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"
};
protected final String[] parties = new String[] {
"Party A", "Party B", "Party C", "Party D", "Party E", "Party F", "Party G", "Party H",
"Party I", "Party J", "Party K", "Party L", "Party M", "Party N", "Party O", "Party P",
"Party Q", "Party R", "Party S", "Party T", "Party U", "Party V", "Party W", "Party X",
"Party Y", "Party Z"
};
private static final int PERMISSION_STORAGE = 0;
protected Typeface tfRegular;
protected Typeface tfLight;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tfRegular = Typeface.createFromAsset(getAssets(), "OpenSans-Regular.ttf");
tfLight = Typeface.createFromAsset(getAssets(), "OpenSans-Light.ttf");
}
protected float getRandom(float range, float start) {
return (float) (Math.random() * range) + start;
}
@Override
public void onBackPressed() {
super.onBackPressed();
overridePendingTransition(R.anim.move_left_in_activity, R.anim.move_right_out_activity);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_STORAGE) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
saveToGallery();
} else {
Toast.makeText(getApplicationContext(), "Saving FAILED!", Toast.LENGTH_SHORT)
.show();
}
}
}
protected void requestStoragePermission(View view) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Snackbar.make(view, "Write permission is required to save image to gallery", Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(DemoBase.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_STORAGE);
}
}).show();
} else {
Toast.makeText(getApplicationContext(), "Permission Required!", Toast.LENGTH_SHORT)
.show();
ActivityCompat.requestPermissions(DemoBase.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_STORAGE);
}
}
protected void saveToGallery(Chart chart, String name) {
if (chart.saveToGallery(name + "_" + System.currentTimeMillis(), 70))
Toast.makeText(getApplicationContext(), "Saving SUCCESSFUL!",
Toast.LENGTH_SHORT).show();
else
Toast.makeText(getApplicationContext(), "Saving FAILED!", Toast.LENGTH_SHORT)
.show();
}
protected abstract void saveToGallery();
}
If you don't want the line above yl.setEnabled(false);
YAxis yl = chart.getAxisLeft();
yl.setEnabled(false);
yl.setTypeface(tfLight);
yl.setDrawAxisLine(true);
yl.setDrawGridLines(false);
yl.setAxisMinimum(0f); // this replaces setStartAtZero(true)
// yl.setInverted(true);
If you want a rounded histogram modify
HorizontalBarChartRenderer.java
if (isCustomFill) {
dataSet.getFill(pos)
.fillRect(
c, mRenderPaint,
buffer.buffer[j],
buffer.buffer[j + 1],
buffer.buffer[j + 2],
buffer.buffer[j + 3],
isInverted ? Fill.Direction.LEFT : Fill.Direction.RIGHT);
}
else {
// c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
// buffer.buffer[j + 3], mRenderPaint);
RectF rectF=new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],buffer.buffer[j + 3]);
c.drawRoundRect(rectF,(float)25,(float)25,mRenderPaint);
}
However, if you want to customize the radians of the four corners, you need to use the Path class to draw. Make the following changes:
HorizontalBarChartRenderer.java
if (isCustomFill) {
dataSet.getFill(pos)
.fillRect(
c, mRenderPaint,
buffer.buffer[j],
buffer.buffer[j + 1],
buffer.buffer[j + 2],
buffer.buffer[j + 3],
isInverted ? Fill.Direction.LEFT : Fill.Direction.RIGHT);
}
else {
// c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
// buffer.buffer[j + 3], mRenderPaint);
// RectF rectF=new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],buffer.buffer[j + 3]);
// c.drawRoundRect(rectF,(float)25,(float)25,mRenderPaint);
RectF rectF=new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],buffer.buffer[j + 3]);
Path path = new Path();
//float数组中4个角分别是左上、右上、右下、左下
path.addRoundRect(rectF,new float[]{0, 0, 20, 20, 20, 20, 0, 0},Path.Direction.CCW);
c.drawPath(path,mRenderPaint);
}
Adjust the width of the graph.