From 2b575c1e6ba0d7f33ea5a8669508a72967679852 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Wed, 24 Dec 2025 08:15:22 +0100 Subject: [PATCH] Kotlin CombinedChart --- .../mikephil/charting/charts/CombinedChart.kt | 213 ++++++++++++++++++ .../renderer/CombinedChartRenderer.kt | 5 +- .../chartexample/CombinedChartActivity.kt | 7 +- 3 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt new file mode 100644 index 000000000..97057e7d3 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt @@ -0,0 +1,213 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.util.Log +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BubbleData +import com.github.mikephil.charting.data.CandleData +import com.github.mikephil.charting.data.CombinedData +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.ScatterData +import com.github.mikephil.charting.highlight.CombinedHighlighter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import com.github.mikephil.charting.renderer.CombinedChartRenderer + +/** + * This chart class allows the combination of lines, bars, scatter and candle + * data all displayed in one chart area. + */ +@Suppress("unused") +open class CombinedChart : BarLineChartBase, CombinedDataProvider { + /** + * if set to true, all values are drawn above their bars, instead of below + * their top + */ + override var isDrawValueAboveBarEnabled: Boolean = true + + + /** + * @return true the highlight operation is be full-bar oriented, false if single-value + */ + /** + * Set this to true to make the highlight operation full-bar oriented, + * false to make it highlight single values (relevant only for stacked). + */ + /** + * flag that indicates whether the highlight should be full-bar oriented, or single-value? + */ + override var isHighlightFullBarEnabled: Boolean = false + + /** + * if set to true, a grey area is drawn behind each bar that indicates the + * maximum value + */ + override var isDrawBarShadowEnabled: Boolean = false + + protected var drawOrders: Array = arrayOf() + + /** + * enum that allows to specify the order in which the different data objects + * for the combined-chart are drawn + */ + enum class DrawOrder { + BAR, BUBBLE, LINE, CANDLE, SCATTER + } + + constructor(context: Context?) : super(context) + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + protected override fun init() { + super.init() + + // Default values are not ready here yet + drawOrders = arrayOf( + DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.LINE, DrawOrder.CANDLE, DrawOrder.SCATTER + ) + + setHighlighter(CombinedHighlighter(this, this)) + + // Old default behaviour + isHighlightFullBarEnabled = true + + mRenderer = CombinedChartRenderer(this, mAnimator, mViewPortHandler) + } + + override val combinedData: CombinedData? + get() = mData + + override fun setData(data: CombinedData?) { + super.setData(data) + setHighlighter(CombinedHighlighter(this, this)) + (mRenderer as CombinedChartRenderer).createRenderers() + mRenderer.initBuffers() + } + + /** + * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch + * point + * inside the CombinedChart. + */ + override fun getHighlightByTouchPoint(x: Float, y: Float): Highlight? { + if (mData == null) { + Log.e(LOG_TAG, "Can't select by touch. No data set.") + return null + } else { + val h = highlighter.getHighlight(x, y) + if (h == null || !isHighlightFullBarEnabled) { + return h + } + + // For isHighlightFullBarEnabled, remove stackIndex + return Highlight( + h.x, h.y, + h.xPx, h.yPx, + h.dataSetIndex, -1, h.axis + ) + } + } + + override val lineData: LineData + get() = mData!!.lineData!! + + override val barData: BarData + get() = mData!!.barData!! + + override val scatterData: ScatterData? + get() = mData!!.scatterData + + override val candleData: CandleData? + get() = mData!!.candleData + + override val bubbleData: BubbleData? + get() = mData!!.bubbleData + + /** + * If set to true, all values are drawn above their bars, instead of below + * their top. + */ + fun setDrawValueAboveBar(enabled: Boolean) { + this.isDrawValueAboveBarEnabled = enabled + } + + + /** + * If set to true, a grey area is drawn behind each bar that indicates the + * maximum value. Enabling his will reduce performance by about 50%. + */ + fun setDrawBarShadow(value: Boolean) { + this.isDrawBarShadowEnabled = value + } + + var drawOrder: Array + get() = drawOrders + /** + * Sets the order in which the provided data objects should be drawn. The + * earlier you place them in the provided array, the further they will be in + * the background. e.g. if you provide new DrawOrer[] { DrawOrder.BAR, + * DrawOrder.LINE }, the bars will be drawn behind the lines. + */ + set(order) { + if (order.isEmpty()) { + return + } + drawOrders = order + } + + /** + * draws all MarkerViews on the highlighted positions + */ + override fun drawMarkers(canvas: Canvas) { + // if there is no marker view or drawing marker is disabled + + if (mMarkers == null || !isDrawMarkersEnabled || !valuesToHighlight()) { + return + } + + for (i in mIndicesToHighlight.indices) { + val highlight = mIndicesToHighlight[i] + val dataset = mData!!.getDataSetByHighlight(highlight) + + val e = mData!!.getEntryForHighlight(highlight) + if (e == null || dataset == null) { + continue + } + + @Suppress("UNCHECKED_CAST") + val set = dataset as IDataSet + val entryIndex = set.getEntryIndex(e) + + // make sure entry not null + if (entryIndex > set.entryCount * mAnimator.phaseX) { + continue + } + + val pos = getMarkerPosition(highlight) + + // check bounds + if (!mViewPortHandler.isInBounds(pos[0], pos[1])) { + continue + } + + // callbacks to update the content + if (!mMarkers.isEmpty()) { + val markerItem = mMarkers[i % mMarkers.size] + markerItem.refreshContent(e, highlight) + + // draw the marker + markerItem.draw(canvas, pos[0], pos[1]) + } + } + } + + override fun getAccessibilityDescription(): String { + return "This is a combined chart" + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt index af9f15257..1c320a595 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt @@ -19,7 +19,7 @@ open class CombinedChartRenderer(chart: CombinedChart, animator: ChartAnimator, /** * all rederers for the different kinds of data this combined-renderer can draw */ - protected var dataRenderers: MutableList = ArrayList(5) + protected var dataRenderers: MutableList = mutableListOf() protected var weakChart: WeakReference> = WeakReference(chart) @@ -105,9 +105,6 @@ open class CombinedChartRenderer(chart: CombinedChart, animator: ChartAnimator, } val subRenderers: List - /** - * Returns all sub-renderers. - */ get() = dataRenderers fun setSubRenderers(renderers: MutableList) { diff --git a/app/src/main/kotlin/info/appdev/chartexample/CombinedChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/CombinedChartActivity.kt index 184d4fce7..0753f7f24 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/CombinedChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/CombinedChartActivity.kt @@ -51,12 +51,11 @@ class CombinedChartActivity : DemoBase() { binding.chart1.isHighlightFullBarEnabled = false // draw bars behind lines - binding.chart1.setDrawOrder( - arrayOf( - DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.CANDLE, DrawOrder.LINE, DrawOrder.SCATTER - ) + binding.chart1.drawOrder = arrayOf( + DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.CANDLE, DrawOrder.LINE, DrawOrder.SCATTER ) + val l = binding.chart1.legend l.isWordWrapEnabled = true l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM