MPAndroidChart StackedBarChart showing values but

2019-04-09 01:36发布

问题:

I am starting to use the MPAndroidChart library to build a StackedBarChart showing three y values. Here is the code:

public class Plot
{
    final Context context;
    final BarData data;

    private int count;

    public StackedBarPlot(Context context)
    {
        this.context = context;
        data = setData();
    }

    protected BarData setData()
    {
        final List<BarEntry> entries = new ArrayList<>();
        for (DatabaseEntry entry : entryList)
        {
            final float total = (float) entry.getTotal();
            final float[] y = {100 * entry.getN1() / total,
                    100 * entry.getN2() / total, 100 * entry.getN3() / total};
            entries.add(new BarEntry(/*long*/entry.getDate(), y));
        }
        count = entries.size();


        final BarDataSet dataset = new BarDataSet(entries, null);
        dataset.setColors(new int[]{R.color.green, R.color.blue, R.color.red}, context);
        dataset.setStackLabels(labels);
        dataset.setDrawValues(true);
        dataset.setVisible(true);

        final BarData data = new BarData(dataset);
        data.setBarWidth(0.9f);
        return data;
    }

    public BarChart getChart(int id, View view)
    {
        final BarChart chart = (BarChart) view.findViewById(id);   

        chart.getAxisRight().setEnabled(false);
        chart.getAxisLeft().setEnabled(false);
        final Legend legend = chart.getLegend();
        legend.setDrawInside(true);
        legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP);
        legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);

        final XAxis xAxis = chart.getXAxis();
        xAxis.setValueFormatter(dateFormatter);
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
        xAxis.setDrawGridLines(false);
        xAxis.setLabelCount(count);

        chart.getDescription().setEnabled(false);
        chart.setData(data);
        chart.setFitBars(true);
        chart.invalidate();
        return chart;
    }

    private final IAxisValueFormatter dateFormatter = new IAxisValueFormatter()
    {
        @Override
        public String getFormattedValue(float value, AxisBase axis)
        {
            return new DateTime((long) value).toString(context.getString("E, MMM d"));
        }
    };
}

Then in my Fragment, I call:

public class MyFragment extends Fragment
{
    private Plot plot;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        plot = new Plot(getActivity());
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
    {
        final View view = inflater.inflate(R.layout.fragment, parent, false);
        plot.getChart(R.id.chart, view);
        return view;
    }
}

And in MainActivity.java

getFragmentManager().beginTransaction().replace(R.id.content, fragment).commit();

main_activity.xml

 <android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    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"
    tools:context=".MainActivity">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.AppBarLayout>

        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
    </android.support.design.widget.CoordinatorLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/navigation"/>

</android.support.v4.widget.DrawerLayout>

fragment.xml

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <com.github.mikephil.charting.charts.BarChart
        android:id="@+id/chart"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

The problem is the bars are not rendering correctly. I can see values but the bars are not displaying in the chart. Any advice?

回答1:

This is not an error in the library like some of the previous posts have suggested. Probably you are just misunderstanding how the width of each bar is determined like I did at first.

I was using a long, millisecond time-stamp for my x-axis on my bar chart. I realized that by default MPAndroidChart sets the width of a bar to 0.85f. Think about what this means. My first time-stamp was 1473421800000f and the next was 1473508200000f: a difference of 86400000f! Now how could I expect to see a bar that is 0.85f wide when there is 86400000f between each pair of observations?? To fix this problem you need to do something like the following:

barData.setBarWidth(0.6f * widthBetweenObservations);

So the above sets the width of a bar to equal 60% of the distance between observations.



回答2:

I had to start from this StackedBarActivity example and going down one bit at a time until I figured out what's causing the problem. It's the use of the long timestamp from entry.getDate() for the X axis values with or without the custom IAxisValueFormatter. It's a bug in the library reported here.

Here is what I ended up doing as a workaround. I got the duration since the timestamp in days:

long diff = new Duration(entry.getDate(), DateTime.now().getMillis()).getStandardDays();
entries.add(new BarEntry(diff, y));

and then in my custom IAxisValueFormatter:

private final IAxisValueFormatter dateFormatter = new IAxisValueFormatter()
{
    @Override
    public String getFormattedValue(float value, AxisBase axis)
    {
        return LocalDate.now().minusDays((int) value).toString("EEE");
    }
};


回答3:

I've found another workaround, that will work even if you've got timestamps for dates in the past and in the future together, (see the comments in A.A answer for the full story). The trick is similar to the one that you can use when you have to plot multiple datasets with different values in the X-axis (see https://stackoverflow.com/a/28549480/5098038). Instead of putting the timestamps directly into the BarEntries x values, create an ArrayList which contains the timestamps and put the indexes into the BarEntries. Then in the formatter take the values contained into the dataset (the indexes) and use them to get the timestamps contained in the Arraylist.


Create array from original timestamp values:

Long[] xTimestamps = { t1, t2, t3 };
List<Long> xArray  = new ArrayList<>(Arrays.asList(xTimestamps));

Add indexes into BarEntries:

entries.add(new BarEntry(xArray.indexOf(t1), y));

Retrieve data with formatter:

private static final SimpleDateFormat CHART_SDF = new SimpleDateFormat("dd/MM/yyyy", getApplicationContext().getResources().getConfiguration().locale);
private final IAxisValueFormatter dateFormatter = new IAxisValueFormatter() {
            @Override
            public String getFormattedValue(float value, AxisBase axis) {
                Long timestamp = xArray.get((int)value);
                return Chart_SDF.format(new Date(timestamp));
            }
});


回答4:

One more pretty workaround. Just use TimeUnit.MILLISECONDS.toDays in BarEntry and TimeUnit.DAYS.toMillis in formatter

Long dateInMills = someDateObject.getTime();
entries.add(new BarEntry(TimeUnit.MILLISECONDS.toDays(dateInMills), y));

and then in my custom IAxisValueFormatter:

private final IAxisValueFormatter dateFormatter = new IAxisValueFormatter()
{
    @Override
    public String getFormattedValue(float value, AxisBase axis)
    {
            Float fVal = value;
            long mills = TimeUnit.DAYS.toMillis(fVal.longValue());
            ....
            //here convert mills to date and string
            ....
            return convertedString;
    }
};