Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zoom improvements #269

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
sudo: false
dist: xenial
language: node_js
node_js:
- 6.9
- 10.15.3
addons:
firefox: "50.0.2"
firefox: latest
cache:
directories:
- node_modules
Expand All @@ -14,8 +15,8 @@ install:
- npm install
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
script:
- make test
services:
- xvfb
Busteren marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

### 1.4.0 (Unreleased)
Busteren marked this conversation as resolved.
Show resolved Hide resolved

* [#262](https://github.com/marmelab/EventDrops/pull/262): Fixed issue where right bound text would disappear.
Busteren marked this conversation as resolved.
Show resolved Hide resolved
* [#259](https://github.com/marmelab/EventDrops/pull/259): Fixed issue where color was flickering and global was undefined.

### 1.1.x

**1.1.2:**
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ In addition to this configuration object, it also exposes some public members al
* **scale()** provides the horizontal scale, allowing you to retrieve bounding dates thanks to `.scale().domain()`,
* **filteredData()** returns an object with both `data` and `fullData` keys containing respectively bounds filtered data and full dataset.
* **draw(config, scale)** redraws chart using given configuration and `d3.scaleTime` scale
* **zoomToDomain(domain, duration = 0, delay = 0, ease = d3.easeLinear)** programmatically zooms to domain, where domain is `[date, date]` (leftmost date, rightmost date). Ignores restrictPan modifier (default D3 behaviour). By default there is no transition as duration is 0, however this can be tweaked to allow for a more visual appealing zoom.
Busteren marked this conversation as resolved.
Show resolved Hide resolved
* **destroy()** execute this function before to removing the chart from DOM. It prevents some memory leaks due to event listeners.
* **currentBreakpointLabel** returns current breakpoint (for instance `small`) among a [list of breakpoints](./docs/configuration.md#breakpoints).

Expand Down
16 changes: 14 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ const chart = eventDrops({ d3 });

If you use EventDrops without any module bundler, just include D3 script before EventDrops, and everything should work out of the box.

## id

_Default: '' (not added to svg)_
Busteren marked this conversation as resolved.
Show resolved Hide resolved

Convince to add id to the SVG element. This is especially useful if connecting to EventDrops chart together with zoom (through `zoomToDomain`).

## locale

_Default: English locale_
Expand Down Expand Up @@ -345,7 +351,13 @@ _Default: Infinity_

This parameter configures the maximum zoom level available. Set it to a lower value to prevent your users from zooming in too deeply.

### numberDisplayedTicks
### restrictPan

_Default: false_

If set to `true` will restrict panning (dragging behaviour) to the initial date range. If minimumZoom is set to less than 1, the date range can be zoomed out be larger than the initial. However, after the zoom is less than 1, the pan behaviour is disabled.

## numberDisplayedTicks

\_Default:

Expand All @@ -362,7 +374,7 @@ const chart = eventDrops({

When reducing chart width, we need to display less labels on the horizontal axis to keep a readable chart. This parameter aims to solve the issue. Hence, on smallest devices, it displays only 3 labels by default at the same time.

### breakpoints
## breakpoints

\_Default:

Expand Down
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import enLocale from 'd3-time-format/locale/en-US.json';

export default d3 => ({
id: '',
locale: enLocale,
metaballs: {
blurDeviation: 10,
Expand Down Expand Up @@ -59,6 +60,7 @@ export default d3 => ({
onZoomEnd: null,
minimumScale: 0,
maximumScale: Infinity,
restrictPan: false,
},
numberDisplayedTicks: {
small: 3,
Expand Down
67 changes: 58 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import bounds from './bounds';
import defaultConfiguration from './config';
import dropLine from './dropLine';
import zoom from './zoom';
import { getDomainTransform } from './zoom';
import { addMetaballsDefs } from './metaballs';

import './style.css';
Expand All @@ -29,6 +30,7 @@ export default ({
);

const {
id,
drops,
zoom: zoomConfig,
drop: { onClick, onMouseOut, onMouseOver },
Expand Down Expand Up @@ -62,20 +64,59 @@ export default ({
.attr('width', width)
.classed('event-drop-chart', true);

const height = parseFloat(svg.style('height'));

if (id) {
svg.attr('id', id);
}

if (zoomConfig) {
svg.call(zoom(d3, svg, config, xScale, draw, getEvent));
const zoomObject = d3.zoom();
svg.call(
zoom(
d3,
svg,
config,
zoomObject,
xScale,
draw,
getEvent,
width,
height
)
);

chart._zoomToDomain = (domain, duration, delay, ease) => {
const zoomIdentity = getDomainTransform(
d3,
config,
zoomObject,
domain,
xScale,
width
);
svg
.transition()
.ease(ease)
.delay(delay)
.duration(duration)
.call(zoomObject.transform, zoomIdentity);
};
}

if (metaballs) {
svg.call(addMetaballsDefs(config));
}

svg.merge(root).attr(
'height',
d => (d.length + 1) * lineHeight + margin.top + margin.bottom
);
svg
.merge(root)
.attr(
'height',
d => (d.length + 1) * lineHeight + margin.top + margin.bottom
);

svg.append('g')
svg
.append('g')
.classed('viewport', true)
.attr('transform', `translate(${margin.left},${margin.top})`)
.call(draw(config, xScale));
Expand All @@ -90,15 +131,23 @@ export default ({

chart.scale = () => chart._scale;
chart.filteredData = () => chart._filteredData;
chart.zoomToDomain = (
domain,
duration = 0,
delay = 0,
ease = d3.easeLinear
) => {
if (chart._zoomToDomain) {
Busteren marked this conversation as resolved.
Show resolved Hide resolved
chart._zoomToDomain(domain, duration, delay, ease);
}
};
chart.destroy = (callback = () => {}) => {
global.removeEventListener('resize', chart._initialize, true);
callback();
};

const draw = (config, scale) => selection => {
const {
drop: { date: dropDate },
} = config;
const { drop: { date: dropDate } } = config;

const dateBounds = scale.domain().map(d => new Date(d));
const filteredData = selection.data().map(dataSet => {
Expand Down
34 changes: 34 additions & 0 deletions src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ describe('EventDrops', () => {
resetDom();
});

it('should add id if present in config', () => {
const config = {
...defaultConfig,
id: 'test_id',
};

const chart = EventDrops(config);

const root = d3.select('div').data([[{ data: [] }]]);

root.call(chart);
const svg = document.querySelector('svg');

expect(svg.attributes.id.value).toEqual('test_id');
});

it('should enable zoom if and only if zoomConfig is not falsy', () => {
const test = (zoomConfig, shouldZoomBeCalled) => {
resetDom();
Expand Down Expand Up @@ -60,6 +76,24 @@ describe('EventDrops', () => {
]);
});

// it('should zoom to correct domain on zoomToDomain', () => {
Busteren marked this conversation as resolved.
Show resolved Hide resolved
// const chart = EventDrops({
// zoom: {},
// range: {
// start: new Date('2010-01-01'),
// end: new Date('2011-01-01'),
// },
// });
//
// const root = d3.select('div').data([[{ data: [] }]]);
// root.call(chart);
//
// const domain = [new Date('2010-04-01'), new Date('2010-09-01')];
// chart.zoomToDomain(domain);
//
// expect(chart.scale().domain()).toEqual(domain);
// });

it('should allow to select custom data properties', () => {
const chart = EventDrops({
range: {
Expand Down
3 changes: 1 addition & 2 deletions src/isAfter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ import dateIsAfter from 'date-fns/is_after';
export const isAfter = (date, dateBounds) => {
const endingDate = Math.max(...dateBounds);

// @TODO: remove the `new Date()` constructor in the next major version: we need to force it at configuration level.
return dateIsAfter(new Date(date), endingDate);
Busteren marked this conversation as resolved.
Show resolved Hide resolved
return dateIsAfter(date, endingDate);
};
20 changes: 10 additions & 10 deletions src/isAfter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ describe('isAfter', () => {
expect(isAfter(date, dateRange)).toBe(expectedResult);
};

test('2018-05-19', true);
test('2018-05-01', false);
test('2018-04-19', false);
test('2018-04-01', false);
test('2018-01-01', false);
test(new Date('2018-05-19'), true);
test(new Date('2018-05-01'), false);
test(new Date('2018-04-19'), false);
test(new Date('2018-04-01'), false);
test(new Date('2018-01-01'), false);
});

it('should return true if date is after given reverse date range (start date older than end date)', () => {
Expand All @@ -20,10 +20,10 @@ describe('isAfter', () => {
expect(isAfter(date, dateRange)).toBe(expectedResult);
};

test('2018-05-19', true);
test('2018-05-01', false);
test('2018-04-19', false);
test('2018-04-01', false);
test('2018-01-01', false);
test(new Date('2018-05-19'), true);
test(new Date('2018-05-01'), false);
test(new Date('2018-04-19'), false);
test(new Date('2018-04-01'), false);
test(new Date('2018-01-01'), false);
});
});
3 changes: 1 addition & 2 deletions src/isBefore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ import dateIsBefore from 'date-fns/is_before';
export const isBefore = (date, dateBounds) => {
const startingDate = Math.min(...dateBounds);

// @TODO: remove the `new Date()` constructor in the next major version: we need to force it at configuration level.
return dateIsBefore(new Date(date), startingDate);
Busteren marked this conversation as resolved.
Show resolved Hide resolved
return dateIsBefore(date, startingDate);
};
20 changes: 10 additions & 10 deletions src/isBefore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ describe('isBefore', () => {
expect(isBefore(date, dateRange)).toBe(expectedResult);
};

test('2018-01-01', true);
test('2018-05-19', false);
test('2018-05-01', false);
test('2018-04-19', false);
test('2018-04-01', false);
test(new Date('2018-01-01'), true);
test(new Date('2018-05-19'), false);
test(new Date('2018-05-01'), false);
test(new Date('2018-04-19'), false);
test(new Date('2018-04-01'), false);
});

it('should return true if date is before given reverse date range (start date older than end date)', () => {
Expand All @@ -20,10 +20,10 @@ describe('isBefore', () => {
expect(isBefore(date, dateRange)).toBe(expectedResult);
};

test('2018-01-01', true);
test('2018-05-19', false);
test('2018-05-01', false);
test('2018-04-19', false);
test('2018-04-01', false);
test(new Date('2018-01-01'), true);
test(new Date('2018-05-19'), false);
test(new Date('2018-05-01'), false);
test(new Date('2018-04-19'), false);
test(new Date('2018-04-01'), false);
});
});
3 changes: 1 addition & 2 deletions src/withinRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ export const withinRange = (date, dateBounds) => {
const startingDate = Math.min(...dateBounds);
const endingDate = Math.max(...dateBounds);

// @TODO: remove the `new Date()` constructor in the next major version: we need to force it at configuration level.
return isWithinRange(new Date(date), startingDate, endingDate);
return isWithinRange(date, startingDate, endingDate);
Busteren marked this conversation as resolved.
Show resolved Hide resolved
};
16 changes: 8 additions & 8 deletions src/withinRange.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ describe('withinRange', () => {
expect(withinRange(date, dateRange)).toBe(expectedResult);
};

test('2018-04-19', true);
test('2018-04-01', true);
test('2018-05-01', true);
test('2018-05-19', false);
test(new Date('2018-04-19'), true);
test(new Date('2018-04-01'), true);
test(new Date('2018-05-01'), true);
test(new Date('2018-05-19'), false);
});

it('should return true if date is in given reverse date range (start date older than end date)', () => {
Expand All @@ -19,9 +19,9 @@ describe('withinRange', () => {
expect(withinRange(date, dateRange)).toBe(expectedResult);
};

test('2018-04-19', true);
test('2018-04-01', true);
test('2018-05-01', true);
test('2018-05-19', false);
test(new Date('2018-04-19'), true);
test(new Date('2018-04-01'), true);
test(new Date('2018-05-01'), true);
test(new Date('2018-05-19'), false);
});
});
Loading