Skip to content

Conversation

@my-tien
Copy link
Contributor

@my-tien my-tien commented Oct 13, 2025

This PR makes it possible to pass arrays to the pie properties 'legend' and 'showlegend' so that legend assignment and visibility can be configured per slice.

Changes:

Previously, traces.pie.legend and traces.pie.showlegend only accepted one value that applied for all slices. With this PR, slice legend entries can be grouped and individual slices can be hidden in the legend:

{
 "data": [
  {
   "labels": [
     "BBBBBBBBBBBBBBBBBB",
     "AAAAAAAAAAAAAAAAAA",
     "222222222222222222",
     "111111111111111111",
     "unimportant"
    ],
   "values": [10, 15, 13, 19, 1],
   "type": "pie",
   "showlegend": [true, true, true, true, false],
   "legend": ["legend", "legend", "legend2", "legend2"]
  }
 ],
 "layout": {
  "width": 400,
  "height": 400,
  "legend": {
    "title": {
        "text": "Group 1"
    },
    "xanchor": "left",
    "yanchor": "top",
    "x": -0.2,
    "y": -0.1
  },
  "legend2": {
    "title": {
        "text": "Group 2"
    },
    "xanchor": "left",
    "yanchor": "top",
    "x": 0.6,
    "y": -0.1
  }
 }
}

Result

image

Old behavior

No possibility to group slices semantically or to hide a slice. Also takes up more vertical space.
image

Testing

For testing, I added 3 mocks:

  • zz-pie-slice-legend.json verifies that the array entries are associated to the pie slices in declaration order.
  • zz-pie-slice-legend2.json verifies that if the arrays have less entries than there are pie slices, the extra slices are assigned default values.
  • zz-pie-slice-legend3.json verifies that the arrays also work when label0 and dlabel are set instead of labels.

Note:

This feature is not expected to work when pie values are not set.

Disclaimer I am required to add that…

the software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. in no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.

@alexshoe alexshoe self-assigned this Oct 14, 2025
@gvwilson gvwilson added feature something new community community contribution P1 needed for current cycle labels Oct 28, 2025
@my-tien
Copy link
Contributor Author

my-tien commented Nov 12, 2025

@alexshoe Hi Alex! Do you possibly have an estimate when this PR could be tackled? Also, let me know if you have any questions :)

@alexshoe
Copy link
Contributor

Hey @my-tien! Sorry, just working through a backlog of different PRs that I was assigned. I'll start reviewing this tomorrow.

…isibility

# Conflicts:
#	test/plot-schema.json
@my-tien
Copy link
Contributor Author

my-tien commented Nov 25, 2025

@alexshoe Hi Alex, I saw that my branch got out of date and conflicted with the current test/plot-schema.json .
I merged master into my branch, reran npm run schema and saw below output. Is this output currently expected and does it still generate a correct plot-schema.json since the last statement is still »ok plot-schema.json« (I installed dependencies via npm run i -d and I have canvas in my node_modules folder)?

EDIT: After wiping node_modules and re-installing with npm i the errors went away.

plotly.js@3.3.0 schema
node tasks/schema.mjs

build\plotly.js 11.0mb

