Logarithmic Axis
Hi
First of all, thank you so much for creating this library. Even if I was working with an Objective C example and Android documentation, I could create something with ios-charts in no time. (Actually I'm surprised this library is not as popular as CorePlot, as ios-charts is much earlier to pick up in my point of view)
Just a quick question/suggestion. One of my projects requires plotting data in Log Scale on the X-Axis. I read through the documentation on MPAndroidChart, but looks like it's not supported.
Is this going to be supported in the near future?
Is there any existing solution that I could work with?
Thanks a lot!
Simon
Well ios-charts is only about two months old! So I guess there are no new users for CorePlot...
By log scale you mean that you want to have inconsistent x-axis spacing?
On Wed, May 27, 2015 at 2:30 PM, zimonjonez [email protected] wrote:
Hi
First of all, thank you so much for creating this library. Even if I was working with an Objective C example and Android documentation, I could create something with ios-charts in no time. (Actually I'm surprised this library is not as popular as CorePlot, as ios-charts is much earlier to pick up in my point of view)
Just a quick question/suggestion. One of my projects requires plotting data in Log Scale on the X-Axis. I read through the documentation on MPAndroidChart, but looks like it's not supported.
Is this going to be supported in the near future?
Is there any existing solution that I could work with?
Thanks a lot!
Simon
— Reply to this email directly or view it on GitHub https://github.com/danielgindi/ios-charts/issues/109.
Two months only?! You did a great job! I'll definitely going to spread the word
Yes, inconsistent x-axis spacing is what I'm looking for.
See images attached. This is what I did earlier using CorePlot.
(https://cloud.githubusercontent.com/assets/9811701/7835025/084b718c-04b9-11e5-9ff7-f63e340b69d1.png)

You can see that it is on the table, it is planned... https://github.com/PhilJay/MPAndroidChart/issues/12
From what I'm seeing when reading more on logarithmic axises, it seems like this term refers to an actually logarithmic Y axis - where we would have to logarithmically calculate the value sizes and positions. While what you want is just more control on the x-indexes... We are planning to move to x-values soon. But I'm going to work on logarithmic y axis too!
I would love to see logarithmic scaling as well (for both x and y axis). Keep up the good work, this is an awesome project!
@danielgindi can this be a part of in #194 ? since they are all behaving like y axis
+1
it seems like this term refers to an actually logarithmic Y axis
@danielgindi , how to configure Y-axis for using a logarithmic scale? I haven't found any references wither in the github tickets or on the internet (StackOverflow, googling, etc.)
As far as I understood your reply earlier in this thread, the library supports log scale for Y axis. Please correct me if I got you wrong.
@danielgindi , does the library support log scale for Y axis ?
@xxxrr I suppose current master branch should support both X and Y? @danielgindi We could close this if v3 did the job?
@liuxuan30 Thanks a lot.. Can you let me know how to configure it ? I am not able to get any references ..
hmm, I am not familiar with v3 yet.. You can check out v3 ChartsDemo - time line chart. v3 is a big change how x axis values are calculated
@liuxuan30 Thanks.. But not able to find things related to Logarithmic scale in it..Will look deeper into v3.. Thanks..
I mean there is no logarithmic support yet, but seems like v3 can support such type easier than v2. The main difference is for x axis
Hi, any updates about this ?
@ThomasGoujon I think this can be solved by Chart 3.0 (except for those axis styles)
Hi @liuxuan30 @danielgindi, how would I go about plotting Y values logarithmically using Charts 3.0?
just calculate the x, y and draw it!
So if I plot a range of x, y values say between 0 and 1000, how to I get the Y axis to draw logarithmically, for example 0, 1, 10, 100, 1000?
oh I see, you are talking about y axis labels. By default it can't, but you can override computeAxisValues() to feed your own.
Can anyone give me a hint on how to get logarithmic axes like the x-axis of @zimonjonez plot? Including the logarithmic spacing between the 0, 1, 10, 100, ... steps? https://de.wikipedia.org/wiki/Logarithmische_Darstellung#/media/File:Log-Achse.png
@SpaceDuster like I said above, take a look at computeAxisValues(), and override a bunch of methods in axis renderer, to get what you want.
It's not a easy task, since you have to understand how this library works.
@AshRobinson would you be willing to share how you overrode computeAxisValues() to draw the Y axis logarithmically?
@danielgindi Great library! Very appreciative! Has the ability to scale the Y axis logarithmically been added yet?
No, I guess it's not a feature that many people needs.
@liuxuan30 I overrode computeAxisValues() to show only the values I want to show - log base 10, but the chart is still using a linear scale. Do you know how to go about redrawing the scale - keeping the logarithmic intervals equidistant apart?
@SpaceDuster have you had any luck on this?
@danielgindi could you give me an idea of where to start when trying to rescale the Y axis?
@brittanygraft screenshot maybe? 'but the chart is still using a linear scale' is not clear to me. Ideally, you should check the data point it using to draw for each label.
What I figured out so far:
- modify the file XAxisRenderer (you find it in the folder: Charts -> Classes -> Renderers) as follows: right after the lines "open class XAxisRenderer: AxisRendererBase {" add:
public var logarithmic: Bool = false
public var maxOrderOfMagnitudeNegative = 0
public var minOrderOfMagnitudePositive = 0
public var logSetup: [Double] = []
in "open func drawLabels..." after the lines "if xAxis.isWordWrapEnabled { labelMaxSize.width = xAxis.wordWrapWidthPercent * valueToPixelMatrix.a }" add:
let entries: [Double]
if logarithmic == true {
entries = logSetup.map{log10(abs($0)) * $0/abs($0)}
xAxis.entries = logSetup
} else {
entries = xAxis.entries
}
in the same "open func drawLabels" after " if viewPortHandler.isInBoundsX(position.x) {" add and modify as follows:
// **** fraction of entries[i]
var frac: Double
if xAxis.entries[i] > 0 {
frac = 10 ** Double(minOrderOfMagnitudePositive)
} else {
frac = 10 ** Double(maxOrderOfMagnitudeNegative)
}
let label = xAxis.valueFormatter?.stringForValue(xAxis.entries[i]/frac, axis: xAxis) ?? ""
in "open override func renderGridLines" after the line "var position = CGPoint(x: 0.0, y: 0.0)" add:
let entries: [Double]
if logarithmic == true {
entries = logSetup.map{log10(abs($0)) * $0/abs($0)}
} else {
entries = xAxis.entries
}
- use following functions to setup your data and plot for logarithmic plotting:
// function for calculating the order of magnitude (oom) of an Double: bsp. 0.02 -> oom=-2, 10 -> oom = 1
func order(input: Double) -> (Int) {
var order = 0
var temp = abs(input)
if temp < 10 {
while temp < 10 {
temp = temp * 10
order = order - 1
}
} else if temp > 1 {
while temp > 1 {
temp = temp / 10
order = order + 1
}
}
return order
}
// ********** function for calculating the highest (negative) order of the data sets entries < 0 ******************
func negativeOrderOfMagnitudeOfDataSets(dataInput: [([Double], [Double])]) -> (Int, Int, Int, Int) {
// create arrays to hold the positive and negative parts with an absolute value < 1 (log10 < 0) of all data sets
var PositivePartArrX: [Double] = []
var NegativePartArrX: [Double] = []
var PositivePartArrY: [Double] = []
var NegativePartArrY: [Double] = []
// cycle through the data sets and write positive and negative entries (abs < 1) in the acording arrays
for i in 0..<dataInput.count {
for entry in dataInput[i].0 {
if entry < 1 && entry > 0 {
PositivePartArrX.append(entry)
} else if entry > -1 && entry < 0 {
NegativePartArrX.append(entry)
}
}
for entry in dataInput[i].1 {
if entry < 1 && entry > 0 {
PositivePartArrY.append(entry)
} else if entry > -1 && entry < 0 {
NegativePartArrY.append(entry)
}
}
}
// calculate the highest absolute order of the positive and negative part (abs < 1). If the array is empty the order is 0
var orderPositivePartX: Int = 0
if PositivePartArrX != [] {
orderPositivePartX = order(input: PositivePartArrX.min()!)
}
var orderPositivePartY: Int = 0
if PositivePartArrY != [] {
orderPositivePartY = order(input: PositivePartArrY.min()!)
}
var orderNegativePartX: Int = 0
if NegativePartArrX != [] {
orderNegativePartX = order(input: NegativePartArrX.min()!)
}
var orderNegativePartY: Int = 0
if NegativePartArrY != [] {
orderNegativePartY = order(input: NegativePartArrY.min()!)
}
return (orderPositivePartX, orderNegativePartX, orderPositivePartY, orderNegativePartY)
}
// ********** ////////////////////////////////// ******************
// ********** function that prepates the data for plotting and setup labels, markers, colors, ... ******************
public func prepareDataToPlot(dataInput: [([Double], [Double])], labels: [String] = [], marker: Array<ScatterChartDataSet.Shape> = [], color: Array<NSUIColor> = [], logX: Bool = false, logY: Bool = false )->([ScatterChartDataSet]) {
print("start preparing data for plotting")
// if there are no (or a different number than y-values) x values the x-values are substituted by a upcounting array [0, 1, 2, 3, ...]
var dataInput = dataInput
for i in 0..<dataInput.count{
if dataInput[i].0.count != dataInput[i].1.count {
var substitute: [Double] = []
for i in 0..<dataInput[i].0.count {
substitute.append(Double(i))
}
dataInput[i].1 = substitute
}
}
// make data logarythmic
if logX == true {
print(" start making X-axis data logarythmic")
// get absolute values < 1 (log10 < 0) and split them into a negative and positive part
var orderPositiveArr: [Double] = []
var orderNegativeArr: [Double] = []
for i in 0..<dataInput.count {
for entry in dataInput[i].0 {
if entry < 1 && entry > 0 {
orderPositiveArr.append(entry)
} else if entry > -1 && entry < 0 {
orderNegativeArr.append(entry)
}
}
}
// get the highest negative order of the positive and negative entries. bsp. 0.002 -> order -3
var minOrderPositive: Int
if orderPositiveArr != [] {
minOrderPositive = order(input: orderPositiveArr.min()!)
} else {
minOrderPositive = 0
}
var maxOrderNegative: Int
if orderNegativeArr != [] {
maxOrderNegative = order(input: orderNegativeArr.min()!)
} else {
maxOrderNegative = 0
}
// cycle through the data, shift them into the positive (>1) range and calculate the log values. Both for the negative and positive part.
// entries with x value = 0.0 are deleted (also the acording y entry)
for i in 0..<dataInput.count {
var xDataInputLogTemp: [Double] = []
for j in 0..<dataInput[i].0.count {
let xEntry = dataInput[i].0[j]
if xEntry > 0 {
let logValue = (xEntry * (10 ** Double(abs(minOrderPositive))))
xDataInputLogTemp.append(log10(abs(logValue)) * logValue/abs(logValue))
} else if xEntry < 0 {
let logValue = (xEntry * (10 ** Double(abs(maxOrderNegative))))
xDataInputLogTemp.append(log10(abs(logValue)) * logValue/abs(logValue))
} else {
dataInput[i].1.remove(at: j)
}
}
dataInput[i].0 = xDataInputLogTemp
}
print(" done making X-axis data logarythmic)")
}
if logY == true {
print(" start making Y-axis data logarythmic")
// get absolute values < 1 (log10 < 0) and split them into a negative and positive part
var orderPositiveArr: [Double] = []
var orderNegativeArr: [Double] = []
for i in 0..<dataInput.count {
for entry in dataInput[i].1 {
if entry < 1 && entry > 0 {
orderPositiveArr.append(entry)
} else if entry > -1 && entry < 0 {
orderNegativeArr.append(entry)
}
}
}
// get the highest negative order of the positive and negative entries. bsp. 0.002 -> order -3
var minOrderPositive: Int
if orderPositiveArr != [] {
minOrderPositive = order(input: orderPositiveArr.min()!)
} else {
minOrderPositive = 0
}
var maxOrderNegative: Int
if orderNegativeArr != [] {
maxOrderNegative = order(input: orderNegativeArr.min()!)
} else {
maxOrderNegative = 0
}
// cycle through the data, shift them into the positive (>1) range and calculate the log values. Both for the negative and positive part.
// entries with x value = 0.0 are deleted (also the acording y entry)
for i in 0..<dataInput.count {
var xDataInputLogTemp: [Double] = []
for j in 0..<dataInput[i].1.count {
let xEntry = dataInput[i].1[j]
if xEntry > 0 {
let logValue = (xEntry * (10 ** Double(abs(minOrderPositive))))
xDataInputLogTemp.append(log10(abs(logValue)) * logValue/abs(logValue))
} else if xEntry < 0 {
let logValue = (xEntry * (10 ** Double(abs(maxOrderNegative))))
xDataInputLogTemp.append(log10(abs(logValue)) * logValue/abs(logValue))
} else {
dataInput[i].0.remove(at: j)
}
}
dataInput[i].1 = xDataInputLogTemp
}
print(" done making Y-axis data logarythmic")
}
var dataEntries: [[ChartDataEntry]] = []
// setup of markers, colors, ... and their default values if no input is given
var labels = labels
var marker = marker
var color = color
var colors = [NSUIColor.black, NSUIColor.blue, NSUIColor.red, NSUIColor.cyan, NSUIColor.green, NSUIColor.magenta, NSUIColor.orange, NSUIColor.magenta, NSUIColor.brown, NSUIColor.yellow]
// (Marker) Color set-up
if color.count != dataInput.count {
color = []
if dataInput.count < colors.count {
for i in 0..<dataInput.count {
color.append(colors[i])
}
} else {
var counter = 0
for _ in 0..<dataInput.count {
color.append(colors[counter])
if counter == colors.endIndex {
counter = 0
}
}
}
}
// Marker set-up
if marker.count != dataInput.count {
marker = []
for _ in 0..<dataInput.count {
marker.append(ScatterChartDataSet.Shape.x)
}
}
// Description lable set-up
if labels.count != dataInput.count {
labels = [String](repeating: "-", count: dataInput.count)
}
// chart data set-up
for i in 0..<dataInput.count {
var dataEntriesSet: [ChartDataEntry] = []
for j in 0..<dataInput[i].0.count {
dataEntriesSet.append(ChartDataEntry(x: dataInput[i].0[j], y: dataInput[i].1[j]))
}
dataEntries.append(dataEntriesSet)
}
// plot apperance set-up
var scatterChartDataSets: [ScatterChartDataSet] = []
for i in 0..<dataEntries.count {
let scatterChartDataSetI = ScatterChartDataSet(values: dataEntries[i], label: labels[i])
scatterChartDataSetI.setScatterShape(marker[i])
scatterChartDataSetI.colors = [color[i]]
scatterChartDataSets.append(scatterChartDataSetI)
}
// return processed data to be plotted by plot function
print("done preparing data for plotting")
return scatterChartDataSets
}
func negativeOrderOfMagnitudeOfDataSets(dataInput: [([Double], [Double])]) -> (Int, Int, Int, Int) {
// create arrays to hold the positive and negative parts with an absolute value < 1 (log10 < 0) of all data sets
var PositivePartArrX: [Double] = []
var NegativePartArrX: [Double] = []
var PositivePartArrY: [Double] = []
var NegativePartArrY: [Double] = []
// cycle through the data sets and write positive and negative entries (abs < 1) in the acording arrays
for i in 0..<dataInput.count {
for entry in dataInput[i].0 {
if entry < 1 && entry > 0 {
PositivePartArrX.append(entry)
} else if entry > -1 && entry < 0 {
NegativePartArrX.append(entry)
}
}
for entry in dataInput[i].1 {
if entry < 1 && entry > 0 {
PositivePartArrY.append(entry)
} else if entry > -1 && entry < 0 {
NegativePartArrY.append(entry)
}
}
}
// calculate the highest absolute order of the positive and negative part (abs < 1). If the array is empty the order is 0
var orderPositivePartX: Int = 0
if PositivePartArrX != [] {
orderPositivePartX = order(input: PositivePartArrX.min()!)
}
var orderPositivePartY: Int = 0
if PositivePartArrY != [] {
orderPositivePartY = order(input: PositivePartArrY.min()!)
}
var orderNegativePartX: Int = 0
if NegativePartArrX != [] {
orderNegativePartX = order(input: NegativePartArrX.min()!)
}
var orderNegativePartY: Int = 0
if NegativePartArrY != [] {
orderNegativePartY = order(input: NegativePartArrY.min()!)
}
return (orderPositivePartX, orderNegativePartX, orderPositivePartY, orderNegativePartY)
}
// ********** setup the plot for log style plotting ******************
func logAxis(data: [([Double], [Double])], xAxis: Bool = false, yAxis: Bool = false) -> ([Double],[Double]) {
print("start preparing plot for log axis")
func logAxisRange(data: [Double]) -> ([Double]) {
// create arrays to hold the positive and negative parts of the input data
var dataPositive: [Double] = []
var dataNegative: [Double] = []
// cycle through data and copy the positive and negative entries in the regarding arrays
for i in data {
if i > 0 && i < Double.infinity {
dataPositive.append(i)
} else if i < 0 && i > -Double.infinity {
dataNegative.append(i)
}
}
// steps shown in the plot
let arr = [1.0, 2.0, 4.0, 6.0, 8.0]
// the data is split in positive and negative parts. The axis range for both parts are calculated seperatly
var logAxisPositive: [Double] = []
var logAxisNegative: [Double] = []
// if data < 0 exists:
if dataNegative != [] {
// calculating minimun and maximum (range) of the negative part
let minDataNegative = dataNegative.min()!
let maxDataNegative = dataNegative.max()!
// multiplying the step array with -1 and a multiple of 10 to get the drawn steps (gridlines and values) from the minimum to the maximum
for i in order(input: minDataNegative) ... order(input: maxDataNegative)+1 {
logAxisNegative = logAxisNegative + arr.map{-1 * $0 * (10 ** Double(i))}
}
// if the datasets have entries abs < 1 (log10 negative) multiplying all entries with 10 ** abs(negative order) to shift into the positive log10 range.
// this shift is later compensated and allows to distinguish from positive and negative data input by tho minus sign
if order(input: maxDataNegative) < 0 {
logAxisNegative = logAxisNegative.map{$0 * (10 ** Double(abs(order(input: maxDataNegative))))}
}
// reverse the negative part so that the axis draws from right to left
logAxisNegative.reverse()
}
// if data > 0 exists:
if dataPositive != [] {
// calculating minimun and maximum (range) of the negative part
let minDataPositive = dataPositive.min()!
let maxDataPositive = dataPositive.max()!
// multiplying the step array with a multiple of 10 to get the drawn steps (gridlines and values) from the minimum to the maximum
for i in order(input: maxDataPositive)-1 ... order(input: minDataPositive) {
logAxisPositive = logAxisPositive + arr.map{$0 * (10 ** Double(i))}
}
// if the datasets have entries < 1 (log10 negative) multiplying all entries with 10 ** abs(negative order) to shift into the positive log10 range.
// this shift is later compensated and allows to distinguish from positive and negative data input by tho minus sign
if order(input: minDataPositive) < 0 {
logAxisPositive = logAxisPositive.map{$0 * (10 ** Double(abs(order(input: minDataPositive))))}
}
}
// the logarithmized axis is set together from the negative and positive parts
let logAxis = logAxisNegative + logAxisPositive
return logAxis
}
var logXAxis: [Double] = []
var logYAxis: [Double] = []
if xAxis == true {
var xAxisData: [Double] = []
for i in 0..<data.count {
xAxisData = xAxisData + data[i].0
}
logXAxis = logAxisRange(data: xAxisData)
}
if yAxis == true {
var yAxisData: [Double] = []
for i in 0..<data.count {
yAxisData = yAxisData + data[i].1
}
logYAxis = logAxisRange(data: yAxisData)
}
print("done preparing plot for log axis")
return (logXAxis, logYAxis)
}
// ********** function to setup the plot for logarithmic plotting ******************
func setupPlotForLog (data: [([Double], [Double])], logXAxis: Bool = false, logYAxis: Bool = false) -> () {
if logXAxis == true {
plotArea.xAxisRenderer.logarithmic = true
plotArea.xAxisRenderer.logSetup = logAxis(data: data, xAxis: true).0
plotArea.xAxisRenderer.minOrderOfMagnitudePositive = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).0)
plotArea.xAxisRenderer.maxOrderOfMagnitudeNegative = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).1)
}
if logYAxis == true {
plotArea.leftYAxisRenderer .logarithmic = true
plotArea.leftYAxisRenderer.logSetup = logAxis(data: data, yAxis: true).1
plotArea.leftYAxisRenderer.minOrderOfMagnitudePositive = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).2)
plotArea.leftYAxisRenderer.maxOrderOfMagnitudeNegative = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).3)
}
}
possible that I forgot a step here. Please let me know if that works for you.
hello
i have some errors on "order" Use of unresolved identifier 'order'
order(input: maxDataNegative) order(input: minDataNegative)-1 etc.....
thanks