plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.9k stars 1.85k forks source link

add new "proportional" tick mode #6824

Open ayjayt opened 9 months ago

ayjayt commented 9 months ago

What

Add a new tick mode:

# Usage:
xaxis: {
    tickmode: 'proportional',
    tickvals: [0, .5, 1],
}

Effect:

Plotly will make ticks propotional to the range: So if the x range is [50, 150], the tick marks will appear at 50, 100, and 150. If the xrange is [-0.5, 1.5], the tick marks will appear at -0.5, 0.5, and 1.5.

Why

Auto tick formatting is usually ugly (for any value ntick), and the other modes aren't responsive to user zooms.

Workaround: Using python w/ jlab, but I can calculate ticks manually on range changes and during rendering, and then use array mode, but it is clunky, and requires my users to have the plotly installed in their kernel and on jlab server. My proposal doesn't seem that different than modifying auto behavior, so I'll take a shot.

Who am I

Working on analysis software for geologists who design wells.

alexcjohnson commented 8 months ago

@ayjayt thanks for the issue and related PR.

Just to make sure I understand your goals here: Our automatic ticks try to give nice round numbers, but the cost of doing that, in general, is that you won't have ticks exactly at the ends of the axis range. What you're proposing here, putting ticks at precise fractions of the axis length, does show the axis range exactly (at least if you include 0 and 1 in the list) at the expense of round values. I'll note that we have the same tradeoff with tickmode='sync', where the tick positions are set by the other axis and therefore their values are in general not nice round numbers.

Another way to accomplish a subset of the functionality you're proposing but with a more concise API would be to continue using nticks but make a tickmode that forces ticks at both ends and then divides up the axis evenly, ie the tick spacing is exactly (range[1] - range[0]) / (nticks - 1), so your example would have nticks=3. If you instead wanted ticks at the ends and every quarter of the axis you would set nticks=5.

That feels to me like it would cover most of the uses for this feature, do you ever foresee wanting to use this with tickvals that are either (a) not evenly spaced or (b) not spanning exactly 0 to 1? We could of course start with one of these and then add the other later, but if nearly everyone will use the simpler one we should start there.

Re: naming - to me the key here is that (in the variant I'm proposing) the ticks span the whole domain, so maybe we just say that: tickmode='full domain'? And in the original variant you provide an array of fractions of the axis domain, so perhaps this mode could be called tickmode='domain array'?

ayjayt commented 8 months ago

Yeah, that's it.

Some contexts:

1) These scientists don't work w/ round numbers- units are radiation and density and molarity and such, no constants are round. They (read: I, as I am making their API) will use formatting to be aware of precision/error. 2) Trying to achieve feature parity w/ a competitor, which motivates this request.

Re: naming - to me the key here is that (in the variant I'm proposing) the ticks span the whole domain, so maybe we just say that: tickmode='full domain'? And in the original variant you provide an array of fractions of the axis domain, so perhaps this mode could be called tickmode='domain array'?

I'm totally fine w/ changing the name. The nice thing about the domain array strategy is that it's a very simple pre-calc step before your existing code does the tickmode='array' calc. The code calculates what the equivalent tickmode='array' values would be, and then pretends to be tickmode='array'. I would be happy to implement the full domain strategy if I could just do the same thing- generate a domain array based on full domain values, and then generate a regular array based on that.

alexcjohnson commented 8 months ago

OK great. I suspect if the goal is feature parity, these ticks will always be evenly spaced, ie what I'm proposing as tickmode='full domain'. So let's start with just that and if a need arises later we can add tickmode='domain array'.

In that case the implementation can be even simpler than you're suggesting: generate tick0 = range[0] and dtick = Math.abs((range[1] - range[0]) / (nticks - 1)) and then let the auto/linear algorithm take it from there.

Please note a few extra situations we'll want to pay attention to:

ayjayt commented 8 months ago

Sounds good, will try and get it done post haste.

ayjayt commented 8 months ago

a pull request with the feature, but with my original proportional implementation, is ready if that's acceptable.

Otherwise next week after I have some response on parameterization and on ticktext I'll use the auto algo as suggested above.

The majority of work here was in testing (it allowed me to find the duplicate-tick bug we patched) and downgrading my ES6 to ES5, actual implementation easy-peazy .

Video demo of current implementation of domain array (was called proportional).