| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sounds interesting! I agree that the axes could use could use more flexibility.
At the moment I have a hard time imagining how this would look exactly without going into a lot of detail, but you have clearly given this some thought and the idea definitely sounds promising. I'd be happy to review anything you can come up with in this regard.
Linking #1255 : inexact maths will always be troublesome, but something to keep in mind.
Linking #1721 : this is a good use-case for 'inline-side' bands.
how are you progressing in the composite axis design? I am really looking forward to the opportunity to draw financial charts without gaps.
@karuzzo not looked at it for a while. I rushed some notes a while back, but got caught up in designing a new layout system (https://github.com/VisualMelon/oxyplot/blob/ComposableAxis/Source/OxyPlot/Axes/ComposableAxis/Composable%20Axis.pptx) and have been focussed on other things lately.
My conclusion for making axis less painful was to define a new space Interaction Space which sits between the Dataspace and Screen Space that only the axis need know about, and that just provides a way to provide 'linear' behaviour to a non-linear or non-continuous data-space (can't use screen space, because that depends on layout).
However, there is a whole set of suffering to be had in making things render properly on such axes, because the assumption of linear interpolation doesn't work on a non-linear/discontinuous axis: we need to permit series to query where discontinuities occur so that it they can render accordingly (in the same way that Axis.IsLogarithmic changes the behaviour of e.g. LineAnnotation: we need to implement a way of performing non-linear/discontinuous interpolation in an IAxis interface and update all the series to support it: not as painful as it sounds: points will be as ever, and we can change any code that draws lines to use a common InterpolateLine(Axis x, Axis y, List<DataPoint> dataPoints, List<ScreenPoint> screenPoints) (screenPoints si the output buffer) which deals with the new interface. (note: this should also be generic for perf reasons)
The new interface for interpolation will probably need to look something like
public interface IAxisScreenMappingThing
{
bool IsNonLinear { get; } // for fast-pathing
bool IsDiscontinuous { get; } // not sure this helps, but why not
void LocateDiscontenuities(TData start, TData end, IList<Discontinuity> discontinuities); // adds to an (ordered) list of discontinuities, which are a start/end pair (start can equal end)
TSpace Transform(TData value); // Data->Screen mapping
TData InverseTransform(TSpace value); // Screen->Data mapping: may need to be expanded to deal
// maybe additional stuff that computes gradients where cheap
}
The DrawLineOnAxes method would start by locating any discontinuities. Then it would go through the list of data-points, and reject any segment (i.e. DataPoint pair) that intersects a discontinuity (replace them with an invalid ScreenPoint instead of interpolating into the output buffer). Segments that are not rejected need to to be interpolated (if either axis is non-linear) using something like https://github.com/VisualMelon/oxyplot/blob/vectorfieldseries/Source/OxyPlot/Series/VectorSeries.cs#L318.
Note: this interface would be provided by a generic struct that wraps a mapping from Data to Interaction space and view information (i.e. Offset and Scale)
It would probably be good for me to implement all this without the layout mechanism soon, because the layout mechanism is going to be a nightmare and the whole point is that its composable, so if I can't implement this independently then I'm doomed fail whatever I do.
@Jonarw @objorke how would we feel about trying to make axis types statically typed for 3.0? That is, LinearAxis would implement IAxis<double>, and LineSeries would become LineSeries<XData, YData> (DataSample<XData, YData> would replace DataPoint). (existing types can be recast as double versions of new interfaces for compatibility).
The idea is that the 'type gap' would be closed when axes are assigned to series (by Key), so there wouldn't be any additional complexity from the user, just the joy of being able to define an IAxis<DateTime> and Series<DateTime, double>. If a series asks to use an axis of the wrong time, it's a run-time failure.
The benefit would be that users can use 'proper' types instead of turning everything into double. This will help with
Below is mindless commentary, mostly for my benefit.
I may end up butchering everything, but I'm going to try and get this working in my Composable Axis branch, because a lot of the work to provide powerful axes requires strongly typed axes. The big issue I'm facing (or rather avoiding) at the moment is that I need to replace the Axis abstraction with IAxis, and that will mean effort, but I can't yet see any reason this couldn't be done without breaking everything.
If we are very lucky, the anguish associated with resolving all the types at run-time will mean we don't suffer hideous performance from the abstractions. Instead, we will have hideous types like TypedXYRenderHelper: because all the providers can be structs, the JIT should do a good job of producing efficient code (potentially faster that what we have now, but obviously that's wishful thinking) as there shouldn't be any virtual calls in any tight loops: instead, there will be additional overhead in initialising such render helpers (which can exist for 1, 2, 3, etc. axis (e.g. X,(Y),(Color)))
Other notes:
Looking into replacing Axis as the lowest Axis abstraction (i.e. what PlotModel records and the interaction logic looks at), I am rapidly coming to the conclusion that the interaction space should be exposed (though perhaps through an IInteractiveAxis interface so that it can be hidden from most usage). It will greatly simplify - and I suppose I shouldn't be surprised by this - interaction logic. Indeed, on reflection, this has to be done, because you can't interact in data space if it is discontinuous or bounded.
With that out out of the way, the number of places that axis type parameters will need to be 'passed' should go right down... we won't even need it for series (but it would be nice to keep the option open, as it may help with performance (though, I think performance is pretty much limited by rendering).
I'll shelve my work for the moment, and go from the other end (i.e. start by working out the Axis abstraction, now that I know what Axes themselves can look like).
Thanks for your hard work!
Linking #1612: GetValue is documented as "Gets the value from an axis coordinate, converts from a coordinate" and is needed for e.g. DateTime axes so that things can use the real value. By working in the 'value' space from the outset, and converting to an explicit 'interaction' space, this sort of method will become less important (it will, of course, still exist, as a conversion from interaction to data space).
Just look at this (OxyPlot + SkiaSharp) https://youtu.be/6y4X1a494kM?t=114
your library is great, it only lacks the ability to add exclusion gaps to the axes
Thanks for pinging this. I've since changed the plan for handling discontinuities. I still need to work out how to fit my plan into the existing library, but for the moment I'm looking at how painful it will be to implement a series using typed axes (including all the power of discontinuities, etc.) because it's easier.
Note to self: may need to know where discontinuities are precisely in order to render them... but that's probably a different type of discontinuity that should be customisable within the axis, not the data provision. Note: can probably do that quite nicely by wrapping the data-provider with a known-type data-provider that supports flexible gaps (and make this known to the axis renderer).
The library should not have a mechanism for self-determination of gaps. But it would be great if the DateTime axis had a method for adding a break of the form axis.AddGap (double, double)
My ComposableAxis branch has predictably become a nightmare, but I'm getting to the point where I should be able to start putting typed axes on a plot. (then I need to sort out typed series and typed annotations). Remaining tasks:
Not really looked into the Series abstraction yet... but I'm hoping it will be easier than Axis because they aren't interrogated much:
Some notes on things that are producing lots of overhead though and aren't totally on point but I need to remember:
I am totally convinced of the value of the data/interaction/screen space separation, and once you have that, you might as well make everything typed...
Other:
Finally got a plot on the screen:

Changes:
Next tasks:
0. I was working on a TickBand, looking to implement a TickRenderHelper for X/Y pairs, when I realised that I don't have a sensible X/Y pair abstraction yet, and really rather need one, so that's the next task. It would be really nice if I can make that a struct, but it's compositional, so that might be a problem.
Other:
Progress:
Next Tasks:
Tick band redesign:
Since all the work is around typing, I'm considering putting all this in a separate OxyPlot.Typed namespace, so that it has some prospect of being merged (and so tested) before I have to re-implement literally everything (though the composable nature of it means it's only the rendering which is a pain for X/Y stuff; not thought about polar plots yet... but since when has anyone wanted to put anything other than linear axes on a polar plot?)
I don't think the CI failures are related to the new stuff: https://github.com/VisualMelon/oxyplot/runs/1806133454?check_suite_focus=true
Also, I was able to rebase the Composable Axis branch (forked from 2.1preview1) without any conflicts, so that's reassuring.
Thinking about it... why not expose interaction space to everyone? Series (e.g. LineSeries) could project their samples into interaction space before doing anything else, (including information on breaks/discontinuities), then rendering would just be a linear transformation. This would produce the same render-time performance for any type of axis, as e.g. logs would only be computed once.
Other benefits:
This is a ludicrously appealing prospect... which I shall ignore for the moment. It should be easy to implement this alongside what I already have later.
That said... this would be much closer to the idea of a pre-transform on axis: it could be easy to bolt on-top of existing stuff by just attaching the pre-transform for to the axis, and looking for it in the series (rather than re-fabbing everything): redefine interaction space as data-space, and data-space as pre-transform-space. This wouldn't be as semantically nice, but it would be much easier. It also has nothing to do with composition: it might be easier to separate the need for a composable axis from the desire for typed everything.
Instead of doing anything useful today, I looked into the performance of the new LineSeries: it was very disappointing. After making everything readonly and in-lining a couple of things in ExtractNextContinuousLineSegment, it is generally as fast as the old one now. This method takes about 20ns to process each DataPoint in the best case of a pair of Linear axes (i.e. there is no 'extra' overhead in performing transformations or discontinuity checks) on my not-slow machine. This is probably good enough, but it's a bit disappointing (though it does suggest the CLR is doing a good job already on the LineSeries version with all its possibly-virtual calls).
Progress:
Notes on non-obvious deficiencies in the new LineSeries:
Other notes:
New TODO list:
Progress:
TODO:
Progress:
Problems I am ignoring for the moment or that I might get around to during the week:
Hopefully I'll find more time for this next weekend, when I'll focus on trying to make one nice example with lots of shiny new features.
I think I need to sort out some nice wrapper APIs before getting feedback on this stuff; however, I don't want to try for drop-in-replacement before discussing the architecture and general approach (and whether any of this is desirable).
Last thought: I was thinking about a Samples or SampleSource abstraction earlier. By abstracting the source away it could:
Surprise first pass at Candle Stick on discontinuous axis, based on an existing Candle Stick example.
Data is the same as in the existing Candle Stick examples, just with Sunday, Saturday, and 20:00 to 08:00 removed (hence the big jumps) You can see that each day cycled back to 08:00 instead of reaching 20:00, and that it skips June 19 and 20, which are a weekend.
Example code is here: https://github.com/VisualMelon/oxyplot/blob/d7eab0f2fc4f2ff8cbce2f8a1755481f812183d0/Source/Examples/ExampleLibrary/Axes/ComposableAxisExamples.cs#L802
The bits that are 'unique' to the example are the use of the WeekdayTransform on the X axis, and the filtering (which involved removing any samples from the data which the WeekdayTransform considers to be in a discontinuous region.
Other notes:
you are wonderful! tomorrow I'll study your example
@karuzzo any and all feedback welcome, especially if it's along the lines of "you've totally missed the point".
The bit that enables discontinuous axes to work is the IDataTransformation<TData, TDataProvider> abstraction (which is what WeekdayTransformation implements): it maps the 'data space' information (e.g. a DateTime) to a linear and continuous 'interaction space' (doubles), which undergoes a linear projection into screen space (mediated by ViewInfo and some other stuff that is pretty far removed). This mapping (Transform) must be monotonic for any hope of sanity, but doesn't need to continuous. The data transformation also allows things to query whether a discontinuity exists between two points in data space, which is necessary for e.g. LineSeries on a discontinuous axis (not tested yet... yes it is...).
The DataProvider is just a type that helps perform basic operations on a data-type, specifically comparisons and interpolations. Notably it doesn't provide addition, because you can't e.g. add a DateTime to a DateTime.
I don't anticipate much of what I've been doing lately to end up in the final library: it's just too complicated/bity. I would be very interested to hear ideas about which bits are important, so that we can try to work them in without creating too many compatibility headaches.
Progress:
Notes:
TODO:
Having stopped looking at this regularly for a bit, I think I need to start focus on writing code that can actually be used in the library.
I think the place to start is by fixing on a new axis abstraction (because everything else depends on them; everything else is pretty much stand-alone otherwise). I was going to write lots of stuff about it here... but then I looked at what I already have, and I'm pretty happy with it. See
One thing that is missing is an explicit provision for operating in interaction space: that is, currently the design expects you to feed data-space stuff to the render helper, but there are benefits to being about to perform 'pre transforms', taking data-space to interaction space and then rendering all that:
Providing such a capability means that we would have 2 ways of implementing typed axes: one using 'pre-transforms', and one not. It also means we could fairly easily retro-fit existing series, but changing the rendering logic to work in interaction space, and just put a mapping infront of this.
Notes:
| Back | FazBrowse Home | New Git URL |
Feature description
We have axes that override formatting (Time), that override the transforms (Logarithmic), that override the Tick and Label placement (Category), and all these features are difficult to combine - despite dealing with independent concerns - because they are based on inheritance and internal implementations. I propose we design a composable axis class that allows you to 'plug in' the separate behaviours.
Obvious candidate extension points (with no real organisation) would be
Additionally, by formalising these boundaries and providing nice implementations, they can be recomposed in exciting ways e.g. to provide axes with multiple 'banks' of ticks (#1357, #1336, #1629), or (if they are ever supported) non-XY/polar (e.g. triangle #1136, or even 3D (would of course need dedicated front-end support)) plots, would minimise duplication.
This is something I've tried a number of times in the past (thinking it could go in oxy-contrib), but I've always come a gutser because it would involve duplicated the work done in HorizontalAndVerticalAxisRenderer, and my astounding laziness prevents me from trying stuff without a clear design goal, so I just end up fudging whatever I actually need at the time.
Some random notes :
Anyhow, I thought it worth putting an issue together where we can put ideas, because I'm certainly never going to get around to it otherwise, though it's something on which I'd be keen to work.