Skip to content
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

When scale distr:2, scaleMin and scaleMax have wrong values #555

Open
kchudoba opened this issue Jul 26, 2021 · 4 comments
Open

When scale distr:2, scaleMin and scaleMax have wrong values #555

kchudoba opened this issue Jul 26, 2021 · 4 comments
Labels
question Further information is requested

Comments

@kchudoba
Copy link

Not sure if this is a bug, but when I set distr:2 on the x scale, the scaleMin and scaleMax values that are passed to the function:
space: (self, axisIdx, scaleMin, scaleMax, plotDim)
are not the values of the x-data array but their indexes. So , in my case, instead of getting timestamps, I get the min and max index of the timestamp array.

I am trying to get somewhat meaningful splits/ticks with temporal data and distr:2 (for a simple stock price line chart).
Do you maybe have some examples how to achieve this? The default splits (e.g. for months) don't start at the beginning of the month)

@leeoniya
Copy link
Owner

leeoniya commented Jul 26, 2021

this is "by design" (see #212). to get timestamps you should use self.data[0][min]

I am trying to get somewhat meaningful splits/ticks with temporal data and distr:2 (for a simple stock price line chart).
Do you maybe have some examples how to achieve this? The default splits (e.g. for months) don't start at the beginning of the month)

uPlot natively only works with continuous scales that can be interpolated using math functions. ordinal scales are discontinuous and unpredictable, so you'll have to manually scan your data to figure out where you want the ticks. let's say there was no trading on the weekend, and that Sunday falls on the 1st of the next month, plus Monday is a national holiday also without trading, where do you put the tick for the 1st? what if you're zoomed and the 1st of the month is out of visible range, where should the 1st tick go? and what if that tick lands on a weekend? ​what about DST roll-overs? timezone offsets? that's just a few of a bunch of situations that makes discontinuous scales tricky to get "correct" from logical and performance perspectives.

if you'd like to take a stab at it with a custom axis.splits function, i'd be interested in how you handle the above questions. if all you want to do is show ticks at existing datapoints that land "somewhere" on the 1st (in your local timezone), regardless of zoom level, then it should be quite easy, but unlikely to work generically.

@leeoniya leeoniya added the question Further information is requested label Jul 26, 2021
@kchudoba
Copy link
Author

Most of the stock charts (e.g. yahoo) show the month label at the first trading day of the month (or between the last trading day of the old month and the first trading day of the new month)
image

I was hoping that you (or someone) has already come up with a solution for that, as it seems a common use case. I am having a hard time trying to understand the various options described in https://github.com/leeoniya/uPlot/tree/master/docs#axis--grid-opts
and axis.splits are not described there at all.

Could you maybe help to clarify the following:

  • space is the minimum space between adjacent ticks in CSS pixels, regardless of the data behind it (whether its simple numbers or timestamps etc), correct?

  • what do incrs do? what is their unit? The values of the axis' data? What is the relation to "space"? How is the incr chosen - the one generating the most ticks that are still satisfying the "space" requirement?

  • what do "values" do? Are they only for formatting the tick labels on the axis?

  • what are the "splits"?

  • How do you define where to place the first tick? Is there some logic behind it?

I'm sorry if the questions seem basic, but I am almost sure that I'm not the only one confused with this part.

@leeoniya
Copy link
Owner

leeoniya commented Jul 26, 2021

yes, sorry, the "docs" are in very poor shape. most of the API "docs" are simply reading the comments in the typings file:

uPlot/dist/uPlot.d.ts

Lines 926 to 986 in b079030

export interface Axis {
/** axis on/off */
show?: boolean;
/** scale key */
scale?: string;
/** side of chart - 0: top, 1: rgt, 2: btm, 3: lft */
side?: Axis.Side;
/** height of x axis or width of y axis in CSS pixels alloted for values, gap & ticks, but excluding axis label */
size?: Axis.Size;
/** gap between axis values and axis baseline (or ticks, if enabled) in CSS pixels */
gap?: number;
/** font used for axis values */
font?: CanvasRenderingContext2D['font'];
/** color of axis label & values */
stroke?: Axis.Stroke;
/** axis label text */
label?: string;
/** height of x axis label or width of y axis label in CSS pixels alloted for label text + labelGap */
labelSize?: number;
/** gap between label baseline and tick values in CSS pixels */
labelGap?: number;
/** font used for axis label */
labelFont?: CanvasRenderingContext2D['font'];
/** minimum grid & tick spacing in CSS pixels */
space?: Axis.Space;
/** available divisors for axis ticks, values, grid */
incrs?: Axis.Incrs;
/** determines how and where the axis must be split for placing ticks, values, grid */
splits?: Axis.Splits;
/** can filter which splits are passed to axis.values() for rendering. e.g splits.map(v => v % 2 == 0 ? v : null) */
filter?: Axis.Filter;
/** formats values for rendering */
values?: Axis.Values;
/** values rotation in degrees off horizontal (only bottom axes w/ side: 2) */
rotate?: Axis.Rotate;
/** text alignment of axis values - 1: left, 2: right */
align?: Axis.Align;
/** gridlines to draw from this axis' splits */
grid?: Axis.Grid;
/** ticks to draw from this axis' splits */
ticks?: Axis.Ticks;
}

axis.splits() is the final function that is used to determine where to place the grid & ticks along an axis. it must return an array of scale positions. if the scale is distr: 2, then it can return integer or fractional indices, e.g. your xmin = 5 and xmax = 10, you can place a split at 5.2 and 6.5 by returning [5.2, 6.5], but then you must modify axis.values() to figure out how to render/format that fractional index split "as a date" for tick value rendering. so, in short, all you need to worry about is customizing axis.splits and axis.values and can ignore incrs or space.

you can think of distr: 2 scales as evenly-spaced-labels, with some labels being skipped when they're too dense. the algorithm for them is identical to integer (non-time) scales with a range of 0..N. it just takes every 10th or every 5th or every 2nd index from the dataset and formats the timestamp of the datapoint at that index as a date. there is zero knowledge of a calendar, or months or hours.

i looked at how https://www.tradingview.com/chart/ does this, and as you said, it will simply call the label "Nov" if it's actually Nov 2 instead of trying to place labels on Nov 1. if you want similar behavior, basically just override axis.splits() and return an array of indices where you want to put the ticks/grid/labels. once you're happy with that, then you can override axis.values() to format the tick labels at those splits to your desired format.

@leeoniya
Copy link
Owner

btw, feel free to PR your changes to the candlestick demo if you're happy with them, and i'll review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants