【问题标题】:Intelligently calculating chart tick positions智能计算图表刻度位置
【发布时间】:2011-02-09 16:30:25
【问题描述】:

无论我使用 matplotlib、Open-Flash-Charts 还是其他图表框架,我总是最终需要找到一种方法来设置 x/y 比例限制和间隔,因为内置函数不够智能(或者根本没有...... )

只需在 pylab (ipyhton -pylab) 中尝试此操作即可理解我的意思:

In [1]: a, b, x = np.zeros(10), np.ones(10), np.arange(10)

In [2]: plot(x, a); plot(x, b)

你会看到一个空的框架网格,它隐藏了它的顶部和底部边框下的 2 条水平线。

我想知道是否有一些算法(我可以移植到 python)来巧妙地设置上下 y 限制和步骤,并计算每个有多少值显示 x 厚。

例如,假设我有 475 个度量,如 (datetime, temperature)(x, y)

2011-01-15 10:45:00 < datetime < 2011-01-17 02:20:00

(每 5 分钟一次)和

26.5 < temperature < 28.3

我对这种特殊情况的建议可能是设置:

26.4 &lt;= y_scale &lt;= 28.4加粗隔.2

每 12 个项目在 x_scale 上打勾(每小时一次)。

但是,如果我在 20 天内使用 -21.5 &lt; temperature &lt; 38.7 进行了 20 次测量,以此类推呢?有没有标准化的方法?

