Closed manderson23 closed 7 years ago
No, this won't happen. The plotly.js data
/ layout
API is meant to be JSON-serializable - so functions as values are strictly forbidden.
Sorry for the inconvenience.
@etpinard What are your ideas on something like the following? This would allow you to still support JSON, but allow people who need customization to specify the name of a function to reference.
In my case, I'm looking to convert bytes
and milliseconds
to human-readable format. Also, I am looking into the tickvals
and ticktext
, but the y-axis shows all of the values and while I could limit the values coming back, I'd prefer to have all of them to enable zooming.
// tickFormat could be a string or object
tickFormat: {
type: 'function',
function: {
className: 'window',
name: 'nameOfAFunction'
}
}
@bputt I'm afraid that won't do hit. Guaranteeing JSON-serializability for our "data"
/ "layout"
attribute isn't just for fun. We do so to guarantee portability across applications. Here, by referring to a function on the window
object, you'd would have to copy that function from application to application to generate the same graph.
@etpinard I understand why you chose JSON and appreciate portability, just trying to think through a problem and finding solutions.
Let's say I want to take bytes and make it human-readable, so if I provided ALL of the tickVals
that are used on the y-axis and then incorporated translation using the ticktext
field, how can I make sure that there isn't a line per tickval on the y-axis? I could set a max number of tickVals
to limit the number of lines, but lose visibility when zooming.
var tickVals = [ 10424, 2195452, 925952, 1521212, ... ];
{
yaxis: {
tickvals: tickVals,
ticktext: tickVals.map(function(val){ return Math.round(val / 1024) + 'Kb'; })
}
}
@etpinard Well I am not sure if json compatibility is such a high value, that avoids a useful functionality (Infact it would not be a big deal to implement). But anyway I want to suggest a (coffeescript) solution for @bputt:
# monkey patch d3 locale to support a new "milliseconds" number format
org_locale = Plotly.d3.locale
Plotly.d3.locale = (locale) ->
result = org_locale(locale)
org_number_format = result.numberFormat
twodigts = org_number_format("02d")
threedigts = org_number_format("03d")
result.numberFormat = (format) ->
return org_number_format(format) if format != "milliseconds"
return (miliseconds) ->
seconds = miliseconds // 1000
miliseconds %= 1000
minutes = seconds // 60
seconds %= 60
hours = minutes // 60
minutes %= 60
miliseconds = threedigts(miliseconds)
seconds = twodigts(seconds)
minutes = twodigts(minutes)
return "#{hours}:#{minutes}:#{seconds}#{locale.decimal}#{miliseconds}"\
if hours
return "#{minutes}:#{seconds}#{locale.decimal}#{miliseconds}"
# there is also result.timeFormat, that can be patched
return result
If you set tickformat
to "milliseconds"
you get the new format.
Well I am not sure if json compatibility is such a high value
Sorry. JSON-compatibility is probably the most important thing about plotly.js. That's how as a company we extend plotly.js to python, R and many more languages, not to mention saving graphs in the cloud on https://plot.ly/. So yeah, there's a 0% chance of us breaking that assumption.
Just a matter of thought: You will never reach full customization by a pure declarative model, but there will be always special cases, users want to implement. Instead of just rejecting you could say: There is the canonical way to do it, which is compatible to other languages. And there is a custom way for special cases, that will never be saved in a cloud but makes users happy. Anyway it is your library (and it is well implemented), and you can decide which strategy to follow.
I've done this...a bit ugly, but solved the issue for me because I needed to add a suffix:
// PATCH: extend d3.format to allow a string suffix
var oldFormat = Plotly.d3.format;
Plotly.d3.format = function(str) {
var data = str.split("###");
return function(val) {
return oldFormat(str)(val) + " " + data[1];
}
}
Then, in the hoverformat, anything after "###" is treated as a suffix. This way, you can express the format like .2###[unit]
and the hover text will say "2.00 [unit]"
@etpinard While it's commendable that data
should remain JSON-serializable, chaining the layout
(i.e. the rendering) to the same standard is perhaps taking portability a little too far into the extreme.
But nevertheless, working within that restriction, what about an API to customise the format function? Something not a part of data
and layout
.
For example, something like:
const d3Format = require('d3-format');
Plotly.tickFormat = function tickFormat(specifier) {
if (specifier === 'specialSauce') { return specialSauceFormatter; }
return d3Format(specifier);
};
In theory this API could be used across all languages, if someone wanted to implement their specialSauce
formatter in each language for maximum JSON layout
compatability.
@vdh Could I please ask if you might be able to provide more hints for how to get your suggestion to work?
I tried to copy your code and then use this, but it did not work:
var layout = {xaxis: {tickformat: 'specialSauce'}}
The tick labels appear unchanged, as if tickformat
was left as the default value.
Following in the footsteps of @kochelmonster, this is what worked for me:
// Somewhere in the global scope, overwrite the original locale function:
var org_locale = Plotly.d3.locale;
Plotly.d3.locale = (locale) => {
var result = org_locale(locale);
var org_number_format = result.numberFormat;
result.numberFormat = (format) => {
if (format != 'pow2') {
return org_number_format(format)
}
return (x) => {
return Math.pow(2, Number.parseFloat(x)).toString();
}
}
return result;
}
// When specifying tickformat, now we can use the string we chose:
var layout = {xaxis: {tickformat: 'pow2'}}
I wanted to convert the tick labels to be 2^x
instead of x
, and this worked.
@slowkow It was only an API suggestion example, I didn't actually implement it.
@slowkow Odd, that doesn't seem to have any effect for me. even adding a console.log
to the callback for testing if the overwrite is called, and it does not. So apparently in my case, Plotly.d3.locale
is not the one containing the numberFormat
method, but when I debugged the code, it did go to d3_locale_numberFormat
, instead. So maybe the overwrite was done too late?
@slowkow d3.locale seems to be not a part of Plotly.js anymore. Neither is d3_locale_numberFormat. Can you suggest an alternate way to achieve the same result?
Not being able to provide a custom formatter is a bit of a blocker: in our use case we need the ticks along the Y axis to automatically adjust to trillion/billion/million, and show the full word for the unit accordingly. Using @slowkow's example above works great with v1.58.5, but this means we'll most likely soon be stuck with an unsupported version.
Could the Plotly.d3
object at least be exposed as an option, so custom locales can be created?
Hi all, is there any chance this will be implemented?
It would be useful if a function could be specified as the tickformat to give complete control.
For example, I would like to use the multiFormat function from https://github.com/d3/d3-time-format/blob/master/README.md to format dates.