A 2d function plotter powered by d3
Function Plot is a powerful library built on top of D3.js whose purpose is to render functions with little configuration (think of it as a little clone of Google's plotting utility: y = x * x
The library currently supports interactive line charts and scatterplots, whenever the graph scale is modified the function is evaluated again with the new bounds, result: infinite graphs!
$ npm install --save function-plot
var d3 = window.d3
var functionPlot = require('function-plot');
functionPlot({
// options below
})
All the available options are described in the homepage
var functionPlot = require('function-plot');
params, All the params are optional unless otherwise stated
options
{Object}target
{string} the selector of the parent element to render the graph totitle
{string} If set the chart will have it as a title on the topxAxis
{Object}type
{string} default value:'linear'
the scale of this axis, possible valueslinear|log
domain
{number[]} initial ends of the axislabel
{string} label to show near the axis
yAxis
{Object}type
{string} default value:'linear'
the scale of this axis, possible valueslinear|log
domain
{number[]} initial ends of the axislabel
{string} label to show near the axis
disableZoom
{boolean} true to disable drag and zoom on the graphgrid
{boolean} true to show a gridtip
{object} configuration passed tolib/tip
, it's the helper shown on mouseover on the closest function to the current mose positionxLine
{boolean} true to show a line parallel to the X axis on mouseoveryLine
{boolean} true to show a line parallel to the Y axis on mouseoverrenderer
{function} Function to be called to define custom rendering on mouseover, called with thex
andf(x)
of the function which is closest to the mouse position (args:x, y
)
annotations
{Object[]} An array defining parallel lines to the y-axis or the x-axisx
{number} x-coordinate of the line parallel to the y-axisy
{number} y-coordinate of the line parallel to the x-axistext
{string} text shown next to the parallel line
data
{array} required An array defining the functions to be renderedplugins
{array} An array describing plugins to be run when the graph is initialized, check out the examples on the main page
An array of objects, each object contains info of a function to render and can have the following options
title
{string} title of the functionskipTip
{boolean=false} true to make the tip ignore this functionrange
{number[]=[-Infinity, Infinity]} an array with two numbers, the function will only be evaluated with values that belong to this intervalnSamples
{number} The number of values to be taken fromrange
to evaluate the function, note that if interval-arithmetic is used the function will be evaluated with intervals instead of single valuesgraphType
{string='interval'} The type of graph to render, available values areinterval|polyline|scatter
fnType
{string='linear'} The type of function to render, available values arelinear|parametric|implicit|polar|points|vector
sampler
{string='interval'} The sampler to take samples fromrange
, available values areinterval|builtIn
- NOTE:
builtIn
should only be used whengraphType
ispolyline|scatter
- NOTE: when math.js is included in the webpage it will be used instead of the bundled sampler
- NOTE:
Additional style related options
color
{string} color of the function to renderattr
{Object} additional attributes set on the svg node that represents this datumclosed
{boolean=false} (only ifgraphType: 'polyline'
orgraphType: 'scatter'
) True to close the path, for any segment of the closed area graphy0
will be 0 andy1
will bef(x)
When derivative
{Object} is present on a datum
derivative.fn
{string|Function} The derivative offn
derivative.x0
{number} The abscissa of the point which belongs to the curve represented byfn
whose tangent will be computed (i.e. the tangent line to the pointx0, fn(x0)
)derivative.updateOnMouseMove
{boolean} True to compute the tangent line by evaluatingderivative.fn
with the current mouse position (i.e. letx0
be the abscissa of the mouse position transformed to local coordinates, the tangent line to the pointx0, fn(x0)
)
When secants
{Array} is present on a datum
secants[i].x0
{number} The abscissa of the first pointsecants[i].x1
{number} (optional ifupdateOnMouseMove
is set) The abscissa of the second pointsecants[i].updateOnMouseMove
{boolean} (optional) True to update the secant line by evaluatingfn
with the current mouse position (x0
is the fixed point andx1
is computed dynamically based on the current mouse position)
fn
{string|Function} the function that represents the curve, this function is evaluated with values which are insiderange
x
{string|Function} the x-coordinate of a point to be sampled with a parametert
y
{string|Function} the y-coordinate of a point to be sampled with a parametert
range = [0, 2 * Math.PI]
{Array} therange
property in parametric equations is used to determine the possible values oft
, remember that the number of samples is set in the propertysamples
r
{string|Function} a polar equation in terms oftheta
range = [-Math.PI, Math.PI]
therange
property in polar equations is used to determine the possible values oftheta
, remember that the number of samples is set in the propertysamples
fn
{string|Function} a function which needs to be expressed in terms ofx
andy
NOTE: implicit functions can only be rendered using interval-arithmetic
points
{Array} an array of 2-number array which hold the coordinates of the points to render
NOTE: make sure your type of graph is either scatter
or polyline
vector
{Array} an 2-number array which has the ends of the vectoroffset
{Array=[0, 0]} (optional) vector's offset
instance.id
{string} a random generated id made out of letters and numbersinstance.linkedGraphs
{array} array of function-plot instances linked to the events of this instance, i.e. when the zoom event is dispatched on this instance it's also dispatched on all the instances of this arrayinstance.meta
{object}instance.meta.margin
{object} graph's left,right,top,bottom marginsinstance.meta.width
{number} width of the canvas (minus the margins)instance.meta.height
{number} height of the canvas (minus the margins)instance.meta.xScale
{d3.scale.linear} graph's x-scaleinstance.meta.yScale
{d3.scale.linear} graph's y-scaleinstance.meta.xAxis
{d3.svg.axis} graph's x-axisinstance.meta.yAxis
{d3.svg.axis} graph's y-axis
instance.root
{d3.selection}svg
element that holds the graph (canvas + title + axes)instance.canvas
{d3.selection}g.canvas
element that holds the area where the graphs are plotted (clipped with a mask)
Events
An instance can subscribe to any of the following events by doing instance.on([eventName], callback)
,
events can be triggered by doing instance.emit([eventName][, params])
mouseover
fired whenever the mouse is over the canvasmousemove
fired whenever the mouse is moved inside the canvas, callback params: a single object{x: number, y: number}
(in canvas space coordinates)mouseout
fired whenever the mouse is moved outside the canvasbefore:draw
fired before drawing all the graphsafter:draw
fired after drawing all the graphszoom:scaleUpdate
fired whenever the scale of another graph is updated, callback paramsxScale
,yScale
(x-scale and y-scale of another graph whose scales were updated)tip:update
fired whenever the tip position is updated, callback paramsx
,y
,index
(in canvas space coordinates,index
is the index of the graph where the tip is on top of)eval
fired whenever the sampler evaluates a function, callback paramsdata
(an array of segment/points),index
(the index of datum in thedata
array),isHelper
(true if the data is created for a helper e.g. for the derivative/secant)
The following events are dispatched to all the linked graphs
all:mouseover
same asmouseover
but it's dispatched in each linked graphall:mousemove
same asmousemove
but it's dispatched in each linked graphall:mouseout
same asmouseout
but it's dispatched in each linked graphall:zoom:scaleUpdate
same aszoom:scaleUpdate
but it's dispatched in each linked graphall:zoom
fired whenever there's scaling/translation on the graph, dispatched on all the linked graphs
When the definite-integral
plugin is included the instance will fire the following events
definite-integral
datum
{object} The datum whose definite integral was computedi
{number} The index of the datum in thedata
arrayvalue
{number} The value of the definite integrala
{number} the left endpoint of the intervalb
{number} the right endpoint of the interval
var y = functionPlot.eval.builtIn(datum, fnProperty, scope)
Where datum
is an object that has a function to be evaluated in the property fnProperty
,
to eval this function we need an x
value which is sent through the scope
e.g.
var datum = {
fn: 'x^2'
}
var scope = {
x: 2
}
var y = functionPlot.eval.builtIn(datum, 'fn', scope)
Every element of the data
property sent to functionPlot
is saved on instance.options.data
,
if you want to get the evaluated values of all the elements here run
var instance = functionPlot( ... )
instance.options.data.forEach(function (datum) {
var datum = {
fn: 'x^2'
}
var scope = {
// a value for x
x: 2
}
var y = functionPlot.eval.builtIn(datum, 'fn', scope)
}
Just call instance.programmaticZoom
with the desired x
and y
domains
var instance = functionPlot( ... )
var xDomain = [-3, 3]
var yDomain = [-1.897, 1.897]
instance.programmaticZoom(xDomain, yDomain)
Given the xDomain
values you can compute the corresponding yDomain
values to main
the aspect ratio between the axes
function computeYScale (width, height, xScale) {
var xDiff = xScale[1] - xScale[0]
var yDiff = height * xDiff / width
return [-yDiff / 2, yDiff / 2]
}
var width = 800
var height = 400
// desired xDomain values
var xScale = [-10, 10]
functionPlot({
width: width,
height: height,
xDomain: xScale,
yDomain: computeYScale(width, height, xScale),
target: '#demo',
data: [{
fn: 'x^2',
derivative: {
fn: '2x',
updateOnMouseMove: true
}
}]
})
var instance = functionPlot({
target: '#complex-plane',
xLabel: 'real',
yLabel: 'imaginary'
})
// old format
var format = instance.meta.yAxis.tickFormat()
var imaginaryFormat = function (d) {
// new format = old format + ' i' for imaginary
return format(d) + ' i'
}
// update format
instance.meta.yAxis.tickFormat(imaginaryFormat)
// redraw the graph
instance.draw()
Selectors (sass)
.function-plot {
.x.axis {
.tick {
line {
// grid's vertical lines
}
text {
// x axis labels
}
}
path.domain {
// d attribute defines the graph bounds
}
}
.y.axis {
.tick {
line {
// grid's horizontal lines
}
text {
// y axis labels
}
}
path.domain {
// d attribute defines the graph bounds
}
}
}
After cloning the repo and running npm install
node site.js // generate the examples shown on index.html
npm start
Open 127.0.0.1:5555
and that's it! Local development server powered beefy
Development page: 127.0.0.1:5555/playground.html
Deploy steps:
npm run dist
(make sure to commit the dist files after this command)npm version major|minor|patch
git push origin master
npm run deploy
npm publish
2015 MIT © Mauricio Poppe