Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logarithmic Axis #109

Open
zimonjonez opened this issue May 27, 2015 · 51 comments
Open

Logarithmic Axis #109

zimonjonez opened this issue May 27, 2015 · 51 comments

Comments

@zimonjonez
Copy link

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

@danielgindi
Copy link
Collaborator

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 notifications@github.com
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
#109.

@zimonjonez
Copy link
Author

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)
screen shot 2015-05-27 at 9 40 10 pm

@danielgindi
Copy link
Collaborator

You can see that it is on the table, it is planned... PhilJay/MPAndroidChart#12

@danielgindi
Copy link
Collaborator

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!

@bdhammel
Copy link

bdhammel commented Dec 1, 2015

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!

@liuxuan30
Copy link
Member

@danielgindi can this be a part of in #194 ? since they are all behaving like y axis

@RyGuyM
Copy link

RyGuyM commented Mar 16, 2016

+1

@dodikk
Copy link

dodikk commented Jul 5, 2016

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.

@rooparaman
Copy link

@danielgindi , does the library support log scale for Y axis ?

@liuxuan30
Copy link
Member

liuxuan30 commented Sep 6, 2016

@xxxrr I suppose current master branch should support both X and Y?
@danielgindi We could close this if v3 did the job?

@rooparaman
Copy link

@liuxuan30 Thanks a lot.. Can you let me know how to configure it ? I am not able to get any references ..

@liuxuan30
Copy link
Member

liuxuan30 commented Sep 6, 2016

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

@rooparaman
Copy link

@liuxuan30 Thanks.. But not able to find things related to Logarithmic scale in it..Will look deeper into v3.. Thanks..

@liuxuan30
Copy link
Member

liuxuan30 commented Sep 8, 2016

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

@ThomasGoujon
Copy link

Hi, any updates about this ?

@liuxuan30
Copy link
Member

liuxuan30 commented Nov 16, 2016

@ThomasGoujon I think this can be solved by Chart 3.0 (except for those axis styles)

@ashrobo
Copy link

ashrobo commented Nov 27, 2016

Hi @liuxuan30 @danielgindi, how would I go about plotting Y values logarithmically using Charts 3.0?

@liuxuan30
Copy link
Member

just calculate the x, y and draw it!

@ashrobo
Copy link

ashrobo commented Nov 29, 2016

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?

@liuxuan30
Copy link
Member

oh I see, you are talking about y axis labels. By default it can't, but you can override computeAxisValues() to feed your own.

@SpaceDuster
Copy link

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

@liuxuan30
Copy link
Member

liuxuan30 commented Dec 6, 2016

@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.

@brittanygraft
Copy link

@AshRobinson would you be willing to share how you overrode computeAxisValues() to draw the Y axis logarithmically?

@brittanygraft
Copy link

@danielgindi Great library! Very appreciative! Has the ability to scale the Y axis logarithmically been added yet?

@liuxuan30
Copy link
Member

No, I guess it's not a feature that many people needs.

@brittanygraft
Copy link

@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?

@brittanygraft
Copy link

@danielgindi could you give me an idea of where to start when trying to rescale the Y axis?

@liuxuan30
Copy link
Member

@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.

@SpaceDuster
Copy link

SpaceDuster commented Jan 3, 2017

What I figured out so far:

  1. 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
        }
  1. 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.

@thierryH91200
Copy link
Contributor

hello

i have some errors on
"order" Use of unresolved identifier 'order'

order(input: maxDataNegative)
order(input: minDataNegative)-1
etc.....

thanks

@SpaceDuster
Copy link

OK. There was a big junk of code missing. I updated my previous post.
This is so complicated because I also wanted it to work with negative values and values < 1.
If this ins't required the whole thing can be reduced very much

@thierryH91200
Copy link
Contributor

i have replace 10 ** ... by
log10( ...)
or
log (10, ...)