Done in 7872ms
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (...\plotly.js\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\nodes\HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\generated\HTMLCanvasElement.js:131:58)
at 4359 (about:blank:106137:48)
at __webpack_require__ (about:blank:128019:41)
at 4935 (about:blank:108491:35)
at __webpack_require__ (about:blank:128019:41)
at 3837 (about:blank:103258:32)
at __webpack_require__ (about:blank:128019:41)
at 4100 (about:blank:104906:32) undefined
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (...\plotly.js\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\nodes\HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\generated\HTMLCanvasElement.js:131:58)
at node_modules/svg-path-sdf/index.js (about:blank:148569:24)
at __require (about:blank:42:52)
at src/traces/scattergl/convert.js (about:blank:148638:20)
at __require (about:blank:42:52)
at src/traces/scattergl/calc.js (about:blank:149375:21)
at __require (about:blank:42:52)
at src/traces/scattergl/base_index.js (about:blank:149636:15) undefined
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (...\plotly.js\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\nodes\HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\generated\HTMLCanvasElement.js:131:58)
at node_modules/detect-kerning/index.js (about:blank:163622:24)
at __require (about:blank:42:52)
at node_modules/gl-text/dist.js (about:blank:163834:21)
at __require (about:blank:42:52)
at src/traces/scattergl/plot.js (about:blank:172852:18)
at __require (about:blank:42:52)
at src/traces/scattergl/index.js (about:blank:173168:20) undefined
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (...\plotly.js\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\nodes\HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (...\plotly.js\node_modules\jsdom\lib\jsdom\living\generated\HTMLCanvasElement.js:131:58)
at node_modules/gl-text/dist.js (about:blank:164409:48)
at __require (about:blank:42:52)
at src/traces/scattergl/plot.js (about:blank:172852:18)
at __require (about:blank:42:52)
at src/traces/scattergl/index.js (about:blank:173168:20)
at __require (about:blank:42:52)
at lib/scattergl.js (about:blank:173177:24) undefined
ok plot-schema.json

@camdecoster
Copy link
Contributor

@my-tien could you please add some more information to the OP? I'm interested in seeing sections describing:

  • Changes
  • Before/after pictures or videos
  • Testing instructions

With that, I'll be able to better review the changes.

@my-tien
Copy link
Contributor Author

my-tien commented Nov 26, 2025

Hi @camdecoster,
Thx for looking at my PR! Regarding my error above, this was resolved by completely deleting my node_modules and re-installing again.

I also updated the OP and added a better description of the properties for the plot schema. Please let me know if anything is unclear or missing!

Copy link
Contributor

@camdecoster camdecoster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this feature! There are a few changes to make, but it seems to work well.

Comment on lines 65 to 69
!Array.isArray(trace.showlegend)
? trace.showlegend || trace._dfltShowLegend
: trace.showlegend[index] == null
? trace._dfltShowLegend
: trace.showlegend[index]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please refactor this to avoid nested ternary expressions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope it's clearer now! Leaving the conversation unresolved for your judgement :)

@my-tien my-tien requested a review from camdecoster November 27, 2025 13:36
Copy link
Contributor

@camdecoster camdecoster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are a few more suggestions.

Comment on lines +64 to +71
var showInLegend = trace.showlegend || trace._dfltShowLegend;
if (Array.isArray(trace.showlegend)) {
showInLegend = trace.showlegend[index] ?? trace._dfltShowLegend;
}
if(showInLegend) {
legendReallyHasATrace = true;
legendTraceCount++;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something like this? I'm not sure if you really need the nullish coalescing operator. If you do, then this will need to be adjusted.

Suggested change
var showInLegend = trace.showlegend || trace._dfltShowLegend;
if (Array.isArray(trace.showlegend)) {
showInLegend = trace.showlegend[index] ?? trace._dfltShowLegend;
}
if(showInLegend) {
legendReallyHasATrace = true;
legendTraceCount++;
}
const showInLegend = Array.isArray(trace.showlegend) ? trace.showlegend[index] : trace.showlegend;
if (showInLegend || trace._dfltShowLegend) {
legendReallyHasATrace = true;
legendTraceCount++;
}
Comment on lines +91 to +93
function isBoolean(value) {
return value === true || value === false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a one-liner:

Suggested change
function isBoolean(value) {
return value === true || value === false;
}
const isBoolean = value => value === true || value === false;

Or

Suggested change
function isBoolean(value) {
return value === true || value === false;
}
const isBoolean = value => typeof value === "boolean";
return value === true || value === false;
}

if (opts.arrayOk && isArrayOrTypedArray(v) && v.length > 0 && v.every(isBoolean)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you anticipate typed arrays being used here?

Comment on lines +95 to +98
if (opts.arrayOk && isArrayOrTypedArray(v) && v.length > 0 && v.every(isBoolean)) {
propOut.set(v);
} else if(isBoolean(v)) {
propOut.set(v);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you combine these two paths since they result in the same call?

Comment on lines +240 to +242
function isSubplotId(value) {
return typeof value === 'string' && regex.test(value);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function isSubplotId(value) {
return typeof value === 'string' && regex.test(value);
}
const isSubplotId = value => typeof value === 'string' && regex.test(value);
Comment on lines +243 to +246
if (opts.arrayOk && isArrayOrTypedArray(v) && v.length > 0 && v.every(isSubplotId)) {
propOut.set(v);
return;
} else if(isSubplotId(v)) {
propOut.set(v);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you combine these two paths since they result in the same call?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community community contribution feature something new P1 needed for current cycle

4 participants