Library / SDK
mauriciopoppe/function-plot avatar
mauriciopoppe/function-plot

function-plot: a D3 plotter where the function is the data

A versatile 2d function plotter

1,067 stars124 forksJavaScriptMIT

At a glance

What is it?
function-plot renders mathematical functions as interactive D3 line charts and scatterplots, re-evaluating the expression whenever the scale changes so the graph appears infinite. A version 2 rewrite is in progress, with the API refactor and the render pipeline documented in the repository.
Who is it for?
function-plot fits a developer who needs to show a function rather than a dataset, in a documentation page, a lecture slide or a debugging tool, and who wants D3 output without writing the axes, the zoom behaviour and the sampling by hand.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The function is the data model

function-plot is a JavaScript library built on top of D3.js whose purpose is to render functions with little configuration, and the README describes it as a little clone of Google's plotting utility, the search result that draws `y = x * x`.

That framing explains the whole design. In a charting library the data is a list of points and the library decides how to draw them. In function-plot the data is an expression, and the library is responsible for deciding which points of that expression to draw, and for drawing more of them when the reader changes the scale. The library supports interactive line charts and scatterplots, and the mechanism is stated in one sentence: whenever the graph scale is modified the function is evaluated again with the new bounds, with the result described as infinite graphs.

So the interaction is the point. A reader who zooms into a region of a curve does not get a blurry bitmap or a resampled approximation, they get the curve evaluated over the new interval, and the visible result is indistinguishable from a graph of a function that continues forever. The cost of that is the sampling, which is where the design documents in the repository come in.

The library is MIT licensed, with the copyright line covering 2015 to 2023, the primary language is JavaScript, the default branch is `main`, and the last push was on 2026-10-11. The recent releases are v1.26.0 from 2026-10-11, v1.25.4 from 2026-04-26 and v1.25.3 from 2026-01-03, and the homepage is a live gallery of examples at `mauriciopoppe.github.io/function-plot/`.

Two keys in the options object

The API is small enough that the complete example is the documentation.

javascript
import functionPlot from 'function-plot'
functionPlot({
  target: '#root',
  data: [
    {
      fn: 'x^2',
      derivative: {
        fn: '2*x',
        updateOnMouseMove: true
      }
    }
  ]
})

`target` is the DOM element the graph is drawn into, and `data` is an array of series. A series has an `fn`, which is the expression, and optionally a `derivative`, which is a nested object with its own `fn` and a flag called `updateOnMouseMove` that makes the derivative follow the pointer.

The derivative support is the feature that pushes this past a drawing toy. With a derivative supplied, the library can show the slope at a point, and the `updateOnMouseMove` flag makes it recompute as the pointer moves, which is a tangent line following the cursor. That turns the plot from a static picture into something a reader can interrogate, and it is the kind of detail that separates a plotting library from a curve renderer.

The installation is the usual one:

sh
npm install function-plot

And the README points at the homepage for all the examples, with the generated API reference linked separately as the API page. The examples are the documentation that matters here, because the option surface is large and the type definitions describe it precisely.

The version 2 rewrite is documented in design/

The package version in the repository is `2.0.0-0`, while the latest published release is v1.26.0. That gap is the single most important fact about the project for anyone about to depend on it, and the repository explains itself on the point.

There is a `design/` directory at the root, and the README links it under the question of how the library works. It contains a design documentation index, a vision document, an API refactor proposal, a render pipeline document and a web workers document.

The names describe a rewrite rather than an increment. A vision document states where the project wants to go, an API refactor proposal states what the public interface will look like afterwards, a render pipeline document describes how a frame is produced, and a web workers document describes moving the work off the main thread, which for a library that re-evaluates a function on every scale change is the obvious performance question, and the script `perf:pipeline` in the package metadata is a benchmark for that pipeline at `src/perf/interval-pipeline.ts`.

Two things follow for a user. The first is that the 1.x line is the stable one and the branch is where the next version is being designed, so a project that wants stability should pin a published release and not follow `main`. The second is that the design documents are unusually good reading, because a project that writes down the pipeline it intends to implement has usually thought about where the time goes.

There is also a `CONTEXT.md` and a `DEVELOPMENT.md` at the root, along with an `AGENTS.md`, which is the pattern of a repository that has been worked on with coding assistants and has therefore written down the conventions.

A build that produces a bundle, a type definition and a documentation site

The build script in `package.json` shows the shape of the project, which is a TypeScript source that is compiled twice and then documented.

json
  "scripts": {
    "build": "npx rimraf dist && npm run html && npm run docs && npm run build:typescript && npm run build:webpack && npm run build:site",
    "build:webpack": "NODE_ENV=production webpack",
    "build:typescript": "tsc && mkdir -p dist/samplers && cp src/samplers/*.mjs dist/samplers/",
    "build:site": "cp dist/*function-plot.js* site/",
    "docs": "typedoc --excludeExternals --externalPattern \"**/node_modules/**\" --out site/docs src/index.ts",
    "start": "webpack-dev-server"
  },

TypeScript compiles the sources and produces the type definitions, and the tsc step also copies the sampler modules into `dist/samplers`, which is the detail that matters: the samplers are separate `.mjs` files that the compiled code loads, and the copy step exists because a compiler does not copy assets.

