MPAndroidChart:x轴上的日期-粘滞月份和年份

时间:2018-08-22 15:28:22

标签: android mpandroidchart

我想获得一个类似于Loop-Habit Tracker应用程序中的图表的图表:水平滚动时,月份(和年份)位于图表的左侧,而每月的第一个替换为月份名称。

MPAndroidChart或任何其他库是否有可能?

enter image description here

2 个答案:

答案 0 :(得分:1)

有点麻烦,但是我可以通过将TextView放在粘性标签位置并让AxisValueFormatter设置文本值来使其工作。您无法保证月份中的第一天实际上将是轴标签,因此我将其设置为将给定月份中显示的第一天替换为月份名称。这利用了以下事实:轴值格式化程序始终从最低值开始向上移动。

以下是展示此方法的完整示例(实施并尝试滚动)

Example view

活动布局xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/chart_layout"
    tools:context=".MainActivity">

    <com.github.mikephil.charting.charts.LineChart
        android:id="@+id/test_chart"
        android:layout_width="0dp"
        android:layout_marginRight="16dp"
        android:layout_marginEnd="16dp"
        android:layout_height="0dp"
        android:layout_margin="32dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <TextView
        android:id="@+id/sticky_label"
        android:background="#ffffff"
        android:textColor="#000000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

活动onCreate方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final ConstraintLayout layout = findViewById(R.id.chart_layout);
    final LineChart chart = findViewById(R.id.test_chart);
    final TextView sticky = findViewById(R.id.sticky_label);

    List<Entry> data = new ArrayList<>();
    for(int i = 0; i < 400; ++i) {
        data.add(new Entry(2f*i, (float)Math.sin(0.1f*i)));
    }

    LineDataSet lds = new LineDataSet(data,"data");
    lds.setCircleColor(Color.BLACK);
    lds.setCircleRadius(6f);
    lds.setCircleHoleRadius(3f);
    lds.setCircleColorHole(Color.WHITE);
    lds.setColor(Color.BLACK);
    lds.setLineWidth(4f);
    LineData ld = new LineData(lds);
    ld.setDrawValues(false);
    chart.setData(ld);

    final float textSize = 20f;
    final XAxis xa = chart.getXAxis();
    xa.setGranularity(1f);
    xa.setGranularityEnabled(true);
    xa.setValueFormatter(new StickyDateAxisValueFormatter(chart, sticky));
    xa.setPosition(XAxis.XAxisPosition.BOTTOM);
    xa.setTextSize(textSize);
    xa.setDrawGridLines(true);

    chart.setPinchZoom(false);
    chart.zoom(28f,1f,0f,0f);
    sticky.setTextSize(textSize);

    ViewTreeObserver vto = layout.getViewTreeObserver();
    vto.addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // use removeGlobalOnLayoutListener prior to API 16
            layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            float xo = xa.getXOffset();
            float yo = xa.getYOffset();
            final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
            float rho = displayMetrics.density;

            // WARNING: The 2.2 here was calibrated to the screen I was using. Make sure it works
            // on all resolutions and densities. There may be a pixel/dp inconsistency in here somewhere.
            // The 10f is from the extra bottom offset (set below).
            float ty = chart.getY() + chart.getMeasuredHeight() - rho*yo - 2.2f*textSize - 10f;
            float tx = chart.getX() + rho*xo;

            sticky.setTranslationY(ty);
            sticky.setTranslationX(tx);
        }
    });

    sticky.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.TOP);

    chart.getAxisLeft().setTextSize(textSize);
    chart.setExtraBottomOffset(10f);

    chart.getAxisRight().setEnabled(false);
    Description desc = new Description();
    desc.setText("");
    chart.setDescription(desc);
    chart.getLegend().setEnabled(false);
    chart.getAxisLeft().setDrawGridLines(true);
}

轴值格式化程序:

public class StickyDateAxisValueFormatter implements IAxisValueFormatter {

    private Calendar c;
    private LineChart chart;
    private TextView sticky;
    private float lastFormattedValue = 1e9f;
    private int lastMonth = 0;
    private int lastYear = 0;
    private int stickyMonth = -1;
    private int stickyYear = -1;
    private SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM", Locale.getDefault());


    StickyDateAxisValueFormatter(LineChart chart, TextView sticky) {
        c = new GregorianCalendar();
        this.chart = chart;
        this.sticky = sticky;
    }

    @Override
    public String getFormattedValue(float value, AxisBase axis) {

        // Sometimes this gets called on values much lower than the visible range
        // Catch that here to prevent messing up the sticky text logic
        if( value < chart.getLowestVisibleX() ) {
            return "";
        }

        // NOTE: I assume for this example that all data is plotted in days
        // since Jan 1, 2018. Update for your scheme accordingly.

        int days = (int)value;

        boolean isFirstValue = value < lastFormattedValue;

        if( isFirstValue ) {
            // starting over formatting sequence
            lastMonth = 50;
            lastYear = 5000;

            c.set(2018,0,1);
            c.add(Calendar.DATE, (int)chart.getLowestVisibleX());

            stickyMonth = c.get(Calendar.MONTH);
            stickyYear = c.get(Calendar.YEAR);

            String stickyText = monthFormatter.format(c.getTime()) + "\n" + stickyYear;
            sticky.setText(stickyText);
        }

        c.set(2018,0,1);
        c.add(Calendar.DATE, days);
        Date d = c.getTime();

        int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
        int month = c.get(Calendar.MONTH);
        int year = c.get(Calendar.YEAR);

        String monthStr = monthFormatter.format(d);

        if( (month > stickyMonth || year > stickyYear) && isFirstValue ) {
            stickyMonth = month;
            stickyYear = year;
            String stickyText = monthStr + "\n" + year;
            sticky.setText(stickyText);
        }

        String ret;

        if( (month > lastMonth || year > lastYear) && !isFirstValue ) {
            ret = monthStr;
        }
        else {
            ret = Integer.toString(dayOfMonth);
        }

        lastMonth = month;
        lastYear = year;
        lastFormattedValue = value;

        return ret;
    }
}

答案 1 :(得分:0)

您可以使用IAxisValueFormatter,当您覆盖getFormattedValue时,可以检查该值是否是本月的第一位,然后返回月份名称或日期。