diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 5810311ed99..6a609832e04 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -141,6 +141,22 @@ exports.valObjectMeta = { else propOut.set(dflt); } }, + colorlist: { + description: [ + 'A list of colors.', + 'Must be an {array} containing valid colors.', + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + function isColor(color) { + return tinycolor(color).isValid(); + } + if(!Array.isArray(v) || !v.length) propOut.set(dflt); + else if(v.every(isColor)) propOut.set(v); + else propOut.set(dflt); + } + }, colorscale: { description: [ 'A Plotly colorscale either picked by a name:', diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index c3ee025aa75..7c23ffd77e8 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -175,5 +175,12 @@ module.exports = { role: 'info', editType: 'legend', description: 'Determines whether or not a legend is drawn.' - } + }, + colorway: { + valType: 'colorlist', + dflt: colorAttrs.defaults, + role: 'style', + editType: 'calc', + description: 'Sets the default trace colors.' + }, }; diff --git a/src/plots/plots.js b/src/plots/plots.js index 7e0973196f6..145f08c4f4f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -950,8 +950,9 @@ plots.supplyFrameDefaults = function(frameIn) { }; plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInIndex) { + var colorway = layout.colorway || Color.defaults; var traceOut = {}, - defaultColor = Color.defaults[traceOutIndex % Color.defaults.length]; + defaultColor = colorway[traceOutIndex % colorway.length]; function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt); @@ -1138,6 +1139,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { coerce('separators'); coerce('hidesources'); + coerce('colorway'); + Registry.getComponentMethod( 'calendars', 'handleDefaults' @@ -2171,6 +2174,7 @@ plots.doCalcdata = function(gd, traces) { // for sharing colors across pies (and for legend) fullLayout._piecolormap = {}; + fullLayout._piecolorway = null; fullLayout._piedefaultcolorcount = 0; // If traces were specified and this trace was not included, diff --git a/src/traces/pie/calc.js b/src/traces/pie/calc.js index bb425b1a606..8e31767d917 100644 --- a/src/traces/pie/calc.js +++ b/src/traces/pie/calc.js @@ -21,6 +21,7 @@ module.exports = function calc(gd, trace) { var colors = trace.marker.colors; var cd = []; var fullLayout = gd._fullLayout; + var colorWay = fullLayout.colorway; var colorMap = fullLayout._piecolormap; var allThisTraceLabels = {}; var vTotal = 0; @@ -28,6 +29,10 @@ module.exports = function calc(gd, trace) { var i, v, label, hidden, pt; + if(!fullLayout._piecolorway && colorWay !== Color.defaults) { + fullLayout._piecolorway = generateDefaultColors(colorWay); + } + if(trace.dlabel) { labels = new Array(vals.length); for(i = 0; i < vals.length; i++) { @@ -107,7 +112,10 @@ module.exports = function calc(gd, trace) { pt.color = colorMap[pt.label]; } else { - colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount); + colorMap[pt.label] = pt.color = nextDefaultColor( + fullLayout._piedefaultcolorcount, + fullLayout._piecolorway + ); fullLayout._piedefaultcolorcount++; } } @@ -148,22 +156,29 @@ module.exports = function calc(gd, trace) { */ var pieDefaultColors; -function nextDefaultColor(index) { +function nextDefaultColor(index, pieColorWay) { if(!pieDefaultColors) { // generate this default set on demand (but then it gets saved in the module) var mainDefaults = Color.defaults; - pieDefaultColors = mainDefaults.slice(); + pieDefaultColors = generateDefaultColors(mainDefaults); + } - var i; + var pieColors = pieColorWay || pieDefaultColors; + return pieColors[index % pieColors.length]; +} - for(i = 0; i < mainDefaults.length; i++) { - pieDefaultColors.push(tinycolor(mainDefaults[i]).lighten(20).toHexString()); - } +function generateDefaultColors(colorList) { + var i; - for(i = 0; i < Color.defaults.length; i++) { - pieDefaultColors.push(tinycolor(mainDefaults[i]).darken(20).toHexString()); - } + var pieColors = colorList.slice(); + + for(i = 0; i < colorList.length; i++) { + pieColors.push(tinycolor(colorList[i]).lighten(20).toHexString()); + } + + for(i = 0; i < colorList.length; i++) { + pieColors.push(tinycolor(colorList[i]).darken(20).toHexString()); } - return pieDefaultColors[index % pieDefaultColors.length]; + return pieColors; } diff --git a/test/image/baselines/layout-colorway.png b/test/image/baselines/layout-colorway.png new file mode 100644 index 00000000000..332db30b056 Binary files /dev/null and b/test/image/baselines/layout-colorway.png differ diff --git a/test/image/mocks/layout-colorway.json b/test/image/mocks/layout-colorway.json new file mode 100644 index 00000000000..8ef37ab9578 --- /dev/null +++ b/test/image/mocks/layout-colorway.json @@ -0,0 +1,27 @@ +{ + "data": [ + {"y": [8, 7, 7, 6, 5, 4, 4, 3, 2, 2, 3]}, + {"y": [7, 7, 6, 5, 4, 4, 3, 2, 2, 3, 8]}, + {"y": [7, 6, 5, 4, 4, 3, 2, 2, 3, 8, 7]}, + {"y": [6, 5, 4, 4, 3, 2, 2, 3, 8, 7, 7]}, + {"y": [5, 4, 4, 3, 2, 2, 3, 8, 7, 7, 6]}, + { + "labels": ["a","b","c","c","c","a","d","e","f","f","g","h"], + "type": "pie", + "domain": {"x": [0, 0.4]} + } + ], + "layout": { + "title": "Custom Trace Color Defaults", + "colorway": [ + "#DE5845", + "#E83898", + "#A83DD1", + "#5A38E8", + "#3C71DE" + ], + "xaxis": { + "domain": [0.4, 1] + } + } +} diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 4e97759e6b8..12b61aaa31a 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -1073,6 +1073,15 @@ describe('Test lib.js:', function() { }); }); + it('should work for valType \'colorlist\' where', function() { + var shouldPass = [['red'], ['#ffffff'], ['rgba(0,0,0,1)'], ['red', 'green', 'blue']], + shouldFail = [1, null, undefined, {}, [], 'red', ['red', null]]; + + assert(shouldPass, shouldFail, { + valType: 'colorlist' + }); + }); + it('should work for valType \'colorscale\' where', function() { var good = [ [0, 'red'], [1, 'blue'] ], bad = [ [0.1, 'red'], [1, 'blue'] ], diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index 1f4e109a18f..a1e688e2467 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -546,3 +546,44 @@ describe('Test event data of interactions on a pie plot:', function() { }); }); }); + +describe('pie relayout', function() { + var gd; + + beforeEach(function() { gd = createGraphDiv(); }); + + afterEach(destroyGraphDiv); + + it('will update colors when colorway is updated', function(done) { + var originalColors = [ + 'rgb(255,0,0)', + 'rgb(0,255,0)', + 'rgb(0,0,255)', + ]; + + var relayoutColors = [ + 'rgb(255,255,0)', + 'rgb(0,255,255)', + 'rgb(255,0,255)', + ]; + + function checkRelayoutColor(d, i) { + expect(this.style.fill.replace(/\s/g, '')).toBe(relayoutColors[i]); + } + + Plotly.newPlot(gd, [{ + labels: ['a', 'b', 'c', 'a', 'b', 'a'], + type: 'pie' + }], { + colorway: originalColors + }) + .then(function() { + return Plotly.relayout(gd, 'colorway', relayoutColors); + }) + .then(function() { + var slices = d3.selectAll('.slice path'); + slices.each(checkRelayoutColor); + }) + .then(done); + }); +});