Introduction to jsGraph

jsGraph is a browser-based data plotter. It allows you to display scientific-looking charts and export them in the SVG vector format for high-quality scientific publishing. However, it is also a lot more than that. It implements fast algorithms that redraw your datasets as fast as possible, allowing redrawings to occur in a fraction of a second. Because it is implemented in SVG, jsGraph is an ideal candidate for highly interactive browser-based graphs. You can select series, data points, subscribe to events, zoom in and out, drag the viewing window around, …

Here are some highlights of the library:

  • Scientific-style : Display graphs that are sober looking, but that show exactly what you need.

  • Fast rendering : Millions of points can be drawn in a fraction of a second. We implemented multilevel down-sampling algorithms that allow the fastest rendering time possible. It is expecially effective with the zoom plugin. When zoomed out, only the downsampled version of your data set gets displayed. As you zoom in, the resolution gets finer up to the one defined by your data set.

  • Rich API : Control exactly how you want your axes, series and annotations to be displayed. We hand you the keys to the finest possible level of control. Of course, you can also use a higher level API for simpler manipulation.

  • Multiple axes : Series are referenced to axes, which can be placed at the top, bottom, left or right of the graph. You can even have multiple axes on the same side, master-slave axes (for unit conversion, for example) or floating axes, displayed in the middle of the graph.

  • Various series : jsGraph allows you to display line plots, scatter plots, category plots or box plots, or to combine them together.

TL;DR Show me an example !

Here is the exact source code that generated this example, with comments

// Let's take the j-V curve of a solar cell as an example:

// Let's assume we have it in a javascript array, in the format [ [ x1, x2, ... xn ], [ y1, y2, ... yn ] ]
const data = [[-1, -0.95, -0.8999999999999999, -0.8499999999999999, -0.7999999999999998, -0.7499999999999998, -0.6999999999999997, -0.6499999999999997, -0.5999999999999996, -0.5499999999999996, -0.4999999999999996, -0.4499999999999996, -0.39999999999999963, -0.34999999999999964, -0.29999999999999966, -0.24999999999999967, -0.19999999999999968, -0.1499999999999997, -0.09999999999999969, -0.049999999999999684, 3.191891195797325e-16, 0.05000000000000032, 0.10000000000000032, 0.15000000000000033, 0.20000000000000034, 0.25000000000000033, 0.3000000000000003, 0.3500000000000003, 0.4000000000000003, 0.4500000000000003, 0.5000000000000003, 0.5500000000000004, 0.6000000000000004, 0.6500000000000005, 0.7000000000000005, 0.7500000000000006, 0.8000000000000006, 0.8500000000000006, 0.9000000000000007, 0.9500000000000007, 1.0000000000000007, 1.0500000000000007, 1.1000000000000008, 1.1500000000000008, 1.2000000000000008, 1.2500000000000009, 1.300000000000001, 1.350000000000001, 1.40000000000000, 1.450000000000001], [-20.499747544838275, -20.499659532985874, -20.499540838115898, -20.49938076340126, -20.499164882847428, -20.4988737412163, -20.498481100712695, -20.497951576424207, -20.497237447419362, -20.49627435611903, -20.494975508366757, -20.493223851506187, -20.490861525550883, -20.48767563678195, -20.483379071684652, -20.477584622168607, -20.469770090226536, -20.45923122725008, -20.44501826687575, -20.425850331676905, -20.4, -20.365137630064282, -20.318121411753218, -20.25471422548477, -20.16920179137358, -20.053877696141516, -19.89834888820463, -19.68859905188818, -19.405725451695528, -19.024235410553235, -18.509748899999998, -17.81590019886833, -16.88015939675398, -15.618197174567083, -13.916285014032407, -11.621045940111184, -8.52563212933044, -4.351083704794043, 1.2788112346498295, 8.87142097487483, 19.11099441051224, 32.920325817120975, 51.543917424350724, 76.66013443300987, 110.53245992908506, 156.21348084543214, 217.8199882640481, 300.90398420341603, 412.9530301645426, 564.065029038078]];;