【问题讨论】:

    标签: python math graph


    【解决方案1】:

    以下是我多年来一直使用的,简单且效果很好。请原谅我是 C,但翻译成 Python 应该不难。

    需要以下功能,来自Graphic Gems第1卷。

    double NiceNumber (const double Value, const int Round) {
      int    Exponent;
      double Fraction;
      double NiceFraction;
    
      Exponent = (int) floor(log10(Value));
      Fraction = Value/pow(10, (double)Exponent);
    
      if (Round) {
        if (Fraction < 1.5) 
          NiceFraction = 1.0;
        else if (Fraction < 3.0)
          NiceFraction = 2.0;
        else if (Fraction < 7.0)
          NiceFraction = 5.0;
        else
          NiceFraction = 10.0;
       }
      else {
        if (Fraction <= 1.0)
          NiceFraction = 1.0;
        else if (Fraction <= 2.0)
          NiceFraction = 2.0;
        else if (Fraction <= 5.0)
          NiceFraction = 5.0;
        else
          NiceFraction = 10.0;
       }
    
      return NiceFraction*pow(10, (double)Exponent);
     }
    

    像下面的例子一样使用它,根据您希望显示的主要刻度数选择轴的“不错”开始/结束。如果您不关心刻度,您可以将其设置为恒定值(例如:10)。

          //Input parameters
      double AxisStart = 26.5;
      double AxisEnd   = 28.3;
      double NumTicks  = 10;
    
      double AxisWidth;
      double NewAxisStart;
      double NewAxisEnd;
      double NiceRange;
      double NiceTick;
    
        /* Check for special cases */
      AxisWidth = AxisEnd - AxisStart;
      if (AxisWidth == 0.0) return (0.0);
    
        /* Compute the new nice range and ticks */
      NiceRange = NiceNumber(AxisEnd - AxisStart, 0);
      NiceTick = NiceNumber(NiceRange/(NumTicks - 1), 1);
    
        /* Compute the new nice start and end values */
      NewAxisStart = floor(AxisStart/NiceTick)*NiceTick;
      NewAxisEnd = ceil(AxisEnd/NiceTick)*NiceTick;
    
      AxisStart = NewAxisStart; //26.4
      AxisEnd = NewAxisEnd;     //28.4
    

    【讨论】:

    • 越用越觉得它很聪明,谢谢分享。
    【解决方案2】:

    我在这里报告我上面 C 代码的 python 版本,如果它可能对某人有任何帮助:

    import math
    
    
    def nice_number(value, round_=False):
        '''nice_number(value, round_=False) -> float'''
        exponent = math.floor(math.log(value, 10))
        fraction = value / 10 ** exponent
    
        if round_:
            if fraction < 1.5:
                nice_fraction = 1.
            elif fraction < 3.:
                nice_fraction = 2.
            elif fraction < 7.:
                nice_fraction = 5.
            else:
                nice_fraction = 10.
        else:
            if fraction <= 1:
                nice_fraction = 1.
            elif fraction <= 2:
                nice_fraction = 2.
            elif fraction <= 5:
                nice_fraction = 5.
            else:
                nice_fraction = 10.
    
        return nice_fraction * 10 ** exponent
    
    
    def nice_bounds(axis_start, axis_end, num_ticks=10):
        '''
        nice_bounds(axis_start, axis_end, num_ticks=10) -> tuple
        @return: tuple as (nice_axis_start, nice_axis_end, nice_tick_width)
        '''
        axis_width = axis_end - axis_start
        if axis_width == 0:
            nice_tick = 0
        else:
            nice_range = nice_number(axis_width)
            nice_tick = nice_number(nice_range / (num_ticks - 1), round_=True)
            axis_start = math.floor(axis_start / nice_tick) * nice_tick
            axis_end = math.ceil(axis_end / nice_tick) * nice_tick
    
        return axis_start, axis_end, nice_tick
    

    用作:

    >>> nice_bounds(26.5, 28.3)
    (26.4, 28.4, 0.2)
    

    还添加一个 javascript 移植:

    function nice_number(value, round_){
        //default value for round_ is false
        round_ = round_ || false;
        // :latex: \log_y z = \frac{\log_x z}{\log_x y}
        var exponent = Math.floor(Math.log(value) / Math.log(10));
        var fraction = value / Math.pow(10, exponent);
    
        if (round_)
            if (fraction < 1.5)
                nice_fraction = 1.
            else if (fraction < 3.)
                nice_fraction = 2.
            else if (fraction < 7.)
                nice_fraction = 5.
            else
                nice_fraction = 10.
        else
            if (fraction <= 1)
                nice_fraction = 1.
            else if (fraction <= 2)
                nice_fraction = 2.
            else if (fraction <= 5)
                nice_fraction = 5.
            else
                nice_fraction = 10.
    
        return nice_fraction * Math.pow(10, exponent)
    }
    
    function nice_bounds(axis_start, axis_end, num_ticks){
        //default value is 10
        num_ticks = num_ticks || 10;
        var axis_width = axis_end - axis_start;
    
        if (axis_width == 0){
            axis_start -= .5
            axis_end += .5
            axis_width = axis_end - axis_start
        }
    
        var nice_range = nice_number(axis_width);
        var nice_tick = nice_number(nice_range / (num_ticks -1), true);
        var axis_start = Math.floor(axis_start / nice_tick) * nice_tick;
        var axis_end = Math.ceil(axis_end / nice_tick) * nice_tick;
        return {
            "min": axis_start,
            "max": axis_end,
            "steps": nice_tick
        }
    }
    

    【讨论】:

    • 你可以在 JavaScript 中使用Math.log10()
    【解决方案3】:

    下面是我的自动计算刻度的python代码,它需要数据范围和最大刻度数。

    例如:

    auto_tick([-120, 580], max_tick=10, tf_inside=False)
    Out[224]: array([-100.,   -0.,  100.,  200.,  300.,  400.,  500.])
    auto_tick([-120, 580], max_tick=20, tf_inside=False)
    Out[225]: array([-100.,  -50.,   -0.,   50.,  100.,  150.,  200.,  250.,  300., 350.,  400.,  450.,  500.,  550.])
    

    下面是函数的Python代码

    def auto_tick(data_range, max_tick=10, tf_inside=False):
        """
        tool function that automatically calculate optimal ticks based on range and the max number of ticks
        :param data_range:   range of data, e.g. [-0.1, 0.5]
        :param max_tick:     max number of ticks, an interger, default to 10
        :param tf_inside:    True/False if only allow ticks to be inside
        :return:             list of ticks
        """
        data_span = data_range[1] - data_range[0]
        scale = 10.0**np.floor(np.log10(data_span))    # scale of data as the order of 10, e.g. 1, 10, 100, 0.1, 0.01, ...
        list_tick_size_nmlz = [5.0, 2.0, 1.0, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01]   # possible tick sizes for normalized data in range [1, 10]
        tick_size_nmlz = 1.0     # initial tick size for normalized data
        for i in range(len(list_tick_size_nmlz)):                 # every loop reduces tick size thus increases tick number
            num_tick = data_span/scale/list_tick_size_nmlz[i]     # number of ticks for the current tick size
            if num_tick > max_tick:                               # if too many ticks, break loop
                tick_size_nmlz = list_tick_size_nmlz[i-1]
                break
        tick_size = tick_size_nmlz * scale             # tick sizse for the original data
        ticks = np.unique(np.arange(data_range[0]/tick_size, data_range[1]/tick_size).round())*tick_size    # list of ticks
    
        if tf_inside:     # if only allow ticks within the given range
            ticks = ticks[ (ticks>=data_range[0]) * (ticks<=data_range[1])]
    
        return ticks
    

    【讨论】:

      【解决方案4】:

      这是 TypeScript / JavaScript ES6 中 @uesp answer 的版本:

      function niceNumber(value: number, round = false) {
      
        const exponent = Math.floor(Math.log10(value));
        const fraction = value / Math.pow(10, exponent);
      
        let niceFraction: number;
      
        if (round) {
          if (fraction < 1.5) {
            niceFraction = 1.0;
          } else if (fraction < 3.0) {
            niceFraction = 2.0;
          } else if (fraction < 7.0) {
            niceFraction = 5.0;
          } else {
            niceFraction = 10.0;
          }
        } else {
          if (fraction <= 1.0) {
            niceFraction = 1.0;
          } else if (fraction <= 2.0) {
            niceFraction = 2.0;
          } else if (fraction <= 5.0) {
            niceFraction = 5.0;
          } else {
            niceFraction = 10.0;
          }
        }
      
        return niceFraction * Math.pow(10, exponent);
      
      }
      
      export function calcTicks(minValue: number, maxValue: number, ticksCount: number) {
      
        const range = niceNumber(maxValue - minValue);
      
        const tickValue = niceNumber(range / (ticksCount - 1), true);
      
        const ticks: number[] = [];
        for (let i = 0; i < ticksCount; i++) {
          ticks.push(minValue + tickValue * i);
        }
      
        return ticks;
      
      }
      

      但是,calcTicks() 函数在这里返回一个刻度数组,而不是开始和结束边界。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-07
        • 1970-01-01
        相关资源
        最近更新 更多