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 7 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
41 changes: 27 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# CHANGELOG

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

[#269](https://github.com/marmelab/EventDrops/pull/269):

* Removed `new Date()` in isAfter, isBefore and withinRange (objects were already a date).
* Fixed TravisCI build.
* Restrict panning. Option in config to restrict the zooming and panning, so it will only be between start and end in range.
* Zoom to domain. Exposed a function to zoom to domain, as well as the option to add in a transition.

[#262](https://github.com/marmelab/EventDrops/pull/262): Fixed issue where right bound text would disappear.

[#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 Expand Up @@ -28,25 +41,25 @@

## 1.0.0

* Huge performance boost (almost 10 times faster when zooming or panning)
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
* Review configuration to get more intuitive naming
* Simplify tick format configuration passing only time formats instead of a whole function
* Fix zoom and panning center
* Better integration with module bundlers (allowing to pass a local D3 object instead of the global one)
* Huge performance boost (almost 10 times faster when zooming or panning)
* Review configuration to get more intuitive naming
* Simplify tick format configuration passing only time formats instead of a whole function
* Fix zoom and panning center
* Better integration with module bundlers (allowing to pass a local D3 object instead of the global one)

We took profit of this major version change to improve the API - unfortunately, we couldn't keep backwards compatibility. See the [migration guide](./MIGRATION-4.0.md) for more informations.

## 0.3.0

* API Change: The data for each event line object must now be in the `data` property (was `date`).
* Pass any data object to each drop and specify the date property with a callback.
* The SVG is now responsive and fit with its parent
* Rename `eventHover`, `eventClick` and `eventZoom` events to `mouseover`, `click` and `zoomend` respectively.
* Adding `mouseout` handler
* API Change: The data for each event line object must now be in the `data` property (was `date`).
* Pass any data object to each drop and specify the date property with a callback.
* The SVG is now responsive and fit with its parent
* Rename `eventHover`, `eventClick` and `eventZoom` events to `mouseover`, `click` and `zoomend` respectively.
* Adding `mouseout` handler

## 0.2.0

* Display metaballs by default instead of simple dots
* Adding `eventClick` event handler on drops
* Use of Webpack instead of Babel for development tasks
* Full rewrite of the code base for better code splitting (may cause some BC breaks)
* Display metaballs by default instead of simple dots
* Adding `eventClick` event handler on drops
* Use of Webpack instead of Babel for development tasks
* Full rewrite of the code base for better code splitting (may cause some BC breaks)
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ You can either use D3 as a specific import (specifying it in first argument of `

In addition to this configuration object, it also exposes some public members allowing you to customize your application based on filtered data:

* **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
* **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).
* **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](./docs/configuration.md#restrictpan) modifier; if set to true, the function can still zoom out of restriction. By default there is no transition as duration is 0, however this can be tweaked to allow for a more visual appealing zoom.
* **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).

Hence, if you want to display number of displayed data and time bounds as in the [demo](https://marmelab.com/EventDrops/), you can use the following code:

Expand Down
10 changes: 8 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,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
Busteren marked this conversation as resolved.
Show resolved Hide resolved

_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 +368,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
1 change: 1 addition & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default d3 => ({
onZoomEnd: null,
minimumScale: 0,
maximumScale: Infinity,
restrictpan: false,
},
numberDisplayedTicks: {
small: 3,
Expand Down
68 changes: 58 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { getBreakpointLabel } from './breakpoint';
import bounds from './bounds';
import defaultConfiguration from './config';
import dropLine from './dropLine';
import zoom from './zoom';
import zoomFactory from './zoom';
import { getDomainTransform } from './zoom';
import { addMetaballsDefs } from './metaballs';

import './style.css';
Expand Down Expand Up @@ -62,20 +63,55 @@ export default ({
.attr('width', width)
.classed('event-drop-chart', true);

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

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

chart._zoomToDomain = (domain, duration, delay, ease) => {
const zoomIdentity = getDomainTransform(
d3,
config,
zoom,
domain,
xScale,
width
);
svg
.transition()
.ease(ease)
.delay(delay)
.duration(duration)
.call(zoom.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 +126,27 @@ export default ({

chart.scale = () => chart._scale;
chart.filteredData = () => chart._filteredData;
chart.zoomToDomain = (
domain,
duration = 0,
delay = 0,
ease = d3.easeLinear
) => {
if (typeof chart._zoomToDomain === 'function') {
chart._zoomToDomain(domain, duration, delay, ease);
} else {
throw new Error(
'Calling "zoomToDomain" requires zooming to be enabled.'
);
}
};
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
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