// Create a waveform, which is used to represent data in a general sense. It has also a few cool tricks
const wave1 = Graph.newWaveform().setData(data[1], data[0]);

// For example, you can duplicate it into a second wave and do some point-to-point mathematics, in this case calculate the power density
const wave2 = wave1.duplicate().math((x, y) => x * y);

// Let's create a new example and place it in the placeholder <div id="graph-example-1" />
var g = new Graph("graph-example-1", {});

// You need to set the size if the container doesn't have one already
g.resize(400, 300);

// Let us create a new serie, called "jV", with red thick line and markers
var jV = g
    .newSerie("jV", {
        lineColor: 'red',
        lineWidth: 2,
        markers: true,
        // Setting the style of the markers
        markerStyles: {
            // For the default "unselected" style
            unselected: {
                // Default look
                default: {
                    shape: 'rect',
                    strokeWidth: 1,
                    x: -2,
                    y: -2,
                    width: 4,
                    height: 4,
                    stroke: 'rgb( 200, 0, 0 )',
                    fill: 'white'
                }
            },
            // Maybe we want to display only one marker every five points. Nothing easier !
            modifiers: (x, y, index, domShape, style) => index % 5 == 0 ? style : false
        }
    })
    .autoAxis() // Assign it automatically to the left and the bottom axis (which are created by default if they don't exist)
    .setWaveform(wave1) // Assign a waveform to this serie

// How about a second serie ?
var pV = g
    .newSerie("pV") // Give it a unique name, otherwise you'll overwrite the first one
    .setXAxis(g.getXAxis()) // The x axis is the same...
    .setYAxis(g.getRightAxis()) //  But the y axis is different, let's get the first right axis (created by default)
    .setWaveform(wave2); // And assign the second waveform

// How about some styling of the axes ?
g
    .getXAxis() // Retrieve the bottom axis
    .setUnit("V") // Set the unit voltage
    .setUnitWrapper("(", ")") // Wrap in parentheses ==> (V)
    .setLabel("Voltage") // Show the axis label
    .secondaryGridOff(); // Turn off the secondary grid

g
    .getLeftAxis()  // Retrieve the left axis
    .setUnit("mA cm^-2")
    .setUnitWrapper("(", ")")
    .setLabel("Current density")
    .secondaryGridOff()
    .forceMin(-25) // Force the minimum of the axis
    .forceMax(60);  // And its maximum

g
    .getRightAxis()
    .setUnit("mW cm^-2")
    .setUnitWrapper("(", ")")
    .setLabel("Power density")
    .gridsOff(); // Do not display any grid for the right axis

// Adds some spacing to the right of the axis. This is 30% of the "data width" of the axis (which is the max value - the min value for all series sharing this axis)
g.getBottomAxis().setAxisDataSpacing(0, 0.3);

// We want the 0 of the right axis to match the 0 of the left axis. It's much more natural like that
// Use .adaptTo() to enforce this behaviour adaptTo( otherAxis, myRef, otherRef, clamp )
// The clamp "min" basically says that the normal scaling behaviour applies to the 0 and to the min value of the axis. The max value is therefore the one calculated as a function of the master axis (the left one)
g.getRightAxis().adaptTo(g.getLeftAxis(), 0, 0, "min");

// Some more styling, note how you can change the color of the axis, the ticks, the tick labels and the axis label
g
    .getLeftAxis()
    .setAxisColor('red')
    .setPrimaryTicksColor('red')
    .setSecondaryTicksColor('rgba( 150, 10, 10, 0.9 )')
    .setTicksLabelColor('#880000')
    .setLabelColor('red');

// autoscale has to be called before the first rendering when more than one serie was added
g.autoScaleAxes();

// And finally, let's draw it !
g.draw();