Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 4.0.1

### Added

- `YAxis.labelRotationAngle` rotates the y axis labels, like `XAxis.labelRotationAngle` does for the x axis. The chart reserves the width the rotated labels need.
- `PieChart.isRoundedSlicesReversed` rounds the slice ends the other way around.
- `PieChart.isTransparentCircleClippedToSlices` draws the translucent ring around the hole only over the slices, so the space between slices stays open.
- Pie entry labels and values that contain a line break are drawn as several lines; the legend shows the line break as a space.
- `chart.toBitmap(width, height)` draws the chart at any size, also a chart built only in code that was never laid out. A chart on screen is laid out at that size for the drawing and put back afterwards.

### Fixed

- A tap inside a bar of a combined chart selected a nearby line point instead of the bar.
- In a bar chart whose grouped data sets use different y axes, a tap often selected the bar of the other axis.
- Limit line labels at the edge of the content area were cut off. Labels of limit lines outside the visible area are no longer drawn.
- Auto scaling skipped a y axis that was disabled, even when data sets depend on it.
- Building a chart on a thread without a Looper, for example with an asynchronous layout inflater, crashed. The gesture detector is now created on the first touch.
- The default value formatter rebuilt its number format on every data change, even when the number of decimals stayed the same.

## 4.0.0

A rewrite of the library in Kotlin. Same class, package and interface names, so imports survive; the call sites change. [MIGRATION.md](MIGRATION.md) lists the changes that need edits in your code, and the guides at [philjay.cc/mpandroidchart/docs](https://philjay.cc/mpandroidchart/docs/) cover the whole library in 35 chapters.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.github.mikephil.charting.devicetest

import android.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.Paint
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.LimitLine
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.utils.Utils
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AxisDrawTest {

private fun lineChart(entries: List<Entry<Nothing>>): LineChart {
val chart = Fixtures.lay { LineChart(Fixtures.context()) }
chart.legend.isEnabled = false
chart.description.isEnabled = false
chart.data = LineData(Fixtures.lineSet(entries))
Fixtures.resetViewport(chart)
return chart
}

@Test
fun autoScaleRescalesAnAxisThatIsNotDrawn() {
val chart = lineChart(List(100) { Entry(it.toFloat(), it.toFloat()) })
chart.axisLeft.isEnabled = false
chart.axisRight.isEnabled = false
chart.isAutoScaleMinMaxEnabled = true

val matrix = Matrix(chart.viewPortHandler.matrixTouch)
matrix.postScale(10f, 1f, 0f, 0f)
chart.viewPortHandler.refresh(matrix, chart, false)
chart.draw(Fixtures.canvas())

assertTrue("left axis max ${chart.axisLeft.axisMaximum}", chart.axisLeft.axisMaximum < 20f)
}

@Test
fun limitLineLabelsAtTheContentEdgeStayInsideTheContent() {
val chart = lineChart(List(11) { Entry(it.toFloat(), it * 10f) })
chart.axisLeft.axisMinimum = 0f
chart.axisLeft.axisMaximum = 100f
val yLimit = LimitLine(100f, "top label").apply { labelPosition = LimitLine.LimitLabelPosition.RIGHT_TOP }
val xLimit = LimitLine(10f, "right label").apply { labelPosition = LimitLine.LimitLabelPosition.RIGHT_TOP }
chart.axisLeft.addLimitLine(yLimit)
chart.xAxis.addLimitLine(xLimit)
chart.notifyDataSetChanged()

val canvas = RecordingCanvas(Bitmap.createBitmap(Fixtures.WIDTH, Fixtures.HEIGHT, Bitmap.Config.ARGB_8888))
chart.draw(canvas)

val paint = Paint().apply { textSize = Utils.convertDpToPixel(yLimit.textSize) }
val content = chart.viewPortHandler.contentRect
val top = canvas.texts.single { it.text == "top label" }
val right = canvas.texts.single { it.text == "right label" }
val topHeight = Utils.calcTextHeight(paint, "top label")
val rightWidth = Utils.calcTextWidth(paint, "right label")

assertTrue("top label baseline ${top.y}, content top ${content.top}", top.y - topHeight >= content.top - 0.5f)
assertTrue("right label x ${right.x}, content right ${content.right}", right.x + rightWidth <= content.right + 0.5f)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.mikephil.charting.devicetest

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BubbleChart
import com.github.mikephil.charting.charts.CandleStickChart
import com.github.mikephil.charting.charts.CombinedChart
import com.github.mikephil.charting.charts.HorizontalBarChart
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.charts.RadarChart
import com.github.mikephil.charting.charts.ScatterChart
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class BackgroundThreadTest {

@Test
fun everyChartCanBeBuiltOnAThreadWithoutALooper() {
val context = Fixtures.context()
var failure: Throwable? = null
val thread = Thread {
try {
LineChart(context)
BarChart(context)
HorizontalBarChart(context)
ScatterChart(context)
BubbleChart(context)
CandleStickChart(context)
CombinedChart(context)
PieChart(context)
RadarChart(context)
} catch (t: Throwable) {
failure = t
}
}
thread.start()
thread.join()
assertNull(failure)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.github.mikephil.charting.devicetest

import android.graphics.Bitmap
import android.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.LineData
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class BitmapExportTest {

private fun <T> onMain(block: () -> T): T {
var result: T? = null
InstrumentationRegistry.getInstrumentation().runOnMainSync { result = block() }
@Suppress("UNCHECKED_CAST")
return result as T
}

private fun drawnPixels(bitmap: Bitmap): Int {
val pixels = IntArray(bitmap.width * bitmap.height)
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
return pixels.count { it != Color.WHITE }
}

@Test
fun aChartThatWasNeverLaidOutExportsAtTheAskedSize() {
val (unsized, exported) = onMain {
val chart = LineChart(Fixtures.context())
chart.data = LineData(Fixtures.lineSet(Fixtures.lineEntries(50)))
chart.toBitmap() to chart.toBitmap(400, 300)
}

assertEquals(1, unsized.width)
assertEquals(1, unsized.height)
assertEquals(400, exported.width)
assertEquals(300, exported.height)
assertTrue("the exported image holds no chart", drawnPixels(exported) > 1000)
}

@Test
fun aChartOnScreenExportsAtAnotherSizeAndKeepsItsOwn() {
val chart = Fixtures.lay { LineChart(Fixtures.context()).apply { data = LineData(Fixtures.lineSet(Fixtures.lineEntries(50))) } }
val contentBefore = onMain { chart.viewPortHandler.contentRect.width() }

val exported = onMain { chart.toBitmap(Fixtures.WIDTH / 2, Fixtures.HEIGHT / 2) }

assertEquals(Fixtures.WIDTH / 2, exported.width)
assertEquals(Fixtures.HEIGHT / 2, exported.height)
assertTrue("the exported image holds no chart", drawnPixels(exported) > 1000)
onMain {
assertEquals(Fixtures.WIDTH, chart.width)
assertEquals(Fixtures.HEIGHT, chart.height)
assertEquals(contentBefore, chart.viewPortHandler.contentRect.width(), 0.5f)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.github.mikephil.charting.devicetest

import android.graphics.Bitmap
import android.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.math.cos
import kotlin.math.sin

@RunWith(AndroidJUnit4::class)
class PieRendererTest {

private fun pie(vararg entries: PieEntry<Nothing>, configure: PieChart.(PieDataSet<Nothing>) -> Unit = {}): PieChart {
val chart = Fixtures.lay { PieChart(Fixtures.context()) }
chart.legend.isEnabled = false
chart.description.isEnabled = false
chart.isDrawCenterTextEnabled = false
val set = PieDataSet(entries.toList(), "pie")
chart.configure(set)
chart.data = PieData(set)
return chart
}

private fun texts(chart: PieChart): List<TextDraw> {
val canvas = RecordingCanvas(Bitmap.createBitmap(Fixtures.WIDTH, Fixtures.HEIGHT, Bitmap.Config.ARGB_8888))
chart.draw(canvas)
return canvas.texts
}

@Test
fun aLabelWithLineBreaksIsDrawnAsLinesCenteredOnTheSingleLineSpot() {
val labelOnly: PieChart.(PieDataSet<Nothing>) -> Unit = { it.isDrawValuesEnabled = false }
val single = texts(pie(PieEntry(1f, "Single"), configure = labelOnly)).single()
val twoLines = pie(PieEntry(1f, "Top\nBottom"), configure = labelOnly)
val lines = texts(twoLines)

assertEquals(listOf("Top", "Bottom"), lines.map { it.text })
assertEquals("Top Bottom", twoLines.legend.entries.first().label)
assertEquals(single.x, lines[0].x, 0.01f)
assertEquals(single.x, lines[1].x, 0.01f)
assertEquals(single.y, (lines[0].y + lines[1].y) / 2f, 0.01f)
}

/** The pixel of [chart] drawn at [angle] degrees and [radiusPercent] of the pie radius from the center. */
private fun pixelAt(chart: PieChart, angle: Float, radiusPercent: Float): Int {
val bitmap = Bitmap.createBitmap(Fixtures.WIDTH, Fixtures.HEIGHT, Bitmap.Config.ARGB_8888)
chart.draw(android.graphics.Canvas(bitmap))
val center = chart.centerCircleBox
val r = chart.radius * radiusPercent / 100f
val radians = Math.toRadians(angle.toDouble())
return bitmap.getPixel((center.x + r * cos(radians)).toInt(), (center.y + r * sin(radians)).toInt())
}

@Test
fun reversedRoundedSlicesBulgeTheOtherWay() {
fun roundedPie(reversed: Boolean) = pie(PieEntry(1f), PieEntry(1f)) { set ->
set.colors = listOf(Color.RED, Color.BLUE)
set.isDrawValuesEnabled = false
isDrawEntryLabelsEnabled = false
isDrawRoundedSlicesEnabled = true
isRoundedSlicesReversed = reversed
rotationAngle = 270f
}

// The red slice starts at the top; the blue one ends there.
val normal = roundedPie(false)
assertEquals(Color.BLUE, pixelAt(normal, 262f, 75f))
assertEquals(Color.BLUE, pixelAt(normal, 278f, 75f))

val reversed = roundedPie(true)
assertEquals(Color.RED, pixelAt(reversed, 262f, 75f))
assertEquals(Color.RED, pixelAt(reversed, 278f, 75f))
}

@Test
fun aClippedTransparentCircleLeavesTheSliceSpaceClear() {
fun spacedPie(clipped: Boolean) = pie(PieEntry(1f), PieEntry(1f)) { set ->
set.colors = listOf(Color.RED, Color.BLUE)
set.sliceSpace = 12f
set.isDrawValuesEnabled = false
isDrawEntryLabelsEnabled = false
transparentCircleRadius = 70f
isTransparentCircleClippedToSlices = clipped
rotationAngle = 270f
}

// The space between the slices runs straight down from the center.
assertNotEquals(0, Color.alpha(pixelAt(spacedPie(false), 90f, 60f)))

val clipped = spacedPie(true)
assertEquals(0, Color.alpha(pixelAt(clipped, 90f, 60f)))
assertNotEquals(Color.RED, pixelAt(clipped, 0f, 60f))
assertEquals(Color.RED, pixelAt(clipped, 0f, 85f))
}
}
12 changes: 12 additions & 0 deletions MPChartLib/api/MPChartLib.api
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ public abstract class com/github/mikephil/charting/charts/Chart : android/view/V
protected final fun setupDefaultFormatter (FF)V
public final fun stopAnimations ()V
public final fun toBitmap ()Landroid/graphics/Bitmap;
public final fun toBitmap (I)Landroid/graphics/Bitmap;
public final fun toBitmap (II)Landroid/graphics/Bitmap;
public static synthetic fun toBitmap$default (Lcom/github/mikephil/charting/charts/Chart;IIILjava/lang/Object;)Landroid/graphics/Bitmap;
public final fun valuesToHighlight ()Z
}

Expand Down Expand Up @@ -581,6 +584,8 @@ public class com/github/mikephil/charting/charts/PieChart : com/github/mikephil/
public final fun isDrawHoleEnabled ()Z
public final fun isDrawRoundedSlicesEnabled ()Z
public final fun isDrawSlicesUnderHoleEnabled ()Z
public final fun isRoundedSlicesReversed ()Z
public final fun isTransparentCircleClippedToSlices ()Z
public final fun isUsePercentValuesEnabled ()Z
public final fun needsHighlight (I)Z
protected fun onDetachedFromWindow ()V
Expand All @@ -605,7 +610,9 @@ public class com/github/mikephil/charting/charts/PieChart : com/github/mikephil/
public final fun setHoleRadius (F)V
public final fun setMaxAngle (F)V
public final fun setMinAngleForSlices (F)V
public final fun setRoundedSlicesReversed (Z)V
public final fun setTransparentCircleAlpha (I)V
public final fun setTransparentCircleClippedToSlices (Z)V
public final fun setTransparentCircleColor (I)V
public final fun setTransparentCircleRadius (F)V
public final fun setUsePercentValuesEnabled (Z)V
Expand Down Expand Up @@ -1029,6 +1036,7 @@ public class com/github/mikephil/charting/components/YAxis : com/github/mikephil
public fun calculate (FF)V
public final fun getAxisDependency ()Lcom/github/mikephil/charting/components/YAxis$AxisDependency;
public final fun getLabelPosition ()Lcom/github/mikephil/charting/components/YAxis$YAxisLabelPosition;
public final fun getLabelRotationAngle ()F
public final fun getLabelXOffset ()F
public final fun getMaxWidth ()F
public final fun getMinWidth ()F
Expand All @@ -1048,6 +1056,7 @@ public class com/github/mikephil/charting/components/YAxis : com/github/mikephil
public final fun setDrawZeroLineEnabled (Z)V
public final fun setInverted (Z)V
public final fun setLabelPosition (Lcom/github/mikephil/charting/components/YAxis$YAxisLabelPosition;)V
public final fun setLabelRotationAngle (F)V
public final fun setLabelXOffset (F)V
public final fun setMaxWidth (F)V
public final fun setMinWidth (F)V
Expand Down Expand Up @@ -1788,6 +1797,7 @@ public class com/github/mikephil/charting/highlight/BarHighlighter : com/github/
protected fun getData ()Lcom/github/mikephil/charting/data/BarLineScatterCandleBubbleData;
protected fun getDistance (FFFF)F
public fun getHighlight (FF)Lcom/github/mikephil/charting/highlight/Highlight;
protected fun getHighlightsAtXValue (FFF)Ljava/util/List;
public final fun getStackedHighlight (Lcom/github/mikephil/charting/highlight/Highlight;Lcom/github/mikephil/charting/interfaces/datasets/IBarDataSet;FF)Lcom/github/mikephil/charting/highlight/Highlight;
}

Expand All @@ -1810,6 +1820,7 @@ public class com/github/mikephil/charting/highlight/ChartHighlighter : com/githu
public class com/github/mikephil/charting/highlight/CombinedHighlighter : com/github/mikephil/charting/highlight/ChartHighlighter {
public fun <init> (Lcom/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider;Lcom/github/mikephil/charting/interfaces/dataprovider/BarDataProvider;)V
protected final fun getBarHighlighter ()Lcom/github/mikephil/charting/highlight/BarHighlighter;
protected fun getHighlightForX (FFF)Lcom/github/mikephil/charting/highlight/Highlight;
protected fun getHighlightsAtXValue (FFF)Ljava/util/List;
}

Expand Down Expand Up @@ -2569,6 +2580,7 @@ public class com/github/mikephil/charting/renderer/PieChartRenderer : com/github
public fun drawHighlighted (Landroid/graphics/Canvas;Ljava/util/List;)V
protected fun drawHole (Landroid/graphics/Canvas;)V
protected fun drawRoundedSlices (Landroid/graphics/Canvas;)V
public fun drawValue (Landroid/graphics/Canvas;Lcom/github/mikephil/charting/formatter/IValueFormatter;FLcom/github/mikephil/charting/data/Entry;IFFI)V
public fun drawValues (Landroid/graphics/Canvas;)V
protected final fun getBitmapCanvas ()Landroid/graphics/Canvas;
protected final fun getChart ()Lcom/github/mikephil/charting/charts/PieChart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ public abstract class BarLineChartBase<T : BarLineScatterCandleBubbleData<out IB

xAxis.calculate(data.xMin, data.xMax)

if (axisLeft.isEnabled) axisLeft.calculate(data.getYMin(AxisDependency.LEFT), data.getYMax(AxisDependency.LEFT))
if (axisRight.isEnabled) axisRight.calculate(data.getYMin(AxisDependency.RIGHT), data.getYMax(AxisDependency.RIGHT))
axisLeft.calculate(data.getYMin(AxisDependency.LEFT), data.getYMax(AxisDependency.LEFT))
axisRight.calculate(data.getYMin(AxisDependency.RIGHT), data.getYMax(AxisDependency.RIGHT))

calculateOffsets()
}
Expand Down
Loading
Loading