Android 圖表開源庫調研及使用範例

2023-12-09 12:00:31

原文地址: Android圖表開源庫調研及使用範例 - Stars-One的雜貨小窩

之前做的幾個專案都是需要實現圖表統計展示,於是做之前調研了下,做下記錄

概述

目前用的就是AAChartCore-Kotlin這個庫

還有些其他的,沒細看了,連結貼出來:

AAChartCore-Kotlin

基於hcharts這個js庫整的圖表庫,所以如果有不能實現的效果,可以先去看下js庫的實現

實質上Android庫只是封裝了對應的實體類,之後也是會將實體類轉為json設定,從而讓js庫繪製出圖表

折線圖實現範例

效果:

<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
	android:id="@+id/aa_chart_view"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

</com.github.aachartmodel.aainfographics.aachartcreator.AAChartView>
val arrayData = arrayOf<Any>(
	AASeriesElement()
		.name("Tokyo")
		.color("#e5473d")
		.data(arrayOf(86,125,112,168,123,131,119,95,112,86)),
	AASeriesElement()
		.name("NewYork")
		.color("#40a0e2")
		.data(arrayOf(42,89,76,126,87,91,73,67,80,110)),
	AASeriesElement()
		.name("London")
		.color("#0bb142")
		.data(arrayOf(40,78,62,83,58,67,31,53,68,74)),
)



val aaChartModel = AAChartModel()
	.chartType(AAChartType.Spline)
	.backgroundColor("#2f2f2f")
	.dataLabelsEnabled(false)
	.markerSymbol(AAChartSymbolType.Circle)
	.zoomType(AAChartZoomType.X)
	.series(arrayData)
	.categories(arrayOf(
		"5.19",
		"5.20",
		"5.21",
		"5.22",
		"5.23",
		"5.24",
		"5.25",
		"5.26",
		"5.27",
		"5.28",
	))

/*圖表檢視物件呼叫圖表模型物件,繪製最終圖形*/
binding.aaChartView.aa_drawChartWithChartModel(aaChartModel)

有兩種繪製資料的方法:

  • aa_drawChartWithChartModel(AAChartModel)
  • aa_drawChartWithChartOptions(AAOptions)

AAChartModel可以轉為AAOptions,通過aa_toAAOptions()方法

說明

AAOptions實際上就是官方對應的json資料格式,如果需要自定義設定,可以參考官方的API檔案,對AAOptions裡的資料進行修改

// 圖表設定
var options = {
	chart: {
		type: 'bar'                          //指定圖表的型別,預設是折線圖(line)
	},
	title: {
		text: '我的第一個圖表'                 // 標題
	},
	xAxis: {
		categories: ['蘋果', '香蕉', '橙子']   // x 軸分類
	},
	yAxis: {
		title: {
			text: '吃水果個數'                // y 軸標題
		}
	},
	series: [{                              // 資料列
		name: '小明',                        // 資料列名
		data: [1, 0, 4]                     // 資料
	}, {
		name: '小紅',
		data: [5, 7, 3]
	}]
};
// 圖表初始化函數
var chart = Highcharts.chart('container', options);

上面的options變數,就是對應的AAOptions這個實體類(轉換json的操作不需要我們去做)

官方demo如何參考?

首先,我們到此網站Highcharts 演示 | Highcharts,找到一個符合我們需要的基本圖表,比如說我找了個基礎折線圖 | Highcharts

之後可以看到,下面有個編輯原始碼的功能,點選進去會進入到一個線上執行js程式碼的網站

此時,我們再開啟Highcharts JS API 檔案,對demo進行樣式上的修改,最終得到我們需要的js程式碼

之後參考js程式碼,來進行我們AAChartCore-Kotlin的程式碼設定設定AAOptions即可

當然,建議還是先看一遍官方檔案,比如這個文章圖表主要組成 | Highcharts 使用教學,可以知道圖表的基本組成及一些通用名稱,這樣後面去找api檔案也比較方便

AAChartCore-Kotlin封裝的時候,可能有些屬性沒有考慮到,所以這個時候我們沒法通過官方的屬性來進行設定,而且官方的很多設定類都是final型別,沒有open關鍵字,導致我們需要去修改原始碼

於是我就是給開發者提了幾個pull request,雖然都被接受合併了,不過開發者什麼時候發個版本不確定,於是我就fork一份,自行改了並行布JitPack(各位有需要可以使用我的版本)

版本也沒什麼大改動,只是改了下某些類的開放性,方便繼承擴充套件其他屬性欄位,依賴如下:

implementation 'com.github.stars-one:AAChartCore-Kotlin:1.0'

左右滑動的實現

也是看了半天的js庫的檔案和嘗試,才實現的效果

//這幾個是資料
val arrayData = arrayOf<Any>(
	AASeriesElement()
		.color("#e5473d")
		.data(sysArr),
	AASeriesElement()
		.color("#40a0e2")
		.data(diaArr),
	AASeriesElement()
		.color("#0bb142")
		.data(pulesArr),
)
//構造繪製的model
val aaChartModel = AAChartModel()
	.chartType(AAChartType.Spline)
	.backgroundColor("#2f2f2f")
	.dataLabelsEnabled(false)
	.markerSymbol(AAChartSymbolType.Circle)
	.zoomType(AAChartZoomType.X)
	.categories(categoriesArr)
	.series(arrayData)
	.yAxisVisible(true)
	.touchEventEnabled(false)
	.tooltipEnabled(false)
	.legendEnabled(false)
	.scrollablePlotArea(AAScrollablePlotArea().minWidth(categoriesArr.size*40).scrollPositionX(0))

核心的方法zoomType(AAChartZoomType.X).scrollablePlotArea(AAScrollablePlotArea().minWidth(categoriesArr.size*40).scrollPositionX(0))

這裡要給圖表設定個最小寬度才能實現左右滑動,我就以每個資料給了40px的寬度,這裡各位可以看情況改

柱形圖(範圍)程式碼範例

實現效果如下下圖:

private fun initChart() {

	val jsArray = arrayOf(
		arrayOf(60,130),
		arrayOf(80,190),
		arrayOf(60,130),
		arrayOf(90,160),
		arrayOf(68,145),
		arrayOf(75,126),
		arrayOf(67,139),
	)
	val categoriesArr = arrayOf(
		"5.19",
		"5.20",
		"5.21",
		"5.22",
		"5.23",
		"5.24",
		"5.25"
	)

	val kotlinArray: Array<Any> =
		jsArray.map { it.map { it.toInt()  }.toTypedArray() }.toTypedArray()

	val arrayData = arrayOf<Any>(
		AASeriesElement()
			.marker(AAMarker().enabled(false))
			.data(kotlinArray)
	)

	val aaChartModel = AAChartModel()
		.categories(categoriesArr)
		.legendEnabled(false)
		.touchEventEnabled(false)
		.tooltipEnabled(false)
		.chartType(AAChartType.Columnrange)
		.xAxisVisible(true)
		.yAxisVisible(true)
		.yAxisLabelsEnabled(true)
		.xAxisLabelsEnabled(true)
		.backgroundColor("#04081a")
		.dataLabelsEnabled(false)
		.gradientColorEnable(true)
		.borderRadius(25)
		.markerRadius(25)
		.series(arrayData)

	val options = aaChartModel.aa_toAAOptions().apply {
		val list = listOf("#d14664")
		val colorList = (0..6).map { list.random() }

		yAxis?.apply {
			//改變y座標軸的寬度(設定
			//lineWidth = 0
			//y座標軸的顏色
			//lineColor = "red"

			//y座標軸的左邊文字隱藏
			title(AATitle().text(""))

			//y座標軸的水平刻度的樣式
			gridLineColor = "#2b2745"
			gridLineWidth= 1
			gridLineDashStyle = AAChartLineDashStyleType.LongDash.value
			
			//起始刻度
			min=30
			//刻度間隔
			tickInterval = 30
		}
		xAxis?.apply {
			lineWidth=0
		}

		plotOptions?.columnrange =
			AAColumnRange(
				SizeUtils.dp2px(2f),
				SizeUtils.dp2px(8f),
				0,
				colorList,
				AAChartLineDashStyleType.Dash
			)
	}


	/*圖表檢視物件呼叫圖表模型物件,繪製最終圖形*/
	binding.aaChartView.aa_drawChartWithChartOptions(options)

}
@Keep	
data class AAColumnRange(
    /**
     * 柱形圖的圓角
     */
    var borderRadius: Number,
    /**
     * 柱形圖的寬度
     */
    var pointWidth: Number,
    /**
     * 柱形圖的邊框寬度
     */
    var borderWidth: Number = 0,
    /**
     * 每個柱形圖的顏色
     */
    var colors: List<String>,
    /**
     * 設定為true才會使用上面的顏色陣列
     */
    var dashStyle: AAChartLineDashStyleType,

    var colorByPoint: Boolean = true,
)	

柱形圖漸變色實現

原倉庫是沒有封裝漸變的屬性的,這樣我就是參考js程式碼及對應的檔案,整了幾個類來實現

效果圖:

@Keep
data class AAColumnGradientColor(val linearGradient: LinearGradient, val stops: List<List<Any>>)

@Keep
data class LinearGradient(val x1: Float, val y1: Float, val x2: Float, val y2: Float)

