IndexAxisValueFormatter无法按预期工作

4
我正在使用MPAndroidChart创建一个柱状图。我的配置如下:
<string-array name="months_initials">
       <item>J</item>
       <item>F</item>
       <item>M</item>
       <item>A</item>
       <item>M</item>
       <item>J</item>
       <item>J</item>
       <item>A</item>
       <item>S</item>
       <item>O</item>
       <item>N</item>
       <item>D</item>
   </string-array>

...

String[] months = getResources().getStringArray(R.array.months_initials);
chart.getXAxis().setCenterAxisLabels(true);
chart.getXAxis().setLabelCount(months.length, true);
chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(months) {
    @Override
    public String getFormattedValue(float value, AxisBase axis) {
        Timber.i("index = %s", value);
        return super.getFormattedValue(value, axis);
    }
});

index = 0.5
index = 1.5909091
index = 2.6818182
index = 3.7727275
index = 4.8636365
index = 5.9545455
index = 7.0454545
index = 8.136364
index = 9.227273
index = 10.318182
index = 11.409091

这是结果:
enter image description here

现在,如果我改成:return super.getFormattedValue(value-0.5f, axis);

结果为:
enter image description here

再次更改:chart.getXAxis().setLabelCount(Integer.MAX_VALUE, true);

index = 0.5
index = 1.0
index = 1.5
index = 2.0
index = 2.5
index = 3.0
index = 3.5
index = 4.0
index = 4.5
index = 5.0
index = 5.5
index = 6.0
index = 6.5
index = 7.0
index = 7.5
index = 8.0
index = 8.5
index = 9.0
index = 9.5
index = 10.0
index = 10.5
index = 11.0
index = 11.5
index = 12.0
index = 12.5

结果为:
enter image description here

这样有点“暴力”,但对我来说可以用,不幸的是标签没有正确居中。 那么,这里发生了什么?我错过了什么?如何实现我的最终结果?

感谢您的时间。

附:已经打开了一个问题

编辑完整设置代码:

    String[] months = getResources().getStringArray(R.array.months_initials);
    BarChart chart = binding.barChart;

    chart.setTouchEnabled(false);
    chart.getDescription().setEnabled(false);
    chart.getLegend().setEnabled(false);
    chart.getAxisLeft().setEnabled(false);
    chart.getAxisRight().setEnabled(false);
    chart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
    chart.getXAxis().setDrawAxisLine(false);
    chart.getXAxis().setDrawGridLines(false);
    chart.getXAxis().setCenterAxisLabels(true);
    chart.getXAxis().setLabelCount(Integer.MAX_VALUE, true);
    chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(months) {
        @Override
        public String getFormattedValue(float value, AxisBase axis) {
            Timber.i("index = %s", value);
            return super.getFormattedValue(value - 0.5f, axis);
        }
    });
    chart.getXAxis().setTextColor(ContextCompat.getColor(this, R.color.grey));
    chart.getXAxis().setTextSize(12);

    BarDataSet barData = new BarDataSet(data, "data");
    barData.setColor(ContextCompat.getColor(this, R.color.chart_bar));
    barData.setDrawValues(false);

    ArrayList<IBarDataSet> dataSets = new ArrayList<>();
    dataSets.add(barData);
    binding.barChart.setData(new BarData(dataSets));
    binding.barChart.animateY(1000, Easing.EasingOption.Linear);
3个回答

3

我无法确定你代码中的问题所在,但很可能与图表的细节有关。 以下是一个简单的可行示例,希望对你有所帮助!

        String[] months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};

        BarChart mChart = (BarChart) findViewById(R.id.barChart);
        mChart.setDrawBarShadow(false);
        mChart.setDrawValueAboveBar(false);
        mChart.getDescription().setEnabled(false);
        mChart.setDrawGridBackground(false);

        XAxis xaxis = mChart.getXAxis();
        xaxis.setDrawGridLines(false);
        xaxis.setPosition(XAxis.XAxisPosition.BOTTOM);
        xaxis.setGranularity(1f);
        xaxis.setDrawLabels(true);
        xaxis.setDrawAxisLine(false);
        xaxis.setValueFormatter(new IndexAxisValueFormatter(months));

        YAxis yAxisLeft = mChart.getAxisLeft();
        yAxisLeft.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
        yAxisLeft.setDrawGridLines(false);
        yAxisLeft.setDrawAxisLine(false);
        yAxisLeft.setEnabled(false);

        mChart.getAxisRight().setEnabled(false);

        Legend legend = mChart.getLegend();
        legend.setEnabled(false);

        ArrayList<BarEntry> valueSet1 = new ArrayList<BarEntry>();

        for (int i = 0; i < 6; ++i) {
            BarEntry entry = new BarEntry(i, (i+1)*10);
            valueSet1.add(entry);
        }

        List<IBarDataSet> dataSets = new ArrayList<>();
        BarDataSet barDataSet = new BarDataSet(valueSet1, " ");
        barDataSet.setColor(Color.CYAN);
        barDataSet.setDrawValues(false);
        dataSets.add(barDataSet);

        BarData data = new BarData(dataSets);
        mChart.setData(data);
        mChart.invalidate();

