From 0fc6ed34a09ed30e56574226db71a86f1bc1d23a Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Tue, 16 May 2017 19:20:25 +0300 Subject: [PATCH 01/16] Resolved conflict on time-axis-ticks-formatting rebase. --- src/scales/scale.time.js | 159 +++++++++++++++++++++++++++++++++ test/specs/scale.time.tests.js | 2 +- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index f9f80386e89..eb5bf47c450 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -39,6 +39,165 @@ 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 range = max - min; + var sizeInUnits = Math.ceil(range / unitSizeInMilliSeconds); + var multiplier = 1; + + 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; + var startTick = options.min !== undefined ? options.min : niceRange.min; + var seniorUnitStart = moment(startTick).add(1, options.seniorUnit).startOf(options.seniorUnit); + var startRange = seniorUnitStart.valueOf() - startTick; + var startFraction = startRange % (interval[options.unit].size * stepSize); + var alignedTick = startTick; + ticks.push(startTick); + + if (startTick !== alignedTick + startFraction && + options.seniorUnit && + !options.timeOpts.round && + !options.timeOpts.isoWeekday) { + alignedTick += startFraction; + ticks.push(alignedTick); + } + + var cur = moment(alignedTick); + + 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) { diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 3843ee8d2f4..035ac4294aa 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -219,7 +219,7 @@ describe('Time scale tests', function() { // Counts down because the lines are drawn top to bottom expect(xScale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes - expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes + // expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes }); it('should build ticks using the config unit', function() { From 0572606c3818942e860e8bb1802ddcacd5562a7b Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Fri, 19 May 2017 09:59:43 +0300 Subject: [PATCH 02/16] Changed seniorUnit to majorUnit. Added majorUnit check. --- src/scales/scale.time.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index eb5bf47c450..c8e0013e71e 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -139,14 +139,16 @@ module.exports = function(Chart) { if (options.maxTicks) { var stepSize = options.stepSize; var startTick = options.min !== undefined ? options.min : niceRange.min; - var seniorUnitStart = moment(startTick).add(1, options.seniorUnit).startOf(options.seniorUnit); - var startRange = seniorUnitStart.valueOf() - startTick; + var majorUnitStart = startTick; + if (options.majorUnit) { + majorUnitStart = moment(startTick).add(1, options.majorUnit).startOf(options.majorUnit); + } + var startRange = majorUnitStart.valueOf() - startTick; var startFraction = startRange % (interval[options.unit].size * stepSize); var alignedTick = startTick; ticks.push(startTick); - if (startTick !== alignedTick + startFraction && - options.seniorUnit && + options.majorUnit && !options.timeOpts.round && !options.timeOpts.isoWeekday) { alignedTick += startFraction; From 890f1c52f1818b68d54de385621606534333ea8f Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Thu, 18 May 2017 10:24:24 +0300 Subject: [PATCH 03/16] Time scale majorUnit alignment fixes. --- src/core/core.scale.js | 19 ++++++++++++++++--- src/scales/scale.time.js | 20 ++++++++++++++------ test/specs/scale.time.tests.js | 27 ++++++++++++++------------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 596ccb0ea3b..f02e1d9823a 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,5 +1,8 @@ 'use strict'; +var moment = require('moment'); +moment = typeof(moment) === 'function' ? moment : window.moment; + module.exports = function(Chart) { var helpers = Chart.helpers; @@ -503,7 +506,7 @@ module.exports = function(Chart) { var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); var tickFont = parseFontOptions(optionTicks); - + var seniorTickFont = helpers.fontString(tickFont.size, 'bold', tickFont.family); var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); @@ -651,7 +654,7 @@ module.exports = function(Chart) { }); // Draw all of the tick labels, tick marks, and grid lines at the correct places - helpers.each(itemsToDraw, function(itemToDraw) { + helpers.each(itemsToDraw, function(itemToDraw, index) { if (gridLines.display) { context.save(); context.lineWidth = itemToDraw.glWidth; @@ -681,7 +684,17 @@ module.exports = function(Chart) { context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - context.font = tickFont.font; + var font = tickFont.font; + if (me.ticksAsTimestamps) { + // Add bold style to senior units + var tickMoment = moment(me.ticksAsTimestamps[index]); + var tickMomentClone = tickMoment.clone(); + if (me.majorUnit && + tickMoment.valueOf() === tickMomentClone.startOf(me.majorUnit).valueOf()) { + font = seniorTickFont; + } + } + context.font = font; context.textBaseline = itemToDraw.textBaseline; context.textAlign = itemToDraw.textAlign; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index c8e0013e71e..a4ced9c615d 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -26,8 +26,8 @@ module.exports = function(Chart) { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM - hour: 'MMM D, hA', // Sept 4, 5PM - day: 'll', // Sep 4 2015 + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? month: 'MMM YYYY', // Sept 2015 quarter: '[Q]Q - YYYY', // Q3 @@ -154,9 +154,7 @@ module.exports = function(Chart) { alignedTick += startFraction; ticks.push(alignedTick); } - var cur = moment(alignedTick); - while (cur.add(stepSize, options.unit).valueOf() < niceRange.max) { ticks.push(cur.valueOf()); } @@ -165,7 +163,6 @@ module.exports = function(Chart) { ticks.push(realMax); } } - return ticks; } @@ -343,7 +340,18 @@ module.exports = function(Chart) { }, // Function to format an individual tick mark tickFormatFunction: function(tick, index, ticks) { - var formattedTick = tick.format(this.displayFormat); + var formattedTick; + var tickClone = tick.clone(); + if (this.majorUnit && + this.seniorDisplayFormat && + tick.valueOf() === tickClone.startOf(this.majorUnit).valueOf()) { + // format as senior unit + formattedTick = tick.format(this.seniorDisplayFormat); + } else { + // format as base unit + formattedTick = tick.format(this.displayFormat); + } + var tickOpts = this.options.ticks; var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 035ac4294aa..c59db4ffffa 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -94,11 +94,11 @@ describe('Time scale tests', function() { displayFormat: false, minUnit: 'millisecond', displayFormats: { - millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM - hour: 'MMM D, hA', // Sept 4, 5PM - day: 'll', // Sep 4 2015 + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? month: 'MMM YYYY', // Sept 2015 quarter: '[Q]Q - YYYY', // Q3 @@ -125,7 +125,7 @@ describe('Time scale tests', function() { var scaleOptions = Chart.scaleService.getScaleDefaults('time'); var scale = createScale(mockData, scaleOptions); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); + expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); it('should accept labels as date objects', function() { @@ -134,7 +134,7 @@ describe('Time scale tests', function() { }; var scale = createScale(mockData, Chart.scaleService.getScaleDefaults('time')); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); + expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); it('should accept data as xy points', function() { @@ -180,7 +180,7 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; xScale.update(800, 200); - expect(xScale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); + expect(xScale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); }); @@ -218,8 +218,8 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; // Counts down because the lines are drawn top to bottom - expect(xScale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes - // expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes + expect(xScale.ticks[0]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes + expect(xScale.ticks[1]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes }); it('should build ticks using the config unit', function() { @@ -233,7 +233,7 @@ describe('Time scale tests', function() { var scale = createScale(mockData, config); scale.update(2500, 200); - expect(scale.ticks).toEqual(['Jan 1, 8PM', 'Jan 1, 9PM', 'Jan 1, 10PM', 'Jan 1, 11PM', 'Jan 2, 12AM', 'Jan 2, 1AM', 'Jan 2, 2AM', 'Jan 2, 3AM', 'Jan 2, 4AM', 'Jan 2, 5AM', 'Jan 2, 6AM', 'Jan 2, 7AM', 'Jan 2, 8AM', 'Jan 2, 9AM', 'Jan 2, 10AM', 'Jan 2, 11AM', 'Jan 2, 12PM', 'Jan 2, 1PM', 'Jan 2, 2PM', 'Jan 2, 3PM', 'Jan 2, 4PM', 'Jan 2, 5PM', 'Jan 2, 6PM', 'Jan 2, 7PM', 'Jan 2, 8PM', 'Jan 2, 9PM']); + expect(scale.ticks).toEqual(['8PM', '9PM', '10PM', '11PM', 'Jan 2', '1AM', '2AM', '3AM', '4AM', '5AM', '6AM', '7AM', '8AM', '9AM', '10AM', '11AM', '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM']); }); it('build ticks honoring the minUnit', function() { @@ -245,7 +245,7 @@ describe('Time scale tests', function() { config.time.minUnit = 'day'; var scale = createScale(mockData, config); - expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015']); + expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3']); }); it('should build ticks using the config diff', function() { @@ -261,7 +261,7 @@ describe('Time scale tests', function() { scale.update(800, 200); // last date is feb 15 because we round to start of week - expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 1, 2015', 'Feb 8, 2015', 'Feb 15, 2015']); + expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 2015', 'Feb 8, 2015', 'Feb 15, 2015']); }); describe('when specifying limits', function() { @@ -279,7 +279,7 @@ describe('Time scale tests', function() { config.time.min = '2014-12-29T04:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[0]).toEqual('Dec 29, 2014'); + expect(scale.ticks[0]).toEqual('Dec 29'); }); it('should use the max option', function() { @@ -287,7 +287,8 @@ describe('Time scale tests', function() { config.time.max = '2015-01-05T06:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 6, 2015'); + + expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 5'); }); }); From f5ddc579eb1b7715f183aa26d2c5ce1fe5e57937 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Fri, 19 May 2017 10:13:29 +0300 Subject: [PATCH 04/16] Scale time tests fix. --- test/specs/scale.time.tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index c59db4ffffa..bf6775b2e3f 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -218,8 +218,8 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; // Counts down because the lines are drawn top to bottom - expect(xScale.ticks[0]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes - expect(xScale.ticks[1]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes + expect(xScale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes + expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes }); it('should build ticks using the config unit', function() { From d43f9eebf686b699db7a8cd87023ea2203a89eff Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Mon, 22 May 2017 10:05:11 +0300 Subject: [PATCH 05/16] Renamed seniorDisplayFormat to majorDisplayFormat. Changed aligned tick 'if' statement formatting. Removed isoWeekday from generateTick ooptions due to redundancy. --- src/scales/scale.time.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index a4ced9c615d..4bafc8005d8 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -147,10 +147,7 @@ module.exports = function(Chart) { var startFraction = startRange % (interval[options.unit].size * stepSize); var alignedTick = startTick; ticks.push(startTick); - if (startTick !== alignedTick + startFraction && - options.majorUnit && - !options.timeOpts.round && - !options.timeOpts.isoWeekday) { + if (startTick !== alignedTick + startFraction && options.majorUnit && !options.timeOpts.round && !options.timeOpts.isoWeekday) { alignedTick += startFraction; ticks.push(alignedTick); } @@ -175,7 +172,7 @@ module.exports = function(Chart) { Chart.Ticks.generators.time = function(options, dataRange) { var niceMin; var niceMax; - var isoWeekday = options.isoWeekday; + var isoWeekday = options.timeOpts.isoWeekday; if (options.unit === 'week' && isoWeekday !== false) { niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); @@ -343,10 +340,10 @@ module.exports = function(Chart) { var formattedTick; var tickClone = tick.clone(); if (this.majorUnit && - this.seniorDisplayFormat && + this.majorDisplayFormat && tick.valueOf() === tickClone.startOf(this.majorUnit).valueOf()) { // format as senior unit - formattedTick = tick.format(this.seniorDisplayFormat); + formattedTick = tick.format(this.majorDisplayFormat); } else { // format as base unit formattedTick = tick.format(this.displayFormat); From b471c7345909fa3e2e6672193ccfadece6e928fc Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Fri, 26 May 2017 20:10:15 +0300 Subject: [PATCH 06/16] Added major tick determining in timeHelpers. --- src/core/core.scale.js | 19 +--- src/scales/scale.time.js | 172 +-------------------------------- test/specs/scale.time.tests.js | 16 +-- 3 files changed, 14 insertions(+), 193 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index f02e1d9823a..596ccb0ea3b 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,8 +1,5 @@ 'use strict'; -var moment = require('moment'); -moment = typeof(moment) === 'function' ? moment : window.moment; - module.exports = function(Chart) { var helpers = Chart.helpers; @@ -506,7 +503,7 @@ module.exports = function(Chart) { var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); var tickFont = parseFontOptions(optionTicks); - var seniorTickFont = helpers.fontString(tickFont.size, 'bold', tickFont.family); + var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); @@ -654,7 +651,7 @@ module.exports = function(Chart) { }); // Draw all of the tick labels, tick marks, and grid lines at the correct places - helpers.each(itemsToDraw, function(itemToDraw, index) { + helpers.each(itemsToDraw, function(itemToDraw) { if (gridLines.display) { context.save(); context.lineWidth = itemToDraw.glWidth; @@ -684,17 +681,7 @@ module.exports = function(Chart) { context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - var font = tickFont.font; - if (me.ticksAsTimestamps) { - // Add bold style to senior units - var tickMoment = moment(me.ticksAsTimestamps[index]); - var tickMomentClone = tickMoment.clone(); - if (me.majorUnit && - tickMoment.valueOf() === tickMomentClone.startOf(me.majorUnit).valueOf()) { - font = seniorTickFont; - } - } - context.font = font; + context.font = tickFont.font; context.textBaseline = itemToDraw.textBaseline; context.textAlign = itemToDraw.textAlign; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 4bafc8005d8..f9f80386e89 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -26,8 +26,8 @@ module.exports = function(Chart) { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM - hour: 'hA', // 5PM - day: 'MMM D', // Sep 4 + hour: 'MMM D, hA', // Sept 4, 5PM + day: 'll', // Sep 4 2015 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? month: 'MMM YYYY', // Sept 2015 quarter: '[Q]Q - YYYY', // Q3 @@ -39,161 +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 range = max - min; - var sizeInUnits = Math.ceil(range / unitSizeInMilliSeconds); - var multiplier = 1; - - 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; - var startTick = options.min !== undefined ? options.min : niceRange.min; - var majorUnitStart = startTick; - if (options.majorUnit) { - majorUnitStart = moment(startTick).add(1, options.majorUnit).startOf(options.majorUnit); - } - var startRange = majorUnitStart.valueOf() - startTick; - var startFraction = startRange % (interval[options.unit].size * stepSize); - var alignedTick = startTick; - ticks.push(startTick); - if (startTick !== alignedTick + startFraction && options.majorUnit && !options.timeOpts.round && !options.timeOpts.isoWeekday) { - alignedTick += startFraction; - ticks.push(alignedTick); - } - var cur = moment(alignedTick); - 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.timeOpts.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) { @@ -337,18 +182,7 @@ module.exports = function(Chart) { }, // Function to format an individual tick mark tickFormatFunction: function(tick, index, ticks) { - var formattedTick; - var tickClone = tick.clone(); - if (this.majorUnit && - this.majorDisplayFormat && - tick.valueOf() === tickClone.startOf(this.majorUnit).valueOf()) { - // format as senior unit - formattedTick = tick.format(this.majorDisplayFormat); - } else { - // format as base unit - formattedTick = tick.format(this.displayFormat); - } - + var formattedTick = tick.format(this.displayFormat); var tickOpts = this.options.ticks; var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index bf6775b2e3f..1e2eaaacb41 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -97,8 +97,8 @@ describe('Time scale tests', function() { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM - hour: 'hA', // 5PM - day: 'MMM D', // Sep 4 + hour: 'MMM D, hA', // Sept 4, 5PM + day: 'll', // Sep 4 2015 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? month: 'MMM YYYY', // Sept 2015 quarter: '[Q]Q - YYYY', // Q3 @@ -125,7 +125,7 @@ describe('Time scale tests', function() { var scaleOptions = Chart.scaleService.getScaleDefaults('time'); var scale = createScale(mockData, scaleOptions); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); }); it('should accept labels as date objects', function() { @@ -134,7 +134,7 @@ describe('Time scale tests', function() { }; var scale = createScale(mockData, Chart.scaleService.getScaleDefaults('time')); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); }); it('should accept data as xy points', function() { @@ -180,7 +180,7 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; xScale.update(800, 200); - expect(xScale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + expect(xScale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); }); }); @@ -245,7 +245,7 @@ describe('Time scale tests', function() { config.time.minUnit = 'day'; var scale = createScale(mockData, config); - expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3']); + expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015']); }); it('should build ticks using the config diff', function() { @@ -261,7 +261,7 @@ describe('Time scale tests', function() { scale.update(800, 200); // last date is feb 15 because we round to start of week - expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 2015', 'Feb 8, 2015', 'Feb 15, 2015']); + expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 1, 2015', 'Feb 8, 2015', 'Feb 15, 2015']); }); describe('when specifying limits', function() { @@ -279,7 +279,7 @@ describe('Time scale tests', function() { config.time.min = '2014-12-29T04:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[0]).toEqual('Dec 29'); + expect(scale.ticks[0]).toEqual('Dec 29, 2014'); }); it('should use the max option', function() { From d88a03a4737b8a5c36b709d668980c230835f8d2 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Fri, 26 May 2017 20:38:57 +0300 Subject: [PATCH 07/16] Added majorTick formatting. --- src/core/core.scale.js | 15 +++++++++++++-- src/scales/scale.time.js | 17 ++++++++++++++--- test/specs/scale.time.tests.js | 22 +++++++++++----------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 596ccb0ea3b..4d3862741da 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -503,6 +503,7 @@ module.exports = function(Chart) { var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); var tickFont = parseFontOptions(optionTicks); + var majorTickFont = helpers.fontString(tickFont.size, 'bold', tickFont.family); var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; @@ -651,7 +652,7 @@ module.exports = function(Chart) { }); // Draw all of the tick labels, tick marks, and grid lines at the correct places - helpers.each(itemsToDraw, function(itemToDraw) { + helpers.each(itemsToDraw, function(itemToDraw, index) { if (gridLines.display) { context.save(); context.lineWidth = itemToDraw.glWidth; @@ -681,7 +682,17 @@ module.exports = function(Chart) { context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - context.font = tickFont.font; + var font = tickFont.font; + if (me.ticksAsTimestamps) { + // Add bold style to major units + var tickMoment = moment(me.ticksAsTimestamps[index]); + var tickMomentClone = tickMoment.clone(); + if (me.majorUnit && + tickMoment.valueOf() === tickMomentClone.startOf(me.majorUnit).valueOf()) { + font = majorTickFont; + } + } + context.font = font; context.textBaseline = itemToDraw.textBaseline; context.textAlign = itemToDraw.textAlign; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index f9f80386e89..d968d6d0cde 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -26,8 +26,8 @@ module.exports = function(Chart) { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM - hour: 'MMM D, hA', // Sept 4, 5PM - day: 'll', // Sep 4 2015 + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? month: 'MMM YYYY', // Sept 2015 quarter: '[Q]Q - YYYY', // Q3 @@ -182,7 +182,18 @@ module.exports = function(Chart) { }, // Function to format an individual tick mark tickFormatFunction: function(tick, index, ticks) { - var formattedTick = tick.format(this.displayFormat); + var formattedTick; + var tickClone = tick.clone(); + if (this.majorUnit && + this.majorDisplayFormat && + tick.valueOf() === tickClone.startOf(this.majorUnit).valueOf()) { + // format as senior unit + formattedTick = tick.format(this.majorDisplayFormat); + } else { + // format as base unit + formattedTick = tick.format(this.displayFormat); + } + var tickOpts = this.options.ticks; var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 1e2eaaacb41..306f93d76b4 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -97,13 +97,13 @@ describe('Time scale tests', function() { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM - hour: 'MMM D, hA', // Sept 4, 5PM - day: 'll', // Sep 4 2015 + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? month: 'MMM YYYY', // Sept 2015 quarter: '[Q]Q - YYYY', // Q3 year: 'YYYY' // 2015 - } + }, } }); @@ -125,7 +125,7 @@ describe('Time scale tests', function() { var scaleOptions = Chart.scaleService.getScaleDefaults('time'); var scale = createScale(mockData, scaleOptions); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); + expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); it('should accept labels as date objects', function() { @@ -134,7 +134,7 @@ describe('Time scale tests', function() { }; var scale = createScale(mockData, Chart.scaleService.getScaleDefaults('time')); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); + expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); it('should accept data as xy points', function() { @@ -180,7 +180,7 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; xScale.update(800, 200); - expect(xScale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']); + expect(xScale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); }); @@ -218,8 +218,8 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; // Counts down because the lines are drawn top to bottom - expect(xScale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes - expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes + expect(xScale.ticks[0]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes + expect(xScale.ticks[1]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes }); it('should build ticks using the config unit', function() { @@ -245,7 +245,7 @@ describe('Time scale tests', function() { config.time.minUnit = 'day'; var scale = createScale(mockData, config); - expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015']); + expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3']); }); it('should build ticks using the config diff', function() { @@ -261,7 +261,7 @@ describe('Time scale tests', function() { scale.update(800, 200); // last date is feb 15 because we round to start of week - expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 1, 2015', 'Feb 8, 2015', 'Feb 15, 2015']); + expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 2015', 'Feb 8, 2015', 'Feb 15, 2015']); }); describe('when specifying limits', function() { @@ -279,7 +279,7 @@ describe('Time scale tests', function() { config.time.min = '2014-12-29T04:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[0]).toEqual('Dec 29, 2014'); + expect(scale.ticks[0]).toEqual('Dec 29'); }); it('should use the max option', function() { From dd66acb7e37cb8912842d2d8bd9ca8d14cff26c8 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Mon, 29 May 2017 18:35:27 +0300 Subject: [PATCH 08/16] Minor fixes. --- test/specs/scale.time.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 306f93d76b4..d3faceb7c3f 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -94,7 +94,7 @@ describe('Time scale tests', function() { displayFormat: false, minUnit: 'millisecond', displayFormats: { - millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM second: 'h:mm:ss a', // 11:20:01 AM minute: 'h:mm:ss a', // 11:20:01 AM hour: 'hA', // 5PM From 86c42590751f8fa8353437e95bca4794e9536251 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Tue, 30 May 2017 09:50:09 +0300 Subject: [PATCH 09/16] Changed time unit formatting. Minor fixes. --- src/core/core.scale.js | 3 +-- src/scales/scale.time.js | 2 +- test/specs/scale.time.tests.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 4d3862741da..daa5853ca46 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -687,8 +687,7 @@ module.exports = function(Chart) { // Add bold style to major units var tickMoment = moment(me.ticksAsTimestamps[index]); var tickMomentClone = tickMoment.clone(); - if (me.majorUnit && - tickMoment.valueOf() === tickMomentClone.startOf(me.majorUnit).valueOf()) { + if (me.majorUnit && tickMoment.valueOf() === tickMomentClone.startOf(me.majorUnit).valueOf()) { font = majorTickFont; } } diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index d968d6d0cde..04b35357643 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -25,7 +25,7 @@ module.exports = function(Chart) { displayFormats: { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, second: 'h:mm:ss a', // 11:20:01 AM - minute: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm a', // 11:20 AM hour: 'hA', // 5PM day: 'MMM D', // Sep 4 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index d3faceb7c3f..c3197db3483 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -96,7 +96,7 @@ describe('Time scale tests', function() { displayFormats: { millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM second: 'h:mm:ss a', // 11:20:01 AM - minute: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm a', // 11:20 AM hour: 'hA', // 5PM day: 'MMM D', // Sep 4 week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? From 241ad82ae68d4cec1be379a8579a267bbdf29a1e Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Tue, 30 May 2017 09:58:17 +0300 Subject: [PATCH 10/16] Added import of moment into core.scale.js. --- src/core/core.scale.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index daa5853ca46..abc5be0ccaf 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,5 +1,8 @@ 'use strict'; +var moment = require('moment'); +moment = typeof(moment) === 'function' ? moment : window.moment; + module.exports = function(Chart) { var helpers = Chart.helpers; From 086bf4981ef0bffd0993d9c84bf700069976c679 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Tue, 30 May 2017 10:19:00 +0300 Subject: [PATCH 11/16] Time scale testts fix. --- test/specs/scale.time.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index c3197db3483..cb35b44bdab 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -288,7 +288,7 @@ describe('Time scale tests', function() { var scale = createScale(mockData, config); - expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 5'); + expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 6'); }); }); From 7242f9636dd6ad52dd9948317d481c2e1b3c7f64 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Wed, 31 May 2017 13:10:00 +0300 Subject: [PATCH 12/16] Changed time scale ticks store to array of objects. --- samples/scales/time/line-point-data.html | 1 - src/core/core.scale.js | 20 +++-------- src/scales/scale.time.js | 20 +++++++---- test/specs/scale.time.tests.js | 44 +++++++++++++++++------- 4 files changed, 50 insertions(+), 35 deletions(-) diff --git a/samples/scales/time/line-point-data.html b/samples/scales/time/line-point-data.html index e28f883f223..537fdf17d1a 100644 --- a/samples/scales/time/line-point-data.html +++ b/samples/scales/time/line-point-data.html @@ -104,7 +104,6 @@ window.onload = function() { var ctx = document.getElementById("canvas").getContext("2d"); window.myLine = new Chart(ctx, config); - }; document.getElementById('randomizeData').addEventListener('click', function() { diff --git a/src/core/core.scale.js b/src/core/core.scale.js index abc5be0ccaf..298fe78de4d 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,8 +1,5 @@ 'use strict'; -var moment = require('moment'); -moment = typeof(moment) === 'function' ? moment : window.moment; - module.exports = function(Chart) { var helpers = Chart.helpers; @@ -551,7 +548,8 @@ module.exports = function(Chart) { var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl; var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom; - helpers.each(me.ticks, function(label, index) { + helpers.each(me.ticks, function(tick, index) { + var label = typeof tick === 'object' && typeof tick.value !== 'undefined' ? tick.value : tick; // If the callback returned a null or undefined value, do not draw this line if (label === undefined || label === null) { return; @@ -649,13 +647,14 @@ module.exports = function(Chart) { glBorderDashOffset: borderDashOffset, rotation: -1 * labelRotationRadians, label: label, + major: tick.major === true, textBaseline: textBaseline, textAlign: textAlign }); }); // Draw all of the tick labels, tick marks, and grid lines at the correct places - helpers.each(itemsToDraw, function(itemToDraw, index) { + helpers.each(itemsToDraw, function(itemToDraw) { if (gridLines.display) { context.save(); context.lineWidth = itemToDraw.glWidth; @@ -685,16 +684,7 @@ module.exports = function(Chart) { context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - var font = tickFont.font; - if (me.ticksAsTimestamps) { - // Add bold style to major units - var tickMoment = moment(me.ticksAsTimestamps[index]); - var tickMomentClone = tickMoment.clone(); - if (me.majorUnit && tickMoment.valueOf() === tickMomentClone.startOf(me.majorUnit).valueOf()) { - font = majorTickFont; - } - } - context.font = font; + context.font = itemToDraw.major ? majorTickFont : tickFont.font; context.textBaseline = itemToDraw.textBaseline; context.textAlign = itemToDraw.textAlign; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 04b35357643..b9a47b87325 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -184,11 +184,12 @@ module.exports = function(Chart) { tickFormatFunction: function(tick, index, ticks) { var formattedTick; var tickClone = tick.clone(); - if (this.majorUnit && - this.majorDisplayFormat && - tick.valueOf() === tickClone.startOf(this.majorUnit).valueOf()) { + var tickTimestamp = tick.valueOf(); + var major = false; + if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) { // format as senior unit formattedTick = tick.format(this.majorDisplayFormat); + major = true; } else { // format as base unit formattedTick = tick.format(this.displayFormat); @@ -198,9 +199,15 @@ module.exports = function(Chart) { var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); if (callback) { - return callback(formattedTick, index, ticks); + return { + value: callback(formattedTick, index, ticks), + major: major + }; } - return formattedTick; + return { + value: formattedTick, + major: major + }; }, convertTicksToLabels: function() { var me = this; @@ -268,11 +275,12 @@ module.exports = function(Chart) { var me = this; me.displayFormat = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation - var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []); + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []).value; var tickLabelWidth = me.getLabelWidth(exampleLabel); var innerWidth = me.isHorizontal() ? me.width : me.height; var labelCapacity = innerWidth / tickLabelWidth; + return labelCapacity; } }); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index cb35b44bdab..21d99a7c62d 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -17,6 +17,12 @@ describe('Time scale tests', function() { return scale; } + function getTicksValues(ticks) { + return ticks.map(function(tick) { + return tick.value; + }); + } + beforeEach(function() { // Need a time matcher for getValueFromPixel jasmine.addMatchers({ @@ -125,7 +131,9 @@ describe('Time scale tests', function() { var scaleOptions = Chart.scaleService.getScaleDefaults('time'); var scale = createScale(mockData, scaleOptions); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + var ticks = getTicksValues(scale.ticks); + + expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); it('should accept labels as date objects', function() { @@ -134,7 +142,9 @@ describe('Time scale tests', function() { }; var scale = createScale(mockData, Chart.scaleService.getScaleDefaults('time')); scale.update(1000, 200); - expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + var ticks = getTicksValues(scale.ticks); + + expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); it('should accept data as xy points', function() { @@ -180,7 +190,9 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; xScale.update(800, 200); - expect(xScale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + var ticks = getTicksValues(xScale.ticks); + + expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); }); }); @@ -218,8 +230,8 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; // Counts down because the lines are drawn top to bottom - expect(xScale.ticks[0]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes - expect(xScale.ticks[1]).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes + expect(xScale.ticks[0].value).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes + expect(xScale.ticks[1].value).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes }); it('should build ticks using the config unit', function() { @@ -232,8 +244,9 @@ describe('Time scale tests', function() { var scale = createScale(mockData, config); scale.update(2500, 200); + var ticks = getTicksValues(scale.ticks); - expect(scale.ticks).toEqual(['8PM', '9PM', '10PM', '11PM', 'Jan 2', '1AM', '2AM', '3AM', '4AM', '5AM', '6AM', '7AM', '8AM', '9AM', '10AM', '11AM', '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM']); + expect(ticks).toEqual(['8PM', '9PM', '10PM', '11PM', 'Jan 2', '1AM', '2AM', '3AM', '4AM', '5AM', '6AM', '7AM', '8AM', '9AM', '10AM', '11AM', '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM']); }); it('build ticks honoring the minUnit', function() { @@ -245,7 +258,9 @@ describe('Time scale tests', function() { config.time.minUnit = 'day'; var scale = createScale(mockData, config); - expect(scale.ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3']); + var ticks = getTicksValues(scale.ticks); + + expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3']); }); it('should build ticks using the config diff', function() { @@ -259,9 +274,10 @@ describe('Time scale tests', function() { var scale = createScale(mockData, config); scale.update(800, 200); + var ticks = getTicksValues(scale.ticks); // last date is feb 15 because we round to start of week - expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 2015', 'Feb 8, 2015', 'Feb 15, 2015']); + expect(ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 2015', 'Feb 8, 2015', 'Feb 15, 2015']); }); describe('when specifying limits', function() { @@ -279,7 +295,7 @@ describe('Time scale tests', function() { config.time.min = '2014-12-29T04:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[0]).toEqual('Dec 29'); + expect(scale.ticks[0].value).toEqual('Dec 29'); }); it('should use the max option', function() { @@ -288,7 +304,7 @@ describe('Time scale tests', function() { var scale = createScale(mockData, config); - expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 6'); + expect(scale.ticks[scale.ticks.length - 1].value).toEqual('Jan 6'); }); }); @@ -306,7 +322,9 @@ describe('Time scale tests', function() { // Wednesday config.time.isoWeekday = 3; var scale = createScale(mockData, config); - expect(scale.ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']); + var ticks = getTicksValues(scale.ticks); + + expect(ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']); }); describe('when rendering several days', function() { @@ -395,7 +413,7 @@ describe('Time scale tests', function() { it('should build the correct ticks', function() { // Where 'correct' is a two year spacing. - expect(xScale.ticks).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2019']); + expect(getTicksValues(xScale.ticks)).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2019']); }); it('should have ticks with accurate labels', function() { @@ -405,7 +423,7 @@ describe('Time scale tests', function() { for (var i = 0; i < ticks.length - 1; i++) { var offset = 2 * pixelsPerYear * i; expect(xScale.getValueForPixel(xScale.left + offset)).toBeCloseToTime({ - value: moment(ticks[i] + '-01-01'), + value: moment(ticks[i].value + '-01-01'), unit: 'day', threshold: 0.5, }); From 5bd9cb6ee79532a0e8c2d2f4f8c1e60e871a4774 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Wed, 31 May 2017 15:34:46 +0300 Subject: [PATCH 13/16] Added majorTick configuration via options. --- docs/axes/styling.md | 11 +++++++++++ src/core/core.scale.js | 11 ++++++----- src/scales/scale.time.js | 26 +++++++++++++++++++++++++- test/specs/scale.time.tests.js | 13 +++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 48a4d7d0539..84230f0dbe7 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -35,3 +35,14 @@ The tick configuration is nested under the scale configuration in the `ticks` ke | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). | `reverse` | `Boolean` | `false` | Reverses order of tick labels. + +## Major Tick Configuration +The majorTick configuration is nested under the scale configuration in the `majorTicks` key. It defines options for the major tick marks that are generated by the axis. Omitted options are onherited from `ticks` configuration. + +| Name | Type | Default | Description +| -----| ---- | --------| ----------- +| `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). +| `fontColor` | Color | `'#666'` | Font color for tick labels. +| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. +| `fontSize` | `Number` | `12` | Font size for the tick labels. +| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 298fe78de4d..50aac0e874b 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -487,6 +487,7 @@ module.exports = function(Chart) { var context = me.ctx; var globalDefaults = Chart.defaults.global; var optionTicks = options.ticks; + var optionMajorTicks = options.majorTicks ? options.majorTicks : optionTicks; var gridLines = options.gridLines; var scaleLabel = options.scaleLabel; @@ -503,7 +504,8 @@ module.exports = function(Chart) { var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); var tickFont = parseFontOptions(optionTicks); - var majorTickFont = helpers.fontString(tickFont.size, 'bold', tickFont.family); + var majorTickFontColor = helpers.getValueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); + var majorTickFont = parseFontOptions(optionMajorTicks); var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; @@ -514,9 +516,6 @@ module.exports = function(Chart) { var cosRotation = Math.cos(labelRotationRadians); var longestRotatedLabel = me.longestLabelWidth * cosRotation; - // Make sure we draw text in the correct color and font - context.fillStyle = tickFontColor; - var itemsToDraw = []; if (isHorizontal) { @@ -681,10 +680,12 @@ module.exports = function(Chart) { } if (optionTicks.display) { + // Make sure we draw text in the correct color and font context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - context.font = itemToDraw.major ? majorTickFont : tickFont.font; + context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; + context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; context.textBaseline = itemToDraw.textBaseline; context.textAlign = itemToDraw.textAlign; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index b9a47b87325..ce98f5679a8 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -36,6 +36,19 @@ module.exports = function(Chart) { }, ticks: { autoSkip: false + }, + majorTicks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + callback: Chart.Ticks.formatters.values } }; @@ -45,8 +58,17 @@ module.exports = function(Chart) { throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); } + this.mergeTicksOptions(); + Chart.Scale.prototype.initialize.call(this); }, + mergeTicksOptions: function() { + for (var key in this.options.ticks) { + if (typeof this.options.majorTicks[key] === 'undefined') { + this.options.majorTicks[key] = this.options.ticks[key]; + } + } + }, determineDataLimits: function() { var me = this; var timeOpts = me.options.time; @@ -186,16 +208,18 @@ module.exports = function(Chart) { var tickClone = tick.clone(); var tickTimestamp = tick.valueOf(); var major = false; + var tickOpts; if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) { // format as senior unit formattedTick = tick.format(this.majorDisplayFormat); + tickOpts = this.options.majorTicks; major = true; } else { // format as base unit formattedTick = tick.format(this.displayFormat); + tickOpts = this.options.ticks; } - var tickOpts = this.options.ticks; var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); if (callback) { diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 21d99a7c62d..94a84bcc543 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -91,6 +91,19 @@ describe('Time scale tests', function() { autoSkipPadding: 0, labelOffset: 0 }, + majorTicks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + callback: defaultConfig.majorTicks.callback, // make this nicer, then check explicitly below, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0 + }, time: { parser: false, format: false, From 3ab65fc64ed1738baded690241ef5e4357a25e4e Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Mon, 12 Jun 2017 10:22:24 +0300 Subject: [PATCH 14/16] Changed minor and major ticks configuration in ticks options. --- docs/axes/styling.md | 15 ++++++++++++- src/core/core.controller.js | 1 + src/core/core.scale.js | 30 +++++++++++++++++++++++--- src/scales/scale.time.js | 28 ++++-------------------- test/specs/core.helpers.tests.js | 4 ++++ test/specs/scale.category.tests.js | 4 +++- test/specs/scale.linear.tests.js | 4 +++- test/specs/scale.logarithmic.tests.js | 4 +++- test/specs/scale.radialLinear.tests.js | 4 +++- test/specs/scale.time.tests.js | 17 +++------------ 10 files changed, 65 insertions(+), 46 deletions(-) diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 84230f0dbe7..255b779d6e3 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -35,9 +35,22 @@ The tick configuration is nested under the scale configuration in the `ticks` ke | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). | `reverse` | `Boolean` | `false` | Reverses order of tick labels. +| `minor` | `object` | `{}` | Minor ticks configuration. Ommited options are inherited from options above. +| `major` | `object` | `{}` | Major ticks configuration. Ommited options are inherited from options above. + +## Minor Tick Configuration +The minorTick configuration is nested under the ticks configuration in the `minor` key. It defines options for the minor tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. + +| Name | Type | Default | Description +| -----| ---- | --------| ----------- +| `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). +| `fontColor` | Color | `'#666'` | Font color for tick labels. +| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. +| `fontSize` | `Number` | `12` | Font size for the tick labels. +| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). ## Major Tick Configuration -The majorTick configuration is nested under the scale configuration in the `majorTicks` key. It defines options for the major tick marks that are generated by the axis. Omitted options are onherited from `ticks` configuration. +The majorTick configuration is nested under the ticks configuration in the `major` key. It defines options for the major tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. | Name | Type | Default | Description | -----| ---- | --------| ----------- diff --git a/src/core/core.controller.js b/src/core/core.controller.js index f8c471f3d28..0104b0d8080 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -265,6 +265,7 @@ module.exports = function(Chart) { }); scales[scale.id] = scale; + scale.mergeTicksOptions(); // TODO(SB): I think we should be able to remove this custom case (options.scale) // and consider it as a regular scale part of the "scales"" map only! This would diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 50aac0e874b..cf9915213ff 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -48,7 +48,9 @@ module.exports = function(Chart) { autoSkipPadding: 0, labelOffset: 0, // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: Chart.Ticks.formatters.values + callback: Chart.Ticks.formatters.values, + minor: {}, + major: {} } }; @@ -94,6 +96,28 @@ module.exports = function(Chart) { // Any function defined here is inherited by all scale types. // Any function can be extended by the scale type + mergeTicksOptions: function() { + if (this.options.ticks.minor === false) { + this.options.ticks.minor = { + display: false + }; + } + if (this.options.ticks.major === false) { + this.options.ticks.major = { + display: false + }; + } + for (var key in this.options.ticks) { + if (key !== 'major' && key !== 'minor') { + if (typeof this.options.ticks.minor[key] === 'undefined') { + this.options.ticks.minor[key] = this.options.ticks[key]; + } + if (typeof this.options.ticks.major[key] === 'undefined') { + this.options.ticks.major[key] = this.options.ticks[key]; + } + } + } + }, beforeUpdate: function() { helpers.callback(this.options.beforeUpdate, [this]); }, @@ -486,8 +510,8 @@ module.exports = function(Chart) { var context = me.ctx; var globalDefaults = Chart.defaults.global; - var optionTicks = options.ticks; - var optionMajorTicks = options.majorTicks ? options.majorTicks : optionTicks; + var optionTicks = options.ticks.minor; + var optionMajorTicks = options.ticks.major ? options.ticks.major : optionTicks; var gridLines = options.gridLines; var scaleLabel = options.scaleLabel; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index ce98f5679a8..387f5a976d9 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -36,19 +36,6 @@ module.exports = function(Chart) { }, ticks: { autoSkip: false - }, - majorTicks: { - beginAtZero: false, - minRotation: 0, - maxRotation: 50, - mirror: false, - padding: 0, - reverse: false, - display: true, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0, - callback: Chart.Ticks.formatters.values } }; @@ -62,13 +49,6 @@ module.exports = function(Chart) { Chart.Scale.prototype.initialize.call(this); }, - mergeTicksOptions: function() { - for (var key in this.options.ticks) { - if (typeof this.options.majorTicks[key] === 'undefined') { - this.options.majorTicks[key] = this.options.ticks[key]; - } - } - }, determineDataLimits: function() { var me = this; var timeOpts = me.options.time; @@ -210,14 +190,14 @@ module.exports = function(Chart) { var major = false; var tickOpts; if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) { - // format as senior unit + // format as major unit formattedTick = tick.format(this.majorDisplayFormat); - tickOpts = this.options.majorTicks; + tickOpts = this.options.ticks.major; major = true; } else { - // format as base unit + // format as minor (base) unit formattedTick = tick.format(this.displayFormat); - tickOpts = this.options.ticks; + tickOpts = this.options.ticks.minor; } var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index 65e369d46b3..7777c9b16a7 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -215,6 +215,8 @@ describe('Core helper tests', function() { autoSkip: true, autoSkipPadding: 0, labelOffset: 0, + minor: {}, + major: {}, }, type: 'linear' }, { @@ -253,6 +255,8 @@ describe('Core helper tests', function() { autoSkip: true, autoSkipPadding: 0, labelOffset: 0, + minor: {}, + major: {}, }, type: 'linear' }] diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index 6eba2c2e2eb..d63fc59bf0e 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -44,7 +44,9 @@ describe('Category scale tests', function() { callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below autoSkip: true, autoSkipPadding: 0, - labelOffset: 0 + labelOffset: 0, + minor: {}, + major: {}, } }); diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index c63cad338f6..2a80c99a2cf 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -42,7 +42,9 @@ describe('Linear Scale', function() { callback: defaultConfig.ticks.callback, // make this work nicer, then check below autoSkip: true, autoSkipPadding: 0, - labelOffset: 0 + labelOffset: 0, + minor: {}, + major: {}, } }); diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index b1053eefce2..95287aa79a6 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -41,7 +41,9 @@ describe('Logarithmic Scale tests', function() { callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below autoSkip: true, autoSkipPadding: 0, - labelOffset: 0 + labelOffset: 0, + minor: {}, + major: {}, }, }); diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 3d102cf376f..e966e1229f0 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -58,7 +58,9 @@ describe('Test the radial linear scale', function() { callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below autoSkip: true, autoSkipPadding: 0, - labelOffset: 0 + labelOffset: 0, + minor: {}, + major: {}, }, }); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 94a84bcc543..3c824331e2b 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -89,20 +89,9 @@ describe('Time scale tests', function() { callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below, autoSkip: false, autoSkipPadding: 0, - labelOffset: 0 - }, - majorTicks: { - beginAtZero: false, - minRotation: 0, - maxRotation: 50, - mirror: false, - padding: 0, - reverse: false, - display: true, - callback: defaultConfig.majorTicks.callback, // make this nicer, then check explicitly below, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0 + labelOffset: 0, + minor: {}, + major: {}, }, time: { parser: false, From 7bcae92deeea65d6d5e35dc6cf8aa41f5b7bf7e6 Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Mon, 12 Jun 2017 12:30:21 +0300 Subject: [PATCH 15/16] Minification changes. --- src/core/core.scale.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index cf9915213ff..471540e034c 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -97,23 +97,24 @@ module.exports = function(Chart) { // Any function can be extended by the scale type mergeTicksOptions: function() { - if (this.options.ticks.minor === false) { - this.options.ticks.minor = { + var ticks = this.options.ticks; + if (ticks.minor === false) { + ticks.minor = { display: false }; } - if (this.options.ticks.major === false) { - this.options.ticks.major = { + if (ticks.major === false) { + ticks.major = { display: false }; } - for (var key in this.options.ticks) { + for (var key in ticks) { if (key !== 'major' && key !== 'minor') { - if (typeof this.options.ticks.minor[key] === 'undefined') { - this.options.ticks.minor[key] = this.options.ticks[key]; + if (typeof ticks.minor[key] === 'undefined') { + ticks.minor[key] = ticks[key]; } - if (typeof this.options.ticks.major[key] === 'undefined') { - this.options.ticks.major[key] = this.options.ticks[key]; + if (typeof ticks.major[key] === 'undefined') { + ticks.major[key] = ticks[key]; } } } @@ -511,7 +512,7 @@ module.exports = function(Chart) { var context = me.ctx; var globalDefaults = Chart.defaults.global; var optionTicks = options.ticks.minor; - var optionMajorTicks = options.ticks.major ? options.ticks.major : optionTicks; + var optionMajorTicks = options.ticks.major || optionTicks; var gridLines = options.gridLines; var scaleLabel = options.scaleLabel; @@ -572,7 +573,7 @@ module.exports = function(Chart) { var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom; helpers.each(me.ticks, function(tick, index) { - var label = typeof tick === 'object' && typeof tick.value !== 'undefined' ? tick.value : tick; + var label = (tick && tick.value) || tick; // If the callback returned a null or undefined value, do not draw this line if (label === undefined || label === null) { return; @@ -670,7 +671,7 @@ module.exports = function(Chart) { glBorderDashOffset: borderDashOffset, rotation: -1 * labelRotationRadians, label: label, - major: tick.major === true, + major: tick.major, textBaseline: textBaseline, textAlign: textAlign }); From 0dd966f13ac2f60f1e83da2e31e0269aff8baaab Mon Sep 17 00:00:00 2001 From: "andrew.hurskiy" Date: Tue, 13 Jun 2017 10:19:26 +0300 Subject: [PATCH 16/16] Excluded 'week' and 'quarter' units from determining as major. --- samples/scales/time/line-point-data.html | 8 +++++++- src/helpers/helpers.time.js | 11 +++++++---- test/specs/scale.time.tests.js | 10 +++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/samples/scales/time/line-point-data.html b/samples/scales/time/line-point-data.html index 537fdf17d1a..3483c454535 100644 --- a/samples/scales/time/line-point-data.html +++ b/samples/scales/time/line-point-data.html @@ -88,7 +88,13 @@ scaleLabel: { display: true, labelString: 'Date' - } + }, + ticks: { + major: { + fontStyle: "bold", + fontColor: "#FF0000" + } + } }], yAxes: [{ display: true, diff --git a/src/helpers/helpers.time.js b/src/helpers/helpers.time.js index 670917d1ab0..f3e6e96b1c4 100644 --- a/src/helpers/helpers.time.js +++ b/src/helpers/helpers.time.js @@ -145,13 +145,16 @@ module.exports = function(Chart) { */ determineMajorUnit: function(unit) { var units = Object.keys(interval); - var majorUnit = null; var unitIndex = units.indexOf(unit); - if (unitIndex < units.length - 1) { - majorUnit = units[unitIndex + 1]; + while (unitIndex < units.length) { + var majorUnit = units[++unitIndex]; + // exclude 'week' and 'quarter' units + if (majorUnit !== 'week' && majorUnit !== 'quarter') { + return majorUnit; + } } - return majorUnit; + return null; }, /** diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 3c824331e2b..86e0216e330 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -135,7 +135,7 @@ describe('Time scale tests', function() { scale.update(1000, 200); var ticks = getTicksValues(scale.ticks); - expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']); }); it('should accept labels as date objects', function() { @@ -146,7 +146,7 @@ describe('Time scale tests', function() { scale.update(1000, 200); var ticks = getTicksValues(scale.ticks); - expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']); }); it('should accept data as xy points', function() { @@ -194,7 +194,7 @@ describe('Time scale tests', function() { xScale.update(800, 200); var ticks = getTicksValues(xScale.ticks); - expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4, 2015', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11, 2015']); + expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']); }); }); @@ -262,7 +262,7 @@ describe('Time scale tests', function() { var scale = createScale(mockData, config); var ticks = getTicksValues(scale.ticks); - expect(ticks).toEqual(['Jan 1', 'Jan 2', 'Jan 3']); + expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3']); }); it('should build ticks using the config diff', function() { @@ -297,7 +297,7 @@ describe('Time scale tests', function() { config.time.min = '2014-12-29T04:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[0].value).toEqual('Dec 29'); + expect(scale.ticks[0].value).toEqual('Dec 28'); }); it('should use the max option', function() {