Skip to content

Commit

Permalink
feat: added a weekly pattern discontinuity provider
Browse files Browse the repository at this point in the history
* feat: initial skipWeeklyPattern provider

* feat: split weeklyPattern into local and utc

* chore: fixed jest config for d3 module imports

* chore: split functions into own files

* chore: name refactoring

* chore: fixed formatting

* chore: fixed bugs & added tests

* chore: fixed build errors

* chore: using timezone-mock in test

* chore: removed timezone-mock

* chore: removed timezone specific tests

* chore: setting TZ in jest config + refinements

* chore: fixed prettier issue, actioned PR comments

* chore: fixed typo

* chore: corrected check-box value

* chore: fixed linting errors
  • Loading branch information
murcikan-scottlogic authored Jun 21, 2022
1 parent b535374 commit 79f2d1c
Show file tree
Hide file tree
Showing 19 changed files with 1,451 additions and 11 deletions.
21 changes: 21 additions & 0 deletions examples/discontinuous-week-axis/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Discontinuous Axis - Removing Weekly Pattern

This is another example that demonstrates how to render a financial candlestick chart on a discontinuous scale that skips a predefined weekly pattern. Try clicking the checkbox to observe the difference, when checked, the time ranges, where no trading occurs, are removed from the chart.

This example demonstrates how the D3FC discontinuous scale can be used to adapt a D3 time scale adding in discontinuity provider that skips predefined time ranges on any particular day.

for a non-trading pattern:

{
Monday: [["07:45", "08:30"], ["13:20", "19:00"]],
Tuesday: [["07:45", "08:30"], ["13:20", "19:00"]],
Wednesday: [["07:45", "08:30"], ["13:20", "19:00"]],
Thursday: [["07:45", "08:30"], ["13:20", "19:00"]],
Friday: [["07:45", "08:30"], ["13:20", "EOD"]],
Saturday: [["SOD", "EOD"]],
Sunday: [["SOD", "19:00"]]
};

const skipWeeklyPatternScale = fc
.scaleDiscontinuous(d3.scaleTime())
.discontinuityProvider(fc.discontinuitySkipWeeklyPattern(nonTradingHoursPattern));
8 changes: 8 additions & 0 deletions examples/discontinuous-week-axis/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
it('should match the image snapshot', async () => {
await d3fc.loadExample(module);
const image = await page.screenshot({
omitBackground: true
});
expect(image).toMatchImageSnapshot();
await d3fc.saveScreenshot(module, image);
});
23 changes: 23 additions & 0 deletions examples/discontinuous-week-axis/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<script src="../../node_modules/seedrandom/seedrandom.js"></script>
<script>Math.seedrandom('a22ebc7c488a3a47');</script>
<script src="../../node_modules/mockdate/src/mockdate.js"></script>
<script>MockDate.set('2000-01-01', 0);</script>
<script src="../../node_modules/d3/dist/d3.js"></script>
<script src="../../packages/d3fc/build/d3fc.js"></script>
<script src="../index.js"></script>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="../index.css">
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">
</head>

<body>
<label id="checkbox"><input type="checkbox" id="skip"/>skip non-trading periods</label>
<div id="chart"></div>

<script src="index.js"></script>
</body>

</html>
111 changes: 111 additions & 0 deletions examples/discontinuous-week-axis/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const checkbox = document.getElementById('skip');

// define non-trading time ranges for any day of the week
const nonTradingHoursPattern = {
Monday: [
['07:45', '08:30'],
['13:20', '19:00']
],
Tuesday: [
['07:45', '08:30'],
['13:20', '19:00']
],
Wednesday: [
['07:45', '08:30'],
['13:20', '19:00']
],
Thursday: [
['07:45', '08:30'],
['13:20', '19:00']
],
Friday: [
['07:45', '08:30'],
['13:20', 'EOD']
],
Saturday: [['SOD', 'EOD']],
Sunday: [['SOD', '19:00']]
};