RESULT

enter image description here


谢谢你的回答。我认为粒度并不是关键,因为图表中的所有触摸事件都被禁用了,没有缩放粒度就会被“忽略”。 我已经添加了完整的设置代码。 - GuilhE
2
你可以尝试的另一件事是删除这行代码 chart.getXAxis().setCenterAxisLabels(true);。这在分组条形图的情况下非常有用,可以将标签居中放置在相邻的条形之间。 - sauvik
如果我删除那行代码,标签就会在条形图之间对齐(“白色柱”)。 - GuilhE

2
处理轴标签有时会非常复杂。您首先必须确保标签以所需的间隔和间距显示,然后必须使用轴值格式化程序将它们转换为其他字符串。
简而言之:
- `setCenterAxisLabels` 并不像其名称所示那样居中标签,不要使用它。默认情况下,标签已经居中在其轴值下方。该方法会使标签居中在轴值之间。 - 使用数字标签来调试标签位置,然后应用 `IndexAxisValueFormatter` 确保整数值在预期位置上。 - `IndexAxisValueFormatter` 存在一些错误的舍入逻辑,并且不会显示 X.5 和 X.999999 之间的值的标签,而 X.0 到 X.499999 之间的值会被舍入到 X,并显示 `labels[X]`。使用自定义的 ValueFormatter 来解决这些问题。
长篇回答:
首先,我使用了以下测试数据来重现您看到的行为(如果您的x值从0而不是1开始,则行为会略有不同,但是相同类型的问题将会出现)。这里的代码是用Kotlin编写的,但解决方案同样适用于Java。
val months = listOf("J","F","M","A","M","J","J","A","S","O","N","D")
val yvals  = listOf(1f,2f,5f,0.1f,0.5f,1f,0.5f,2f,1f,2.5f,2f,0.2f)

val entries = yvals.mapIndexed { i, y -> BarEntry((i+1).toFloat(),y)}
val barDataSet = BarDataSet(entries,"data")

最初的方法

当您使用最初的图表设置绘制时

val xAxis = barChart.xAxis
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.setDrawGridLines(true)
xAxis.setDrawAxisLine(true)
xAxis.setCenterAxisLabels(true)
xAxis.setLabelCount(months.size, true)
xAxis.valueFormatter = IndexAxisValueFormatter(months)

你看到的第一个行为是,一半的标签消失了,剩下的标签没有被放置在条形图下面。

case 1

为了更好地理解这是为什么,我们可以使用不同的轴值格式化程序来查看MPAndroidChart试图放置标签的位置。这将写入数值而不是尝试将它们转换为数组索引:
class VerboseAxisValueFormatter : ValueFormatter() {
    override fun getFormattedValue(value: Float): String {
        return "%.2f".format(value)
    }
}

这意味着(数值与您调试时看到的数值相匹配)。

case 2

查看 IndexAxisValueFormatter 的源代码。
public String getFormattedValue(float value, AxisBase axis) {
    int index = Math.round(value);

    if (index < 0 || index >= mValueCount || index != (int)value)
        return "";

    return mValues[index];
}

在这里,最后的检查(index != (int)value)意味着如果四舍五入的值不等于截断(向下取整)的值,则结果为空。这意味着对于从1.0到1.499999的值,它将返回mValues[1],但对于从1.5到1.99999的值,它将返回空字符串。这就是为什么整个图表的前半部分缺少标签-它选择绘制轴标签的x值具有大于0.5的小数部分。
第二种方法
因此,在您的第二次迭代中,您将标签计数设置为Integer.MAX_VALUE,并将值向下移动了0.5。在这里执行此操作(但保留数字标签),可以得到以下结果,其中标签具有良好的均匀间距(默认粒度为0.5)。如果您替换这里的整数值(0.01.0等),则会得到您在“偏移标签情况”中看到的结果(标签存在但未居中)。

