Skip to content

Custom library integration

The chart library has from the very beginning been created with extensibility in mind. Barchart’s own data feeds use exactly the same infrastructure that third parties use. If you’d like to integrate this library into your own Website or product, this page is for you.

Downloading the library

Please click this link to download the library. Apart from the library itself - barchart.chart.js - you will also find a demo page index.html and some example code and data. Please find the instructions on how to run the demo site in the README.md file.

Making a custom data feed

As described in how to put a chart on a page the main role of the data feed is to serve as a factory for various data sources: timeseries (historical) data, metadata (profile) and record (quote, streaming) data.

If your data source supports all three concepts together, you may have a single object implementing/fullfiling the role of all three data sources.

The data sources themselves are extremely simple:

  • TimeSeriesSource should have (at least) a method getTimeSeries(query) where given query object (see below) you should return timeseries object (see below)
  • MetaDataSource should have (at least) a method getMetaData(symbol) where given a symbol name it returns a metadata object (see below)
  • RecordDataSource should have (at least) a method getRecord(symbol) where given a symbol name it returns a record object (see below)

The feed itself has other functionality, but it’s all already written in the base class called BaseDataFeed.

The whole custom feed functionality can be as simple as this (using a single object):

class ExampleFeed extends BaseDataFeed {
constructor(config) {
super(config);
const ds = new ExampleDataSource();
this.timeSeriesSource = this.metaDataSource = this.recordSource = ds;
}
}
class ExampleDataSource {
getTimeSeries(query) {
const kind = query.seriesKind || SeriesKind.Normal;
const isAmazon = query.symbol === "AMZN";
if (kind === SeriesKind.Normal) return isAmazon ? new ExampleTimeSeries(query) : null;
else if (kind === SeriesKind.Events) return isAmazon ? new ExampleEventsSeries(query) : null;
return null;
}
getMetaData(symbol) {
return new ExampleMetaData(symbol === "AMZN" ? 2 : 0);
}
getRecord(symbol) {
return symbol === "AMZN" ? new ExampleRecord() : null;
}
}

The reason we split the data source in 3 interfaces instead of having just one interface is that real-life implementations are likely going to be much longer than almost-hardcoded examples we’ve provided here (there’s practically always at least the aspect of caching involved); in other words, the above is only for illustration.

There are several kinds of time series the chart supports, all enumerated through the SeriesKind enum. There is a matching seriesKind property of the query parameter for the getTimeSeries method, this helps you disambiguate the kind of the series the chart is trying to load. The query parameter is described in detail in the TypeScript declaration file.

Note on asynchronous behavior

There are a lot of places which require asynchronous (non-blocking) behavior - data fetching, notifications between the host and the chart etc. The chart uses JavaScript standard for the async behavior which is the Promise object, please find more details here.

There are some subtle issues with async code regarding reentrancy, cancellation and so on. Therefore we have developed a small async helper which we use in all relevant places. The helper is exported for you to use (if desired) under the name asyncReady. We use it whenever we need to asynchronously wait for a result of an operation. It makes sure that only one actual async call is performed, regardless of the number of places the async method gets called from. We know this sounds very abstract, please read below for actual examples which should make things clear.

Timeseries

If you are only going to load a single chunk of the timeseries data - versus loading new chunks on demand as user scrolls into the past - the following example is all that you need (adapted from the index.js example code provided in the download):

class ExampleTimeSeries {
constructor(query) {
this.query = query;
this.container = null;
this.loadingOngoing = null;
}
get hasData() {
return this.container !== null;
}
async loadAndParse() {
const resp = await fetch(`./${query.symbol}_Daily.json`);
const data = await resp.json();
this.container = new TimeSeriesContainer([Fields.DateTime, Fields.Close]);
for (let row of data) {
this.container.addDataPoint(Fields.DateTime, new Date(row.date));
this.container.addDataPoint(Fields.Close, row.close);
// ... typically we also add Open, High, Low, Change etc.
}
return true;
}
async ready() {
return asyncReady.call(this, this.loadAndParse);
}
}

The only two relevant methods here are hasData and ready (which is async).

The former is very simple - it returns true when there’s some data and false otherwise.