// create discontinous date range
const dates = d3.timeMinute
.range(new Date(2018, 0, 1), new Date(2018, 0, 8))
.filter(dt => {
const dow = dt.getDay();
switch (dow) {
case 1:
case 2:
case 3:
case 4:
case 5:
if (
(dt.getHours() === 7 && dt.getMinutes() >= 45) ||
(dt.getHours() === 8 && dt.getMinutes() < 30) ||
(dt.getHours() === 13 && dt.getMinutes() >= 20) ||
(dt.getHours() >= 14 && dow !== 5 && dt.getHours() < 19) ||
(dt.getHours() >= 14 && dow === 5)
) {
return false;
}
return true;
case 6:
return false;
case 0:
return dt.getHours() >= 19;
}
});

// create some test data that skips weekends
const data = fc.randomFinancial()(dates.length);

for (let i = 0; i < dates.length; i++) {
// console.log(`Changing: ${data[i].date} to ${dates[i]}`);
data[i].date = dates[i];
}

// use the date to determine the x extent, padding by one day at each end
const xExtent = fc
.extentDate()
.accessors([d => d.date])
.padUnit('domain')
.pad([60 * 1000, 60 * 1000]);

// compute the y extent from the high / low values, padding by 10%
const yExtent = fc
.extentLinear()
.accessors([d => d.high, d => d.low])
.pad([0.1, 0.1]);

// Create the gridlines and series
const gridlines = fc.annotationSvgGridline();
const candlestick = fc.seriesSvgCandlestick();

// add them to the chart via a multi-series
const multi = fc.seriesSvgMulti().series([gridlines, candlestick]);

// adapt the d3 time scale in a discontinuous scale that skips weekends
const skipWeeklyPatternScale = fc
.scaleDiscontinuous(d3.scaleTime())
.discontinuityProvider(
fc.discontinuitySkipWeeklyPattern(nonTradingHoursPattern)
);

function renderChart() {
// create a chart
const chart = fc
.chartCartesian(
checkbox.checked ? skipWeeklyPatternScale : d3.scaleTime(),
d3.scaleLinear()
)
.xDomain(xExtent(data))
.yDomain(yExtent(data))
.xTicks(30)
.svgPlotArea(multi);

// render the chart
d3.select('#chart')
.datum(data)
.call(chart);
}

renderChart();
checkbox.addEventListener('click', renderChart);
Binary file added examples/discontinuous-week-axis/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions examples/discontinuous-week-axis/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#checkbox {
position: absolute;
top: 10px;
right: 10px;
font-size: 1.2em;
}
2 changes: 2 additions & 0 deletions packages/d3fc-discontinuous-scale/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export { default as discontinuitySkipWeekends } from './src/discontinuity/skipWe
export { default as discontinuitySkipUtcWeekends } from './src/discontinuity/skipUtcWeekends';
export { default as discontinuityIdentity } from './src/discontinuity/identity';
export { default as discontinuityRange } from './src/discontinuity/range';
export { default as discontinuitySkipWeeklyPattern } from './src/discontinuity/skipWeeklyPattern';
export { default as discontinuitySkipUtcWeeklyPattern } from './src/discontinuity/skipUtcWeeklyPattern';
199 changes: 188 additions & 11 deletions packages/d3fc-discontinuous-scale/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { utcDay, utcMillisecond } from 'd3-time';
import { base } from './skipWeeklyPattern';
import { dateTimeUtility } from './skipWeeklyPattern/dateTimeUtility';

export const utcDateTimeUtility = dateTimeUtility(
(date, hh, mm, ss, ms) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), hh, mm, ss, ms)),
date => date.getUTCDay(),
date => [date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()],
utcDay,
utcMillisecond
);

export default (nonTradingUtcHoursPattern) => base(nonTradingUtcHoursPattern, utcDateTimeUtility);
Loading

0 comments on commit 79f2d1c

Please sign in to comment.