case 3

然而,左边栏居中于1.0,这与标签绘制的位置非常不同。这是因为 xAxis.setCenterAxisLabels(true) 并不将标签居中在它们的值下面(尽管这样做是有道理的,但实际上这已经是默认行为了),而是将标签居中在实际轴值之间。关闭此功能并删除 0.5f 偏移,就会得到以下结果:

case 4

现在,我们终于有了整数值标签,它们位于条形图下方并且位置正确,这意味着我们可以使用IndexAxisValueFormatter。然而,数组索引是基于0的,而数据集是基于1的。要解决这个问题,您可以在数据集或格式化程序中从x值中减去1。即便如此,它也不完全正确-我们在左边缘得到了一个额外的标签,因为那里的-0.5会四舍五入并截断为0。

case 5

解决方案

最终,一旦我们正确设置了标签位置,最好的选择仍然是编写一个自定义的ValueFormatter,它没有内置的IndexAxisValueFormatter中的所有问题。我们使用abs(index - x) > 0.01f而不是index != (int)value,这样行为就是对称的,并且只显示实际在整数位置的标签。

class FixedIndexAxisValueFormatter(private val offset: Float, private val labels: List<String>) : ValueFormatter()
{
    override fun getFormattedValue(value: Float): String {
        val x = value - offset
        val index = x.roundToInt()
        return if( index < 0 || index >= labels.size || abs(index - x) > 0.01f) ""
               else labels[index]
    }
}

使用这个格式化程序最终得到了正确的结果。

case 6

这是获得此结果的最终设置。
// Force it to draw labels at all positions controlled by the axis
// bounds and granularity
xAxis.setLabelCount(Integer.MAX_VALUE, true)

// These are already the defaults in this case so they're not necessary, 
// but good to specify explicitly in case the defaults change later
xAxis.axisMinimum = 0.5f
xAxis.axisMaximum = 12.5f
xAxis.granularity = 0.5f

// Use a better value formatter, with an offset of 1 since the
// data set started at 1
xAxis.valueFormatter = FixedIndexAxisValueFormatter(1f, months)

2

我曾经遇到过同样的问题,通过以下步骤解决了:

barChart.getXAxis().setLabelCount(barDataSet.getEntryCount());

此外,如果您只使用一个数据集,则可以改为以下方式:
ArrayList<IBarDataSet> dataSets = new ArrayList<>();
dataSets.add(barData);

使用:

BarDataSet barDataSet = new BarDataSet(yourValauesHere, "Legend Text here");

以下是参考示例:

    barEntries = new ArrayList<BarEntry>();

    barEntries.add(new BarEntry(0, 1));
    barEntries.add(new BarEntry(1, 2));
    barEntries.add(new BarEntry(2, 4));
    barEntries.add(new BarEntry(3, 6));
    barEntries.add(new BarEntry(4, 5));
    barEntries.add(new BarEntry(5, 7));

    barDataSet = new BarDataSet(barEntries, "Contracts");
    barDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
    barDataSet.setColor(getColor("defaultYellow"));
    barDataSet.setHighlightEnabled(true);
    barDataSet.setHighLightColor(Color.RED);
    barDataSet.setValueTextSize(defaultValueTextSize);
    barDataSet.setValueTextColor(getColor("primaryDark"));

    BarData barData = new BarData(barDataSet);

    barChart.getDescription().setText("No. of Contracts signed in 6 
    months");
    barChart.getDescription().setTextSize(12);
    barChart.getAxisLeft().setAxisMinimum(0);
    barChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTH_SIDED);
    barChart.getXAxis().setValueFormatter(new 
    IndexAxisValueFormatter(getXAxisValues()));
    barChart.animateY(1000);
    barChart.setData(barData);

获取x轴数值的方法:

private ArrayList<String> getXAxisValues()
{
    ArrayList<String> labels = new ArrayList<String> ();

    labels.add( "JAN");
    labels.add( "FEB");
    labels.add( "MAR");
    labels.add( "APR");
    labels.add( "MAY");
    labels.add( "JUN");
    return labels;
}

你好,感谢你的回答,但不幸的是它并没有起作用。使用 barDataSet.getEntryCount()months.length 或者简单地使用 12,结果都一样。关于数据集,我已经按照那种方式做了。ArrayList<IBarDataSet> 是在 new BarDataSet(data, "data"); 之后创建的。 谢谢。 - GuilhE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接