A CANVAS plotter Class supports drawing Line, Mountain, Bar, Dashed-line, Stock OHLC.
npm install plotters
plotters=========
[![NPM version][icon-nv]][link-nv][![Build Status][icon-bs]][link-bs][![Clean Code][icon-cc]][link-cc][![Dependency Status][icon-ds]][link-ds][![devDependency Status][icon-dds]][link-dds]![License][icon-li]
!Screenshot
Chrome, Safari, Firefox, Opera, IE9+
``shell npm install plotters
shell npm install plotters
or
jspm install npm:plotters`
This is a full stock chart:
!A full 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.
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.
directly, the code is clean.
An example (pretty long) :
`js
js
/** * 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 = $('
').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];
must have 'getPosition' method. yAxis : this._yAxis, //
must have 'getPosition' method. numberOfPoints : len, // Number of data points. numberOfTicks : len, numberOfTicksEstimate: len, /** * msMeasure: dates' measure in milliseconds. * ( Since dates in
are something like
,
, *
, 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. ] */
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();
``
1. Clone this repo.2. Run npm install ( You can skip this step if you have a globally installed jspm.)3. Run jspm install.4. Run live-server ( [live-server][link-ls] is very useful, but if you have other server tools you don't have to use it.)5. Open demo.html in a browser.
[icon-nv]: https://img.shields.io/npm/v/plotters.svg?style=flat[link-nv]: https://www.npmjs.com/package/plotters[icon-bs]: https://travis-ci.org/wenwuwu/plotters.svg?branch=master[link-bs]: https://travis-ci.org/wenwuwu/plotters[icon-ds]: https://img.shields.io/david/wenwuwu/plotters.svg?style=flat[link-ds]: https://david-dm.org/wenwuwu/plotters[icon-dds]: https://img.shields.io/david/dev/wenwuwu/plotters.svg?style=flat[link-dds]: https://david-dm.org/wenwuwu/plotters#info=devDependencies[icon-cc]: https://img.shields.io/badge/code-clean-orange.svg?style=flat[link-cc]: https://github.com/wenwuwu/code-convention-js [link-ls]: https://www.npmjs.com/package/live-server[icon-li]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat