diff --git a/package-lock.json b/package-lock.json
index 11fcb19be53..83d2a25b01f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2451,6 +2451,11 @@
         "d3-timer": "1"
       }
     },
+    "d3-format": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz",
+      "integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw=="
+    },
     "d3-hierarchy": {
       "version": "1.1.9",
       "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz",
@@ -2482,6 +2487,19 @@
         "d3-path": "1"
       }
     },
+    "d3-time": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
+      "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
+    },
+    "d3-time-format": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz",
+      "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==",
+      "requires": {
+        "d3-time": "1"
+      }
+    },
     "d3-timer": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz",
diff --git a/package.json b/package.json
index 875885a4576..c11d98ebee3 100644
--- a/package.json
+++ b/package.json
@@ -69,8 +69,10 @@
     "country-regex": "^1.1.0",
     "d3": "^3.5.17",
     "d3-force": "^1.2.1",
+    "d3-format": "^1.4.4",
     "d3-hierarchy": "^1.1.9",
     "d3-interpolate": "^1.4.0",
+    "d3-time-format": "^2.2.3",
     "delaunay-triangulate": "^1.1.6",
     "es6-promise": "^4.2.8",
     "fast-isnumeric": "^1.1.4",
diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js
index d8f99f96535..273e8217679 100644
--- a/src/components/drawing/index.js
+++ b/src/components/drawing/index.js
@@ -10,6 +10,7 @@
 'use strict';
 
 var d3 = require('d3');
+var numberFormat = require('d3-format').format;
 var isNumeric = require('fast-isnumeric');
 var tinycolor = require('tinycolor2');
 
@@ -275,7 +276,7 @@ function makePointPath(symbolNumber, r) {
 
 var HORZGRADIENT = {x1: 1, x2: 0, y1: 0, y2: 0};
 var VERTGRADIENT = {x1: 0, x2: 0, y1: 1, y2: 0};
-var stopFormatter = d3.format('~.1f');
+var stopFormatter = numberFormat('~f');
 var gradientInfo = {
     radial: {node: 'radialGradient'},
     radialreversed: {node: 'radialGradient', reversed: true},
diff --git a/src/constants/docs.js b/src/constants/docs.js
index 72e06e9af84..d3138ffef2c 100644
--- a/src/constants/docs.js
+++ b/src/constants/docs.js
@@ -9,6 +9,6 @@
 'use strict';
 
 module.exports = {
-    FORMAT_LINK: 'https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format',
-    DATE_FORMAT_LINK: 'https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format'
+    FORMAT_LINK: 'https://github.com/d3/d3-format#format',
+    DATE_FORMAT_LINK: 'https://github.com/d3/d3-time-format#locale_format'
 };
diff --git a/src/lib/dates.js b/src/lib/dates.js
index 52ca20433e1..8b81836c195 100644
--- a/src/lib/dates.js
+++ b/src/lib/dates.js
@@ -25,7 +25,7 @@ var EPOCHJD = constants.EPOCHJD;
 
 var Registry = require('../registry');
 
-var utcFormat = d3.time.format.utc;
+var utcFormat = require('d3-time-format').utcFormat;
 
 var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d(:?\d\d)?)?)?)?)?)?\s*$/m;
 // special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months
diff --git a/src/lib/index.js b/src/lib/index.js
index 331c5dbefb4..37bd910929a 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -8,7 +8,8 @@
 
 'use strict';
 
-var d3 = require('d3');
+var numberFormat = require('d3-format').format;
+var utcFormat = require('d3-time-format').utcFormat;
 var isNumeric = require('fast-isnumeric');
 
 var numConstants = require('../constants/numerical');
@@ -1077,12 +1078,12 @@ function templateFormatString(string, labels, d3locale) {
         if(format) {
             var fmt;
             if(format[0] === ':') {
-                fmt = d3locale ? d3locale.numberFormat : d3.format;
+                fmt = numberFormat;
                 value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value);
             }
 
             if(format[0] === '|') {
-                fmt = d3locale ? d3locale.timeFormat.utc : d3.time.format.utc;
+                fmt = d3locale ? d3locale.utcFormat : utcFormat;
                 var ms = lib.dateTime2ms(value);
                 value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt);
             }
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index 573362f0047..63d42bc6de3 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -9,6 +9,7 @@
 'use strict';
 
 var d3 = require('d3');