The latter is very simple if you use the asyncReady helper; please note that we’ve used the JavaScript call facility because we want to preserve this during the execution of the method actually fetching the data which is loadAndParse.

The way the data is retrieved is not imporant - after all, you are free to do it any way you like. What is important is the final result, which is data stored in the TimeSeriesContainer (class provided by us).

The container object is the actual data storage. It’s a simple mapping between a field like Close and the series of values for this field. Every time series container should have at least a date/time field (since all time series are based on time) and one or more data fields like Open, High, Low and Change. You provide the list of fields in the constructor and add new data points using addDataPoint(field, value).

Events

In addition to aggregated trade data like Open, High, Low, Close etc. for some symbols there may be “events” associated with them. These are a lot sparser data points which carry additional information not expressed through the price alone. At the moment, we support 3 kinds of events: dividends, earnings and splits. This is indicated by the presence (or lack) of identically named properties in the events query. For example, to load dividends for Amazon, the query would be { symbol: "AMZN", seriesKind: SeriesKind.Events, dividends: true }

class ExampleEventsSeries {
constructor(query) {
this.query = query;
this.container = null;
}
get hasData() {
return this.container !== null;
}
async loadAndParse() {
const data = await getData(eventsFileName);
const suffix = "T00:00:00";
this.container = new TimeSeriesContainer([
Fields.DateTime,
Fields.Dividends,
Fields.Earnings,
Fields.Splits,
]);
const addValue = (field, row) => {
const matching = field.id === row.kind ? row.value : null;
this.container.addDataPoint(field, matching);
};
data.forEach(row => {
this.container.addDataPoint(Fields.DateTime, new Date(Date.parse(row.date + suffix)));
this.container.fields.filter(f => f !== Fields.DateTime).forEach(f => addValue(f, row));
});
return Promise.resolve(true);
}
async ready() {
return asyncReady.call(this, this.loadAndParse);
}
}

The events’ data is the same as the other time series data, except that all the built-in fields are of String type (please take a look at the field taxonomy). It’s a string and not a number because some events like splits aren’t technically numbers (and we may want to support custom events in the near future).

Metadata

For now, the chart uses metadata only in order to format the values of a given instrument, though you are free to keep other information about the instrument like its exchange, its type and so on. Here’s an example implementation:

class ExampleMetaData {
async ready() {
return true;
}
format(price, field, options) {
return formatPriceWithDecimals(price, 2);
}
}

The example is deliberately oversimplified. In the ready method you will likely do some async work (using our asyncReady helper, very much like we did in the above case of timeseries data) and then the only real work takes place in the format method.

The format accepts three arguments: price to format (a number), a field whose value this is and options which are just hints for the formatting (currently these are Barchart specific and should be ignored).

The above implementation ignores everything but price and uses helper function we provide called formatPriceWithDecimals which does exactly what its name indicates.

The reason this method accepts a field is that it’s often the case that certain fields need to be formatted in a way different from the default formatting for the given instrument. For example, the percent change is never going to be formatted as a fractional number even if the instrument normally us. Also, volume is often shown in abbreviated form, for example 122K instead of 122,000.

Record

A Record is something which stores the quote data, typically updated from a streaming data source.

NOTE The example we provide does not yet show how to implement streaming. Please bear with us as we decouple the implementation from our own internal message passing mechanism. This document will be updated in the next week or so as soon as the modification are introduced.

Here’s an example:

class ExampleRecord {
async ready() {
return true;
}
hasField(field) {
return [Fields.DateTime, Fields.Close].includes(field);
}
getValue(field) {
if (!this.hasValue(field)) return null;
return field === Fields.Close ? 123 : new Date();
}
}

Again a very rudimentary example but shows everything that the chart requires - three methods in total.

The ready method serves exactly the same purpose as above - it tells the caller that we have fetched what we could and are ready to return some data (if there’s any).

The hasField is just a sanity check - every record stores a limited list of fields (typically provided in the constructor); we return true when we are in fact storing a value for the given field.

The getValue logically, returns the current - latest or newest - value for a given field. As streaming updates come in, the previous value of a given field is overwritten.

TBD: a better example of RecordDataSource which demonstrates streaming quotes.