-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Revamp of mode bar's createIcon #2762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
f0b99ee
789ce44
ccba6e9
3f45a8f
52f307e
586ff25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
'use strict'; | ||
|
||
var d3 = require('d3'); | ||
var isNumeric = require('fast-isnumeric'); | ||
|
||
var Lib = require('../../lib'); | ||
var Icons = require('../../../build/ploticon'); | ||
|
@@ -155,7 +156,13 @@ proto.createButton = function(config) { | |
button.setAttribute('data-toggle', config.toggle || false); | ||
if(config.toggle) d3.select(button).classed('active', true); | ||
|
||
button.appendChild(this.createIcon(config.icon || Icons.question, config.name)); | ||
var icon = config.icon; | ||
if(typeof icon === 'function') { | ||
button.appendChild(icon()); | ||
} | ||
else { | ||
button.appendChild(this.createIcon(icon || Icons.question, config.name)); | ||
} | ||
button.setAttribute('data-gravity', config.gravity || 'n'); | ||
|
||
return button; | ||
|
@@ -169,7 +176,9 @@ proto.createButton = function(config) { | |
* @Return {HTMLelement} | ||
*/ | ||
proto.createIcon = function(thisIcon, name) { | ||
var iconHeight = thisIcon.ascent - thisIcon.descent, | ||
var iconHeight = isNumeric(thisIcon.height) ? | ||
Number(thisIcon.height) : | ||
thisIcon.ascent - thisIcon.descent, | ||
svgNS = 'http://www.w3.org/2000/svg', | ||
icon = document.createElementNS(svgNS, 'svg'), | ||
path = document.createElementNS(svgNS, 'path'); | ||
|
@@ -178,12 +187,19 @@ proto.createIcon = function(thisIcon, name) { | |
icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em'); | ||
icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' ')); | ||
|
||
var transform = name === 'toggleSpikelines' ? | ||
'matrix(1.5 0 0 -1.5 0 ' + thisIcon.ascent + ')' : | ||
'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')'; | ||
|
||
path.setAttribute('d', thisIcon.path); | ||
path.setAttribute('transform', transform); | ||
|
||
if(thisIcon.transform) { | ||
path.setAttribute('transform', thisIcon.transform); | ||
} | ||
else if(thisIcon.ascent !== undefined) { | ||
// Legacy icon transform calculation | ||
var transform = name === 'toggleSpikelines' ? | ||
'matrix(1.5 0 0 -1.5 0 ' + thisIcon.ascent + ')' : | ||
'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for moving |
||
path.setAttribute('transform', transform); | ||
} | ||
|
||
icon.appendChild(path); | ||
|
||
return icon; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would
config.icon
ever be a function?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make it easier to manually create the SVG element and bypass
createIcon
altogether.For example, I have a version of this patched into my own code, and I use the
icon
function to generate an SVG with twog
group elements with two iconpath
elements, with the second one set toopacity: 0
. I can then use CSS based aroundmodebar-btn
and theactive
class to toggle between the two icon paths when Plotly toggles the active state of the button.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We like to keep our API JSON-serializable, unless we have to (like modebar button click handlers), so I'd vote 👎
The way you implemented the use case you described seems a little hacky. Maybe instead we could add a field named e.g
activeIcon
that would be used whenactive
class is turned on?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@etpinard
This issue arose because the current custom icon support is extremely tailored and twisted to only support the structure that the Plotly icons have been stored in. I mean, you've got:
viewBox
is pieced together from disparate config variablesascent
anddescent
is never documented anywhere, leaving poor users to guess at (or poke at source code) to even understand how this will change the resulting SVG.I've tried very hard with this PR to keep everything supported, smooth over some of those wrinkles, while also opening it up enough for sane custom icons… so why is this small
function
-based escape hatch so bad? Amongst a config already full of existing support for functions?What if someone wants to use SVG sprite sheets? What if they just want to load in their own SVG source files without jumping through hoops? What if they want to use CSS-based icon sets, to load the images in via their existing asset systems? Or heaven forbid, use more than just one path element?! Arbitrarily forcing your users to be restricted to one little use case for their SVGs just to force a JSON structure is maddening! It's not even like I'm forcing this option either, the JSON structure option still works just fine!
I'm sorry to get so heated about this, but calling it "a little hacky" is pot calling the kettle black 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha @vdh you raise some good points. FWIW most of the quirks you mention above are due to the structure of SVG fonts - for example https://stackoverflow.com/questions/14203365/why-svg-font-is-mirrored - because that's where we do our own design in the first place, and at least at one point we were also using these icons as a font, though I'm not sure we're still doing that anywhere... not that this justifies an obtuse API, just to explain how we got here.
You're right that
config
items don't have a hard requirement for JSON (unlikedata
andlayout
where we cannot allow functions, for portability). @etpinard has good reason to encourage JSON, as it makes cross-language integrations (Python, R, etc) much cleaner. And, pots and kettles notwithstanding, new API gets held to a higher standard than old locked-in API, #420 is bloated enough already 😉But I have to say I can see the appeal of a more flexible arrangement, and I don't really see a good alternative here. Multiple paths is a good example, what if you want regions with different colors or something? The use cases you've mentioned could be covered by a string of raw HTML, but I don't think that's better, as it could open an XSS vector. So yeah, I think in this case a function may be our best option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vdh sorry, I didn't mean to criticize your implementation by calling it hacky. Finding the right hack can be the smartest thing one can do in some situations. But we can't expect every plotly.js user to be come up with that hack. I was simply wondering if there was another (I won't call it less hacky, say more user-intermediate-user-friendly) solution to your use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can get behind this (at least as a solution for v1).
👌 for
typeof icon === 'function'
.