+var numberFormat = require('d3-format').format;
 var tinycolor = require('tinycolor2');
 var supportsPassive = require('has-passive-events');
 
@@ -983,11 +984,11 @@ function getEndText(ax, end) {
         return initialVal;
     } else if(ax.type === 'log') {
         dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3;
-        return d3.format('.' + dig + 'g')(Math.pow(10, initialVal));
+        return numberFormat('.' + dig + 'g')(Math.pow(10, initialVal));
     } else { // linear numeric (or category... but just show numbers here)
         dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) -
             Math.floor(Math.log(diff) / Math.LN10) + 4;
-        return d3.format('.' + String(dig) + 'g')(initialVal);
+        return numberFormat('.' + String(dig) + 'g')(initialVal);
     }
 }
 
diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js
index 508d3731b00..2d34c2ad629 100644
--- a/src/plots/cartesian/set_convert.js
+++ b/src/plots/cartesian/set_convert.js
@@ -9,6 +9,8 @@
 'use strict';
 
 var d3 = require('d3');
+var numberFormat = require('d3-format').format;
+var utcFormat = require('d3-time-format').utcFormat;
 var isNumeric = require('fast-isnumeric');
 
 var Lib = require('../../lib');
@@ -954,13 +956,13 @@ module.exports = function setConvert(ax, fullLayout) {
     // Fall back on default format for dummy axes that don't care about formatting
     var locale = fullLayout._d3locale;
     if(ax.type === 'date') {
-        ax._dateFormat = locale ? locale.timeFormat.utc : d3.time.format.utc;
+        ax._dateFormat = locale ? locale.utcFormat : utcFormat;
         ax._extraFormat = fullLayout._extraFormat;
     }
     // occasionally we need _numFormat to pass through
     // even though it won't be needed by this axis
     ax._separators = fullLayout.separators;
-    ax._numFormat = locale ? locale.numberFormat : d3.format;
+    ax._numFormat = numberFormat;
 
     // and for bar charts and box plots: reset forced minimum tick spacing
     delete ax._minDtick;
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 9fad0247f05..62a7522a75b 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -9,6 +9,7 @@
 'use strict';
 
 var d3 = require('d3');
+var timeFormatLocale = require('d3-time-format').timeFormatLocale;
 var isNumeric = require('fast-isnumeric');
 
 var Registry = require('../registry');
@@ -723,7 +724,7 @@ function getFormatter(formatObj, separators) {
     formatObj.decimal = separators.charAt(0);
     formatObj.thousands = separators.charAt(1);
 
-    return d3.locale(formatObj);
+    return timeFormatLocale(formatObj);
 }
 
 function fillMetaTextHelpers(newFullData, newFullLayout) {
diff --git a/src/traces/indicator/defaults.js b/src/traces/indicator/defaults.js
index 717f99984c8..2b6b14c4b3e 100644
--- a/src/traces/indicator/defaults.js
+++ b/src/traces/indicator/defaults.js
@@ -64,7 +64,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
         }
         coerce('delta.reference', traceOut.value);
         coerce('delta.relative');
-        coerce('delta.valueformat', traceOut.delta.relative ? '2%' : '');
+        coerce('delta.valueformat', traceOut.delta.relative ? '.0%' : '');
         coerce('delta.increasing.symbol');
         coerce('delta.increasing.color');
         coerce('delta.decreasing.symbol');
diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js
index d314f9ad4c8..4dfc01edbdc 100644
--- a/src/traces/parcoords/parcoords.js
+++ b/src/traces/parcoords/parcoords.js
@@ -9,6 +9,7 @@
 'use strict';
 
 var d3 = require('d3');
+var numberFormat = require('d3-format').format;
 var rgba = require('color-rgba');
 
 var Axes = require('../../plots/cartesian/axes');
@@ -85,7 +86,7 @@ function domainScale(height, padding, dimension, tickvals, ticktext) {
     var extent = dimensionExtent(dimension);
     if(tickvals) {
         return d3.scale.ordinal()
-            .domain(tickvals.map(toText(d3.format(dimension.tickformat), ticktext)))
+            .domain(tickvals.map(toText(numberFormat(dimension.tickformat), ticktext)))
             .range(tickvals
                 .map(function(d) {
                     var unitVal = (d - extent[0]) / (extent[1] - extent[0]);
@@ -272,7 +273,7 @@ function viewModel(state, callbacks, model) {
 
             // ensure ticktext and tickvals have same length
             if(!Array.isArray(ticktext) || !ticktext.length) {
-                ticktext = tickvals.map(d3.format(dimension.tickformat));
+                ticktext = tickvals.map(numberFormat(dimension.tickformat));
             } else if(ticktext.length > tickvals.length) {
                 ticktext = ticktext.slice(0, tickvals.length);
             } else if(tickvals.length > ticktext.length) {
diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js
index d85a2da7b7b..be066ebcaae 100644
--- a/src/traces/sankey/plot.js
+++ b/src/traces/sankey/plot.js
@@ -9,6 +9,7 @@
 'use strict';
 
 var d3 = require('d3');
+var numberFormat = require('d3-format').format;
 var render = require('./render');
 var Fx = require('../../components/fx');
 var Color = require('../../components/color');
@@ -200,7 +201,7 @@ module.exports = function plot(gd, calcData) {
             link.fullData = link.trace;
             obj = d.link.trace.link;
             var hoverCenter = hoverCenterPosition(link);
-            var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(link.value) + d.valueSuffix};
+            var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(link.value) + d.valueSuffix};
 
             hoverItems.push({
                 x: hoverCenter[0],
@@ -210,7 +211,7 @@ module.exports = function plot(gd, calcData) {
                     link.label || '',
                     sourceLabel + link.source.label,
                     targetLabel + link.target.label,
-                    link.concentrationscale ? concentrationLabel + d3.format('%0.2f')(link.flow.labelConcentration) : ''
+                    link.concentrationscale ? concentrationLabel + numberFormat('%0.2f')(link.flow.labelConcentration) : ''
                 ].filter(renderableValuePresent).join('<br>'),
                 color: castHoverOption(obj, 'bgcolor') || Color.addOpacity(link.color, 1),
                 borderColor: castHoverOption(obj, 'bordercolor'),
@@ -289,14 +290,14 @@ module.exports = function plot(gd, calcData) {
         var hoverCenterX1 = boundingBox.right + 2 - rootBBox.left;
         var hoverCenterY = boundingBox.top + boundingBox.height / 4 - rootBBox.top;
 
-        var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix};
+        var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix};
         d.node.fullData = d.node.trace;
 
         var tooltip = Fx.loneHover({
             x0: hoverCenterX0,
             x1: hoverCenterX1,
             y: hoverCenterY,
-            name: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix,
+            name: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix,
             text: [
                 d.node.label,
                 incomingLabel + d.node.targetLinks.length,
diff --git a/src/traces/table/plot.js b/src/traces/table/plot.js
index 9b2bddbed9f..f28b0d99a01 100644
--- a/src/traces/table/plot.js
+++ b/src/traces/table/plot.js
@@ -10,6 +10,7 @@
 
 var c = require('./constants');
 var d3 = require('d3');
+var numberFormat = require('d3-format').format;
 var gup = require('../../lib/gup');
 var Drawing = require('../../components/drawing');
 var svgUtil = require('../../lib/svg_text_utils');
@@ -534,7 +535,7 @@ function populateCellText(cellText, tableControlView, allColumnBlock, gd) {
             var suffix = latex ? '' : gridPick(d.calcdata.cells.suffix, col, row) || '';
             var format = latex ? null : gridPick(d.calcdata.cells.format, col, row) || null;
 
-            var prefixSuffixedText = prefix + (format ? d3.format(format)(d.value) : d.value) + suffix;
+            var prefixSuffixedText = prefix + (format ? numberFormat(format)(d.value) : d.value) + suffix;
 
             var hasWrapSplitCharacter;
             d.wrappingNeeded = !d.wrapped && !userBrokenText && !latex && (hasWrapSplitCharacter = hasWrapCharacter(prefixSuffixedText));
diff --git a/test/image/mocks/texttemplate_scatter.json b/test/image/mocks/texttemplate_scatter.json
index 3fff2e746f4..132aa727211 100644
--- a/test/image/mocks/texttemplate_scatter.json
+++ b/test/image/mocks/texttemplate_scatter.json
@@ -17,7 +17,7 @@
         "Toronto",
         "Vancouver"
       ],
-      "texttemplate": "%{text}: (%{lon:.0f}, %{lat:0.f}): %{customdata:.2s}",
+      "texttemplate": "%{text}: (%{lon:.0f}, %{lat:~f}): %{customdata:.2s}",
       "textposition": "top center",
       "customdata": [1780000, 2930000, 675218],
       "geo": "geo"
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index 5c19984ce9e..f9c7b2a347c 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -1,5 +1,6 @@
 var Plotly = require('@lib/index');
 var d3 = require('d3');
+var utcFormat = require('d3-time-format').utcFormat;
 
 var Plots = require('@src/plots/plots');
 var Lib = require('@src/lib');
@@ -5206,7 +5207,7 @@ function getZoomOutButton(gd) {
 }
 
 function getFormatter(format) {
-    return d3.time.format.utc(format);
+    return utcFormat(format);
 }
 
 describe('Test Axes.getTickformat', function() {
diff --git a/test/jasmine/tests/indicator_test.js b/test/jasmine/tests/indicator_test.js
index 4bc28714830..1d65f57ec20 100644
--- a/test/jasmine/tests/indicator_test.js
+++ b/test/jasmine/tests/indicator_test.js
@@ -48,7 +48,7 @@ describe('Indicator defaults', function() {
 
     it('defaults to displaying relative changes in percentage', function() {
         var out = _supply({type: 'indicator', mode: 'delta', delta: {relative: true}, value: 1});
-        expect(out.delta.valueformat).toBe('2%');
+        expect(out.delta.valueformat).toBe('.0%');
     });
 
     it('should not ignore empty valueformat', function() {
@@ -164,7 +164,7 @@ describe('Indicator plot', function() {
             Plotly.newPlot(gd, [{
                 type: 'indicator',
                 value: 500,
-                number: {valueformat: '0.f'}
+                number: {valueformat: '~f'}
             }], {width: 400, height: 400})
             .then(function() {
                 checkNumbersScale(1, 'initialy at normal scale');
@@ -185,7 +185,7 @@ describe('Indicator plot', function() {
             Plotly.newPlot(gd, [{
                 type: 'indicator',
                 value: 1,
-                number: {valueformat: '0.f'}
+                number: {valueformat: '~f'}
             }], {width: 400, height: 400})
             .then(function() {
                 checkNumbersScale(1, 'initialy at normal scale');
@@ -208,7 +208,7 @@ describe('Indicator plot', function() {
                     type: 'indicator',
                     mode: 'number+delta',
                     value: 1,
-                    number: {valueformat: '0.f'}
+                    number: {valueformat: '~f'}
                 };
                 figure[numberType] = {font: {size: 100}};
                 Plotly.newPlot(gd, [figure], {width: 400, height: 400})
diff --git a/test/jasmine/tests/localize_test.js b/test/jasmine/tests/localize_test.js
index 92d2d0f556b..d787c83fea4 100644
--- a/test/jasmine/tests/localize_test.js
+++ b/test/jasmine/tests/localize_test.js
@@ -3,6 +3,7 @@ var _ = Lib._;
 var Registry = require('@src/registry');
 
 var d3 = require('d3');
+var utcFormat = require('d3-time-format').utcFormat;
 
 var Plotly = require('@lib');
 var createGraphDiv = require('../assets/create_graph_div');
@@ -236,7 +237,7 @@ describe('localization', function() {
             expect(firstYLabel()).toBe('0~5');
             var d0 = new Date(0); // thursday, Jan 1 1970 (UTC)
             // sanity check that d0 is what we think...
-            expect(d3.time.format.utc('%a %b %A %B')(d0)).toBe('Thu Jan Thursday January');
+            expect(utcFormat('%a %b %A %B')(d0)).toBe('Thu Jan Thursday January');
             // full names were not overridden, so fall back on english
             expect(gd._fullLayout.xaxis._dateFormat('%a %b %A %B')(d0)).toBe('t !1 Thursday January');
         })
@@ -306,15 +307,10 @@ describe('localization', function() {
         .then(function() {
             expect(firstYLabel()).toBe('0p5');
 
-            return Plotly.relayout(gd, {'yaxis.tickformat': '.3f'});
-        })
-        .then(function() {
-            expect(firstYLabel()).toBe('0p500');
-
             return Plotly.relayout(gd, {separators: null});
         })
         .then(function() {
-            expect(firstYLabel()).toBe('0D500');
+            expect(firstYLabel()).toBe('0D5');
         })
         .catch(failTest)
         .then(done);