-
-
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
Conversation
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.
Very nice PR. Thanks very much!
We'll need to add a few tests in modebar_test.js before merging, let me know if you need help.
button.appendChild(this.createIcon(config.icon || Icons.question, config.name)); | ||
var icon = config.icon; | ||
if(typeof icon === 'function') { | ||
button.appendChild(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 two g
group elements with two icon path
elements, with the second one set to opacity: 0
. I can then use CSS based around modebar-btn
and the active
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 when active
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:
- Forced transformation inversion of the entire image… just because…?
- Limited to only one path element
- The
viewBox
is pieced together from disparate config variables ascent
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.- The fact that the Javascript is manually building the SVGs piece by piece in the first place, instead of loading the SVGs in directly, or applying them via CSS…
- A custom transformation being applied to one special icon, but not actually via the icon's own config… no… it's actually applied via the name of the button config that just so happens to use that icon…???
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 (unlike data
and layout
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.
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.
I can get behind this (at least as a solution for v1).
👌 for typeof icon === 'function'
.
src/components/modebar/modebar.js
Outdated
@@ -169,7 +175,9 @@ proto.createButton = function(config) { | |||
* @Return {HTMLelement} | |||
*/ | |||
proto.createIcon = function(thisIcon, name) { | |||
var iconHeight = thisIcon.ascent - thisIcon.descent, | |||
var iconHeight = thisIcon.height !== undefined ? |
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.
Let's be more strict here:
isNumeric(this.height) ? Number(thisIcon.height) : thisIcon.ascent - this.decent
@etpinard I've added some tests, how do they look? |
Your tests look amazing. Thanks very much! |
src/components/modebar/modebar.js
Outdated
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for moving pull_font_svg
to the new transform
property - it's definitely cleaner to calculate that upfront - and for maintaining the legacy version to support any users who did go to the trouble of figuring out ascent
and descent
. But I don't think we need to carry the toggleSpikelines
hack over here - lets take it out, then we don't even need name
in createIcon
🎉
now that this is baked into pull_font_svg
@etpinard @vdh I removed the legacy |
Thanks @alexcjohnson ! Merging this thing 💃 |
@etpinard @alexcjohnson
|
Heh, this broke my code temporarily, but the fix was just to replace my own |
As a fix for #1335, a backwards-compatible change to
createIcon
in modebar.js.icon
config is given, it behaves the same as before.icon
config definesheight
, it will be used instead ofascent
anddescent
.icon
config definestransform
, it will be used instead of the default transform.icon
config doesn't define either atransform
or anascent
, it will not be transformed at all.icon
config is a function it will be called instead ofcreateIcon
, in the case where someone wants to completely customise the icon element generation (i.e. inline their own SVGs, or another element).The pull_font_svg.js util was also updated to regenerate a new ploticon.js, containing
height
andtransform
props instead of the previousascent
anddescent
props. Assuming that this icon data is only used for its intended purpose in the mode bar (i.e. not used/manipulated manually for anything else by users), this shouldn't cause any breakages.