Chart.js/src/scales/scale.logarithmic.js
Simon Brunel 82b1e5cd99 Handle effective dataset visibility per chart
Introduced a new meta.hidden 3 states flag (null|true|false) to be able to override dataset.hidden when interacting with the chart (i.e., true or false to ignore the dataset.hidden value). This is required in order to be able to correctly share dataset.hidden between multiple charts.

For example: 2 charts are sharing the same data and dataset.hidden is initially false: the dataset will be displayed on both charts because meta.hidden is null. If the user clicks the legend of the first chart, meta.hidden is changed to true and the dataset is only hidden on the first chart. If dataset.hidden changes, only the second chart will have the dataset visibility updated and that until the user click again on the first chart legend, switching the meta.hidden to null.
2016-04-26 12:46:27 +02:00

197 lines
6.1 KiB
JavaScript

"use strict";
module.exports = function(Chart) {
var helpers = Chart.helpers;
var defaultConfig = {
position: "left",
// label settings
ticks: {
callback: function(value, index, arr) {
var remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value))));
if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
return value.toExponential();
} else {
return '';
}
}
}
};
var LogarithmicScale = Chart.Scale.extend({
determineDataLimits: function() {
// Calculate Range
this.min = null;
this.max = null;
if (this.options.stacked) {
var valuesPerType = {};
helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
var meta = this.chart.getDatasetMeta(datasetIndex);
if (this.chart.isDatasetVisible(datasetIndex) && (this.isHorizontal() ? meta.xAxisID === this.id : meta.yAxisID === this.id)) {
if (valuesPerType[meta.type] === undefined) {
valuesPerType[meta.type] = [];
}
helpers.each(dataset.data, function(rawValue, index) {
var values = valuesPerType[meta.type];
var value = +this.getRightValue(rawValue);
if (isNaN(value)) {
return;
}
values[index] = values[index] || 0;
if (this.options.relativePoints) {
values[index] = 100;
} else {
// Don't need to split positive and negative since the log scale can't handle a 0 crossing
values[index] += value;
}
}, this);
}
}, this);
helpers.each(valuesPerType, function(valuesForType) {
var minVal = helpers.min(valuesForType);
var maxVal = helpers.max(valuesForType);
this.min = this.min === null ? minVal : Math.min(this.min, minVal);
this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
}, this);
} else {
helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
var meta = this.chart.getDatasetMeta(datasetIndex);
if (this.chart.isDatasetVisible(dataset, datasetIndex) && (this.isHorizontal() ? meta.xAxisID === this.id : meta.yAxisID === this.id)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +this.getRightValue(rawValue);
if (isNaN(value)) {
return;
}
if (this.min === null) {
this.min = value;
} else if (value < this.min) {
this.min = value;
}
if (this.max === null) {
this.max = value;
} else if (value > this.max) {
this.max = value;
}
}, this);
}
}, this);
}
this.min = this.options.ticks.min !== undefined ? this.options.ticks.min : this.min;
this.max = this.options.ticks.max !== undefined ? this.options.ticks.max : this.max;
if (this.min === this.max) {
if (this.min !== 0 && this.min !== null) {
this.min = Math.pow(10, Math.floor(helpers.log10(this.min)) - 1);
this.max = Math.pow(10, Math.floor(helpers.log10(this.max)) + 1);
} else {
this.min = 1;
this.max = 10;
}
}
},
buildTicks: function() {
// Reset the ticks array. Later on, we will draw a grid line at these positions
// The array simply contains the numerical value of the spots where ticks will be
this.ticks = [];
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var tickVal = this.options.ticks.min !== undefined ? this.options.ticks.min : Math.pow(10, Math.floor(helpers.log10(this.min)));
while (tickVal < this.max) {
this.ticks.push(tickVal);
var exp = Math.floor(helpers.log10(tickVal));
var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
if (significand === 10) {
significand = 1;
++exp;
}
tickVal = significand * Math.pow(10, exp);
}
var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal;
this.ticks.push(lastTick);
if (this.options.position === "left" || this.options.position === "right") {
// We are in a vertical orientation. The top value is the highest. So reverse the array
this.ticks.reverse();
}
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
this.max = helpers.max(this.ticks);
this.min = helpers.min(this.ticks);
if (this.options.ticks.reverse) {
this.ticks.reverse();
this.start = this.max;
this.end = this.min;
} else {
this.start = this.min;
this.end = this.max;
}
},
convertTicksToLabels: function() {
this.tickValues = this.ticks.slice();
Chart.Scale.prototype.convertTicksToLabels.call(this);
},
// Get the correct tooltip label
getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
},
getPixelForTick: function(index, includeOffset) {
return this.getPixelForValue(this.tickValues[index], null, null, includeOffset);
},
getPixelForValue: function(value, index, datasetIndex, includeOffset) {
var pixel;
var newVal = +this.getRightValue(value);
var range = helpers.log10(this.end) - helpers.log10(this.start);
if (this.isHorizontal()) {
if (newVal === 0) {
pixel = this.left + this.paddingLeft;
} else {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
pixel = this.left + (innerWidth / range * (helpers.log10(newVal) - helpers.log10(this.start)));
pixel += this.paddingLeft;
}
} else {
// Bottom - top since pixels increase downard on a screen
if (newVal === 0) {
pixel = this.top + this.paddingTop;
} else {
var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (helpers.log10(newVal) - helpers.log10(this.start)));
}
}
return pixel;
}
});
Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig);
};