and
public func prepareDataToPlot(dataInput: [([Double], [Double])], labels: [String] = [], marker: Array<ScatterChartDataSet.Shape> = [], color: Array = [], logX: Bool = false, logY: Bool = false )->([ScatterChartDataSet]) {

is duplicate

@SpaceDuster
Copy link

Deleted the duplicated section.
For the ** (operator for potentiation) to work you have to add the following to your project:

infix operator **
public func ** (radix: Double, power: Double) -> Double {
return pow((radix), (power))
}

@thierryH91200
Copy link
Contributor

I'm almost there

but how use

dataInput : [([Double], [Double])] ???

@SpaceDuster
Copy link

this are the x and y coordinates of datasets you want to plot: e.g. dataSets = [([1.0, 2.0, 3.0], [-20.0, -10.0, -8.0), ([3.0, 4.0, 5.0, 6.0, 7.0], [0.2, 0.4, 0.6, 6.0, 8.0])]
or just a single data set: dataSet = [([1.0, 2.0, 3.0], [-20.0, -10.0, -8.0])]

@thierryH91200
Copy link
Contributor

And here is the result
This will help me a lot in a "scatter"

capture d ecran 2017-01-03 a 17 34 57

thanks

@thierryH91200
Copy link
Contributor

thierryH91200 commented Jan 3, 2017

another sample for @brittanygraft

Before
capture d ecran 2017-01-03 a 18 48 44

After
capture d ecran 2017-01-03 a 18 08 27

@thierryH91200
Copy link
Contributor

Hello I think it should also change YAxisRenderer as for XAxisrenderer
I think you have forgotten this step
Because you can not make log / log but only log / lin
could you help me ??
thank you

@thierryH91200
Copy link
Contributor

@SpaceDuster
i have modified To make it more dynamic

// ********** function to setup the plot for logarithmic plotting ******************
func setupPlotForLog (data: [([Double], [Double])], logXAxis: Bool = false, logYAxis: Bool = false) -> ()

// ********** function to setup the plot for logarithmic plotting ******************
func setupPlotForLog (data: [([Double], [Double])], logXAxis: Bool = false, logYAxis: Bool = false) -> ()
{
    if logXAxis == true {
        lineChartView.xAxisRenderer.logarithmic = true
        lineChartView.xAxisRenderer.logSetup = logAxis(data: data, xAxis: true).0
        lineChartView.xAxisRenderer.minOrderOfMagnitudePositive = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).0)
        lineChartView.xAxisRenderer.maxOrderOfMagnitudeNegative = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).1)
    }
    else
    {
        lineChartView.xAxisRenderer.minOrderOfMagnitudePositive = 0
        lineChartView.xAxisRenderer.maxOrderOfMagnitudeNegative = 0
        lineChartView.xAxisRenderer.logarithmic = false
        
    }
    
    if logYAxis == true {
        lineChartView.leftYAxisRenderer .logarithmic = true
        lineChartView.leftYAxisRenderer.logSetup = logAxis(data: data, yAxis: true).1
        lineChartView.leftYAxisRenderer.minOrderOfMagnitudePositive = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).2)
        lineChartView.leftYAxisRenderer.maxOrderOfMagnitudeNegative = abs(negativeOrderOfMagnitudeOfDataSets(dataInput: data).3)
    }
    else
    {
        lineChartView.leftYAxisRenderer.minOrderOfMagnitudePositive = 0
        lineChartView.leftYAxisRenderer.maxOrderOfMagnitudeNegative = 0
        lineChartView.leftYAxisRenderer.logarithmic = false
        
    }
}

How to make changes to YAxisRenderer ??

@thierryH91200
Copy link
Contributor

For what it interests
It is in beta version
Https://github.com/thierryH91200/Charts-log
I have a problem for axis oy
It's in the macos part

@thierryH91200
Copy link
Contributor

thierryH91200 commented Jan 14, 2017

Hello @SpaceDuster , @brittanygraft

I have done cleaning to be able to better integrate in Charts
I have one problems
If anyone could help me
You will find here 2 screenshots showing the problems

capture d ecran 2017-01-14 a 12 22 23

capture d ecran 2017-01-14 a 12 24 34

@liuxuan30
Copy link
Member

For the out of bound issue, you can simply check the lower labels y point is bigger than contentRect's origin.y + contentRect.height

@thierryH91200
Copy link
Contributor

It's almost the same problem as before

  • Either I decrease the size of the font
  • Either I change the format
  • Either you have an idea

If not everything is ok

Would there be beta testers among you ?? To test all possible combinations

capture d ecran 2017-01-17 a 16 14 18

capture d ecran 2017-01-17 a 16 14 04

@desnyki
Copy link

desnyki commented Feb 17, 2017