@Keep
data class AAColumnRange(
    /**
     * 柱形圖的圓角
     */
    var borderRadius: Number,
    /**
     * 柱形圖的寬度
     */
    var pointWidth: Number,
    /**
     * 柱形圖的邊框寬度
     */
    var borderWidth: Number = 0,
    /**
     * 每個柱形圖的顏色
     */
    var colors: List<AAColumnGradientColor>,

    /**
     * y軸水平刻度線條
     */
    var dashStyle: AAChartLineDashStyleType,
    /**
     * 設定為true才會使用上面的顏色colors陣列
     */
    var colorByPoint: Boolean = true,

)

val gradient = AAColumnGradientColor(
	LinearGradient(0f, 0f, 0f, 1f),
	listOf(
		listOf(0, "#fceed3"),    // 顏色的起始位置
		listOf(1, "#ce395a")     // 顏色的結束位置
	)
)

val list = listOf(gradient)

plotOptions?.columnrange =AAColumnRange(
	SizeUtils.dp2px(2f),
	SizeUtils.dp2px(8f),
	0,
	colorList,
	AAChartLineDashStyleType.Dash
)

混淆規則

# 圖表庫
-keep class com.github.aachartmodel.** {*;}

//這個是官方檔案上寫的混淆規則,如果出現問題可以使用上面這個
-keep class com.github.aachartmodel.aainfographics.** { *; }

注意:如果使用上面自定義了些欄位,對應的類也要新增忽略混淆,不然圖表的樣式會出現問題,我上面是加了個@Keep註解來忽略

MPChart

似乎是開發者要搞收費了,檔案都看不到,暫且記錄下之前寫的一個簡單圖表demo程式碼範例

private fun testDrawChart2() {
	val chart = binding.mpChart


	// 設定 X 軸座標值
	val labels = arrayOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
	val xAxis = chart.xAxis
	xAxis.valueFormatter = IndexAxisValueFormatter(labels)


	// 將資料集新增到資料物件中
	val lineData = LineData(listOf(
		createDataSet(),
		createDataSet()
	))
	chart.data = lineData


	chart.invalidate()

	val yAxisLeft = chart.axisLeft
	yAxisLeft.removeAllLimitLines()
	chart.invalidate()

}

private fun createDataSet(): LineDataSet {
	// 建立資料點 (日期, 溫度)
	val entries =(0..6).map{
	   Entry(it.toFloat(),Random.nextInt(0..30).toFloat())
	}
	//val entries = listOf(
	//
	//    Entry(1f, 27.4f),
	//    Entry(2f, 28.9f),
	//    Entry(3f, 29.6f),
	//    Entry(4f, 25.8f),
	//    Entry(5f, 26.2f),
	//    Entry(6f, 30.2f),
	//    Entry(7f, 31.1f)
	//)

	// 將資料點新增到資料集中
	val dataSet = LineDataSet(entries, "")

	dataSet.color = Color.GREEN
	dataSet.lineWidth = 2f
	dataSet.setDrawCircles(true)

	//設定每個座標點的顏色
	dataSet.setDrawCircleHole(true)
	dataSet.circleHoleColor = Color.GREEN

	dataSet.circleRadius = 4f
	dataSet.valueTextSize = 12f
	//需要曲線
	dataSet.mode = LineDataSet.Mode.HORIZONTAL_BEZIER
	return dataSet
}

/**
 * 圖表元件的一些設定
 */
private fun configChartView() {
	val chart = binding.mpChart
	// 顯示動畫
	chart.animateXY(1000, 1000)

	// 設定屬性
	chart.description.isEnabled = false
	chart.setTouchEnabled(true)
	chart.isDragEnabled = true
	chart.setScaleEnabled(true)
	chart.setPinchZoom(true)

	//不顯示底部的圖例樣式
	chart.legend.form = Legend.LegendForm.NONE


	val xAxis = chart.xAxis
	//設定x座標在下面(預設是在上面的)
	xAxis.position = XAxis.XAxisPosition.BOTTOM
	xAxis.granularity = 1f
	xAxis.textColor = Color.parseColor("#898989")
	xAxis.textSize = SizeUtils.dp2px(10f).toFloat()

	// 設定 Y 軸座標值
	val yAxisLeft = chart.axisLeft
	yAxisLeft.setStartAtZero(false)
	yAxisLeft.setTextColor(Color.parseColor("#898989"))
	yAxisLeft.setTextSize(SizeUtils.dp2px(10f).toFloat())

	// 建立 LimitLine 物件,並設定相關屬性
	val limitLine = LimitLine(30f, "Threshold")
	limitLine.lineColor = Color.RED                                   // 設定線條顏色為紅色
	limitLine.lineWidth = 2f                                          // 設定線條寬度為 2 畫素
	limitLine.enableDashedLine(10f,5f,0f)

	// 新增 LimitLine 物件到 YAxis 物件中,並重新整理圖表
	yAxisLeft.addLimitLine(limitLine)
	chart.invalidate()

}