I want to draw a chart, where values of y-axis (on a left) will be inside of chart, but between this axis value and grid (lines) should be a space, so axis value should not overlap grid.
For now numbers fit inside chart, but there is no gap between them and line. How to do this? You can see the current result at a picture
val values = mutableListOf<Entry>()
values.add(Entry(1f, 666f))
values.add(Entry(2f, 1000f))
values.add(Entry(3f, 333f))
values.add(Entry(4f, 985f))
values.add(Entry(5f, 3000f))
values.add(Entry(6f, 2f))
values.add(Entry(7f, 50f))
val lineChart = LineChart(context)
lineChart.setTouchEnabled(true)
lineChart.isDragEnabled = true
lineChart.legend.isEnabled = false
lineChart.description.isEnabled = false
lineChart.setDrawBorders(false)
lineChart.xAxis.setDrawAxisLine(false)
lineChart.xAxis.position = XAxis.XAxisPosition.BOTTOM
lineChart.axisRight.isEnabled = false
lineChart.axisLeft.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART)
lineChart.axisLeft.setDrawGridLines(true)
lineChart.axisLeft.setDrawGridLinesBehindData(false)
lineChart.axisLeft.setDrawLimitLinesBehindData(false)
lineChart.xAxis.setDrawGridLines(false)
lineChart.isHapticFeedbackEnabled = true
lineChart.xAxis.valueFormatter = object: ValueFormatter() {
override fun getFormattedValue(value: Float): String {
if (value == values.first().x || value == values.last().x) {
return value.toString()
} else {
return ""
}
}
}
val set1 = LineDataSet(values, "DataSet 1")
set1.setDrawIcons(false)
set1.color = Color.YELLOW
set1.lineWidth = 1f
set1.setDrawValues(false)
val lineDataSet = LineData(set1)
lineChart.data = lineDataSet
There is no way and that doesn't make sense to do it (because the effect will just like the label set outside the grid line). In you case you should only set the position of labels to YAxis.YAxisLabelPosition.OUTSIDE_CHART
Related
my problem is how to know or define the other(couple) value of the y-axis or x-axis.
My code :
formatterValueY = new ValueFormatter() {
#Override
public String getAxisLabel(float valueY, AxisBase axis) {
//how to find the valueX of valueY ??
// need it to return string
//note: I know the search solution(data loop
//search),
// it is useless if there is two equal values y1=y2
//this is example of what I wanna achieve,
// this is simple example,
float x = findTheRealXOfY(valueY);
//or
//float x = findTheRealXOfY(valueY,axis);
if(x %2==0)
{
return "Pair:"+valueY;
}
else{
return "inPair:"+valueY;
}
}
}
YAxis yAxis = myBarChar.getYAxis();
yAxis.setValueFormatter(formatterValueY);
so if there is way to find the real pair value of Y using the valueY and axis.
This is an example of what I want
Depending on where you want these labels you have a couple options. If you want to set the labels along the bottom of the chart, you can use the IndexAxisValueFormatter(labels) on the x axis. If you want to add labels on the top of the bars you can set a ValueFormatter on the BarDataSet. Here is an example that sets "L" or "M" on the lower axis depending on the bar height, and "Less" or "More" on the bar label itself - also depending on the bar height.
For setting the x axis labels, the relevant command is
xaxis.valueFormatter = IndexAxisValueFormatter(labels)
and for the bar labels
barDataSet.valueFormatter = object : ValueFormatter() {
override fun getBarLabel(barEntry: BarEntry?): String {
var label = ""
barEntry?.let { e ->
// here you can access both x and y
label = if( e.y > 25 ) {
"More"
} else {
"Less"
}
}
return label
}
}
If you want the labels above the bars, you can use
barChart.setDrawValueAboveBar(true)
Complete Example
val barChart = findViewById<BarChart>(R.id.bar_chart)
// the values on your bar chart - wherever they may come from
val yVals = listOf(10f, 20f, 30f, 40f, 50f, 60f)
val valueSet1 = ArrayList<BarEntry>()
val labels = ArrayList<String>()
// You can create your data sets and labels at the same time -
// then you have access to the x and y values and can
// determine what labels to set
for (i in yVals.indices) {
val yVal = yVals[i]
val entry = BarEntry(i.toFloat(), yVal)
valueSet1.add(entry)
if( yVal > 20) {
labels.add("M")
}
else {
labels.add("L")
}
}
val dataSets: MutableList<IBarDataSet> = ArrayList()
val barDataSet = BarDataSet(valueSet1, " ")
barChart.setDrawBarShadow(false)
barChart.setDrawValueAboveBar(false)
barChart.description.isEnabled = false
barChart.setDrawGridBackground(false)
val xaxis: XAxis = barChart.xAxis
xaxis.setDrawGridLines(false)
xaxis.position = XAxis.XAxisPosition.BOTTOM
xaxis.granularity = 1f
xaxis.isGranularityEnabled = true
xaxis.setDrawLabels(true)
xaxis.setDrawAxisLine(false)
xaxis.valueFormatter = IndexAxisValueFormatter(labels)
xaxis.textSize = 15f
val yAxisLeft: YAxis = barChart.axisLeft
yAxisLeft.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART)
yAxisLeft.setDrawGridLines(true)
yAxisLeft.setDrawAxisLine(true)
yAxisLeft.isEnabled = true
yAxisLeft.textSize = 15f
barChart.axisRight.isEnabled = false
barChart.extraBottomOffset = 10f
barChart.extraLeftOffset = 10f
val legend: Legend = barChart.legend
legend.isEnabled = false
barDataSet.color = Color.CYAN
barDataSet.valueTextSize = 15f
// You can also set a value formatter on the bar data set itself (and enable
// setDrawValues). In this you get a BarEntry which has both the x position
// and bar height.
barDataSet.setDrawValues(true)
barDataSet.valueFormatter = object : ValueFormatter() {
override fun getBarLabel(barEntry: BarEntry?): String {
var label = ""
barEntry?.let { e ->
// here you can access both x and y
// values with e.x and e.y
label = if( e.y > 25 ) {
"More"
} else {
"Less"
}
}
return label
}
}
dataSets.add(barDataSet)
val data = BarData(dataSets)
barChart.data = data
barChart.invalidate()
example of what I needed, this is the best solution, with no over-code
this is the code:
BarData data = new BarData(colorBarSet);
data.setValueFormatter(new ValueFormatter() {
#Override
public String getBarLabel(BarEntry barEntry) {
Log.e("TAG", "this is the X value: " + barEntry.getX());
Log.e("TAG", "this is the Y value: " + barEntry.getY());
// this will return "Girl" for red color with x value=1,
// value of x is the index of the color in bar chart
return getMajeureVoteOfColor(barEntry.getX);
}
});
I try to display some charts in my android app. I use MPAndroidChart library. But somehow my chart is always compress on the x-axis. The value seems to have all the x value 0.
In the data set this is not the case. I can not figure out what I am doing wrong.
val chart = binding.statsGraph
chart.description.isEnabled = false
chart.setDrawGridBackground(false)
chart.setDrawBarShadow(false)
chart.isHighlightFullBarEnabled = false
chart.setBackgroundColor(Color.WHITE)
chart.setDrawGridBackground(false)
chart.setDrawBarShadow(false)
chart.setHighlightFullBarEnabled(false)
// draw bars behind lines
chart.setDrawOrder(
arrayOf(
CombinedChart.DrawOrder.BAR,
CombinedChart.DrawOrder.BUBBLE,
CombinedChart.DrawOrder.CANDLE,
CombinedChart.DrawOrder.LINE,
CombinedChart.DrawOrder.SCATTER
)
)
val legend = chart.getLegend()
legend.isWordWrapEnabled = true
legend.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
legend.horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER
legend.orientation = Legend.LegendOrientation.HORIZONTAL
legend.setDrawInside(false)
val rightAxis = chart.getAxisRight()
rightAxis.setDrawGridLines(false)
rightAxis.axisMinimum = 0f // this replaces setStartAtZero(true)
val leftAxis = chart.getAxisLeft()
leftAxis.setDrawGridLines(false)
leftAxis.axisMinimum = 0f // this replaces setStartAtZero(true)
val xAxis = chart.getXAxis()
xAxis.position = XAxis.XAxisPosition.BOTH_SIDED
xAxis.axisMinimum = 0f
xAxis.granularity = 1f
class MyValueFormatter : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return value.toInt().toString()
}
}
xAxis.valueFormatter = MyValueFormatter()
val data = CombinedData()
data.setData(test())
xAxis.axisMaximum = data.xMax + 0.25f
chart.setData(data)
chart.invalidate()
You have to make sure that your layout and its parent layout has a "match_parent" width.
Was wondering if it is possible to change the line and fill color for the line chart based on if the y-axis values are positive or negative. An example of it is below
Below is what i could achieve with the following code
private fun setUpLineChart() {
val lineData = getDataSet()
view.lineChart.apply {
data = lineData
description.isEnabled = false
setScaleEnabled(false)
setTouchEnabled(false)
legend.isEnabled = false
axisLeft.apply {
setDrawLabels(false)
setDrawGridLines(false)
setDrawAxisLine(false)
spaceBottom = 30f
}
axisRight.apply {
setDrawLabels(false)
setDrawGridLines(false)
setDrawAxisLine(false)
}
xAxis.apply {
setDrawLabels(false)
setDrawGridLines(false)
setDrawAxisLine(false)
}
animateXY(700, 1000, Easing.EaseInOutQuad)
}
}
private fun getDataSet(): LineData {
val entries = mutableListOf<Entry>()
val dataList = listOf(1, 20, -20, 33, 54, 7, -18, 2)
dataList.forEachIndexed { index, element ->
entries.add(Entry(index.toFloat(), element.toFloat()))
}
val dataSet = LineDataSet(entries, "")
dataSet.apply {
setDrawCircles(false)
valueTextSize = 0f
lineWidth = 3f
mode = LineDataSet.Mode.HORIZONTAL_BEZIER
color = ContextCompat.getColor(view.context, R.color.colorOnSurface)
setDrawFilled(true)
fillColor = ContextCompat.getColor(view.context, R.color.colorSurface2)
}
return LineData(dataSet)
}
The line and fill colors are bind to a specific LineDataSet. So to achieve the result you want according to the above example you have to separate your current dataSet into 4 LineDataSets (2 positive and 2 negative) and by doing this each one then can have its own fill and line colors and you are flexible to have as many colors you want for each dataSet. Of course you have to do your own logic to separate the positive LineDataSets from negative LineDataSets. I have modified your getDataSet() function to give you an example of how to achieve the separation of positive LineDataSets from negative LineDataSets each one having its own line and fill colors.
private fun getDataSet(): LineData? {
val dataSets: MutableList<ILineDataSet> = ArrayList()
val yArray = floatArrayOf(1f, 20f, -20f, 33f, 54f, 7f, -18f, 2f)
var entries = ArrayList<Entry?>()
var prevValueIsPositive = false
var prevValueIsNegative = false
val step = 1f
for (i in yArray.indices) {
val y = yArray[i]
//positive y values
if (y >= 0) {
//we are changing to positive values so draw the current negative dataSets
if (prevValueIsNegative) {
//calculate the common mid point between a positive and negative y
val midEntry = Entry(i.toFloat() - step / 2, 0f)
entries.add(midEntry)
//draw the current negative dataSet to Red color
dataSets.add(getLineDataSet(entries, android.R.color.holo_red_dark, android.R.color.holo_purple))
//and initialize a new DataSet starting from the above mid point Entry
entries = ArrayList()
entries.add(midEntry)
prevValueIsNegative = false
}
//we are already in a positive dataSet continue adding positive y values
entries.add(Entry(i.toFloat(), y))
prevValueIsPositive = true
//not having any other positive-negative changes so add the remaining positive values in the final dataSet
if (i == yArray.size - 1) {
dataSets.add(getLineDataSet(entries, android.R.color.holo_green_light, android.R.color.holo_orange_dark))
}
} else {
//we are changing to negative values so draw the current positive dataSets
if (prevValueIsPositive) {
//calculate the common mid point between a positive and negative y
val midEntry = Entry(i.toFloat() - step / 2, 0f)
entries.add(midEntry)
//draw the current positive dataSet to Green color
dataSets.add(getLineDataSet(entries, android.R.color.holo_green_light, android.R.color.holo_orange_dark))
//and initialize a new DataSet starting from the above mid point Entry
entries = ArrayList()
entries.add(midEntry)
prevValueIsPositive = false
}
//we are already in a negative dataSet continue adding negative y values
entries.add(Entry(i.toFloat(), y))
prevValueIsNegative = true
//not having any other positive-negative changes so add the remaining negative values in the final dataSet
if (i == yArray.size - 1) {
dataSets.add(getLineDataSet(entries, android.R.color.holo_red_dark, android.R.color.holo_purple))
}
}
}
return LineData(dataSets)
}
with the usage of the below helper function to prepare a new LineDataSet with its specified line and fill colors:
private fun getLineDataSet(entries: ArrayList<Entry?>, fillColor: Int, lineColor: Int): LineDataSet {
val dataSet = LineDataSet(entries, "")
dataSet.setDrawCircles(false)
dataSet.valueTextSize = 0f
dataSet.lineWidth = 3f
dataSet.mode = LineDataSet.Mode.HORIZONTAL_BEZIER
dataSet.color = ContextCompat.getColor(this, lineColor)
dataSet.setDrawFilled(true)
dataSet.fillColor = ContextCompat.getColor(this, fillColor)
return dataSet
}
I want to set the negative values of the bars to the left of the bars but at present some of the bars are overlapping these values.
How can i remove this overlapping of the negative values and if i set horizonatal_chart.setDrawValueAboveBar(true) then the positive bar will overlap the positive values.
Here's the code:
private fun filterHorizontalBarData(myList: List<Response>?) {
try {
val brand = ArrayList<String>()
var index = 0f
var color_index = 0
val data = BarData()
val values_adapter = ArrayList<String>()
for (item in myList!!) {
if (item.kPI.equals("Value % Growth") || item.kPI.equals("Volume % Growth")) {
val kpiValue = ArrayList<BarEntry>()
kpiValue.add(BarEntry(index,item.kPIvalue.toFloat()))
brand.add(item.brand)
values_adapter.add(item.kPIvalue.toString())
val barDataSet = BarDataSet(kpiValue, item.brand)
if(color_index<7)
barDataSet.setColor(getColor(horizonatal_chart.context, getColorID(color_index)))
else
barDataSet.setColor(getColorID(color_index))
barDataSet.valueTextSize = 8f
// barDataSet.setDrawValues(false)
data.addDataSet(barDataSet)
index++
color_index++
}
}
// values_adapter.sortDescending()
// first_bar_values.adapter = AdapterValuesHorizontalBar(values_adapter)
setHorizontalChart(data, brand)
}catch (e: Exception){
e.printStackTrace()
}
}
private fun setHorizontalChart(data : BarData, brand: ArrayList<String>){
horizonatal_chart.setDrawBarShadow(false)
val description = Description()
description.text = ""
horizonatal_chart.description = description
horizonatal_chart.legend.setEnabled(false)
horizonatal_chart.setPinchZoom(false)
horizonatal_chart.setScaleEnabled(false)
horizonatal_chart.setDrawValueAboveBar(true)
//Display the axis on the left (contains the labels 1*, 2* and so on)
val xAxis = horizonatal_chart.getXAxis()
xAxis.setDrawGridLines(false)
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
xAxis.setEnabled(true)
xAxis.setDrawAxisLine(false)
xAxis.textColor = Color.parseColor("#a1a1a1")
xAxis.xOffset = 5f
val yLeft = horizonatal_chart.axisLeft
//Set the minimum and maximum bar lengths as per the values that they represent
yLeft.axisMaximum = 100f
yLeft.axisMinimum = 0f
yLeft.isEnabled = false
yLeft.setDrawZeroLine(true)
yLeft.granularity = 1f
//Now add the labels to be added on the vertical axis
xAxis.valueFormatter = IAxisValueFormatter { value, axis ->
if (value.toInt() < brand.size) {
brand.get(value.toInt())
} else {
"0"
}
}
val yRight = horizonatal_chart.axisRight
yRight.setDrawAxisLine(true)
yRight.setDrawGridLines(false)
yRight.isEnabled = false
//Set bar entries and add necessary formatting
horizonatal_chart.axisLeft.setAxisMinimum(data.yMin)
data.barWidth = 0.9f
// val myCustomChartRender = MyCustomChartRender(horizonatal_chart, horizonatal_chart.animator, horizonatal_chart.viewPortHandler)
// //Add animation to the graph
// horizonatal_chart.renderer = myCustomChartRender
horizonatal_chart.animateY(2000)
horizonatal_chart.data = data
horizonatal_chart.setTouchEnabled(false)
horizonatal_chart.invalidate()
}
Please help me !
The problem seems that you've not provided minimum and maximum cap for rightAxis.
horizonatal_chart.axisRight.setAxisMinimum(-100)
horizonatal_chart.axisRight.setAxisMaximum(100)
Reference : The same issue is mentioned at issue and was solved by providing axis minimum and maximum
I am trying to show the data using LineChart, and ran into issues displaying x axis labels. I use data.start and data.end to set the x-axis bounds in my chart. So let's say API returns data for two days only (Thursday and Friday), start will be Monday and end will be Saturday.
Note:X values are ALL unix timestamps.
here's my code:
class MyChart(chart: LineChart?, val data: MyData) {
private val startValue = data.start.toEpochSecond(ZoneOffset.UTC)
init {
chart?.run {
setScaleEnabled(false)
legend.isEnabled = false
description.isEnabled = false
axisRight.run {
setDrawGridLines(true)
setDrawLabels(false)
axisMinimum = 0.1f // hide 0.0 values
}
axisLeft.run {
setDrawGridLines(true)
axisMinimum = 0.1f // hide 0.0 values
}
setupXAxis(xAxis)
Log.e("MPTEST, startValue: ", startValue.toString())
val entries = ArrayList<Entry>()
data.list?.forEachIndexed { _, data ->
val timeInSeconds = data.time.toEpochSecond(ZoneOffset.UTC)
val xIndex = (timeInSeconds - startValue).toFloat()
Log.e("MPTEST, xIndex: ", xIndex.toString())
entries.add(Entry(xIndex, data.value))
}
val data = LineData(LineDataSet(entries, ""))
setData(data)
invalidate()
}}
private fun setupXAxis(xAxis: XAxis?) {
xAxis?.run {
position = XAxis.XAxisPosition.BOTTOM
val startInSeconds = data.start.toEpochSecond(ZoneOffset.UTC)
axisMinimum = (startInSeconds - startValue).toFloat()
Log.e("MPTEST, axisMinimum: ", axisMinimum.toString())
val endInSeconds = data.end.toEpochSecond(ZoneOffset.UTC)
axisMaximum = (endInSeconds - startValue).toFloat()
Log.e("MPTEST, axisMaximum: ", axisMaximum.toString())
setCenterAxisLabels(true)
setDrawGridLines(false)
// I need to fix something here I think
setLabelCount(7, true)
valueFormatter = IndexAxisValueFormatter(data.labels)
}
} }
Here's the logcat output:
E/MPTEST, axisMinimum:: 0.0
E/MPTEST, axisMaximum:: 604799.0
E/MPTEST, startValue:: 1546732800
E/MPTEST, xIndex:: 366180.0
E/MPTEST, xIndex:: 467893.0
That's what I get - https://i.ibb.co/YD2DnhJ/Screen-Shot-2019-01-16-at-7-20-23-AM.png
(as you can see only first x axis label is displayed)
and this is what I am trying to achieve - https://i.ibb.co/M8b9pyP/Screen-Shot-2019-01-16-at-7-24-15-AM.png