Why not scale your linear data to log using x'i = (log(xi)-log(xmin)) / (log(xmax)-log(xmin))​

Then subclass xAxisRenderer to override computeAxisValues and drawLabels for drawing the grid and labels using that formula.

I achieved this result for a LPF

jgvb33x

This is all you need to do to set up your values.

XAxisLogRenderer *xAxisRenderer = [[XAxisLogRenderer alloc] initWithViewPortHandler:_lineChart.viewPortHandler
                                                       xAxis:_lineChart.xAxis
                                                       transformer:[_lineChart getTransformerForAxis:AxisDependencyLeft]];
_lineChart.xAxisRenderer = xAxisRenderer;

values = [NSMutableArray array];
    int xMin = 20;
    int xMax = 1000;
    int scale = xMax - xMin - 1;
    for (int i = xMin; i <=xMax; i+=10)
    {
        [values addObject:[[ChartDataEntry alloc] initWithX:(int)(xMin + ((double)scale * (log10((double)i/(double)xMin)/log10((double)xMax/(double)xMin)))) y:xMin*log10([self lpf:i centerFreq:100])]];
    }

Save this file in your project, you might want to modify # of labels and interval to your liking.

//
//  XAxisLogRenderer.swift
//  Charts
//
//  Created by Marcin Deszczynski on 2/16/17.
//
//

import UIKit
import Foundation
import CoreGraphics

@objc class XAxisLogRenderer: XAxisRenderer {
    
    public var labelInterval: Double = 0.0
  
    open override func computeAxisValues(min: Double, max: Double)
    {

            guard let axis = self.axis else { return }
            
            let xMin = min
            let xMax = max
            
            let range = abs(xMax - xMin)
//            let labelCount = Int(floor(range/interval))+1
            let labelCount = 18

            if labelCount == 0 || range <= 0 || range.isInfinite
            {
                axis.entries = [Double]()
                axis.centeredEntries = [Double]()
                return
            }
            
            var n = axis.centerAxisLabelsEnabled ? 1 : 0
            
            // Ensure stops contains at least n elements.
            axis.entries.removeAll(keepingCapacity: true)
            axis.entries.reserveCapacity(labelCount)
            var v = xMin
            var interval = 20.0;
            for _ in 0 ..< labelCount
            {
                /*
                 Linear to Log scale
                 x = fMin * (Log10(f/fMin)/Log10(fMax/fMin))
                 */
                v = xMin + (Double(range) * (log10(Double(interval) / Double(xMin)) / log10(Double(xMax + 1) / Double(xMin))))
                axis.entries.append(v)
                if interval < 100 {
                    interval += 10.0
                } else {
                    interval += 100.0
                }
            }
            
            n = labelCount
            
            // set decimals
            if interval < 1
            {
                axis.decimals = Int(ceil(-log10(interval)))
            }
            else
            {
                axis.decimals = 0
            }
            
            if axis.centerAxisLabelsEnabled
            {
                axis.centeredEntries.reserveCapacity(n)
                axis.centeredEntries.removeAll()
                
                let offset: Double = interval / 2.0
                
                for i in 0 ..< n
                {
                    axis.centeredEntries.append(axis.entries[i] + offset)
                }
            }
        computeSize()
    }
    open override func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint)
    {
        guard
            let xAxis = self.axis as? XAxis,
            let viewPortHandler = self.viewPortHandler,
            let transformer = self.transformer
            else { return }
        
        #if os(OSX)
            let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle
        #else
            let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        #endif
        paraStyle.alignment = .center
        
        let labelAttrs = [NSFontAttributeName: xAxis.labelFont,
                          NSForegroundColorAttributeName: xAxis.labelTextColor,
                          NSParagraphStyleAttributeName: paraStyle] as [String : NSObject]
        let labelRotationAngleRadians = xAxis.labelRotationAngle * ChartUtils.Math.FDEG2RAD
        
        let centeringEnabled = xAxis.isCenterAxisLabelsEnabled
        
        let valueToPixelMatrix = transformer.valueToPixelMatrix
        
        var position = CGPoint(x: 0.0, y: 0.0)
        
        var labelMaxSize = CGSize()
        
        if xAxis.isWordWrapEnabled
        {
            labelMaxSize.width = xAxis.wordWrapWidthPercent * valueToPixelMatrix.a
        }
        
        let entries = xAxis.entries
        
        for i in stride(from: 0, to: entries.count, by: 1)
        {
            if centeringEnabled
            {
                position.x = CGFloat(xAxis.centeredEntries[i])
            }
            else
            {
                position.x = CGFloat(entries[i])
            }
            
            position.y = 0.0
            position = position.applying(valueToPixelMatrix)
            
            if viewPortHandler.isInBoundsX(position.x)
            {
                /*
                 Log to linear scale
                 f = fMin * (fMax/fMin)^(x/s)
                 */
                
//                let label = xAxis.valueFormatter?.stringForValue(20.0 * pow(1000.0 / 20.0, (xAxis.entries[i] - 20.0) / 979.0), axis: xAxis) ?? ""
                let xMax = xAxis.axisMaximum + 1
                let xMin = xAxis.axisMinimum
                let range = xMax - xMin - 1
                print("xMax \(xMax) xMin \(xMin) range \(range)");
                let label = xAxis.valueFormatter?.stringForValue(xMin * pow(xMax / xMin, (xAxis.entries[i] - xMin) / range), axis: xAxis) ?? ""

                let labelns = label as NSString
                
                if xAxis.isAvoidFirstLastClippingEnabled
                {
                    // avoid clipping of the last
                    if i == xAxis.entryCount - 1 && xAxis.entryCount > 1
                    {
                        let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
                        
                        if width > viewPortHandler.offsetRight * 2.0
                            && position.x + width > viewPortHandler.chartWidth
                        {
                            position.x -= width / 2.0
                        }
                    }
                    else if i == 0
                    { // avoid clipping of the first
                        let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
                        position.x += width / 2.0
                    }
                }
                
                drawLabel(context: context,
                          formattedLabel: label,
                          x: position.x,
                          y: pos,
                          attributes: labelAttrs,
                          constrainedToSize: labelMaxSize,
                          anchor: anchor,
                          angleRadians: labelRotationAngleRadians)
            }
        }
    }
}

