Skip to content

Commit 823e244

Browse files
committed
Improved financial sample
1 parent 7eb0c2c commit 823e244

File tree

3 files changed

+254
-206
lines changed

3 files changed

+254
-206
lines changed

samples/advanced/financial.html

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
<!doctype html>
2+
<html>
3+
4+
<head>
5+
<title>Line Chart</title>
6+
<script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/moment.min.js"></script>
7+
<script src="../../dist/Chart.min.js"></script>
8+
<script src="../utils.js"></script>
9+
<style>
10+
canvas {
11+
-moz-user-select: none;
12+
-webkit-user-select: none;
13+
-ms-user-select: none;
14+
}
15+
</style>
16+
</head>
17+
18+
<body>
19+
<div style="width:1000px">
20+
<p>This example demonstrates a time series scale with custom logic for generating minor and major ticks. Major ticks are bolded</p>
21+
<p>For more specific functionality for financial charts, please see <a href="https://github.com/chartjs/chartjs-chart-financial">chartjs-chart-financial</a></p>
22+
<canvas id="chart1"></canvas>
23+
</div>
24+
<br>
25+
<br>
26+
Chart Type:
27+
<select id="type">
28+
<option value="line">Line</option>
29+
<option value="bar">Bar</option>
30+
</select>
31+
<select id="unit">
32+
<option value="second">Second</option>
33+
<option value="minute">Minute</option>
34+
<option value="hour">Hour</option>
35+
<option value="day" selected>Day</option>
36+
<option value="month">Month</option>
37+
<option value="year">Year</option>
38+
</select>
39+
<button id="update">update</button>
40+
<script>
41+
function isFirstUnitOfPeriod(date, unit, period) {
42+
date = moment(date);
43+
let first = date.clone().startOf(period);
44+
while (first.isoWeekday() > 5) {
45+
first.add(1, 'days');
46+
}
47+
if (unit === 'second' || unit === 'minute' || unit === 'hour') {
48+
first = first.hours(9).minutes(30);
49+
}
50+
return date.isSame(first);
51+
}
52+
53+
// Generate data between the stock market hours of 9:30am - 5pm.
54+
// This method is slow and unoptimized, but in real life we'd be fetching it from the server.
55+
function generateData() {
56+
const unit = document.getElementById('unit').value;
57+
58+
function unitLessThanDay() {
59+
return unit === 'second' || unit === 'minute' || unit === 'hour';
60+
}
61+
62+
function beforeNineThirty(date) {
63+
return date.hour() < 9 || (date.hour() === 9 && date.minute() < 30);
64+
}
65+
66+
// Returns true if outside 9:30am-4pm on a weekday
67+
function outsideMarketHours(date) {
68+
if (date.isoWeekday() > 5) {
69+
return true;
70+
}
71+
if (unitLessThanDay() && (beforeNineThirty(date) || date.hour() > 16)) {
72+
return true;
73+
}
74+
return false;
75+
}
76+
77+
function randomNumber(min, max) {
78+
return Math.random() * (max - min) + min;
79+
}
80+
81+
function randomBar(date, lastClose) {
82+
const open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
83+
const close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
84+
return {
85+
t: date.valueOf(),
86+
y: close
87+
};
88+
}
89+
90+
let date = moment('Jan 01 1990', 'MMM DD YYYY');
91+
const now = moment();
92+
const data = [];
93+
const lessThanDay = unitLessThanDay();
94+
for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
95+
if (outsideMarketHours(date)) {
96+
if (!lessThanDay || !beforeNineThirty(date)) {
97+
date = date.clone().add(date.isoWeekday() >= 5 ? 8 - date.isoWeekday() : 1, 'day');
98+
}
99+
if (lessThanDay) {
100+
date = date.hour(9).minute(30).second(0);
101+
}
102+
}
103+
data.push(randomBar(date, data.length > 0 ? data[data.length - 1].y : 30));
104+
}
105+
106+
return data;
107+
}
108+
109+
const ctx = document.getElementById('chart1').getContext('2d');
110+
ctx.canvas.width = 1000;
111+
ctx.canvas.height = 300;
112+
113+
const color = Chart.helpers.color;
114+
const cfg = {
115+
data: {
116+
datasets: [{
117+
label: 'CHRT - Chart.js Corporation',
118+
backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
119+
borderColor: window.chartColors.red,
120+
data: generateData(),
121+
type: 'line',
122+
pointRadius: 0,
123+
fill: false,
124+
lineTension: 0,
125+
borderWidth: 2
126+
}]
127+
},
128+
options: {
129+
animation: {
130+
duration: 0
131+
},
132+
scales: {
133+
x: {
134+
type: 'time',
135+
distribution: 'series',
136+
offset: true,
137+
ticks: {
138+
major: {
139+
enabled: true,
140+
},
141+
fontStyle: function(context) {
142+
return context.tick.major ? 'bold' : undefined;
143+
},
144+
source: 'labels', // We provided no labels. Generate no ticks. We'll make our own
145+
autoSkip: true,
146+
autoSkipPadding: 75,
147+
maxRotation: 0,
148+
sampleSize: 100
149+
},
150+
// Custom logic that chooses ticks from dataset timestamp by choosing first timestamp in time period
151+
afterBuildTicks: function(scale) {
152+
// Determine units according to our own logic
153+
// Make sure there's at least 10 ticks generated. autoSkip will remove any extras
154+
const units = ['second', 'minute', 'hour', 'day', 'month', 'year'];
155+
const adapter = scale._adapter;
156+
const unit = document.getElementById('unit').value;
157+
let minorUnit = unit;
158+
for (let i = units.indexOf(minorUnit); i < units.length; i++) {
159+
const periods = adapter.diff(scale.max, scale.min, units[i]);
160+
if (periods < 10) {
161+
break;
162+
}
163+
minorUnit = units[i];
164+
}
165+
let majorUnit;
166+
if (units.indexOf(minorUnit) !== units.length - 1) {
167+
majorUnit = units[units.indexOf(minorUnit) + 1];
168+
}
169+
170+
// Generate ticks according to our own logic
171+
const timestamps = scale._cache.all;
172+
173+
function findIndex(ts) {
174+
// Note that we could make this faster by doing a binary search
175+
// However, Chart.helpers.collection._lookup requires key and it's already pretty fast
176+
let result = -1;
177+
for (let i = 0; i < timestamps.length; i++) {
178+
if (timestamps[i] >= ts) {
179+
result = i;
180+
break;
181+
}
182+
}
183+
if (result === 0) {
184+
return isFirstUnitOfPeriod(timestamps[0], unit, minorUnit) ? 0 : 1;
185+
}
186+
return result;
187+
}
188+
189+
// minor ticks
190+
// Calculate the first period of each minor unit, find the next timestamp, and make it a tick
191+
const start = adapter.startOf(scale.min, minorUnit);
192+
const values = new Set();
193+
for (let date = start; date < scale.max; date = adapter.add(date, 1, minorUnit)) {
194+
const index = findIndex(date);
195+
if (index !== -1) {
196+
values.add(timestamps[index]);
197+
}
198+
}
199+
const ticks = Array.from(values, value => ({value}));
200+
201+
// major ticks
202+
for (let i = 0; i < ticks.length; i++) {
203+
if (!majorUnit || isFirstUnitOfPeriod(ticks[i].value, unit, majorUnit)) {
204+
ticks[i].major = true;
205+
}
206+
}
207+
scale.ticks = ticks;
208+
}
209+
},
210+
y: {
211+
type: 'linear',
212+
gridLines: {
213+
drawBorder: false
214+
},
215+
scaleLabel: {
216+
display: true,
217+
labelString: 'Closing price ($)'
218+
}
219+
}
220+
},
221+
tooltips: {
222+
intersect: false,
223+
mode: 'index',
224+
callbacks: {
225+
label: function(tooltipItem, myData) {
226+
let label = myData.datasets[tooltipItem.datasetIndex].label || '';
227+
if (label) {
228+
label += ': ';
229+
}
230+
label += parseFloat(tooltipItem.value).toFixed(2);
231+
return label;
232+
}
233+
}
234+
}
235+
}
236+
};
237+
238+
const chart = new Chart(ctx, cfg);
239+
240+
document.getElementById('update').addEventListener('click', function() {
241+
const type = document.getElementById('type').value;
242+
const dataset = chart.config.data.datasets[0];
243+
dataset.type = type;
244+
dataset.data = generateData();
245+
chart.update();
246+
});
247+
248+
</script>
249+
</body>
250+
251+
</html>

samples/samples.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,6 @@
124124
}, {
125125
title: 'Line (break on 2 day gap)',
126126
path: 'scales/time/line-max-span.html'
127-
}, {
128-
title: 'Time Series',
129-
path: 'scales/time/financial.html'
130127
}, {
131128
title: 'Combo',
132129
path: 'scales/time/combo.html'
@@ -242,6 +239,9 @@
242239
}, {
243240
title: 'Advanced',
244241
items: [{
242+
title: 'Custom minor and major ticks',
243+
path: 'advanced/financial.html'
244+
}, {
245245
title: 'Progress bar',
246246
path: 'advanced/progress-bar.html'
247247
}, {

0 commit comments

Comments
 (0)