/
chartist.bar.js
320 lines (293 loc) · 12.3 KB
/
chartist.bar.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/**
* The bar chart module of Chartist that can be used to draw unipolar or bipolar bar and grouped bar charts.
*
* @module Chartist.Bar
*/
/* global Chartist */
(function(window, document, Chartist){
'use strict';
/**
* This method creates a new bar chart and returns API object that you can use for later changes.
*
* @memberof Chartist.Bar
* @param {String|Node} query A selector query string or directly a DOM element
* @param {Object} data The data object that needs to consist of a labels and a series array
* @param {Object} [options] The options object with options that override the default options. Check the examples for a detailed list.
* @param {Array} [responsiveOptions] Specify an array of responsive option arrays which are a media query and options object pair => [[mediaQueryString, optionsObject],[more...]]
* @return {Object} An object which exposes the API for the created chart
*
* @example
* // These are the default options of the line chart
* var options = {
* // Options for X-Axis
* axisX: {
* // The offset of the labels to the chart area
* offset: 10,
* // If labels should be shown or not
* showLabel: true,
* // If the axis grid should be drawn or not
* showGrid: true,
* // Interpolation function that allows you to intercept the value from the axis label
* labelInterpolationFnc: function(value){return value;}
* },
* // Options for Y-Axis
* axisY: {
* // The offset of the labels to the chart area
* offset: 15,
* // If labels should be shown or not
* showLabel: true,
* // If the axis grid should be drawn or not
* showGrid: true,
* // For the Y-Axis you can set a label alignment property of right or left
* labelAlign: 'right',
* // Interpolation function that allows you to intercept the value from the axis label
* labelInterpolationFnc: function(value){return value;},
* // This value specifies the minimum height in pixel of the scale steps
* scaleMinSpace: 30
* },
* // Specify a fixed width for the chart as a string (i.e. '100px' or '50%')
* width: undefined,
* // Specify a fixed height for the chart as a string (i.e. '100px' or '50%')
* height: undefined,
* // If the line should be drawn or not
* showLine: true,
* // If dots should be drawn or not
* showPoint: true,
* // Specify if the lines should be smoothed (Catmull-Rom-Splines will be used)
* lineSmooth: true,
* // Overriding the natural low of the chart allows you to zoom in or limit the charts lowest displayed value
* low: undefined,
* // Overriding the natural high of the chart allows you to zoom in or limit the charts highest displayed value
* high: undefined,
* // Padding of the chart drawing area to the container element and labels
* chartPadding: 5,
* // Specify the distance in pixel of bars in a group
* seriesBarDistance: 15,
* // Override the class names that get used to generate the SVG structure of the chart
* classNames: {
* chart: 'ct-chart-bar',
* label: 'ct-label',
* series: 'ct-series',
* bar: 'ct-bar',
* point: 'ct-point',
* grid: 'ct-grid',
* vertical: 'ct-vertical',
* horizontal: 'ct-horizontal'
* }
* };
*
* @example
* // Create a simple bar chart
* var data = {
* labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
* series: [
* [5, 2, 4, 2, 0]
* ]
* };
*
* // In the global name space Chartist we call the Bar function to initialize a bar chart. As a first parameter we pass in a selector where we would like to get our chart created and as a second parameter we pass our data object.
* Chartist.Bar('.ct-chart', data);
*
* @example
* // This example creates a bipolar grouped bar chart where the boundaries are limitted to -10 and 10
* Chartist.Bar('.ct-chart', {
* labels: [1, 2, 3, 4, 5, 6, 7],
* series: [
* [1, 3, 2, -5, -3, 1, -6],
* [-5, -2, -4, -1, 2, -3, 1]
* ]
* }, {
* seriesBarDistance: 12,
* low: -10,
* heigh: 10
* });
*
*/
Chartist.Bar = function (query, data, options, responsiveOptions) {
var defaultOptions = {
axisX: {
offset: 10,
showLabel: true,
showGrid: true,
labelInterpolationFnc: Chartist.noop
},
axisY: {
offset: 15,
showLabel: true,
showGrid: true,
labelAlign: 'right',
labelInterpolationFnc: Chartist.noop,
scaleMinSpace: 40
},
width: undefined,
height: undefined,
high: undefined,
low: undefined,
chartPadding: 5,
seriesBarDistance: 15,
classNames: {
chart: 'ct-chart-bar',
label: 'ct-label',
series: 'ct-series',
bar: 'ct-bar',
thin: 'ct-thin',
thick: 'ct-thick',
grid: 'ct-grid',
vertical: 'ct-vertical',
horizontal: 'ct-horizontal'
}
},
optionsProvider,
container = Chartist.querySelector(query),
svg,
eventEmitter = Chartist.EventEmitter();
function createChart(options) {
var xAxisOffset,
yAxisOffset,
seriesGroups = [],
bounds,
normalizedData = Chartist.normalizeDataArray(Chartist.getDataArray(data), data.labels.length);
// Create new svg element
svg = Chartist.createSvg(container, options.width, options.height, options.classNames.chart);
// initialize bounds
bounds = Chartist.getBounds(svg, normalizedData, options, 0);
xAxisOffset = options.axisX.offset;
if (options.axisX.showLabel) {
xAxisOffset += Chartist.calculateLabelOffset(
svg,
data.labels,
[options.classNames.label, options.classNames.horizontal].join(' '),
options.axisX.labelInterpolationFnc,
'height'
);
}
yAxisOffset = options.axisY.offset;
if (options.axisY.showLabel) {
yAxisOffset += Chartist.calculateLabelOffset(
svg,
bounds.values,
[options.classNames.label, options.classNames.horizontal].join(' '),
options.axisY.labelInterpolationFnc,
'width'
);
}
var chartRect = Chartist.createChartRect(svg, options, xAxisOffset, yAxisOffset);
// Start drawing
var labels = svg.elem('g'),
grid = svg.elem('g'),
// Projected 0 point
zeroPoint = Chartist.projectPoint(chartRect, bounds, [0], 0);
Chartist.createXAxis(chartRect, data, grid, labels, options, eventEmitter);
Chartist.createYAxis(chartRect, bounds, grid, labels, yAxisOffset, options, eventEmitter);
// Draw the series
// initialize series groups
for (var i = 0; i < data.series.length; i++) {
// Calculating bi-polar value of index for seriesOffset. For i = 0..4 biPol will be -1.5, -0.5, 0.5, 1.5 etc.
var biPol = i - (data.series.length - 1) / 2,
// Half of the period with between vertical grid lines used to position bars
periodHalfWidth = chartRect.width() / normalizedData[i].length / 2;
seriesGroups[i] = svg.elem('g');
// If the series is an object and contains a name we add a custom attribute
if(data.series[i].name) {
seriesGroups[i].attr({
'series-name': data.series[i].name
}, Chartist.xmlNs.uri);
}
// Use series class from series data or if not set generate one
seriesGroups[i].addClass([
options.classNames.series,
(data.series[i].className || options.classNames.series + '-' + Chartist.alphaNumerate(i))
].join(' '));
for(var j = 0; j < normalizedData[i].length; j++) {
var p = Chartist.projectPoint(chartRect, bounds, normalizedData[i], j),
bar;
// Offset to center bar between grid lines and using bi-polar offset for multiple series
// TODO: Check if we should really be able to add classes to the series. Should be handles with Sass and semantic / specific selectors
p.x += periodHalfWidth + (biPol * options.seriesBarDistance);
bar = seriesGroups[i].elem('line', {
x1: p.x,
y1: zeroPoint.y,
x2: p.x,
y2: p.y
}, options.classNames.bar).attr({
'value': normalizedData[i][j]
}, Chartist.xmlNs.uri);
eventEmitter.emit('draw', {
type: 'bar',
value: normalizedData[i][j],
index: j,
group: seriesGroups[i],
element: bar,
x1: p.x,
y1: zeroPoint.y,
x2: p.x,
y2: p.y
});
}
}
}
// TODO: Currently we need to re-draw the chart on window resize. This is usually very bad and will affect performance.
// This is done because we can't work with relative coordinates when drawing the chart because SVG Path does not
// work with relative positions yet. We need to check if we can do a viewBox hack to switch to percentage.
// See http://mozilla.6506.n7.nabble.com/Specyfing-paths-with-percentages-unit-td247474.html
// Update: can be done using the above method tested here: http://codepen.io/gionkunz/pen/KDvLj
// The problem is with the label offsets that can't be converted into percentage and affecting the chart container
/**
* Updates the chart which currently does a full reconstruction of the SVG DOM
*
* @memberof Chartist.Bar
*/
function update() {
createChart(optionsProvider.currentOptions);
}
/**
* This method can be called on the API object of each chart and will un-register all event listeners that were added to other components. This currently includes a window.resize listener as well as media query listeners if any responsive options have been provided. Use this function if you need to destroy and recreate Chartist charts dynamically.
*
* @memberof Chartist.Bar
*/
function detach() {
window.removeEventListener('resize', update);
optionsProvider.clear();
}
/**
* Use this function to register event handlers. The handler callbacks are synchronous and will run in the main thread rather than the event loop.
*
* @memberof Chartist.Bar
* @param {String} event Name of the event. Check the examples for supported events.
* @param {Function} handler The handler function that will be called when an event with the given name was emitted. This function will receive a data argument which contains event data. See the example for more details.
*/
function on(event, handler) {
eventEmitter.addEventHandler(event, handler);
}
/**
* Use this function to un-register event handlers. If the handler function parameter is omitted all handlers for the given event will be un-registered.
*
* @memberof Chartist.Bar
* @param {String} event Name of the event for which a handler should be removed
* @param {Function} [handler] The handler function that that was previously used to register a new event handler. This handler will be removed from the event handler list. If this parameter is omitted then all event handlers for the given event are removed from the list.
*/
function off(event, handler) {
eventEmitter.removeEventHandler(event, handler);
}
// Initialization of the chart
window.addEventListener('resize', update);
// Obtain current options based on matching media queries (if responsive options are given)
// This will also register a listener that is re-creating the chart based on media changes
optionsProvider = Chartist.optionsProvider(defaultOptions, options, responsiveOptions, eventEmitter);
// Using event loop for first draw to make it possible to register event listeners in the same call stack where
// the chart was created.
setTimeout(function() {
createChart(optionsProvider.currentOptions);
}, 0);
// Public members of the module (revealing module pattern style)
var api = {
version: Chartist.version,
update: update,
on: on,
off: off,
detach: detach
};
container.chartist = api;
return api;
};
}(window, document, Chartist));