From 1cbba830fdef5ac25f997deb5ae0683db9321a79 Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Thu, 4 May 2017 21:34:23 -0700 Subject: [PATCH] Refactor time scale methods into a common location --- src/chart.js | 2 + src/core/core.ticks.js | 4 +- src/helpers/helpers.time.js | 196 +++++++++++++++++++++++++++++++++++ src/scales/scale.time.js | 200 ++---------------------------------- 4 files changed, 211 insertions(+), 191 deletions(-) create mode 100644 src/helpers/helpers.time.js diff --git a/src/chart.js b/src/chart.js index 4771b6d65..f36d240c2 100644 --- a/src/chart.js +++ b/src/chart.js @@ -4,6 +4,8 @@ var Chart = require('./core/core.js')(); require('./core/core.helpers')(Chart); +require('./helpers/helpers.time')(Chart); + require('./platforms/platform.js')(Chart); require('./core/core.canvasHelpers')(Chart); require('./core/core.element')(Chart); diff --git a/src/core/core.ticks.js b/src/core/core.ticks.js index 8d84f1f23..6a6794192 100644 --- a/src/core/core.ticks.js +++ b/src/core/core.ticks.js @@ -141,7 +141,9 @@ module.exports = function(Chart) { ticks.push(lastTick); return ticks; - } + }, + + time: helpers.time.generateTicks }, /** diff --git a/src/helpers/helpers.time.js b/src/helpers/helpers.time.js new file mode 100644 index 000000000..e055fb2b1 --- /dev/null +++ b/src/helpers/helpers.time.js @@ -0,0 +1,196 @@ +'use strict'; + +var moment = require('moment'); +moment = typeof(moment) === 'function' ? moment : window.moment; + +module.exports = function(Chart) { + + var interval = { + millisecond: { + size: 1, + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, + second: { + size: 1000, + steps: [1, 2, 5, 10, 30] + }, + minute: { + size: 60000, + steps: [1, 2, 5, 10, 30] + }, + hour: { + size: 3600000, + steps: [1, 2, 3, 6, 12] + }, + day: { + size: 86400000, + steps: [1, 2, 5] + }, + week: { + size: 604800000, + maxStep: 4 + }, + month: { + size: 2.628e9, + maxStep: 3 + }, + quarter: { + size: 7.884e9, + maxStep: 4 + }, + year: { + size: 3.154e10, + maxStep: false + } + }; + + /** + * Helper for generating axis labels. + * @param options {ITimeGeneratorOptions} the options for generation + * @param dataRange {IRange} the data range + * @param niceRange {IRange} the pretty range to display + * @return {Number[]} ticks + */ + function generateTicksNiceRange(options, dataRange, niceRange) { + var ticks = []; + if (options.maxTicks) { + var stepSize = options.stepSize; + ticks.push(options.min !== undefined ? options.min : niceRange.min); + var cur = moment(niceRange.min); + while (cur.add(stepSize, options.unit).valueOf() < niceRange.max) { + ticks.push(cur.valueOf()); + } + var realMax = options.max || niceRange.max; + if (ticks[ticks.length - 1] !== realMax) { + ticks.push(realMax); + } + } + return ticks; + } + + Chart.helpers = Chart.helpers || {}; + + Chart.helpers.time = { + + /** + * Helper function to parse time to a moment object + * @param axis {TimeAxis} the time axis + * @param label {Date|string|number|Moment} The thing to parse + * @return {Moment} parsed time + */ + parseTime: function(axis, label) { + var timeOpts = axis.options.time; + if (typeof timeOpts.parser === 'string') { + return moment(label, timeOpts.parser); + } + if (typeof timeOpts.parser === 'function') { + return timeOpts.parser(label); + } + if (typeof label.getMonth === 'function' || typeof label === 'number') { + // Date objects + return moment(label); + } + if (label.isValid && label.isValid()) { + // Moment support + return label; + } + var format = timeOpts.format; + if (typeof format !== 'string' && format.call) { + // Custom parsing (return an instance of moment) + console.warn('options.time.format is deprecated and replaced by options.time.parser.'); + return format(label); + } + // Moment format parsing + return moment(label, format); + }, + + /** + * Figure out which is the best unit for the scale + * @param minUnit {String} minimum unit to use + * @param min {Number} scale minimum + * @param max {Number} scale maximum + * @return {String} the unit to use + */ + determineUnit: function(minUnit, min, max, maxTicks) { + var units = Object.keys(interval); + var unit; + var numUnits = units.length; + + for (var i = units.indexOf(minUnit); i < numUnits; i++) { + unit = units[i]; + var unitDetails = interval[unit]; + var steps = (unitDetails.steps && unitDetails.steps[unitDetails.steps.length - 1]) || unitDetails.maxStep; + if (steps === undefined || Math.ceil((max - min) / (steps * unitDetails.size)) <= maxTicks) { + break; + } + } + + return unit; + }, + + /** + * Determines how we scale the unit + * @param min {Number} the scale minimum + * @param max {Number} the scale maximum + * @param unit {String} the unit determined by the {@see determineUnit} method + * @return {Number} the axis step size as a multiple of unit + */ + determineStepSize: function(min, max, unit, maxTicks) { + // Using our unit, figure out what we need to scale as + var unitDefinition = interval[unit]; + var unitSizeInMilliSeconds = unitDefinition.size; + var sizeInUnits = Math.ceil((max - min) / unitSizeInMilliSeconds); + var multiplier = 1; + var range = max - min; + + if (unitDefinition.steps) { + // Have an array of steps + var numSteps = unitDefinition.steps.length; + for (var i = 0; i < numSteps && sizeInUnits > maxTicks; i++) { + multiplier = unitDefinition.steps[i]; + sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); + } + } else { + while (sizeInUnits > maxTicks && maxTicks > 0) { + ++multiplier; + sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); + } + } + + return multiplier; + }, + + /** + * @function generateTicks + * @param options {ITimeGeneratorOptions} the options for generation + * @param dataRange {IRange} the data range + * @return {Number[]} ticks + */ + generateTicks: function(options, dataRange) { + var niceMin; + var niceMax; + var isoWeekday = options.isoWeekday; + if (options.unit === 'week' && isoWeekday !== false) { + niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); + niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); + if (dataRange.max - niceMax > 0) { + niceMax.add(1, 'week'); + } + niceMax = niceMax.valueOf(); + } else { + niceMin = moment(dataRange.min).startOf(options.unit).valueOf(); + niceMax = moment(dataRange.max).startOf(options.unit); + if (dataRange.max - niceMax > 0) { + niceMax.add(1, options.unit); + } + niceMax = niceMax.valueOf(); + } + return generateTicksNiceRange(options, dataRange, { + min: niceMin, + max: niceMax + }); + } + + }; + +}; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 0b356b3c1..1d726e1b3 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -7,44 +7,7 @@ moment = typeof(moment) === 'function' ? moment : window.moment; module.exports = function(Chart) { var helpers = Chart.helpers; - var interval = { - millisecond: { - size: 1, - steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] - }, - second: { - size: 1000, - steps: [1, 2, 5, 10, 30] - }, - minute: { - size: 60000, - steps: [1, 2, 5, 10, 30] - }, - hour: { - size: 3600000, - steps: [1, 2, 3, 6, 12] - }, - day: { - size: 86400000, - steps: [1, 2, 5] - }, - week: { - size: 604800000, - maxStep: 4 - }, - month: { - size: 2.628e9, - maxStep: 3 - }, - quarter: { - size: 7.884e9, - maxStep: 4 - }, - year: { - size: 3.154e10, - maxStep: false - } - }; + var timeHelpers = helpers.time; var defaultConfig = { position: 'bottom', @@ -76,149 +39,6 @@ module.exports = function(Chart) { } }; - /** - * Helper function to parse time to a moment object - * @param axis {TimeAxis} the time axis - * @param label {Date|string|number|Moment} The thing to parse - * @return {Moment} parsed time - */ - function parseTime(axis, label) { - var timeOpts = axis.options.time; - if (typeof timeOpts.parser === 'string') { - return moment(label, timeOpts.parser); - } - if (typeof timeOpts.parser === 'function') { - return timeOpts.parser(label); - } - if (typeof label.getMonth === 'function' || typeof label === 'number') { - // Date objects - return moment(label); - } - if (label.isValid && label.isValid()) { - // Moment support - return label; - } - var format = timeOpts.format; - if (typeof format !== 'string' && format.call) { - // Custom parsing (return an instance of moment) - console.warn('options.time.format is deprecated and replaced by options.time.parser.'); - return format(label); - } - // Moment format parsing - return moment(label, format); - } - - /** - * Figure out which is the best unit for the scale - * @param minUnit {String} minimum unit to use - * @param min {Number} scale minimum - * @param max {Number} scale maximum - * @return {String} the unit to use - */ - function determineUnit(minUnit, min, max, maxTicks) { - var units = Object.keys(interval); - var unit; - var numUnits = units.length; - - for (var i = units.indexOf(minUnit); i < numUnits; i++) { - unit = units[i]; - var unitDetails = interval[unit]; - var steps = (unitDetails.steps && unitDetails.steps[unitDetails.steps.length - 1]) || unitDetails.maxStep; - if (steps === undefined || Math.ceil((max - min) / (steps * unitDetails.size)) <= maxTicks) { - break; - } - } - - return unit; - } - - /** - * Determines how we scale the unit - * @param min {Number} the scale minimum - * @param max {Number} the scale maximum - * @param unit {String} the unit determined by the {@see determineUnit} method - * @return {Number} the axis step size as a multiple of unit - */ - function determineStepSize(min, max, unit, maxTicks) { - // Using our unit, figoure out what we need to scale as - var unitDefinition = interval[unit]; - var unitSizeInMilliSeconds = unitDefinition.size; - var sizeInUnits = Math.ceil((max - min) / unitSizeInMilliSeconds); - var multiplier = 1; - var range = max - min; - - if (unitDefinition.steps) { - // Have an array of steps - var numSteps = unitDefinition.steps.length; - for (var i = 0; i < numSteps && sizeInUnits > maxTicks; i++) { - multiplier = unitDefinition.steps[i]; - sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); - } - } else { - while (sizeInUnits > maxTicks && maxTicks > 0) { - ++multiplier; - sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); - } - } - - return multiplier; - } - - /** - * Helper for generating axis labels. - * @param options {ITimeGeneratorOptions} the options for generation - * @param dataRange {IRange} the data range - * @param niceRange {IRange} the pretty range to display - * @return {Number[]} ticks - */ - function generateTicks(options, dataRange, niceRange) { - var ticks = []; - if (options.maxTicks) { - var stepSize = options.stepSize; - ticks.push(options.min !== undefined ? options.min : niceRange.min); - var cur = moment(niceRange.min); - while (cur.add(stepSize, options.unit).valueOf() < niceRange.max) { - ticks.push(cur.valueOf()); - } - var realMax = options.max || niceRange.max; - if (ticks[ticks.length - 1] !== realMax) { - ticks.push(realMax); - } - } - return ticks; - } - - /** - * @function Chart.Ticks.generators.time - * @param options {ITimeGeneratorOptions} the options for generation - * @param dataRange {IRange} the data range - * @return {Number[]} ticks - */ - Chart.Ticks.generators.time = function(options, dataRange) { - var niceMin; - var niceMax; - var isoWeekday = options.isoWeekday; - if (options.unit === 'week' && isoWeekday !== false) { - niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); - niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); - if (dataRange.max - niceMax > 0) { - niceMax.add(1, 'week'); - } - niceMax = niceMax.valueOf(); - } else { - niceMin = moment(dataRange.min).startOf(options.unit).valueOf(); - niceMax = moment(dataRange.max).startOf(options.unit); - if (dataRange.max - niceMax > 0) { - niceMax.add(1, options.unit); - } - niceMax = niceMax.valueOf(); - } - return generateTicks(options, dataRange, { - min: niceMin, - max: niceMax - }); - }; - var TimeScale = Chart.Scale.extend({ initialize: function() { if (!moment) { @@ -244,7 +64,7 @@ module.exports = function(Chart) { var timestamp; helpers.each(chartData.labels, function(label, labelIndex) { - var labelMoment = parseTime(me, label); + var labelMoment = timeHelpers.parseTime(me, label); if (labelMoment.isValid()) { // We need to round the time @@ -267,7 +87,7 @@ module.exports = function(Chart) { if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null && me.chart.isDatasetVisible(datasetIndex)) { // We have potential point data, so we need to parse this helpers.each(dataset.data, function(value, dataIndex) { - var dataMoment = parseTime(me, me.getRightValue(value)); + var dataMoment = timeHelpers.parseTime(me, me.getRightValue(value)); if (dataMoment.isValid()) { if (timeOpts.round) { @@ -302,7 +122,7 @@ module.exports = function(Chart) { var dataMax = me.dataMax; if (timeOpts.min) { - var minMoment = parseTime(me, timeOpts.min); + var minMoment = timeHelpers.parseTime(me, timeOpts.min); if (timeOpts.round) { minMoment.round(timeOpts.round); } @@ -310,15 +130,15 @@ module.exports = function(Chart) { } if (timeOpts.max) { - maxTimestamp = parseTime(me, timeOpts.max).valueOf(); + maxTimestamp = timeHelpers.parseTime(me, timeOpts.max).valueOf(); } var maxTicks = me.getLabelCapacity(minTimestamp || dataMin); - var unit = timeOpts.unit || determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks); + var unit = timeOpts.unit || timeHelpers.determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks); me.displayFormat = timeOpts.displayFormats[unit]; - var stepSize = timeOpts.stepSize || determineStepSize(minTimestamp || dataMin, maxTimestamp || dataMax, unit, maxTicks); - me.ticks = Chart.Ticks.generators.time({ + var stepSize = timeOpts.stepSize || timeHelpers.determineStepSize(minTimestamp || dataMin, maxTimestamp || dataMax, unit, maxTicks); + me.ticks = timeHelpers.generateTicks({ maxTicks: maxTicks, min: minTimestamp, max: maxTimestamp, @@ -347,7 +167,7 @@ module.exports = function(Chart) { // Format nicely if (me.options.time.tooltipFormat) { - label = parseTime(me, label).format(me.options.time.tooltipFormat); + label = timeHelpers.parseTime(me, label).format(me.options.time.tooltipFormat); } return label; @@ -393,7 +213,7 @@ module.exports = function(Chart) { if (offset === null) { if (!value || !value.isValid) { // not already a moment object - value = parseTime(me, me.getRightValue(value)); + value = timeHelpers.parseTime(me, me.getRightValue(value)); } if (value && value.isValid && value.isValid()) {