plotters
v0.9.19
Published
A CANVAS plotter Class supports drawing Line, Mountain, Bar, Dashed-line, Stock OHLC.
Downloads
4
Readme
plotters
Browser support
Chrome, Safari, Firefox, Opera, IE9+
Installation
npm install plotters
or
jspm install npm:plotters
Usage
This is a full stock chart:
It has necessary functionalities for a stock chart. It contains a CANVAS plotter, timeline axis, number axis, timeline zoom,
chart grid, etc. The CANVAS plotter is the key of a chart. Plotters
class is such a component.
This example contains a simple chart implementation which can generate canvas graphs similiar to the first screenshot.
- This example is based on the demo, you can run the demo to see how it works.
- If you like you can read
demo.js
directly, the code is clean.
An example (pretty long) :
/**
* Load needed modules.
*/
var $ = require('jquery'),
Arrays = require('array-es5'),
Dates = require('date-es5'),
DateIndex = require('date-index'),
Plotters = require('./lib/plotters.js');
/*
* A simple Chart.
* We wrap plotter functionality in a Class called Chart.
*
* 1. plotterStyle() method will create a plotter instance and set plotting style.
* 2. load() method will load data and draw graphs on CANVAS.
*/
var Chart = function (domParent) {
this._parent = domParent;
this._buildUI();
};
Chart.prototype = {
constructor: Chart,
_colors: {
yellow : '#b58900',
orange : '#cb4b16',
red : '#dc322f',
magenta : '#d33682',
violet : '#6c71c4',
blue : '#268bd2',
cyan : '#2aa198',
green : '#859900'
},
_regexPlotterStyle: /^(Line|Bar|OHLC)(?:\?(\w+))?$/,
_buildUI: function () {
var container = $('<div>').appendTo(this._parent)
.addClass('chart');
this._canvas = $('<canvas>').appendTo(container);
this._canvas[0].width = 360;
this._canvas[0].height = 280;
this._header = $('<p>').appendTo(container);
this._xAxis = new DateAxis();
this._yAxis = new NumberAxis();
},
plotter: function () {
return this._plotter;
},
_getPlotterStyle: function (className, style) {
return Plotters[className].Style[style];
},
plotterStyle: function (plotterStyle, hdrText) {
var match = this._regexPlotterStyle.exec(plotterStyle);
if (match === null)
throw "IllegalArgumentException: plotterStyle is not supported: " + plotterStyle;
var className = match[1],
style = match[2];
/**
* Create plotter instance.
*/
this._plotter = new Plotters[className]();
if ( typeof style === 'string'
&& style !== '' )
/**
* Set plotter style.
*/
this._plotter.style(this._getPlotterStyle(className, style));
else
style = '';
this._plotter.canvas(this._canvas[0]); // Set CANVAS object.
if (typeof hdrText === 'undefined') {
hdrText = className;
if (style.length > 0)
hdrText += ': ' + style;
}
this._header.text(hdrText);
return this;
},
setColor: function (color) {
this._plotter.color(this._colors[color]); // Set plotter color.
return this;
},
_getInfo: function (data) {
var dates = [],
min = Infinity,
max = -Infinity,
len = data.length;
for (var i = 0; i < len; i++) {
var row = data[i];
dates.push(row[0]);
for (var j = 1; j < row.length; j++) {
var val = row[j];
if (min > val)
min = val;
if (max < val)
max = val;
}
}
return {
dates: dates,
min: min,
max: max
};
},
load: function (data) {
var len = data.length,
info = this._getInfo(data),
plotter = this._plotter;
this._xAxis.setDates(info.dates); // Set dates info.
this._yAxis.setRange(info.min, info.max); // Set value info.
/**
* Preparation.
*/
plotter.open({
xAxis : this._xAxis, // `xAxis` must have 'getPosition' method.
yAxis : this._yAxis, // `yAxis` must have 'getPosition' method.
numberOfPoints : len, // Number of data points.
numberOfTicks : len,
numberOfTicksEstimate: len,
/**
* msMeasure: dates' measure in milliseconds.
* ( Since dates in `data` are something like `2016-05-01`, `2016-05-02`,
* `2016-05-03`, so the measure (gap) is 1 day. )
*/
msMeasure : Dates.millisPerDay,
recordSize : 1
});
for (var i = 0; i < len; i++) {
var row = data[i];
/**
* put() method add data point to plotter instance.
*/
plotter.put(row.shift(), row.length > 1 ? row : row[0]);
}
/**
* Plotter instance draw CANVAS by calling close() method.
* this._xAxis, this._yAxis both need to implement getPosition() method,
* Plotter instance will call each one's getPosition() to convert date and
* value to corresponding x and y coordinates on the canvas.
*/
plotter.close();
}
};
/**
* A simplified DateAxis class.
*/
var DateAxis = function () {
this._index = new DateIndex();
};
DateAxis.prototype = {
constructor: DateAxis,
setDates: function (dates) {
/**
* Index dates.
*/
this._index.setAll(Arrays.iterator(dates));
this._lowIdx = this._index.get(dates[0]);
this._highIdx = this._index.get(dates[dates.length - 1]);
},
/**
* Get a date's relative position.
*/
getPosition: function (date) {
var idx = this._index.get(date);
if (idx < 0)
throw "IllegalArgumentException: date is not found.";
return ( idx - this._lowIdx )
/ ( this._highIdx - this._lowIdx );
}
};
/**
* A simplified NumberAxis class.
*/
var NumberAxis = function () {
this._min = null;
this._max = null;
this._range = null;
};
NumberAxis.prototype = {
constructor: NumberAxis,
setRange: function (min, max) {
this._min = min;
this._max = max;
this._range = this._max - this._min;
},
/**
* Get relative position of a value.
*/
getPosition: function (number) {
if ( typeof number !== 'number'
|| isNaN(number) )
throw "IllegalArgumentException: number must be a Number.";
return (number - this._min) / this._range;
}
};
/**
* You can ignore this object, its purpose is to generate a series of fake data.
*/
var util = {
_addDay: function (date, i) {
return new Date(date.getTime() + i * Dates.millisPerDay);
},
/**
* Generate an array of date and values according to given value range,
* base date, and number of data points.
*/
generateData: function (baseDateStr, minPrice, maxPrice, numOfPoints, isOHLC) {
var priceGen = this._priceGenerator(minPrice, maxPrice, numOfPoints),
baseDate = Dates.isoStringToUTCDate(baseDateStr),
data = [];
for (var i = 0; i < numOfPoints; i++) {
var date = this._addDay(baseDate, i),
price = priceGen(),
row = [date, price];
if (isOHLC) {
var close = price + Math.random() - 0.5;
row.push(this._toDollars(Math.max(close, price + (Math.random() / 4))));
row.push(this._toDollars(Math.min(close, price - (Math.random() / 4))));
row.push(this._toDollars(close));
}
data.push(row);
}
return data;
},
_priceGenerator: function (minPrice, maxPrice, length) {
var range = (maxPrice - minPrice) / 40; // 5% variation
var variation = range / 5; // point by point variation: 0.25%
var barAt = (maxPrice + minPrice) / 2; // start in the middle.
var inc = 0;
var cnt = 0;
return function () {
if (--cnt <= 0) {
cnt = Math.ceil(Math.random() * 5);
inc = (Math.random() * range) - (range / 2);
if ( (barAt < minPrice + range && inc < 0)
|| (barAt > maxPrice - range && inc > 0) )
inc = -inc;
}
barAt += inc;
return util._toDollars((Math.random() * variation) + barAt);
};
},
_toDollars: function (number) {
return Math.round(number * 100) / 100;
}
};
/*************************************************
********* Draw charts.
**************************************************/
var playGround = $('#play-ground');
var reloadAllCharts = function () {
playGround.html(''); // I am lazy.
loadAllCharts();
};
$('#regen').bind('click', reloadAllCharts);
var loadAllCharts = function () {
var chart = new Chart(playGround),
/**
* Generate an array of data which contains 20 data-points,
* value ranges between 10 and 20.
*/
data = util.generateData('2016-05-01', 10, 20, 20, false);
/**
* The format of generated `data` will be something like:
*
* [ [Date, 10], [Date, 13.5], [Date, 16.7], 0.9.19. ]
*/
chart.plotterStyle('Line') // Plotter Line.
.setColor('yellow')
.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, false);
chart.plotterStyle('Line?MOUNTAIN') // Plotter Mountain.
.setColor('orange')
.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, false);
chart.plotterStyle('Line?MOUNTAIN', 'Line: (BaseValue is 15)');
var plotter = chart.plotter();
plotter.baseline(15); // Plotter MOUNTAIN with base value setting to 15.
chart.setColor('red')
.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, false);
chart.plotterStyle('Line?DASHED') // Dashed Line
.setColor('magenta');
var plotter = chart.plotter();
plotter.lineWidth(2)
.isFirstSegmentSolid(true)
.ratioSpaceToSolid(0.8)
/**
* Draw dashed line starts from '2016-05-07'
*/
.dashedTransitions([Dates.isoStringToUTCDate('2016-05-07')]);
chart.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, false);
chart.plotterStyle('Bar?ABOVE_AND_BELOW') // Bar.
.setColor('violet')
.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, true);
chart.plotterStyle('OHLC') // Stock OHLC.
.setColor('blue')
.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, true);
chart.plotterStyle('OHLC?CANDLESTICKS')
.setColor('cyan')
.load(data);
chart = new Chart(playGround);
data = util.generateData('2016-05-01', 10, 20, 20, true);
chart.plotterStyle('OHLC?CANDLESTICKS_RED_GREEN')
.setColor('green')
.load(data);
};
loadAllCharts();
Demo
- Clone this repo.
- Run npm install ( You can skip this step if you have a globally installed jspm.)
- Run jspm install.
- Run live-server ( live-server is very useful, but if you have other server tools you don't have to use it.)
- Open demo.html in a browser.