Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/8066_change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Describe `config.toImageButtonOptions` in the plot schema [[#8066](https://github.com/plotly/plotly.js/pull/8066)]
1 change: 1 addition & 0 deletions draftlogs/8066_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Correct the image export types: `format` now accepts `full-json`, `width` and `height` accept `null`, and `ToImgopts.format`, `ToImgopts.width`, `ToImgopts.height` and `DownloadImgopts.filename` are optional because `toImage` and `downloadImage` supply them [[#8066](https://github.com/plotly/plotly.js/pull/8066)]
49 changes: 38 additions & 11 deletions src/plot_api/plot_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
* at the moment.
*/

const { format, width, height, scale } = require('./to_image_attributes').default;

// `toImage` owns the scale default, not the button. A default here would reach
// `downloadImage` on every click and change what the caller receives.
const { dflt: scaleDflt, ...buttonScale } = scale;

var configAttributes = {
staticPlot: {
valType: 'boolean',
Expand Down Expand Up @@ -50,6 +56,11 @@ var configAttributes = {
].join(' ')
},
edits: {
description: [
'Determines which parts of the graph a user can edit directly.',
'`editable` sets every key here at once, and a key set here overrides it.',
'`staticPlot` disables all of them.'
].join(' '),
annotationPosition: {
valType: 'boolean',
dflt: false,
Expand Down Expand Up @@ -279,13 +290,24 @@ var configAttributes = {
].join(' ')
},
toImageButtonOptions: {
valType: 'any',
dflt: {},
description: [
'Statically override options for toImage modebar button',
'allowed keys are format, filename, width, height, scale',
'see ../components/modebar/buttons.js'
].join(' ')
'Statically overrides options for the toImage modebar button.',
'The button reads only `format`, `filename`, `width`, `height` and `scale`,',
'and drops every other key.'
].join(' '),
format,
filename: {
valType: 'string',
description: [
'Sets the name of the downloaded file, without an extension.',
'The button appends the extension that matches `format`.',
'The name defaults to the plot title, then the plot subtitle,',
'then *plot-image*.'
].join(' ')
},
width,
height,
scale: buttonScale
},
displaylogo: {
valType: 'boolean',
Expand Down Expand Up @@ -414,12 +436,17 @@ var configAttributes = {
var dfltConfig = {};

function crawl(src, target) {
for(var k in src) {
var obj = src[k];
if(obj.valType) {
target[k] = obj.dflt;
for (const k in src) {
const obj = src[k];
// A container carries its own `description` string beside the child
// attributes. Recursing into a string never terminates.
if (typeof obj !== 'object' || obj === null) continue;
if (obj.valType) {
// An attribute without a `dflt` seeds no key. A key holding
// `undefined` reads as present to `in`, which callers test.
if ('dflt' in obj) target[k] = obj.dflt;
} else {
if(!target[k]) {
if (!target[k]) {
target[k] = {};
}
crawl(obj, target[k]);
Expand Down
60 changes: 2 additions & 58 deletions src/plot_api/to_image.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,15 @@ var helpers = require('../snapshot/helpers');
var toSVG = require('../snapshot/tosvg');
var svgToImg = require('../snapshot/svgtoimg');
var version = require('../version').version;

var attrs = {
format: {
valType: 'enumerated',
values: ['png', 'jpeg', 'webp', 'svg', 'full-json'],
dflt: 'png',
description: 'Sets the format of exported image.'
},
width: {
valType: 'number',
min: 1,
description: [
'Sets the exported image width.',
'Defaults to the value found in `layout.width`',
'If set to *null*, the exported image width will match the current graph width.'
].join(' ')
},
height: {
valType: 'number',
min: 1,
description: [
'Sets the exported image height.',
'Defaults to the value found in `layout.height`',
'If set to *null*, the exported image height will match the current graph height.'
].join(' ')
},
scale: {
valType: 'number',
min: 0,
dflt: 1,
description: [
'Sets a scaling for the generated image.',
'If set, all features of a graphs (e.g. text, line width)',
'are scaled, unlike simply setting',
'a bigger *width* and *height*.'
].join(' ')
},
setBackground: {
valType: 'any',
dflt: false,
description: [
'Sets the image background mode.',
'By default, the image background is determined by `layout.paper_bgcolor`,',
'the *transparent* mode.',
'One might consider setting `setBackground` to *opaque*',
'when exporting a *jpeg* image as JPEGs do not support opacity.'
].join(' ')
},
imageDataOnly: {
valType: 'boolean',
dflt: false,
description: [
'Determines whether or not the return value is prefixed by',
'the image format\'s corresponding \'data:image;\' spec.'
].join(' ')
}
};
const attrs = require('./to_image_attributes').default;

/** Plotly.toImage
*
* @param {object | string | HTML div} gd
* can either be a data/layout/config object
* or an existing graph <div>
* or an id to an existing graph <div>
* @param {object} opts (see above)
* @param {object} opts (see ./to_image_attributes)
* @return {promise}
*/
function toImage(gd, opts) {
Expand Down
66 changes: 66 additions & 0 deletions src/plot_api/to_image_attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { AttributeMap } from '../types/lib/attributes';

/**
* Options for `Plotly.toImage` and `Plotly.downloadImage`.
* `plot_config.js` reuses `format.values` for `config.toImageButtonOptions`.
* A separate module prevents the require cycle that `plot_config.js` would
* close through `to_image.js`.
*/
const attributes = {
format: {
valType: 'enumerated',
values: ['png', 'jpeg', 'webp', 'svg', 'full-json'] as const,
dflt: 'png',
description: 'Sets the format of exported image.'
},
width: {
valType: 'number',
min: 1,
description: [
'Sets the exported image width.',
'Defaults to the value found in `layout.width`',
'If set to *null*, the exported image width will match the current graph width.'
].join(' ')
},
height: {
valType: 'number',
min: 1,
description: [
'Sets the exported image height.',
'Defaults to the value found in `layout.height`',
'If set to *null*, the exported image height will match the current graph height.'
].join(' ')
},
scale: {
valType: 'number',
min: 0,
dflt: 1,
description: [
'Sets a scaling for the generated image.',
'If set, all features of a graphs (e.g. text, line width)',
'are scaled, unlike simply setting',
'a bigger *width* and *height*.'
].join(' ')
},
setBackground: {
valType: 'any',
dflt: false,
description: [
'Sets the image background mode.',
'By default, the image background is determined by `layout.paper_bgcolor`,',
'the *transparent* mode.',
'One might consider setting `setBackground` to *opaque*',
'when exporting a *jpeg* image as JPEGs do not support opacity.'
].join(' ')
},
imageDataOnly: {
valType: 'boolean',
dflt: false,
description: [
'Determines whether or not the return value is prefixed by',
"the image format's corresponding 'data:image;' spec."
].join(' ')
}
} as const satisfies AttributeMap;

export default attributes;
97 changes: 40 additions & 57 deletions src/types/core/config.d.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,80 @@
/**
* Config types
*
* `Config` is built by overlaying a small hand-written interface on top of
* the schema-derived `ConfigBase`. Most fields come straight from the
* schema; the overrides cover seven fields whose schema `valType` is `any`
* because the underlying JS attribute accepts a function value, an
* arbitrary-key map, or a structure too irregular for the schema to model.
* This file overlays a small hand-written interface on the schema-derived
* `ConfigBase` to build `Config`. Most fields come straight from the schema.
* The overrides cover the five fields whose schema `valType` is `any`. Those
* fields accept a function value, an arbitrary-key map, or a structure too
* irregular for the schema to model.
*/

import type { ConfigBase, Edits } from '../generated/schema';
import type { ConfigBase, Edits, ToImageButtonOptions, ToImageFormat } from '../generated/schema';
import type { PlotlyHTMLElement } from './events';
import type { ModeBarButtonAny, ModeBarDefaultButtons } from './layout';

export type { Edits };
export type { Edits, ToImageButtonOptions, ToImageFormat };

// ---------------------------------------------------------------------------
// Image export options
// ---------------------------------------------------------------------------

/**
* Options for `Plotly.toImage`. The graph is rendered to a string suitable
* for use as a data URI or as raw SVG markup.
* Background mode for `Plotly.toImage` and `config.setBackground`.
* A function receives the graph div and the resolved background color.
*/
export interface ToImgopts {
/** Output image format. */
format: 'jpeg' | 'png' | 'webp' | 'svg';
/** If null, uses current graph width */
width: number | null;
/** If null, uses current graph height */
height: number | null;
/** Resolution multiplier for raster formats. */
scale?: number | undefined;
}
export type SetBackground = 'opaque' | 'transparent' | ((gd: PlotlyHTMLElement, bgColor: string) => void);

/**
* Options for `Plotly.downloadImage`. Like `ToImgopts`, but also requires
* a `filename` because the result is saved to disk by the browser.
* Options for `Plotly.toImage`. `toImage` renders the graph to a string that
* works as a data URI or as raw SVG markup. The mode-bar button reads the same
* fields, minus the two below, through `config.toImageButtonOptions`.
*/
export interface DownloadImgopts {
/** Output image format. */
format: 'jpeg' | 'png' | 'webp' | 'svg';
/** Output width in pixels. */
width: number | null;
/** Output height in pixels. */
height: number | null;
/** Filename used for the downloaded file (no extension required). */
filename: string;
export interface ToImgopts extends Omit<ToImageButtonOptions, 'filename'> {
/** Override the background color with a static color name, or with a function that runs on each render */
setBackground?: SetBackground;
/** Return the bare image data, without the leading `data:image;` prefix */
imageDataOnly?: boolean;
}

/**
* Static defaults applied to the mode-bar "download image" button. Set
* via `config.toImageButtonOptions`.
* Options for `Plotly.downloadImage`. Like `ToImgopts`, but adds a `filename`
* because the browser saves the result to disk. `downloadImage` forces
* `imageDataOnly` on, so a caller cannot set it.
*/
export interface ToImageButtonOptions {
/** Output image format. */
format?: 'png' | 'svg' | 'jpeg' | 'webp';
/** Downloaded filename. */
export interface DownloadImgopts extends Omit<ToImgopts, 'imageDataOnly'> {
/**
* Name for the downloaded file, without an extension. `downloadImage`
* appends the extension that matches `format`. The name defaults to the
* plot title, then the plot subtitle, then `plot-image`.
*/
filename?: string;
/** Output height in pixels. */
height?: number;
/** Output width in pixels. */
width?: number;
/** Resolution multiplier for raster formats. */
scale?: number;
}

// ---------------------------------------------------------------------------
// Config hybrid (schema-derived + hand-written overrides)
// Config - hybrid (schema-derived + hand-written overrides)
// ---------------------------------------------------------------------------

/**
* Hand-written overrides for the six `schema.config` fields whose
* `valType` is `any`. These accept functions or arbitrary-key maps that the
* JSON schema fundamentally cannot describe, so they stay typed by hand.
* Hand-written overrides for the five `schema.config` fields whose `valType`
* is `any`. These fields accept functions or arbitrary-key maps, which the
* JSON schema cannot describe.
*/
interface ConfigOverrides {
/** Override the background color: a static color name, or a function called per-render. */
setBackground?: 'opaque' | 'transparent' | ((gd: PlotlyHTMLElement, bgColor: string) => void);
/** Define fully custom mode bar buttons as nested array of button groups. */
/** Override the background color with a static color name, or with a function that runs on each render */
setBackground?: SetBackground;
/** Define fully custom mode bar buttons as a nested array of button groups */
modeBarButtons?: ModeBarButtonAny[][] | false;
/** Add mode bar buttons using config objects or default-button names. */
/** Add mode bar buttons with config objects or default-button names */
modeBarButtonsToAdd?: ModeBarButtonAny[];
/** Remove mode bar buttons by name. */
/** Remove mode bar buttons by name */
modeBarButtonsToRemove?: ModeBarDefaultButtons[];
/** Statically override options for the toImage mode bar button. */
toImageButtonOptions?: ToImageButtonOptions;
/** Localization definitions keyed by locale id (e.g. `'en-US'`, `'fr'`). */
/** Localization definitions under a locale id key, for example `'en-US'` or `'fr'` */
locales?: Record<string, { dictionary?: Record<string, string>; format?: Record<string, any> }>;
}

/**
* Full plot config. Combines `ConfigBase` (schema-derived) with the
* hand-written `ConfigOverrides` so the hand-written entries replace the
* loosely-typed `any` versions from the schema.
* Full plot config. `Config` combines `ConfigBase` (schema-derived) with the
* hand-written `ConfigOverrides`, so the hand-written entries replace the
* loosely-typed versions from the schema.
*/
export type Config = Omit<ConfigBase, keyof ConfigOverrides> & ConfigOverrides;
Loading
Loading