]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Improved financial sample (#6998)
authorBen McCann <322311+benmccann@users.noreply.github.com>
Sun, 26 Jan 2020 20:35:53 +0000 (12:35 -0800)
committerEvert Timberg <evert.timberg+github@gmail.com>
Sun, 26 Jan 2020 20:35:53 +0000 (15:35 -0500)
* Improved financial sample
* Switch from adapter to moment
* Use data instead of cached timestamps

samples/advanced/financial.html [new file with mode: 0644]
samples/samples.js
samples/scales/time/financial.html [deleted file]

diff --git a/samples/advanced/financial.html b/samples/advanced/financial.html
new file mode 100644 (file)
index 0000000..64a4265
--- /dev/null
@@ -0,0 +1,251 @@
+<!doctype html>
+<html>
+
+<head>
+       <title>Line Chart</title>
+       <script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/moment.min.js"></script>
+       <script src="../../dist/Chart.min.js"></script>
+       <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>
+               function isFirstUnitOfPeriod(date, unit, period) {
+                       let first = date.clone().startOf(period);
+                       while (first.isoWeekday() > 5) {
+                               first.add(1, 'days');
+                       }
+                       if (unit === 'second' || unit === 'minute' || unit === 'hour') {
+                               first = first.hours(9).minutes(30);
+                       }
+                       return date.isSame(first);
+               }
+
+               // Generate data between the stock market hours of 9:30am - 5pm.
+               // 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) {
+                               return date.hour() < 9 || (date.hour() === 9 && date.minute() < 30);
+                       }
+
+                       // Returns true if outside 9:30am-4pm on a weekday
+                       function outsideMarketHours(date) {
+                               if (date.isoWeekday() > 5) {
+                                       return true;
+                               }
+                               if (unitLessThanDay() && (beforeNineThirty(date) || date.hour() > 16)) {
+                                       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 {
+                                       t: date.valueOf(),
+                                       y: close
+                               };
+                       }
+
+                       let date = moment('Jan 01 1990', 'MMM DD YYYY');
+                       const now = moment();
+                       const data = [];
+                       const lessThanDay = unitLessThanDay();
+                       for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
+                               if (outsideMarketHours(date)) {
+                                       if (!lessThanDay || !beforeNineThirty(date)) {
+                                               date = date.clone().add(date.isoWeekday() >= 5 ? 8 - date.isoWeekday() : 1, 'day');
+                                       }
+                                       if (lessThanDay) {
+                                               date = date.hour(9).minute(30).second(0);
+                                       }
+                               }
+                               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,
+                                       lineTension: 0,
+                                       borderWidth: 2
+                               }]
+                       },
+                       options: {
+                               animation: {
+                                       duration: 0
+                               },
+                               scales: {
+                                       x: {
+                                               type: 'time',
+                                               distribution: 'series',
+                                               offset: true,
+                                               ticks: {
+                                                       major: {
+                                                               enabled: true,
+                                                       },
+                                                       fontStyle: function(context) {
+                                                               return context.tick.major ? 'bold' : undefined;
+                                                       },
+                                                       source: 'labels', // We provided no labels. Generate no ticks. We'll make our own
+                                                       autoSkip: true,
+                                                       autoSkipPadding: 75,
+                                                       maxRotation: 0,
+                                                       sampleSize: 100
+                                               },
+                                               // Custom logic that chooses ticks from dataset timestamp by choosing first timestamp in time period
+                                               afterBuildTicks: function(scale) {
+                                                       // Determine units according to our own logic
+                                                       // Make sure there's at least 10 ticks generated. autoSkip will remove any extras
+                                                       const units = ['second', 'minute', 'hour', 'day', 'month', 'year'];
+                                                       const duration = moment.duration(moment(scale.max).diff(scale.min));
+                                                       const unit = document.getElementById('unit').value;
+                                                       let minorUnit = unit;
+                                                       for (let i = units.indexOf(minorUnit); i < units.length; i++) {
+                                                               const periods = duration.as(units[i]);
+                                                               if (periods < 10) {
+                                                                       break;
+                                                               }
+                                                               minorUnit = units[i];
+                                                       }
+                                                       let majorUnit;
+                                                       if (units.indexOf(minorUnit) !== units.length - 1) {
+                                                               majorUnit = units[units.indexOf(minorUnit) + 1];
+                                                       }
+
+                                                       // Generate ticks according to our own logic
+                                                       const data = scale.chart.data.datasets[0].data;
+                                                       const firstDate = moment(data[0].t);
+
+                                                       function findIndex(ts) {
+                                                               // Note that we could make this faster by doing a binary search
+                                                               // However, Chart.helpers.collection._lookup requires key and it's already pretty fast
+                                                               let result = -1;
+                                                               for (let i = 0; i < data.length; i++) {
+                                                                       if (data[i].t >= ts) {
+                                                                               result = i;
+                                                                               break;
+                                                                       }
+                                                               }
+                                                               if (result === 0) {
+                                                                       return isFirstUnitOfPeriod(firstDate, unit, minorUnit) ? 0 : 1;
+                                                               }
+                                                               return result;
+                                                       }
+
+                                                       // minor ticks
+                                                       let start = moment(scale.min).startOf(minorUnit);
+                                                       const end = moment(scale.max);
+                                                       const values = new Set();
+                                                       for (let date = start; date.isBefore(end); date.add(1, minorUnit)) {
+                                                               const index = findIndex(+date);
+                                                               if (index !== -1) {
+                                                                       values.add(data[index].t);
+                                                               }
+                                                       }
+                                                       const ticks = Array.from(values, value => ({value}));
+
+                                                       // major ticks
+                                                       for (let i = 0; i < ticks.length; i++) {
+                                                               if (!majorUnit || isFirstUnitOfPeriod(moment(ticks[i].value), unit, majorUnit)) {
+                                                                       ticks[i].major = true;
+                                                               }
+                                                       }
+                                                       scale.ticks = ticks;
+                                               }
+                                       },
+                                       y: {
+                                               type: 'linear',
+                                               gridLines: {
+                                                       drawBorder: false
+                                               },
+                                               scaleLabel: {
+                                                       display: true,
+                                                       labelString: 'Closing price ($)'
+                                               }
+                                       }
+                               },
+                               tooltips: {
+                                       intersect: false,
+                                       mode: 'index',
+                                       callbacks: {
+                                               label: function(tooltipItem, myData) {
+                                                       let label = myData.datasets[tooltipItem.datasetIndex].label || '';
+                                                       if (label) {
+                                                               label += ': ';
+                                                       }
+                                                       label += parseFloat(tooltipItem.value).toFixed(2);
+                                                       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>
index 518b7696c5c936c662479d6a0a5a869f91b824b3..b93cc773e562f3ac0ecbf8742b8c2973dfab0e4c 100644 (file)
                }, {
                        title: 'Line (break on 2 day gap)',
                        path: 'scales/time/line-max-span.html'
-               }, {
-                       title: 'Time Series',
-                       path: 'scales/time/financial.html'
                }, {
                        title: 'Combo',
                        path: 'scales/time/combo.html'
        }, {
                title: 'Advanced',
                items: [{
+                       title: 'Custom minor and major ticks',
+                       path: 'advanced/financial.html'
+               }, {
                        title: 'Progress bar',
                        path: 'advanced/progress-bar.html'
                }, {
diff --git a/samples/scales/time/financial.html b/samples/scales/time/financial.html
deleted file mode 100644 (file)
index 7024682..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-<!doctype html>
-<html>
-
-<head>
-       <title>Line Chart</title>
-       <script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/moment.min.js"></script>
-       <script src="../../../dist/Chart.min.js"></script>
-       <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 by drawing a financial line chart using just the core library. 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>
-               function generateData() {
-                       var unit = document.getElementById('unit').value;
-
-                       function unitLessThanDay() {
-                               return unit === 'second' || unit === 'minute' || unit === 'hour';
-                       }
-
-                       function beforeNineThirty(date) {
-                               return date.hour() < 9 || (date.hour() === 9 && date.minute() < 30);
-                       }
-
-                       // Returns true if outside 9:30am-4pm on a weekday
-                       function outsideMarketHours(date) {
-                               if (date.isoWeekday() > 5) {
-                                       return true;
-                               }
-                               if (unitLessThanDay() && (beforeNineThirty(date) || date.hour() > 16)) {
-                                       return true;
-                               }
-                               return false;
-                       }
-
-                       function randomNumber(min, max) {
-                               return Math.random() * (max - min) + min;
-                       }
-
-                       function randomBar(date, lastClose) {
-                               var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
-                               var close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
-                               return {
-                                       t: date.valueOf(),
-                                       y: close
-                               };
-                       }
-
-                       var date = moment('Jan 01 1990', 'MMM DD YYYY');
-                       var now = moment();
-                       var data = [];
-                       var lessThanDay = unitLessThanDay();
-                       for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
-                               if (outsideMarketHours(date)) {
-                                       if (!lessThanDay || !beforeNineThirty(date)) {
-                                               date = date.clone().add(date.isoWeekday() >= 5 ? 8 - date.isoWeekday() : 1, 'day');
-                                       }
-                                       if (lessThanDay) {
-                                               date = date.hour(9).minute(30).second(0);
-                                       }
-                               }
-                               data.push(randomBar(date, data.length > 0 ? data[data.length - 1].y : 30));
-                       }
-
-                       return data;
-               }
-
-               var ctx = document.getElementById('chart1').getContext('2d');
-               ctx.canvas.width = 1000;
-               ctx.canvas.height = 300;
-
-               var color = Chart.helpers.color;
-               var 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,
-                                       lineTension: 0,
-                                       borderWidth: 2
-                               }]
-                       },
-                       options: {
-                               animation: {
-                                       duration: 0
-                               },
-                               scales: {
-                                       x: {
-                                               type: 'time',
-                                               distribution: 'series',
-                                               offset: true,
-                                               ticks: {
-                                                       major: {
-                                                               enabled: true,
-                                                       },
-                                                       fontStyle: function(context) {
-                                                               return context.tick.major ? 'bold' : undefined;
-                                                       },
-                                                       source: 'data',
-                                                       autoSkip: true,
-                                                       autoSkipPadding: 75,
-                                                       maxRotation: 0,
-                                                       sampleSize: 100
-                                               },
-                                               afterBuildTicks: function(scale) {
-                                                       var majorUnit = scale._majorUnit;
-                                                       var ticks = scale.ticks;
-                                                       var firstTick = ticks[0];
-                                                       var i, ilen, val, tick, currMajor, lastMajor;
-
-                                                       val = moment(ticks[0].value);
-                                                       if ((majorUnit === 'minute' && val.second() === 0)
-                                                                       || (majorUnit === 'hour' && val.minute() === 0)
-                                                                       || (majorUnit === 'day' && val.hour() === 9)
-                                                                       || (majorUnit === 'month' && val.date() <= 3 && val.isoWeekday() === 1)
-                                                                       || (majorUnit === 'year' && val.month() === 0)) {
-                                                               firstTick.major = true;
-                                                       } else {
-                                                               firstTick.major = false;
-                                                       }
-                                                       lastMajor = val.get(majorUnit);
-
-                                                       for (i = 1, ilen = ticks.length; i < ilen; i++) {
-                                                               tick = ticks[i];
-                                                               val = moment(tick.value);
-                                                               currMajor = val.get(majorUnit);
-                                                               tick.major = currMajor !== lastMajor;
-                                                               lastMajor = currMajor;
-                                                       }
-                                               }
-                                       },
-                                       y: {
-                                               type: 'linear',
-                                               gridLines: {
-                                                       drawBorder: false
-                                               },
-                                               scaleLabel: {
-                                                       display: true,
-                                                       labelString: 'Closing price ($)'
-                                               }
-                                       }
-                               },
-                               tooltips: {
-                                       intersect: false,
-                                       mode: 'index',
-                                       callbacks: {
-                                               label: function(tooltipItem, myData) {
-                                                       var label = myData.datasets[tooltipItem.datasetIndex].label || '';
-                                                       if (label) {
-                                                               label += ': ';
-                                                       }
-                                                       label += parseFloat(tooltipItem.value).toFixed(2);
-                                                       return label;
-                                               }
-                                       }
-                               }
-                       }
-               };
-
-               var chart = new Chart(ctx, cfg);
-
-               document.getElementById('update').addEventListener('click', function() {
-                       var type = document.getElementById('type').value;
-                       var dataset = chart.config.data.datasets[0];
-                       dataset.type = type;
-                       dataset.data = generateData();
-                       chart.update();
-               });
-
-       </script>
-</body>
-
-</html>