Quick update - the code in this article was used as the starting point for components in the d3fc project. It’s a bit more advanced than this article, but you might find it interesting to see what this code evolved into!
In this article I’m going to create an interactive Fibonacci fan D3 component, and show you how to add it to a chart in just 7 lines of code.
For many of you, your first question is probably…
What is a Fibonacci fan?
A Fibonacci fan looks like this:
Investopedia explains how to draw a Fibonacci fan:
Fibonacci fans are created by first drawing a trendline through two points (usually the high and low in a given period), and then by dividing the vertical distance between the two points by the key Fibonacci ratios of 38.2%, 50% and 61.8%. The result of these divisions each represent a point within the vertical distance. The three 'fan' lines are then created by drawing a line from the leftmost point to each of the three representing a Fibonacci ratio.
If you want a more detailed explanation of the mathematics behind it, you can find one here.
Below you’ll see a chart, to which I’ve added my interactive Fibonacci fan component. To draw the fan:
- Select your first point by clicking on the chart; you’ll now see a blue trendline from this point to the point closest to the mouse
- Click on the chart again to select your second point; the fan lines are drawn from the first point to the edge of the chart
- Click anywhere on the chart to dismiss the fan
Have a go and see it in action.
The Fibonacci Fan Component
I tackled this project by creating a component with multiple phases, whose mouse move / click behaviour would change depending on which phase it was in.
- In phase 1, we highlight the nearest data point, much like my previous crosshairs component; a mouse click stores this point (let’s call it origin) and moves us on to phase 2
- In phase 2, we again highlight the data point nearest the mouse and draw a trend line between this and the origin point; a click stores this point (target), draws the fan lines in the right places and moves us on to phase 3
- In phase 3, we don’t react to mouse movement at all; a click hides the trend line and fan and returns us to phase 1
Here’s the code for the component - I’ve left it in one block so it’s easier to copy & paste. Underneath, we’ll go through the code and explain what’s going on.
Let’s go through this, bit by bit.
Declarations
At the top of the file we have our field declarations:
- First, the fields for which we have property accessors:
target
(the element to which we’ll attach our event handlers),series
(the data series we’re going to track), and thexScale
andyScale
fields - Then a set of fields which will hold the various SVG elements we need to draw the fan
- Finally, some internal fields:
phase
(which obviously stores the phase the component is in);locationOrigin
andlocationTarget
(which will hold the location of the points at either end of the trend line)
Component function
The component function is pretty trivial for this component - all we’re doing is initialising each of our SVG elements in turn, giving each a unique CSS class and making sure they’re all set to display: none
initially.
Event handlers
We have two event handlers (which are set in the target()
property accessor, see below) - mouseMove()
and mouseClick()
.
- In
mouseMove()
, we set ourlocationOrigin
(in phase 1) andlocationTarget
(in phase 2) fields, and then callupdate()
to set the co-ordinates of the trend line (lineSource
) and circle (circleOrigin
/circleTarget
) SVG elements. In phase 3 we don’t need to respond to mouse movement. - In
mouseClick()
, we call thesetFan()
function if we’re in phase 2, or theclearFan()
function if we’re in phase 3. Regardless of which phase we’re in, we always advance to the next phase.
Helper functions
The two functions that do all the work for this component are setFan()
and clearFan()
.
setFan()
is the big one, but when we break it down, it’s not doing anything magic. First we do a bit of sanity checking - is our target point further to the right than our origin point? If not, switch them round before doing anything else. Then we callcalculateY()
to calculate the gradient of the trend line, and using this we calculate the Y co-ordinate of the line that would exist if we continued this line all the way to the rightmost edge of the chart, as well as the Y co-ordinates of the fan lines (we can calculate these by using the Fibonacci ratios of 0.618, 0.5 and 0.382). Rather than drawing the fan lines in the right place immediately, we add a little visual flair by first drawing them at the highest point (usingsetFanLines()
), then using ad3.transition
to move them down to their correct locations. We then hide the circle SVG elements.clearFan()
is pretty simple - we reset ourlocationOrigin
andlocationTarget
fields to null, and setdisplay: none
on all our SVG elements.
We have three functions which help us to find the data value closest to the mouse pointer:
findLocation()
: This function finds the mouse co-ordinates and converts them to values on our X and Y scales, then usesfindPoint()
andfindField()
to return the data point and field namefindPoint()
: Given a date, this function iterates through the data points to find the point closest to this date.findField()
: Given a value and a data point, this function iterates through the fields on the data point to find the field with the closest value.
The component also provides a public function, update()
, which recalculates the position of each of the SVG elements. I’ve included this because it’s required if the scales change (e.g. because of panning / zooming on the chart) - if you don’t call update()
, the fan will stay in the same place while the chart moves around it.
Property accessors
Nothing unusual here, just get/set accessors for the fields noted above - with the exception of target
, for which we need to set up our event handlers.
Styling
Here’s the CSS that I’ve used - nothing unusual here. Blue for the circles and trend line, grey for the fan lines (with the middle one slightly faded), and a light grey background for the fan.
Adding the fan to the chart
Now that we’ve created the component, the final step is to add the component to a chart. As usual, I’m starting with the OHLC chart that Tom developed in his article on OHLC and candlestick components.
As with my previous article, I’m adding an invisible overlay onto the chart, so that we get mousemove events fired when the mouse moves anywhere on the chart area.
We initialise the Fibonacci fan component, providing values for all the properties it needs, then we add the component to the plot area, as follows:
And with that, we’re done! We’ve created the interactive chart you see above.
Enhancements
Perhaps a useful enhancement for this component would be to add a crosshairs element so that the user has some extra feedback about the data points they’re about to click on. We’d probably want to set it up such that the crosshairs would only be visible when selecting points, and hidden when we’re displaying the fan elements.
Conclusion
We now have a D3 Fibonacci fan component which can be added to a chart with only 6 lines of code. It’s an interactive component, using mouse move / click events to move between the different phases of placing the first point, placing the second point, and viewing the fan. We also use transitions to make the appearance of the fan a little more visually appealing.