目录
1.需求
实现柱状图,能够良好的反应用户某一段时间的各类数据,或者用于展示各个季度的各种财务相关的数据。
这次主要是app内需要实现三种柱状图,圆角柱状图,区间柱状图·,多组柱状图。
2、多组/分组柱状图的具体实现
2.1 xml代码
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/barChart"
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/lineChartTest3" />
2.2 代码实现
具体的API调用和注释都在代码里了,我就不补充了。
private fun initBarChart() {
mBarChart = binding.barChart
binding.barChart.apply {
isHighlightPerTapEnabled = false
}
setChartData()
updateCommonUI()
updateChartUI()
}
/**
* 设置数据源
*/
private fun setChartData() {
val barEntries1: MutableList<BarEntry> = ArrayList()
barEntries1.add(BarEntry(0f, 1500f))
barEntries1.add(BarEntry(1f, 1400f))
barEntries1.add(BarEntry(2f, 800f))
barEntries1.add(BarEntry(3f, 2000f))
val barDataSet1 = BarDataSet(barEntries1, "Q1")
val barEntries2: MutableList<BarEntry> = ArrayList()
barEntries2.add(BarEntry(0f, 1000f))
barEntries2.add(BarEntry(1f, 1200f))
barEntries2.add(BarEntry(2f, 1500f))
barEntries2.add(BarEntry(3f, 900f))
val barDataSet2 = BarDataSet(barEntries2, "Q2")
val barEntries3: MutableList<BarEntry> = ArrayList()
barEntries3.add(BarEntry(0f, 900f))
barEntries3.add(BarEntry(1f, 1000f))
barEntries3.add(BarEntry(2f, 1200f))
barEntries3.add(BarEntry(3f, 1500f))
val barDataSet3 = BarDataSet(barEntries3, "Q3")
val barEntries4: MutableList<BarEntry> = ArrayList()
barEntries4.add(BarEntry(0f, 1400f))
barEntries4.add(BarEntry(1f, 800f))
barEntries4.add(BarEntry(2f, 1000f))
barEntries4.add(BarEntry(3f, 500f))
val barDataSet4 = BarDataSet(barEntries4, "Q4")
val colors: MutableList<Int> = ArrayList()
colors.add(Color.GREEN)
colors.add(Color.BLUE)
colors.add(Color.RED)
colors.add(Color.YELLOW)
barDataSet1.color = colors[0]
barDataSet2.color = colors[1]
barDataSet3.color = colors[2]
barDataSet4.color = colors[3]
val barDataSets: MutableList<IBarDataSet> = ArrayList()
barDataSets.add(barDataSet1)
barDataSets.add(barDataSet2)
barDataSets.add(barDataSet3)
barDataSets.add(barDataSet4)
val ba = BarData(barDataSets)
mBarChart.data = ba
}
//基本设置和y轴设置
private fun updateCommonUI() {
// 不显示图例
mBarChart.legend.isEnabled = false
// 不显示描述
mBarChart.description.text = ""
// 不绘制网格
mBarChart.setDrawGridBackground(false)
//不允许缩放
mBarChart.isScaleYEnabled = false
mBarChart.isScaleXEnabled = false
mBarChart.setScaleEnabled(false)
// 设置左y轴
val yAxis = mBarChart.axisLeft
// 设置y-label显示在图表外
yAxis.setPosition(YAxisLabelPosition.OUTSIDE_CHART)
// Y轴从0开始,不然会上移一点
yAxis.setAxisMinValue(0f)
// 设置y轴不画线
yAxis.setDrawGridLines(false)
// 不显示右y轴
val rightAxis = mBarChart.axisRight
rightAxis.isEnabled = false
}
//x轴设置
private fun updateChartUI() {
val labels = arrayOf("Q1", "Q2", "Q3", "Q4")
val xAxis = mBarChart.xAxis
// 设置自定义的ValueFormatter
xAxis.valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(p0: Float): String {
val index = p0.toInt()
if (index >= 0 && index < labels.size) {
return labels[index]
}
return ""
}
}
// 设置x轴显示在下方
xAxis.position = XAxisPosition.BOTTOM
// 设置x轴不画线
xAxis.setDrawGridLines(false)
// 设置x轴标签从0开始
xAxis.setAxisMinValue(0f)
// 设置x轴显示的最大标签数量
xAxis.setAxisMaxValue(labels.size.toFloat())
// x轴标签居中
xAxis.setCenterAxisLabels(true)
// 接下来这段代码尤其重要,网上几乎99%的资料说的都是错的,这里详解一下
// 1、要想标签跟group中间对齐,必须保证:(barWidth + barSpace) * group + groupSpace = granularity
// 2、granularity < 1,则右边会有空余;
// 3、granularity = 1,则刚好在一个屏幕显示开;
// 4、granularity > 1,则一个屏幕显示不开;
val barSpace = 0.025f
val groupSpace = 0.1f
val barWidth = 0.2f//宽度
val granularity = (barWidth + barSpace) * labels.size + groupSpace
xAxis.granularity = granularity
mBarChart.scaleX = granularity
mBarChart.barData.barWidth = barWidth
mBarChart.groupBars(0f, groupSpace, barSpace)//分组
}
2.3 实现效果
3.顶部圆角柱状图
3.1 代码实现
fun initBarChart2() {
mBarChart2 = binding.barChart2
mBarChart2.apply {
isHighlightPerTapEnabled = false
isScaleYEnabled = false
isHighlightPerDragEnabled = false
isHighlightFullBarEnabled = false
isScaleXEnabled = false
setScaleEnabled(false)
}
binding.barChart2.renderer = RoundedBarChartRenderer(binding.barChart2, binding.barChart2.animator, binding.barChart2.viewPortHandler)
val list: ArrayList<BarEntry> = ArrayList() //实例化一个List用来存储数据
list.add(BarEntry(1f, 13f))
list.add(BarEntry(2f, 28f))
list.add(BarEntry(3f, 36f))
list.add(BarEntry(4f, 19f))
list.add(BarEntry(5f, 49f))
list.add(BarEntry(6f, 59f))
list.add(BarEntry(7f, 89f))
val barDataSet = BarDataSet(list, "测试")
barDataSet.color = getColor(R.color.color_ff8561)
barDataSet.setDrawValues(false)//不展示值
val barData = BarData(barDataSet)
barData.barWidth = 0.5f
mBarChart2.data = barData
mBarChart2.isHighlightPerTapEnabled = false
mBarChart2.setDescription(null)
mBarChart2.axisRight.isEnabled = false;//隐藏右侧Y轴 默认是左右两侧都有Y轴
mBarChart2.xAxis.axisLineColor = Color.TRANSPARENT // 设置 X 轴线条为透明
mBarChart2.axisLeft.axisLineColor = Color.TRANSPARENT // 设置左侧 Y 轴线条为透明
mBarChart2.xAxis.textSize = 11f
mBarChart2.xAxis.textColor = getColor(R.color.color_cacaca)
mBarChart2.axisLeft.textSize = 11f
mBarChart2.axisLeft.textColor = getColor(R.color.color_cacaca)
val labels = arrayOf("SUN", "MON", "Tue", "Wed", "Thu", "Fri", "Sat")
// 设置自定义的ValueFormatter
mBarChart2.xAxis.valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(p0: Float): String {
val index = (p0.toInt() - 1)
if (index >= 0 && index < labels.size) {
return labels[index]
}
return ""
}
}
mBarChart2.xAxis.apply {
position = XAxisPosition.BOTTOM //设置x轴位置
setDrawGridLines(false)
// 设置x轴标签从0开始
setAxisMinValue(0.5f)// 让第一个柱子居中对齐
// 设置x轴显示的最大标签数量,居中对齐
setAxisMaxValue(labels.size.toFloat() + 0.5f)
// x轴标签居中,分组柱状图需要
granularity = 1f
}
mBarChart2.axisLeft.apply {
setPosition(YAxisLabelPosition.OUTSIDE_CHART)// 设置y-label显示在图表外
setAxisMaxValue(100f)
setAxisMinValue(0f)
setDrawGridLines(true)
gridColor = getColor(R.color.ededed)
enableGridDashedLine(12f, 12f, 0f)
gridLineWidth = 1f
labelCount = 5
granularity = 25f
}
}
取消X,Y轴的展示,增加虚线,增加圆角
3.2 圆角代码
BarChartRenderer
是 MPAndroidChart 库中用于绘制 BarChart
(柱状图)的渲染器。它继承自 DataRenderer
,负责绘制 柱形条(Bar)、高亮(Highlight)以及 X/Y 轴值。
我们需要去重写他的drawDataSet
class RoundedBarChartRenderer(
chart: BarDataProvider,
animator: ChartAnimator,
viewPortHandler: ViewPortHandler,
private val cornerRadius: Float = 20f // 设置圆角半径
) : BarChartRenderer(chart, animator, viewPortHandler) {
override fun drawDataSet(c: Canvas, dataSet: IBarDataSet, index: Int) {
val trans = mChart.getTransformer(dataSet.axisDependency)
mShadowPaint.color = dataSet.barShadowColor
mBarBorderPaint.color = dataSet.barBorderColor
mBarBorderPaint.strokeWidth = Utils.convertDpToPixel(dataSet.barBorderWidth)
val drawBorder = dataSet.barBorderWidth > 0.0f
val phaseX = mAnimator.phaseX
val phaseY = mAnimator.phaseY
val buffer = mBarBuffers[index]
buffer.setPhases(phaseX, phaseY)
buffer.setDataSet(index)
buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
buffer.setBarWidth(mChart.barData.barWidth)
buffer.feed(dataSet)
trans.pointValuesToPixel(buffer.buffer)
var j: Int
if (dataSet.colors.size > 1) {
j = 0
while (j < buffer.size()) {
if (mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
break
}
mRenderPaint.color = dataSet.getColor(j / 4)
val rectF = RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3])
if (j % 4 == 0) {
c.drawRect(
buffer.buffer[j],
(buffer.buffer[j + 1] + buffer.buffer[j + 3]) / 2,
buffer.buffer[j + 2],
buffer.buffer[j + 3],
mRenderPaint
)
} else {
c.drawRect(rectF, mRenderPaint)
}
//绘制圆角矩形
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mRenderPaint)
if (drawBorder) {//绘制圆角矩形
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mBarBorderPaint)
}
}
j += 4
}
} else {
mRenderPaint.color = dataSet.color
j = 0
while (j < buffer.size()) {
if (mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
break
}
val rectF = RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3])
if (j % 4 == 0) {
c.drawRect(
buffer.buffer[j],
(buffer.buffer[j + 1] + buffer.buffer[j + 3]) / 2,
buffer.buffer[j + 2],
buffer.buffer[j + 3],
mRenderPaint
)
} else {
c.drawRect(rectF, mRenderPaint)
}
//绘制圆角矩形
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mRenderPaint)
if (drawBorder) {
//绘制圆角矩形
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mBarBorderPaint)
}
}
j += 4
}
}
}
}
3.3 实现效果
4.堆叠柱状图
4.1 代码实现
fun initBarChart4() {
val barChart = binding.barChart4
val entries = ArrayList<BarEntry>()
// 仅存储 (minY, maxY)
entries.add(BarEntry(0f, floatArrayOf(5f, 10f))) // X=0, 从 0-5 到5-10
entries.add(BarEntry(1f, floatArrayOf(3f, 8f))) // X=1, 从 0-3 到 3-8
val barDataSet = BarDataSet(entries, "Data Set").apply {
setDrawValues(false) // 关闭数值显示
setColors(intArrayOf(R.color.purple_200, R.color.color_ff8561), this@ChartTestAc) // 第一部分透明,第二部分填充颜色
}
val barData = BarData(barDataSet).apply {
barWidth = 0.3f // 设置柱宽
}
barChart.data = barData
binding.barChart4.renderer = RoundedBarChartRendererAll(binding.barChart4, binding.barChart4.animator, binding.barChart4.viewPortHandler)
barChart.invalidate() // 刷新图表
}
4.2 实现效果
5.分段/区间柱状图
5.1 需求
需求是需要展示当天的温度区间,用户的血压区间
灵感来自于堆叠柱状图的实现
我只需要把下层数据不展示就可以了。
5.2 实现代码
fun initBarChart3() {
val barChart = binding.barChart3
val entries = ArrayList<BarEntry>()
// 仅存储 (minY, maxY)
entries.add(BarEntry(0f, floatArrayOf(2f, 8f-2f))) // 减去第一区间的值
entries.add(BarEntry(1f, floatArrayOf(3f, 11f-3f)))
val barDataSet = BarDataSet(entries, "Data Set").apply {
setDrawValues(false) // 关闭数值显示
setColors(intArrayOf(R.color.trans, R.color.color_ff8561), this@ChartTestAc) // 第一部分透明,第二部分填充颜色
}
val barData = BarData(barDataSet).apply {
barWidth = 0.3f // 设置柱宽
}
barChart.data = barData
binding.barChart3.renderer = RoundedBarChartRendererAll(binding.barChart3, binding.barChart3.animator, binding.barChart3.viewPortHandler)
barChart.invalidate() // 刷新图表
}
关键点:因为自带的堆叠柱状图会加你的第一个值,如果不减去第一个值,就会绘制2到(2+8)的区间,也就是2-10都会绘制,这肯定不符合需求设计,所以记得减去上一段的Y值。
四个方向的圆角代码
class RoundedBarChartRendererAll(
chart: BarDataProvider,
animator: ChartAnimator,
viewPortHandler: ViewPortHandler,
private val cornerRadius: Float = 20f // 设置圆角半径
) : BarChartRenderer(chart, animator, viewPortHandler) {
override fun drawDataSet(c: Canvas, dataSet: IBarDataSet, index: Int) {
val trans = mChart.getTransformer(dataSet.axisDependency)
mShadowPaint.color = dataSet.barShadowColor
mBarBorderPaint.color = dataSet.barBorderColor
mBarBorderPaint.strokeWidth = Utils.convertDpToPixel(dataSet.barBorderWidth)
val drawBorder = dataSet.barBorderWidth > 0.0f
val phaseX = mAnimator.phaseX
val phaseY = mAnimator.phaseY
val buffer = mBarBuffers[index]
buffer.setPhases(phaseX, phaseY)
buffer.setDataSet(index)
buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
buffer.setBarWidth(mChart.barData.barWidth)
buffer.feed(dataSet)
trans.pointValuesToPixel(buffer.buffer)
var j: Int
if (dataSet.colors.size > 1) {
j = 0
while (j < buffer.size()) {
if (mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
break
}
mRenderPaint.color = dataSet.getColor(j / 4)
val rectF = RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3])
// 绘制圆角矩形(四个角都要圆角)
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mRenderPaint)
if (drawBorder) {
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mBarBorderPaint)
}
}
j += 4
}
} else {
mRenderPaint.color = dataSet.color
j = 0
while (j < buffer.size()) {
if (mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
break
}
val rectF = RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3])
// 绘制圆角矩形(四个角都要圆角)
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mRenderPaint)
if (drawBorder) {
c.drawRoundRect(rectF, cornerRadius, cornerRadius, mBarBorderPaint)
}
}
j += 4
}
}
}
}