2020-01-26 21:35:53 +01:00
<!doctype html>
< html >
< head >
< title > Line Chart< / title >
2020-05-25 22:54:22 +02:00
< script src = "../../dist/chart.min.js" > < / script >
2020-06-29 13:51:05 +02:00
< script src = "https://cdn.jsdelivr.net/npm/luxon@1.24.1" > < / script >
< script src = "https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@0.2.1" > < / script >
2020-01-26 21:35:53 +01:00
< script src = "../utils.js" > < / script >
< style >
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
< / style >
< / head >
< body >
< div style = "width:1000px" >
< p > This example demonstrates a time series scale with custom logic for generating minor and major ticks. Major ticks are bolded< / p >
< p > For more specific functionality for financial charts, please see < a href = "https://github.com/chartjs/chartjs-chart-financial" > chartjs-chart-financial< / a > < / p >
< canvas id = "chart1" > < / canvas >
< / div >
< br >
< br >
Chart Type:
< select id = "type" >
< option value = "line" > Line< / option >
< option value = "bar" > Bar< / option >
< / select >
< select id = "unit" >
< option value = "second" > Second< / option >
< option value = "minute" > Minute< / option >
< option value = "hour" > Hour< / option >
< option value = "day" selected > Day< / option >
< option value = "month" > Month< / option >
< option value = "year" > Year< / option >
< / select >
< button id = "update" > update< / button >
< script >
2020-07-17 15:19:20 +02:00
// Generate data between the stock market hours of 9:30am - 4pm.
2020-01-26 21:35:53 +01:00
// This method is slow and unoptimized, but in real life we'd be fetching it from the server.
function generateData() {
const unit = document.getElementById('unit').value;
function unitLessThanDay() {
return unit === 'second' || unit === 'minute' || unit === 'hour';
}
function beforeNineThirty(date) {
2020-02-28 01:40:31 +01:00
return date.hour < 9 | | ( date . hour = == 9 & & date . minute < 30 ) ;
2020-01-26 21:35:53 +01:00
}
2020-02-08 14:10:20 +01:00
function after4pm(date) {
2020-07-17 15:19:20 +02:00
return date.hour >= 16;
2020-02-08 14:10:20 +01:00
}
2020-01-26 21:35:53 +01:00
// Returns true if outside 9:30am-4pm on a weekday
function outsideMarketHours(date) {
2020-02-28 01:40:31 +01:00
if (date.weekday > 5) {
2020-01-26 21:35:53 +01:00
return true;
}
2020-02-08 14:10:20 +01:00
if (unitLessThanDay() & & (beforeNineThirty(date) || after4pm(date))) {
2020-01-26 21:35:53 +01:00
return true;
}
return false;
}
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomBar(date, lastClose) {
const open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
const close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
return {
2020-02-28 01:40:31 +01:00
x: date.valueOf(),
2020-01-26 21:35:53 +01:00
y: close
};
}
2020-02-28 01:40:31 +01:00
let date = luxon.DateTime.fromFormat('Jan 01 1990', 'LLL dd yyyy');
const now = luxon.DateTime.local();
2020-01-26 21:35:53 +01:00
const data = [];
const lessThanDay = unitLessThanDay();
2020-02-28 01:40:31 +01:00
const plus = {};
plus[unit] = 1;
for (; data.length < 600 & & date < now ; date = date.plus(plus).startOf(unit)) {
2020-01-26 21:35:53 +01:00
if (outsideMarketHours(date)) {
if (!lessThanDay || !beforeNineThirty(date)) {
2020-02-28 01:40:31 +01:00
date = date.plus({day: date.weekday >= 5 ? 8 - date.weekday : 1});
2020-01-26 21:35:53 +01:00
}
if (lessThanDay) {
2020-02-28 01:40:31 +01:00
date = date.set({hour: 9, minute: 30});
2020-01-26 21:35:53 +01:00
}
}
data.push(randomBar(date, data.length > 0 ? data[data.length - 1].y : 30));
}
return data;
}
const ctx = document.getElementById('chart1').getContext('2d');
ctx.canvas.width = 1000;
ctx.canvas.height = 300;
const color = Chart.helpers.color;
const cfg = {
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
borderColor: window.chartColors.red,
data: generateData(),
type: 'line',
pointRadius: 0,
fill: false,
2020-10-31 13:54:14 +01:00
tension: 0,
2020-01-26 21:35:53 +01:00
borderWidth: 2
}]
},
options: {
animation: {
duration: 0
},
scales: {
x: {
2020-06-29 13:51:05 +02:00
type: 'timeseries',
2020-01-26 21:35:53 +01:00
offset: true,
ticks: {
major: {
enabled: true,
},
2020-05-21 23:07:06 +02:00
font: function(context) {
2020-06-29 13:51:05 +02:00
return context.tick & & context.tick.major ? {style: 'bold'} : undefined;
2020-01-26 21:35:53 +01:00
},
2020-02-08 14:10:20 +01:00
source: 'data',
2020-01-26 21:35:53 +01:00
autoSkip: true,
autoSkipPadding: 75,
maxRotation: 0,
sampleSize: 100
},
2020-02-08 14:10:20 +01:00
// Custom logic that chooses major ticks by first timestamp in time period
// E.g. if March 1 & 2 are missing from dataset because they're weekends, we pick March 3 to be beginning of month
2020-01-26 21:35:53 +01:00
afterBuildTicks: function(scale) {
2020-06-29 13:51:05 +02:00
const majorUnit = scale._majorUnit;
2020-02-08 14:10:20 +01:00
const ticks = scale.ticks;
2020-06-29 13:51:05 +02:00
const firstTick = ticks[0];
let val = luxon.DateTime.fromMillis(ticks[0].value);
if ((majorUnit === 'minute' & & val.second === 0)
|| (majorUnit === 'hour' & & val.minute === 0)
|| (majorUnit === 'day' & & val.hour === 9)
|| (majorUnit === 'month' & & val.day < = 3 & & val.weekday === 1)
|| (majorUnit === 'year' & & val.month === 1)) {
firstTick.major = true;
} else {
firstTick.major = false;
}
let lastMajor = val.get(majorUnit);
for (let i = 1; i < ticks.length ; i + + ) {
const tick = ticks[i];
val = luxon.DateTime.fromMillis(tick.value);
const currMajor = val.get(majorUnit);
tick.major = currMajor !== lastMajor;
lastMajor = currMajor;
2020-01-26 21:35:53 +01:00
}
scale.ticks = ticks;
}
},
y: {
type: 'linear',
gridLines: {
drawBorder: false
},
scaleLabel: {
display: true,
labelString: 'Closing price ($)'
}
}
},
tooltips: {
intersect: false,
mode: 'index',
callbacks: {
2020-07-13 00:26:13 +02:00
label: function(context) {
let label = context.dataset.label || '';
2020-01-26 21:35:53 +01:00
if (label) {
label += ': ';
}
2020-07-17 15:19:20 +02:00
label += context.dataPoint.y.toFixed(2);
2020-01-26 21:35:53 +01:00
return label;
}
}
}
}
};
const chart = new Chart(ctx, cfg);
document.getElementById('update').addEventListener('click', function() {
const type = document.getElementById('type').value;
const dataset = chart.config.data.datasets[0];
dataset.type = type;
dataset.data = generateData();
chart.update();
});
< / script >
< / body >
< / html >