Webpack then produces the browser bundle, with `NODE_ENV=production`, and the site build copies the bundle into the `site/` directory that holds the examples gallery. The docs step runs typedoc over `src/index.ts` with externals excluded, so the published API reference is generated from the same source as the library.

The package metadata is careful about the distribution shapes. The package is an ES module, `type` is `module`, `main` and `module` both point at `dist/index.js`, the types are at `dist/index.d.ts`, and the browser build is published under `jsdelivr` and `unpkg` as `dist/function-plot.js` with an `exports` map that offers a `umd` condition and a `default` one. So a bundler, a script tag and a Node import are all supported, and the `files` array ships only `dist`, which keeps the installed package small.

The Node requirement is `>=18.12.0`, and the code style is enforced with the `standard` tool, whose configuration ignores the `sandbox/`, `site/` and `dist/` directories.

Tests that run in a real browser and a formatter that fails the build

The test setup says more about the project than the test count would.

The `test` script runs the Jest suite and then the format check:

json
  "scripts": {
    "test": "npm run test:jest && npm run test:format",
    "test:jest": "NODE_OPTIONS='--experimental-vm-modules' jest",
    "test:format": "prettier -c ."
  },

The `NODE_OPTIONS` line is the interesting one. The library is an ES module, and running Jest against ES module sources needs that flag, so the experimental flag is a deliberate and visible cost of the module format rather than an accident.

Running the formatter as part of the test suite is the other decision worth noting. A formatting difference is a test failure, so the style of the codebase cannot drift.

The browser side has its own configuration. There is a `jest-puppeteer.config.cjs` and a `puppeteer.config.cjs` in the root, which means there are tests that drive a real browser, and that is not a detail for a library that renders D3 output: the behaviour that matters, the axes, the zoom, the pointer-following derivative, is DOM behaviour, and a jsdom test does not exercise it faithfully.

The rest of the tooling is conventional and modern. There is a `jest.config.cjs`, a `webpack.config.cjs`, a `babel.config.cjs`, an `.eslintrc.cjs` in the older style, a `tsconfig.json`, a `.prettierrc.json` with a `.prettierignore`, and an `.editorconfig`.

Where function-plot is the wrong tool

The limits come from the same decision that gives the library its character, which is that the function is the model.

The first is data. If the thing to be drawn is a list of measurements, a time series or a scatter of observations, this library is solving the wrong problem, because there is no expression to evaluate, and the sampling machinery, the re-evaluation on zoom and the derivative support all work against you rather than for you. A charting library that takes arrays of points is the right tool, and the difference in approach is that function-plot's data model is a formula while the alternative's is a table.

The second is the API stability of the version in the repository. The branch carries `2.0.0-0` and the `design/` directory contains an API refactor proposal, so a project that follows the branch writes code against an interface that is still being designed. The 1.x line is the one with published releases, and the gap between the two is large enough that the migration is a project.

The third is the cost of doing it yourself. The library is a D3 layer, and a team that has already built D3 charts, with its own axes, its own scales and its own interaction model, may find that adopting it means replacing the interaction model it already has. A library that re-renders on every scale change is a library with opinions about the frame loop, and integrating it into an existing chart means living with those opinions or forking them.

The fourth is the evaluation of arbitrary expressions. The library takes a string and evaluates it, which means the security question of where that string comes from is a question the application has to answer, and a user-facing plotting tool has to decide what to do with a formula typed by a visitor.

The alternative approach, for someone who needs one chart of one function, is to write the D3 code directly, which is a page of work and gives full control over the result. The difference in approach is that function-plot is a general tool for the case where you will draw many functions, and the alternative is the answer for the case where you will draw one.

Editorial conclusion

function-plot fits a developer who needs to show a function rather than a dataset, in a documentation page, a lecture slide or a debugging tool, and who wants D3 output without writing the axes, the zoom behaviour and the sampling by hand. It is the wrong tool for data, where the model is rows rather than an expression, and it is a poor choice for a production application that needs a stable API, because the package version on the main branch is 2.0.0-0 while the published release line is still 1.x. Before depending on it, check whether the v2 API refactor changes the calls you would write, since the proposal and the new pipeline are documented in the `design/` directory, and pin a published version rather than tracking the branch.

Frequently asked questions

How do I install function-plot?

With `npm install function-plot`. The package metadata requires Node 18.12.0 or later, and the browser build is also published as a UMD bundle on jsdelivr and unpkg for use from a script tag.

How do I plot a function with function-plot?

Call `functionPlot` with a `target` element and a `data` array whose entries carry an `fn` expression and optionally a `derivative` object with its own `fn` and an `updateOnMouseMove` flag that makes the derivative follow the pointer.

What happens when the graph is zoomed or panned in function-plot?

The library supports interactive line charts and scatterplots, and whenever the graph scale is modified the function is evaluated again with the new bounds, which is what makes the graphs appear infinite. The design directory documents the render pipeline and the web worker approach behind it.

Is the function-plot version 2 API stable?

Not yet. The package version on the main branch is 2.0.0-0 while the latest published release is v1.26.0 from 2026-10-11, and the design directory holds the vision document, the API refactor proposal, the render pipeline document and the web workers document for the rewrite.

Official sources

  1. License: MIT
  2. mauriciopoppe/function-plot on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mauriciopoppe-function-plot.svg)](https://hysenlabs.com/projects/mauriciopoppe-function-plot)

Was this useful?