Charts: automatic min estimation

Issue #217 resolved
Anton Fedorov created an issue

Currently min is hardcoded to zero. Need to fix Chart._compute_min_max to recalculate .min if it None

Comments (9)

  1. Anton Fedorov reporter

    Here is my version of function. Working fine for me :)

        def _compute_min_max(self):
            """ compute y axis limits and units """
            # Algo from LibreOffice/ScaleAutomatism::calculateExplicitIncrementAndScaleForLinear
    
            # Step 1
            srcMin = min([self.mymin(s.values) for s in self._series if s.values])
            srcMax = max([self.mymax(s.values) for s in self._series if s.values])
    
            swapNeg = (srcMin<0.0) and (srcMax<0.0)
            if swapNeg:
                srcMin, srcMax = -srcMax, -srcMin
    
            # Step 2
            tmpMin, tmpMax = srcMin, srcMax
    
            if tmpMin > 0.0:
                if tmpMin == tmpMax or tmpMin/tmpMax < 5.0/6.0:
                    if False: # Expand wide values to zero
                        tmpMin = 0.0
                else:
                    if False: # Expand narrow valuesto zero
                        tmpMin -= (tmpMax-tmpMin)/2.0
    
            if tmpMin == tmpMax:
                if tmpMax==0.0:
                    tmpMax = 1.0
                else:
                    tmpMax *= 2.0
    
            # Step 3
            maxTicks = 7
            distMag = 0.0
            distNorm = 0.0
            dist = 0.0
            hasDist = False
            needIter = True
            while needIter:
                if not hasDist:
                    dist = (tmpMax-tmpMin) / maxTicks
                    if dist <= 1.0e-307:
                        distNorm = 1.0
                        distMag = 1.0e-307
                    else:
                        exp = math.floor(math.log10(dist))
                        distMag = math.pow(10.0, exp)
                        distNorm = dist / distMag
                        if distNorm <= 1.0:
                            distNorm = 1.0
                        elif distNorm <= 2.0:
                            distNorm = 2.0
                        elif distNorm <= 5.0:
                            distNorm = 5.0
                        else:
                            distNorm = 1.0
                            distMag *= 10.0
                        hasDist = True
                else:
                    if distNorm == 1.0:
                        distNorm = 2.0
                    elif distNorm == 2.0:
                        distNorm = 5.0
                    else:
                        distNorm = 1.0
                        distMag *= 10.0
                dist = distNorm * distMag
    
                # Step 4
                #axMin = calcMin( tmpMin, dist )
                axMin = math.floor( tmpMin / dist ) * dist
                if axMin > tmpMin-distMag/1e15:
                    axMin -= dist
    
                #axMax = calcMax( tmpMax, dist )
                axMax = math.ceil( tmpMax / dist ) * dist
                if axMax < tmpMax+distMag/1e15:
                    axMax += dist
    
                if swapNeg:
                    axMin, axMax = -axMax, -axMin
                ticks = math.floor( (axMax-axMin)/dist )
                needIter = ticks > maxTicks
    
            self.y_axis.max = axMax
            self.y_axis.min = axMin
            self.y_axis.unit = dist
    
  2. Anton Fedorov reporter

    Just plain usage of log10 are gives not best result in case of rounding and when working near little values (0<all<1)

  3. CharlieC

    That's a fair point. I made the changes to be able to handle negative values and refactored not to use strings to gauge magnitude and to allow for unit tests.

    Without a detailed review and test cases I can't be sure but I think you would only need to submit a patch/pull request (with tests!) for my _max_min() method, I'd be happy to include it.

  4. Anton Fedorov reporter

    Thanks, i'll try your implementation, and probable port to your one that logic. Have not read yet your code, but: do you assume always that min and max are "auto"? do you plan to support override of some of boundary (min/max) and auto-calculate other half?

    === and btw:

    Anyone still support single openpyxl instance?

    I'm prefer to just "easy_install openpyxl" in bootstrap script, rather than installing via checkout of some branch from somewhere...

  5. CharlieC

    I have some working code where you can actively set axes' min and max but it's a bit hit and miss (the values are set and readable but not always reflected in the resultant charts). Although I have changed how things work I have tried to keep behaviour the same for existing users, so the default is auto-calculation. It's probably a bit over the top at the moment to ensure this.

    I can add a download of my current status so this will at least work with easy_install either with a local download or by finding links. I think that's the best we can do at the moment without making an official fork of the project.

  6. CharlieC

    @datacompboy my fix is in the 1.8 branch and includes support for overriding auto axis calculation for charts. Release should be sometime soon.

  7. Log in to comment