2👍
I finally found a solution to this, but it isn’t straightforward.
Thanks to @jordanwillis for the pointer in the right direction in terms of using the scaleMerge()
helper function.
I created a plugin that does all the work:
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);
To apply this simply add a few properties to the options of the chart:
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,
}
The commented out callback on the ticks is for the situation where (like me) you do not want the tick values shown in scientific notation on the log scale.
This gives the output that looks like:
with the max rank property set to 1
or
with it set to 3
or with the callback set:
1👍
You were on the right track, but the problem is for each scale type, there is a corresponding set of properties that are used by the underlying scale service to render the scale.
So when you configure your scale to be ‘linear’, that triggers a set of hidden axis properties to be set that produce the rendered linear scale. However, when you configure your scale to be ‘logarithmic’, the hidden axis properties need to be set differently to produce the rendered logarithmic scale.
Long story short, you must use the Chart.helpers.scaleMerge()
function to handle all this for you (you can’t just simple change the scale type in the chart options and re-render).
I created a working example demonstrating how to achieve what you are after. The essence of how this works is displayed below.
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👍
I find it easier to delete and recreate the chart after changing the axis type. IE if I have a checkbox with the id logScale to set whether my axis is log, I can just say:
$("#logScale").change(function () {
$('#chart').remove();
createChart();
})
where createChart is the code to create the chart. Note that I reference #logScale to set the Y axis type:
$('#chartContainer').append('<canvas id="chart" style="max-height:300px;max-width:95%"></canvas>');
var axisType = $("#logScale").prop("checked") ? 'logarithmic' : 'linear';
ctx = document.getElementById('chart').getContext('2d');
chartObject = new Chart(ctx, {
options: {
scales: {
xAxes: [{
yAxes: [{
type: axisType
}]
} //and whatever other chart data you have