From f241480557763e9f7e327428b37af2f1a66e1e42 Mon Sep 17 00:00:00 2001 From: Gokul Krishna Date: Tue, 25 Aug 2026 04:44:36 +0530 Subject: [PATCH 1/2] Add JSON download modebar button --- src/components/modebar/buttons.js | 13 +++++++ src/components/modebar/constants.js | 2 +- src/components/modebar/manage.js | 2 ++ src/plot_api/plot_config.js | 3 +- src/types/core/layout.d.ts | 9 ++--- src/types/generated/schema.d.ts | 4 +-- test/jasmine/tests/modebar_test.js | 55 +++++++++++++++++++++++++++++ test/plot-schema.json | 4 +-- 8 files changed, 82 insertions(+), 10 deletions(-) diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 358ea14ee03..fe5b2b0872d 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -69,6 +69,19 @@ modeBarButtons.toImage = { } }; +modeBarButtons.downloadJson = { + name: 'downloadJson', + title: function (gd) { + return _(gd, 'Download plot as JSON'); + }, + icon: Icons.disk, + click: function (gd) { + Registry.call('downloadImage', gd, {format: 'full-json'}).catch(function () { + Lib.notifier(_(gd, 'Sorry, there was a problem downloading your JSON file!'), 'long', gd); + }); + } +}; + modeBarButtons.sendChartToCloud = { name: 'sendChartToCloud', title: function (gd) { diff --git a/src/components/modebar/constants.js b/src/components/modebar/constants.js index 788cbb5ae4e..caee7c3a274 100644 --- a/src/components/modebar/constants.js +++ b/src/components/modebar/constants.js @@ -18,7 +18,7 @@ var backButtons = [ 'hovercompare', 'togglehover', 'togglespikelines' -].concat(DRAW_MODES); +].concat(DRAW_MODES, ['downloadJson']); var foreButtons = []; var addToForeButtons = function(b) { diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index c5e2bd230af..eceafdd5f5b 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -246,6 +246,8 @@ function getButtonGroups(gd) { enableHover('hoverClosestGeo'); enableHover('hoverClosest3d'); enableHover('hoverClosestPie'); + } else if(b === 'downloadjson') { + newList.push(modeBarButtons.downloadJson); } } else newList.push(b); } diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 1d65d274e0c..7eacbff31e3 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -262,7 +262,8 @@ var configAttributes = { 'To enable predefined modebar buttons e.g. shape drawing, hover and spikelines,', 'simply provide their string name(s). This could include:', '*v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*,', - '*drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect* and *eraseshape*.', + '*drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape*', + 'and *downloadJson*.', 'Please note that these predefined buttons will only be shown if they are compatible', 'with all trace types used in a graph.' ].join(' ') diff --git a/src/types/core/layout.d.ts b/src/types/core/layout.d.ts index ab8b3c73e9f..3f23a471b32 100644 --- a/src/types/core/layout.d.ts +++ b/src/types/core/layout.d.ts @@ -44,9 +44,10 @@ export type AxisName = XAxisName | YAxisName; * - `config.modeBarButtons` — the button's registry key, resolved against * Plotly's button table; an unknown key throws. * - `config.modeBarButtonsToAdd` — as a *string*, only the shape-drawing - * buttons (`drawline` … `eraseshape`) and the category aliases at the end of - * this union. Any other button has to be added as a `ModeBarButton` object; - * passing its name as a string does not resolve to the built-in button. + * buttons (`drawline` … `eraseshape`), `downloadJson`, and the category + * aliases at the end of this union. Any other button has to be added as a + * `ModeBarButton` object; passing its name as a string does not resolve to + * the built-in button. */ export type ModeBarDefaultButtons = // Cartesian @@ -92,6 +93,7 @@ export type ModeBarDefaultButtons = | 'drawcircle' | 'drawrect' | 'eraseshape' + | 'downloadJson' // Other | 'toImage' | 'sendChartToCloud' @@ -169,4 +171,3 @@ export interface Template { /** Template layout defaults. */ layout?: Partial | undefined; } - diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index b01d6d9185c..5ad8b4bc1fd 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -16166,7 +16166,7 @@ export interface Layout { modebar?: { /** Sets the color of the active or hovered on icons in the modebar. */ activecolor?: Color; - /** Determines which predefined modebar buttons to add. Please note that these buttons will only be shown if they are compatible with all trace types used in a graph. Similar to `config.modeBarButtonsToAdd` option. This may include *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape*. */ + /** Determines which predefined modebar buttons to add. Please note that these buttons will only be shown if they are compatible with all trace types used in a graph. Similar to `config.modeBarButtonsToAdd` option. This may include *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape*, *downloadJson*. */ add?: string | string[]; /** Sets the background color of the modebar. */ bgcolor?: Color; @@ -16621,7 +16621,7 @@ export interface ConfigBase { */ modeBarButtons?: any; /** - * Add mode bar button using config objects See ./components/modebar/buttons.js for list of arguments. To enable predefined modebar buttons e.g. shape drawing, hover and spikelines, simply provide their string name(s). This could include: *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect* and *eraseshape*. Please note that these predefined buttons will only be shown if they are compatible with all trace types used in a graph. + * Add mode bar button using config objects See ./components/modebar/buttons.js for list of arguments. To enable predefined modebar buttons e.g. shape drawing, hover and spikelines, simply provide their string name(s). This could include: *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape* and *downloadJson*. Please note that these predefined buttons will only be shown if they are compatible with all trace types used in a graph. * @default [] */ modeBarButtonsToAdd?: any; diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 61653ecee5f..381ce7b7435 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -4,6 +4,7 @@ var createModeBar = require('../../../src/components/modebar/modebar'); var manageModeBar = require('../../../src/components/modebar/manage'); var Plotly = require('../../../lib/index'); +var Lib = require('../../../src/lib'); var Plots = require('../../../src/plots/plots'); var Registry = require('../../../src/registry'); var createGraphDiv = require('../assets/create_graph_div'); @@ -1054,6 +1055,60 @@ describe('ModeBar', function() { }); }); + describe('downloadJson handler', function() { + beforeEach(function() { + spyOn(Registry, 'call').and.returnValue(Promise.resolve()); + gd = createGraphDiv(); + }); + + it('requests a full JSON download when added through config', function(done) { + Plotly.newPlot(gd, {data: [], layout: {}, config: { + modeBarButtonsToAdd: ['downloadJson'] + }}) + .then(function() { + selectButton(gd._fullLayout._modeBar, 'downloadJson').click(); + expect(Registry.call) + .toHaveBeenCalledWith('downloadImage', gd, {format: 'full-json'}); + }) + .then(done, done.fail); + }); + + it('requests a full JSON download when added through layout', function(done) { + Plotly.newPlot(gd, {data: [], layout: { + modebar: {add: ['downloadJson']} + }}) + .then(function() { + selectButton(gd._fullLayout._modeBar, 'downloadJson').click(); + expect(Registry.call) + .toHaveBeenCalledWith('downloadImage', gd, {format: 'full-json'}); + }) + .then(done, done.fail); + }); + + it('reports download failures', function(done) { + spyOn(Lib, 'notifier'); + Registry.call.and.callFake(function() { + return Promise.reject(); + }); + + Plotly.newPlot(gd, {data: [], layout: {}, config: { + modeBarButtonsToAdd: ['downloadJson'] + }}) + .then(function() { + selectButton(gd._fullLayout._modeBar, 'downloadJson').click(); + return Promise.resolve(); + }) + .then(function() { + expect(Lib.notifier).toHaveBeenCalledWith( + 'Sorry, there was a problem downloading your JSON file!', + 'long', + gd + ); + }) + .then(done, done.fail); + }); + }); + describe('cartesian handlers', function() { beforeEach(function(done) { var mockData = [{ diff --git a/test/plot-schema.json b/test/plot-schema.json index 0412ea47de0..32866d982a2 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -244,7 +244,7 @@ "valType": "any" }, "modeBarButtonsToAdd": { - "description": "Add mode bar button using config objects See ./components/modebar/buttons.js for list of arguments. To enable predefined modebar buttons e.g. shape drawing, hover and spikelines, simply provide their string name(s). This could include: *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect* and *eraseshape*. Please note that these predefined buttons will only be shown if they are compatible with all trace types used in a graph.", + "description": "Add mode bar button using config objects See ./components/modebar/buttons.js for list of arguments. To enable predefined modebar buttons e.g. shape drawing, hover and spikelines, simply provide their string name(s). This could include: *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape* and *downloadJson*. Please note that these predefined buttons will only be shown if they are compatible with all trace types used in a graph.", "dflt": [], "valType": "any" }, @@ -4167,7 +4167,7 @@ }, "add": { "arrayOk": true, - "description": "Determines which predefined modebar buttons to add. Please note that these buttons will only be shown if they are compatible with all trace types used in a graph. Similar to `config.modeBarButtonsToAdd` option. This may include *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape*.", + "description": "Determines which predefined modebar buttons to add. Please note that these buttons will only be shown if they are compatible with all trace types used in a graph. Similar to `config.modeBarButtonsToAdd` option. This may include *v1hovermode*, *hoverclosest*, *hovercompare*, *togglehover*, *togglespikelines*, *drawline*, *drawopenpath*, *drawclosedpath*, *drawcircle*, *drawrect*, *eraseshape*, *downloadJson*.", "dflt": "", "editType": "modebar", "valType": "string" From ac2a46fe367a002aa56f77d562fbf30bf4fc454b Mon Sep 17 00:00:00 2001 From: Gokul Krishna Date: Tue, 25 Aug 2026 04:45:47 +0530 Subject: [PATCH 2/2] Add draftlog for JSON download button --- draftlogs/7990_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7990_add.md diff --git a/draftlogs/7990_add.md b/draftlogs/7990_add.md new file mode 100644 index 00000000000..7922e72d55a --- /dev/null +++ b/draftlogs/7990_add.md @@ -0,0 +1 @@ +- Add an opt-in modebar button for downloading Plotly figures as JSON [[#7990](https://github.com/plotly/plotly.js/pull/7990)]