@thierryH91200
Copy link
Contributor

@desnyki

You are definitely right
But my project is almost completed
I have the ability to switch easily from lin to log and the opposite
I can easily set the grid, the sticks and set the labels I want

LineChartView.xAxis.logarithmicEnabled = true / false
LineChartView.leftAxis.logarithmicEnabled = true / false

Optional

Possibility of modifying the grid:

LineChartView.xAxis.stepsAxis = [1.0, 2.0, 4.0, 6.0, 8.0,])
LineChartView.xAxis.stepsLabels = [true, false, false, false, false]

LineChartView.leftAxis.stepsLabels = [true, false, false, false, false, false, false, false, false]
LineChartView.leftAxis.stepsAxis = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

Possibility of modifying the grid with stick

LineChartView.xAxis.stick = false or true
LineChartView.leftAxis.stick = false or true

I just pass the data

DataEntriesSet.append (ChartDataEntry (x: log10 (x), y: log10 (y))

@jandyman
Copy link

jandyman commented May 12, 2018

I'm not clear on this. Is adding log plot capability that "just works" on the road map or not? I just got really excited about moving to Charts from CorePlot, but this excitement came to a screeching halt when I discovered the lack of log plotting capability. Seems like a very basic scientific plotting requirement. I could jump in and try to retrofit it as in all the posts above, but sticking with CorePlot is probably a path of lesser resisistance. Which is too bad, since I really like the look of Charts and that it is pure Swift.

@Gopabandhu17
Copy link

@thierryH91200

I gone through your suggestion but while implementing not getting any property for xAxis as you suggest.I have to set xAxis values as log value.

@thierryH91200
Copy link
Contributor

thierryH91200 commented Feb 4, 2019

@Gopabandhu17
Copy link

@thierryH91200

I have tried to implement what ever written in README file. but still having that same problem.
Can you just tell me that to get access of logarithmic functionality , do i need to implement any specific delegate or have to use any function or what ever that i need to have in my code. or am i not getting the point how to use this functionality.

@thierryH91200
Copy link
Contributor

look at the sample

what is your version of chart
what are your mistakes exactly

@n13
Copy link

n13 commented Sep 7, 2019

Why not just change the Transformer for a LogTransformer that just applies a log at the end... seems like then everything would just work?!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests