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

Finance refactor #2561

Merged
merged 25 commits into from
Apr 17, 2018
Merged

Finance refactor #2561

merged 25 commits into from
Apr 17, 2018

Conversation

alexcjohnson
Copy link
Collaborator

Closes #2510 - Makes finance traces (OHLC, candlestick) into 🥇 first-class trace types.

Along with this I added selection to both of these trace types. I did not however add custom selection styles. I'd like to leave this for a later PR.

cc @etpinard

(or more precisely test that it was already fixed)
- notice I added a legend to finance_style, so we test ohlc legend drawing
- also notice that I changed ohlc line drawing to not overdraw. Directions were chosen to optimize dashes:
  - the ticks are drawn starting from the outside toward the middle
  - the main line is drawn top-to-bottom, so in principle you might not actually see the low value,
    but that was the case before anyway, there's really no way around this with dashes...
removing direction names & showlegend, and picking a composite name
@alexcjohnson alexcjohnson added bug something broken feature something new status: reviewable labels Apr 16, 2018
@etpinard etpinard added this to the v1.36.0 milestone Apr 16, 2018
Copy link
Contributor

@etpinard etpinard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic PR! I've never been this happy seeing some of my old code disappear. Transformed traces are a thing of the past and should never never be resuscitated (cc #1015 and #1217).

I made a few comments, mostly to highlight things that might be hidden in big commits that I was too lazy to examine in detail.

// the rest of this will still apply, so such modules
// can still put things in (x|y|z)Label, text, and name
// and hoverinfo will still determine their visibility
if(d.extraText !== undefined) text += (text ? '<br>' : '') + d.extraText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you have to move this block down?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our previous users of extraText use only extraText, no x/y/z text, so they're essentially independent of the intervening block. But finance traces use extraText in place of yLabel, and still use xLabel. Contrary to the comment (that I just moved down here with extraText) previously if you had both parts, the x/y/z labels would obliterate extraText.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the headsup 👌

@@ -455,7 +455,8 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
id: id,
plotgroup: plotgroup,
xaxis: xa,
yaxis: ya
yaxis: ya,
isRangePlot: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this range-plot range slider fix got 🔒 anywhere. We could add a case in select_test.js selects a point in the main plot and asserts that it is really that main-plot plot that gets the selected style.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for reminding me about the rangeslider + selection fix! 🔒-> c77a8a3

if(!valObject) valObject = baseAttributes[head];

// finally check if we have a transform matching the original trace type
if(!valObject && transforms) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch, this is a holdover from my original attempt to fix these traces without refactoring them. We should never again have a trace type matching a transform type, and we certainly shouldn't have a transform that doesn't exist in gd.data but gets added in during supplyDefaults. I'll take this out.

@@ -68,6 +68,7 @@ module.exports = {
'carpetlayer',
'violinlayer',
'boxlayer',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can box traces and candlestick coexist on the same subplot?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good 👀 - they probably could have before this PR, but this morning they couldn't. Now they can 0b7541e

@@ -203,7 +203,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
// remaining plot traces should also be able to do this. Once implemented,
// we won't need this - which should sometimes be a big speedup.
if(plotinfo.plot) {
plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove();
plotinfo.plot.selectAll('g:not(.scatterlayer):not(.ohlclayer)').selectAll('g.trace').remove();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query is getting ridiculous.

Can we instead loop over constants.traceLayerClasses?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we instead loop over constants.traceLayerClasses?

Somehow we need to distinguish between trace types that know how to update and those that don't - I feel like rather than adding another category for this, we should just teach the remaining types how to update. Probably actually not that hard to do, right? Just adding the right exit() statements and moving things appropriately between enter() and each?

I wouldn't want to do that in this PR of course...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to #2563

Thanks!

pointData.xLabelVal = di.pos;

pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
pointData.xSpike = xa.c2p(di.pos, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From candlestick_double-y-axis, are we expected spike lines over this point to be black:

peek 2018-04-16 15-31

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we expecting spike lines over this point to be black

Yeah, that color fails the tinycolor.readability test so we just pick a color that contrasts with the background.

d3.select(this)
.style('fill', 'none')
.call(Color.stroke, dirLine.color)
.call(Drawing.dashLine, dirLine.dash, dirLine.width)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the finance_style mock, the dash line pattern makes the new split increasing/decreasing legend item look a little strange:

image

compare to e.g. for the ohlc_first mock with layout.showlegend: true:

image

which looks amazing.

Should we just not call Drawing.dashLine here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, it looks weird... but it also looks weird in the plot. I think that just says anyone who would really use line.dash here would want it to be a very short dash pattern, like 'dot'. Hardly anyone will use it, but I don't think we should prohibit it, nor should we draw different dashes in the legend vs the plot.

I've changed that mock to dash: 'dot' f84dfae and I have another mock in 0b7541e that shows it without dashes, so users can see the normal look.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, it looks weird... but it also looks weird in the plot.

Very good point. f84dfae is 👌


// Even if both showlegends are false, leave trace.showlegend out
// My rationale for this is that legends are sufficiently different
// now that it's worthwhile resetting their existence to default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

@@ -2781,6 +2865,53 @@ describe('Test plot api', function() {
.then(done);
});

it('can change data in candlesticks multiple times', function(done) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we added add a finance trace case for the can redraw with no changes as a noop set of tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we added add a finance trace case for the can redraw with no changes as a noop set of tests?

It's there ->

['finance_style', require('@mocks/finance_style.json')],

and the previous fullJson exemption was cleared in this PR -> https://github.com/plotly/plotly.js/pull/2561/files#diff-5a140ce1cc87a420496039bbf8d699f1L2913

.then(done);
});

['ohlc', 'candlestick'].forEach(function(type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, apart from selection and the new legend items, can you list all the new features this PR adds to finance traces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.... mostly for changelog purposes 📚

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's it...

also an image test for #2004 category OHLC and candlestick using box layout attrs
@@ -22,7 +22,7 @@ var SPLOM = 'splom';
function plot(gd) {
var fullLayout = gd._fullLayout;
var _module = Registry.getModule(SPLOM);
var splomCalcData = getModuleCalcData(gd.calcdata, _module);
var splomCalcData = getModuleCalcData(gd.calcdata, _module)[0];
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@etpinard I changed the API for getModuleCalcData in 0b7541e - so that cartesian could avoid double-drawing the same trace when two trace modules share a plotting step (box and candlestick). In principle we could extend this pattern (a plot step removes the calcdata items it plotted and returns the items that are left) which could have a small 🐎 benefit when you have many traces of several types, you don't have to search the whole array each time, only the items that haven't already been handled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for 0b7541e

In principle we could extend this pattern (a plot step removes the calcdata items it plotted and returns the items that are left) which could have a small racehorse benefit when you have many traces of several types, you don't have to search the whole array each time, only the items that haven't already been handled.

Good idea here. We should keep that in mind when we'll 🔪 calcdata completely.

moduleCalcData.push(cd);
}
else {
remainingCalcData.push(cd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliantly efficient. ❤️ this.

.each(stylePoints);
.each(stylePoints)
.each(styleCandles)
.each(styleOHLC);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Non-blocking thinking-out-loud comment alert 🚨

We should perhaps make these trace module method (e.g. named _module.legendStyle) instead of having the legend component handle all trace types by itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, that would be way more efficient too - instead of calling every style routine on every trace and quitting if it's not the right one, just something like:

.each(function(d) { d.trace._module.legendStyle(...) })

@etpinard
Copy link
Contributor

Amazing work 💃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken feature something new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants