【问题标题】:Does chartjs support negative values with logarithmic scale?chartjs 是否支持对数刻度的负值?
【发布时间】:2022-10-12 23:04:50
【问题描述】:

我想以对数刻度绘制 Y 值,其中值包括负数和正数。

这可能吗?将 min 设置为负值无效,它始终从零开始

【问题讨论】:

    标签: chart.js


    【解决方案1】:

    查看代码,对数刻度将最小值设置为 0(用于最小值和最大值)。

    https://github.com/chartjs/Chart.js/blob/master/src/scales/scale.logarithmic.js#L85-L96

    【讨论】:

      【解决方案2】:

      我最近遇到了同样的问题,我最初使用这个Log2 axis implementation,根据这个github issue,他们不打算在对数轴上支持负值。

      我有自己的想法,但经过一番研究后找不到类似的解决方案,所以我决定制作自己的 Log2Axis 版本,它在最新版本 v3.9.1 中运行良好(尤其是参数@987654326 @)。我的方法很简单:

      • 对于正值 > 2 >> 无变化
      • “-2”和base“2”之间的值将转换为[-1,1],以在从log2转换到y = x / 2时保持连续性(阅读下文),因此,零将为0(不是那个"log(0) = 0",但由于使用日志轴的主要原因是同时查看大值和小值,因此受限于负值的日志限制是没有意义的!)。
      • 低于-2 的负值将简单地转换为正值,同时保留它们的符号,如下所示:Math.sign(v) * Math.log2( Math.abs(v) )

      这样,我们可以在同一个区域中同时拥有正负、大值和小值,下面是一个活生生的例子:它是如何工作的:

      //LogAxis definition
      class LogAxis extends Chart.Scale{
        constructor(cfg){ super(cfg); this._startValue = undefined; this._valueRange = 0;
          const log = cfg.chart.config.options.scales.y.log || [2,Math.log2];
          this._base = log[0]; this._log = log[1];}
        parse(raw, index){
          const value = Chart.LinearScale.prototype.parse.apply(this, [raw, index]);
          return isFinite(value) ? value : null;
        }
        determineDataLimits(){
          const {min, max} = this.getMinMax(true);
          this.min = isFinite(min) ? min : null; this.max = isFinite(max) ? max : null;
        }
        buildTicks(){
          const ticks = [], aMin=Math.abs(this.min), aMax=Math.abs(this.max);
          if(aMin<=this._base){ticks.push({value: this._base*Math.sign(this.min)});}
          let v, power = Math.floor( (aMin>this._base ? Math.sign(this.min):1)*this._log( this.options.beginAtZero && this.min>0 ? 1 : (aMin>this._base ? aMin : 1) )),
            maxPower = Math.ceil( (aMax>this._base ? Math.sign(this.max):1)*this._log( this.options.beginAtZero && this.max<0 ? 1 : (aMax>this._base ? aMax : 1) ));
          while(power <= maxPower){
            ticks.push({value: Math.sign(power)*Math.pow(this._base, Math.abs(power)) }); power += 1;
          }
          if(aMax<=this._base){ticks.push({value: this._base*Math.sign(this.max)});}      
          v=ticks.map(x=>x.value);
          this.min = Math.min(...v); this.max = Math.max(...v);
          return ticks;
        }
        getLogVal(v){ var aV=Math.abs(v); return aV>this._base ? Math.sign(v)*this._log(aV) : v/this._base;}
        configure(){/* @protected*/
          const start = this.min; super.configure();
          this._startValue = this.getLogVal(start);
          this._valueRange = this.getLogVal(this.max) - this.getLogVal(start);
        }
        getPixelForValue(value){
          if(value === undefined){value = this.min;}
          return this.getPixelForDecimal( (this.getLogVal(value) - this._startValue) / this._valueRange);
        } 
        getValueForPixel(pixel){
          const decimal = this.getLogVal(this.getDecimalForPixel(pixel));
          return Math.pow(2, this._startValue + decimal * this._valueRange);
        }
      } LogAxis.id = 'mylog'; LogAxis.defaults = {}; Chart.register(LogAxis);
       Chart.register(LogAxis);
      
      //Utils and button handlers
      const Utils={
        RandomNumbers:function(num,min,max){ var i,nums=[];
          for(i=0;i<num;i++){
            nums.push( min+Math.round( (max-min)*Math.random() ));
          }
          return nums;
        }, Randomize:function(canvId,params){
          var chart = Chart.getChart(canvId), min= params[0], max= params[1];
          chart.data.datasets.forEach( (d,i) => {
            d.data = Utils.RandomNumbers(d.data.length,min,max);
          });
          chart.update();
        }
      };
      var maxLog2=10000, log2D0=0;
      bRandData1.onclick= function(e){Utils.Randomize('chart',[0,maxLog2]);};
      bRandData2.onclick= function(e){Utils.Randomize('chart',[-maxLog2,0]);};
      bRandData3.onclick= function(e){Utils.Randomize('chart',[-maxLog2,maxLog2]);};
      bToggle1.onclick= function(e){
        var chart=Chart.getChart("chart"), d0=chart.data.datasets[0].data[0];
        if(d0!=0){log2D0=d0}
        chart.data.datasets[0].data[0]= d0==0?log2D0:0;
        chart.update();
      };
      bToggle2.onclick= function(e){
        var chart=Chart.getChart("chart");
        chart.config.options.scales.y.beginAtZero = !chart.config.options.scales.y.beginAtZero;
        chart.update();
      };
      
      //Chart config
      const data ={
        labels:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        datasets:[ { label:'My Log2 Dataset',
          data: Utils.RandomNumbers(12,0,1000),
          backgroundColor:'blue',
          borderColor:'blue',
          borderWidth:2}
        ]
      }, config ={ type:'line', data:data,
        options:{ responsive:true,
          plugins:{
            title: {
              display:true,
              text:'Log Derived Axis Type',
              color:'#bb8899'
            }, tooltip: {
              interaction: {intersect:false, mode:'nearest', axis:'x'}
            }
          }, scales:{ x:{display:true},
            y:{
              display:true,
              beginAtZero:false,
              type:'mylog', // <-- you have to set the scale id 'mylog' to use LogAxis
              log: [2, Math.log2] // <-- a config array to change the log type directly from the chart options, without changing the class definition.
              // If omitted, it's [2, Math.log2] by default, but you can change it to [10, Math.log10] to use log10 or other log functions.
            }
          }
        }
      };
      const ctx = document.getElementById('chart').getContext('2d');
      new Chart(ctx,config);
      body{white-space:nowrap;}
      button{margin:3px 3px 3px 0;}
      <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
      
      <canvas id="chart"></canvas>
      <br>
      Randomize : <button id="bRandData1">Positive</button>
      <button id="bRandData2">Negative</button> 
      <button id="bRandData3">Both</button> | 
      Toggle : <button id="bToggle1">data[0]=0</button>
      <button id="bToggle2">beginAtZero</button>

      编辑我最初的解决方案不适用于 [-base,base] 之​​间的值(其中 base 是日志基数 2、10 等),但只有经过彻底调查后,我才能通过修改转换函数使其工作:

      x => Math.sign(x) * Math.log2(x) , for Math.abs(x) > 2
      x => x/2 , for Math.abs(x) <= 2
      

      现在您只需在options &gt; scales &gt; y 中设置“log”参数,而无需触及初始类LogAxis,它的工作方式相同,而且我发现 log2 更方便,因为与 log10 相比,它对小值有更多的可见性。

      为了更深入地了解它是如何工作的,您可以使用这个online graph maker,其中一个滑块有助于可视化区间 [-base,base] 以及日志函数和“填充”函数y = x / l

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-03
        • 2017-09-08
        • 2022-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多