Tengo un gráfico de barras en una página que muestra datos que se actualizan dinámicamente. El problema que tengo es que necesito cambiar el tipo del eje y según los datos que se muestran. Necesito que sea una escala lineal cuando los datos tienen un rango de menos de 100 (por ejemplo, 0-99) y logarítmico si es mayor (por ejemplo, 0-1000).

Puedo calcular si necesito una escala logarítmica o no, pero cuando se trata de cambiar el tipo de escala estoy perdido. He intentado cambiar el objeto options del gráfico y llamar a update():

myChart.options/scales.yAxes.map(function (axis) { axis.type = 'logarithmic' });
myChart.update();

Y también intenté cambiar la escala directamente:

Object.keys(mychart.scales).forEach((function (axisName) {
            var axis = myChart.scales[axisName];
            if (axis && axis.id.startsWith('y')) {
                axis.options.type = 'logarithmic'
            }
        }));
myChart.update();

Luego intenté usar la devolución de llamada beforeBuildTicks, pensando que cambiar el tipo al actualizar los datos y volver a dibujar el gráfico puede ser demasiado tarde o en el momento equivocado:

beforeBuildTicks: function (scale) {
                                if (scale.chart) {
                                    console.log('logarithmic');
                                    scale.options.type = 'logarithmic';
                                 }
                            }

Todos estos enfoques cambian el objeto del gráfico, pero la visualización no se ve afectada.

¿Hay alguna manera de cambiar dinámicamente el tipo de escala de un gráfico de barras chartjs sin destruir y recrear todo el gráfico?

0
MikeW 6 mar. 2017 a las 15:23

2 respuestas

La mejor respuesta

Finalmente encontré una solución para esto, pero no es sencillo.

Gracias a @jordanwillis por el puntero en la dirección correcta en términos de uso de la función auxiliar `scaleMerge '.

Creé un plugin que hace todo el trabajo:

var changeScaleTypePlugin = {
beforeUpdate: function (chartInstance) {
    var self = this;
    chartInstance.beforeInit = null;
    if (chartInstance.options.changeScale) {
        self.changeScales(chartInstance);
        if (chartInstance.options.scaleTypeChanged) {
            chartInstance.options.scaleTypeChanged = false;
            Object.keys(chartInstance.scales).forEach(function (axisName) {
                var scale = chartInstance.scales[axisName];
                Chart.layoutService.removeBox(chartInstance, scale);
            });
            chartInstance.initialize();
        }
    }
},
changeScales: function (chartInstance) {
    var maxValue = Math.max.apply(null, chartInstance.data.datasets.map(function (dataset) { return Math.max.apply(null, dataset.data); }));
    var minValue = Math.min.apply(null, chartInstance.data.datasets.map(function (dataset) { return Math.min.apply(null, dataset.data); }));
    var logMax = Math.floor(Math.log(maxValue) / Math.LN10);
    var logMin = Math.floor(Math.log(minValue) / Math.LN10);
    if (logMax - logMin > chartInstance.options.maxRankDifference) {
        if (!chartInstance.options.scaleTypeChanged && chartInstance.options.scales.yAxes.filter(function (axis) { return axis.type !== 'logarithmic'; }).length) {
            console.log('logarithmic');
            chartInstance.options.scaleTypeChanged = true;
            chartInstance.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, { yAxes: chartInstance.options.logarithmicScaleOptions }).yAxes;                
        }
    } else {
        if (!chartInstance.options.scaleTypeChanged && chartInstance.options.scales.yAxes.filter(function (axis) { return axis.type !== 'linear'; }).length) {
            console.log('linear');
            chartInstance.options.scaleTypeChanged = true;
            chartInstance.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, { yAxes: chartInstance.options.linearScaleOptions }).yAxes;
        }
    }
}

};

Chart.pluginService.register (changeScaleTypePlugin);

Y para aplicar esto simplemente agregue algunas propiedades a las opciones del gráfico:

options: {
                     linearScaleOptions: [{
                        id: 'y-axis-0',
                         type: 'linear',
                         tick: {
                             // callback: Chart.Ticks.formatters.linear,
                             min: 0
                         }   
                     }],
                     logarithmicScaleOptions: [{
                        id: 'y-axis-0',
                        type: 'logarithmic',
                        ticks: {
                            // callback: function (tickValue, index, ticks)  {
                            //        var remain = tickValue / (Math.pow(10, Math.floor(Math.log(tickValue) / Math.LN10)));

                            //        if (tickValue === 0) {
                            //            return '0';
                            //        } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
                            //            return tickValue;
                            //        }
                            //        return '';
                            //},
                            min: 0
                        }
                    }],
                     changeScale: true,
                    maxRankDifference: 1,

La devolución de llamada comentada en los ticks es para la situación en la que (como yo) no desea que los valores de ticks se muestren en notación científica en la escala de registro.

Esto da el resultado que se ve así:

logarithmic

Con la propiedad de rango máximo establecida en 1

O

linear

Con esto establecido en 3

O con el conjunto de devolución de llamada:

logarithmic with natural ticks

1
MikeW 7 mar. 2017 a las 11:32

Estaba en el camino correcto, pero el problema es para cada tipo de escala, hay un conjunto correspondiente de propiedades que utiliza el servicio de escala subyacente para representar la escala.

Entonces, cuando configura su escala para que sea 'lineal', eso activa un conjunto de propiedades de eje ocultas que producen la escala lineal representada. Sin embargo, cuando configura su escala para que sea 'logarítmica', las propiedades del eje oculto deben establecerse de manera diferente para producir la escala logarítmica representada.

Para resumir, debe usar la función Chart.helpers.scaleMerge() para manejar todo esto por usted (no puede simplemente cambiar el tipo de escala en las opciones del gráfico y volver a renderizar).

Creé un ejemplo de trabajo que demuestra cómo lograr lo que está buscando. La esencia de cómo funciona esto se muestra a continuación.

document.getElementById('changeScaleAndData').addEventListener('click', function() {
  if (isLinear) {
    myBar.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, {yAxes: logarithmicScaleOptions}).yAxes;
    isLinear = false;
  } else {
    myBar.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, {yAxes: linearScaleOptions}).yAxes;
    isLinear = true;
  }

  myBar.data.datasets[0].data = [
    randomScalingFactor(), 
    randomScalingFactor(), 
    randomScalingFactor(), 
  ];

  myBar.update();
});
0
jordanwillis 6 mar. 2017 a las 17:42