-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
OHLC and Candlestick trace types #1020
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
Conversation
This PR is still very much a WIP. TODO:
|
@jackparmer @chriddyp @alexcjohnson @cldougl (if not on vaca) would you mind looking at the attribute declarations in
for feedback Plotly.plot('graph', [{
type: 'ohlc',
open: [33.01, 33.31, 33.50, 32.06, 34.12, 33.05, 33.31, 33.50],
high: [34.20, 34.37, 33.62, 34.25, 35.18, 33.25, 35.37, 34.62],
low: [31.70, 30.75, 32.87, 31.62, 30.81, 32.75, 32.75, 32.87],
close: [34.10, 31.93, 33.37, 33.18, 31.18, 33.10, 32.93, 33.70],
t: ['2016-09-01', '2016-09-02', '2016-09-03', '2016-09-04', '2016-09-05', '2016-09-06', '2016-09-07', '2016-09-10']
}]); currently gives forget about the rangeslider for now, PR #1017 will fix the issue The same data with |
require('./histogram'), | ||
require('./pie'), | ||
require('./ohlc'), | ||
require('./candlestick') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -830,6 +830,7 @@ function applyTransforms(fullTrace, fullData, layout) { | |||
fullTrace: fullTrace, | |||
fullData: fullData, | |||
layout: layout, | |||
fullLayout: fullLayout, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI @bpostlethwaite
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sweet I was going to ask for this :) Now I can grab the axis.type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact this might allow the whole Streambed Plotly.js build to stop requiring special Axes API.
'If a single string, the same string appears over', | ||
'all the data points.', | ||
'If an array of string, the items are mapped in order to the', | ||
'this trace\'s sample points.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra the/this
description: 'Sets the close values.' | ||
}, | ||
|
||
// TODO find better colors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it helps we used:
increasing = '#3D9970'
decreasing = '#FF4136'
in the python wrapper (but that red is still pretty bright)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 7b19b74
boxpoints: false, | ||
|
||
// TODO could do better | ||
name: direction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if you try to edit this by clicking in the legend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Good question. Let me check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handled in 2b1918c
var boxAttrs = require('../box/attributes'); | ||
|
||
var directionAttrs = { | ||
visible: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that actually something people want, to be able to show only one direction? Seems weird. I can imagine wanting to delete one of them from the legend, like, if I had candlesticks for several companies and I set the colors such that I could tell immediately which increasing/decreasing sets went together but I wanted only one line for each company in the legend... but I can't see why I would want to hide one entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that actually something people want, to be able to show only one direction
I'm ok dropping it. It is an option in the plotly.py
version though. Not sure if parity with the current ohlc / candlestick offerings is a must.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and by dropping increasing.visible
and decreasing.visible
, it would make the name
problem easier to solve.
For example, with
trace = {
type: 'ohlc',
name: 'company A',
// ...
};
// would yield
fullTrace[0] = {
type: 'scatter',
mode: 'lines',
name: 'Company A - increasing',
// ...
};
fullTrace[1] = {
type: 'scatter',
mode: 'lines',
name: 'Company A - decreasing'
// ...
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks like a good default, but we had better let users override the name separately for the two halves as well, including with a blank string in case they want the legend to have both symbols but only one label.
We could ask the plotly.py candlestick authors ( @chriddyp @cldougl ? tools.py is getting a little out of hand!) if there was a specific request for separately controlling the two visibilities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure the goal was to make it so if people were plotting multiple companies they could group decA + incA, decB + incB, etc and only show one legend entry per company to toggle between companies easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the main reason I allowed the 2 visibilities to be controlled was because that was the easiest way for me to make sure users could edit specific attributes of each trace more easily (color/name etc). I think I wasn't sure how'd these would be used/styled and what attributes would be important. So I'd say if we can't think of a use case of allowing separate control of the visibilities here (I can't think of one) then we don't have to make this completely parallel to the python wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in https://github.com/plotly/plotly.js/pull/1020/commits I chose to remove increasing
/ decreasing
visible
and add increasing
/ decreasing
name
and showlegend
so that users can edit legend item name / visibility per direction, but disallowing hiding a direction completely for the graph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's commit 2b1918c
@etpinard this is beautiful work!!! |
// | ||
// | ||
// should we add the option for ohlc along y-axis ?? | ||
t: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a very strong opinion on this, but my gut reaction would have been to make it x
, and change it to y
if we offered an option for horizontal values like we do with bar charts (though I can't find any examples of people actually doing this). Stela says she thinks she's seen these charts against a different axis (asset type, for example). I can't find an example of this, but can imagine folks wanting to see if different stocks or asset types were subject to the same trends during a given time period (you'd have to normalize their values somehow of course). And If that were to happen, it would be awkward to call stock A, stock B, etc t
but calling them x
would be natural.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Let's go with x
then which matches layout.xaxis
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in d2d6845
width: Lib.extendFlat({}, lineAttrs.width), | ||
dash: Lib.extendFlat({}, lineAttrs.dash), | ||
|
||
tickwidth: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be shared between the directions? Not sure if there's a reason people would want up and down to have different widths (if so there could be an override within each direction), but it would be very normal to want to adjust both at once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be shared between the directions?
Probably. I'd vote 👍 for a shared attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 6f3eac2
tickwidth: { | ||
valType: 'number', | ||
min: 0, | ||
max: 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They'll start to overlap at 0.5, right? Not sure if we want to forbid overlap, but normally you wouldn't want to go past this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 6f3eac2
color: Lib.extendFlat({}, boxAttrs.line.color), | ||
width: Lib.extendFlat({}, boxAttrs.line.width), | ||
fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor), | ||
tickwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as for OHLC, should the widths just be shared by both directions? But also, is there precedent for calling the box whiskers "ticks"? That seems to conflict with what ticks mean for OHLC. I might have left it just as "whiskerwidth."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tickwidth
felt more natural for OHLC, then I copied the name over to candlesticks for consistency.
I guess whiskerwidth
in OHLC would make sense too.
I'd vote for whiskerwidth
as a shared attribute for both directions, in both OHLC in candlestick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT following a quick chat with @alexcjohnson :
Let's go with:
tickwidth
for OHLC as per the wiki articlewhiskerwidth
for candlestick on par with ourbox
attributes
whiskerwidth
for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 38e5bd7
- recalc when gd.calcdata doesn't match with fullData (not gd.data)
- so that transform writing can set layout option during transform step
- factor out hacky moves in ohlc/helpers.js - pass open, high, low, close array in transform opts, then pass then to traceOut in transform supplyDefaults - use trace (not trace._fullInput) to filter -> this makes ohlc and candlestick compatible with filters & groupby
- this may have some side effects, but this is necessary to make ohlc and candlestick work with filter and groupby transforms - in brief, we need to make sure 'data_array' of input module are used correctly during filter and groupby operations
- remove `increasing/decreasing.visible` attribute - add increasing/decreasing 'name' and 'showlegend'
- so that editing legend item text restyles increasing / decreasing 'name' instead of trace-wide name
I'll try to add a jasmine test for fbb299b and I haven't pushed the image mocks yet But, apart from that, this PR is ready for (hopefully final) review. |
showlegend: OHLCattrs.increasing.showlegend, | ||
|
||
color: Lib.extendFlat({}, boxAttrs.line.color), | ||
width: Lib.extendFlat({}, boxAttrs.line.width), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I probably should have noticed this earlier (sorry!) but color
and width
seem odd for line color and width. It took me a bit to figure out that this wasn't the width of the candle, and that we don't need an attribute for that because layout.boxgap
does that... This should probably be mentioned somewhere!
Also just like whiskerwidth
I'd think width
is a property shared by both directions. Can we call these linecolor
(in directionAttrs
) and linewidth
(in attributes
)? I guess we could argue that for consistency these should go inside a line
container instead... it seems a bit funny to make a one-member container just to keep the names consistent but maybe that will actually be easier and clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call these linecolor (in directionAttrs) and linewidth (in attributes)?
sounds good.
it seems a bit funny to make a one-member container
Agreed. I much prefer increasing.linecolor
and decreasing.lincolor
- by filling in a 'text' array in the generated traces
if(!(Array.isArray(groups)) || groups.length === 0) { | ||
return trace; | ||
} | ||
|
||
var groupNames = groups.filter(function(g, i, self) { | ||
return self.indexOf(g) === i; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took me a minute to figure out that this is uniquifying the array when I saw it up in findArrayAttributes
... here its purpose is a little clearer. But it makes me think (since it looks like these will get called a lot) that we might want to put a faster one in our Lib - see eg "Performance" in http://stackoverflow.com/a/9229821
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in f6f072f
for ohlc and candlestick traces
- use it in groupby transform and findArrayAttributes
- the default hovermode: 'x' now looks good after adding transformed hover text
- trace-wide 'line' style sets default for increasing / decreasing line styling
This PR is ready for (hopefully final) review. |
|
||
var Lib = require('../../lib'); | ||
|
||
// This routine gets called during the trace supply-defaults step. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bpostlethwaite basically all the hackery I needed is in this file.
I hope you find the comments useful.
@@ -102,7 +102,7 @@ module.exports = { | |||
valType: 'number', | |||
min: 0, | |||
max: 0.5, | |||
dflt: 0.05, | |||
dflt: 0.1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might actually vote for quite a bit higher - like 0.3 perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, either 0.2 or 0.3 is OK. I still sort of prefer 0.3... but having never used these charts myself it's not a very informed opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0.3 from me 3️⃣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in b98835c
@etpinard this looks great! Just a question about the default tickwidth - feels to me like we should be using more of the space we have available, both to guide the eye better to whether one day's close is above or below the next day's open, and so the ticks are more visible when they're really closely spaced. Aside from that, 💃 from me! |
// This routine gets called during the transform supply-defaults step | ||
// where it clears ephemeral transform opts in user data | ||
// and effectively put back user date in its pre-supplyDefaults state. | ||
exports.clearEphemeralTransformOpts = function(traceIn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh man... you know the transform implementation isn't 💯 when you roll out a function like this. Fancy workarounds though 🎉
This PR adds two new trace types the plotly.js:
ohlc
(short for open-high-low-close) andcandlestick
to the main plotly.js bundle - making plotlyjs-finance and the plotly.py figure factories obsolete.This PR also adds a new partial bundle called
plotly-finance
which includesohlc
andcandlestick
along with a few other trace type. We should come to an agreement about which trace type to include before this PR is done.On the technical side of things, this PR introduces a new way of adding trace types: using transforms. In essence, a
ohlc
trace is transformed internally into one increasing and one decreasing line trace. Similarly, acandlestick
trace is transformed transformed internally into one increasing and one decreasing box trace.