Skip to content
Permalink
Browse files

feat: Initial d3fc-label-layout implementation

This project is 'forked' from d3fc with a number of modifications in order to make it compatible with our new modular approach. You can find the full history of the original code and the modifications that were applied in the 'legacy' branch
  • Loading branch information...
ColinEberhardt committed Mar 16, 2016
0 parents commit 1ad2fb302fa3628bbd1edf7d38ca61b921e71a47
@@ -0,0 +1,3 @@
{
"presets": ["es2015-rollup"]
}
@@ -0,0 +1,14 @@
{
"extends": "standard",
"env": {
"jasmine": true
},
"rules": {
"semi": [1, "always"],
"indent": [2, 4],
"space-before-function-paren": [2, "never"],
"padded-blocks": 0,
"no-multi-spaces": 0,
"no-unused-vars": 0
}
}
@@ -0,0 +1,4 @@
node_modules
npm-debug.log
build
site/lib
No changes.
21 LICENSE
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015-2016 Scott Logic Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
157 README.md
@@ -0,0 +1,157 @@
# d3fc-label-layout

A D3 layout that places labels avoiding overlaps, with strategies including simulated annealing, greedy and a strategy that removes overlapping labels.

![d3fc label layout](d3fc-label-layout.png)

For a live demo, see the [GitHub Pages site](http://colineberhardt.github.io/d3fc-label-layout/).

[Main d3fc package](https://github.com/ScottLogic/d3fc)

# Installation

```bash
npm install d3fc-label-layout
```

# API

## General API

The label layout component provides a mechanism for arranging child components based on their rectangular bounding boxes. It is typically used to render labels on maps or charts. A layout strategy is passed to the component in order to arrange the child rectangles avoiding collisions or remove overlaps.

## Example usage

```javascript
var labelPadding = 2;
// the component used to render each label
var textLabel = fc.layout.textLabel()
.padding(labelPadding)
.value(function(d) { return d.properties.name; });
// a strategy that combines simulated annealing with removal
// of overlapping labels
var strategy = fc.layout.removeOverlaps(fc.layout.annealing());
// create the layout that positions the labels
var labels = fc.layout.label(strategy)
.size(function(d) {
// measure the label and add the required padding
var textSize = d3.select(this)
.select('text')
.node()
.getBBox();
return [textSize.width + labelPadding * 2, textSize.height + labelPadding * 2];
})
.position(function(d) { return projection(d.geometry.coordinates); })
.component(textLabel);
// render!
svg.datum(places.features)
.call(labels);
```

## Label

*fc.layout*.**label**(*strategy*)

Constructs a new label layout with the given *strategy*. The label layout creates an array of rectangle bounding boxes which are passed to the strategy, which will typically move the boxes in order to minimise overlaps. Once the layout is complete a data join is used to construct a containing `g` element for each item in the bound array, and the component supplied to the layout is 'call'-ed on each element.

Each `g` element has the following properties set:

- `layout-width`, `layout-height` - the width and height of this label, as provided by the `size` property.
- `display` - set to `inherit` or `hidden`, based on whether the strategy has hidden this label.
- `anchor-x`, `anchor-y` - the original label location in relative coordinates to the this `g` element.


*label*.**size**(*accessor*)

Specifies the size for each item in the associated array. The *accessor* function is invoked exactly once per datum, and should return the size as an array of two values, `[width, height]`. The *accessor* function is invoked with the datum, and index. This function is invoked after the component has been rendered, and the value of the *this* context is the containing `g` element. As a result, you can measure the size of the component if the contents are dynamic, for example, measuring the size of a text label.

*label*.**position**(*accessor*)

Specifies the position for each item in the associated array. The *accessor* function is invoked exactly once per datum, and should return the position as an array of two values, `[x, y]`.

*label*.**component**(*component*)

Specified the component that is used to render each label.

## Strategy

The label component uses a strategy in order to re-locate labels to avoid collisions, or perhaps hide those that overlap.

The strategy is supplied an array of objects that describe the initial location of each label, as obtained via the `position` and `size` properties of `layout`.

Each object has the following structure:

```
{
hidden: ...,
x: ...,
y: ...,
width: ...,
height: ...,
}
```

The strategy should return an array of objects indicating the placement of each label.

### Greedy

The greedy strategy is a very fast way of reducing label overlap. It adds each label in sequence, selecting the position where the label has the lowest overlap with already added rectangles and is inside the container.

*fc.layout*.**greedy**()

Constructs a greedy strategy.

*greedy*.**bounds**(*array*)

Optionally specifies a bounding region, as an array of two values, `[width, height]`. The strategy will try to keep labels within the bounds.

### Simulated Annealing

The simulated annealing strategy runs over a set number of iterations, choosing a different location for one label on each iteration. If that location results in a better result, it is saved for the next iteration. Otherwise, it is saved with probability inversely proportional with the iteration it is currently on. This helps it break out of local optimums, hopefully producing better output. Because of the random nature of the algorithm, it produces variable output.

*fc.layout*.**annealing**()

Constructs an annealing strategy.

*annealing*.**bounds**(*array*)

Optionally specifies a bounding region, as an array of two values, `[width, height]`. The strategy will try to keep labels within the bounds.

*annealing*.**temperature**(*integer*)

*annealing*.**cooling**(*integer*)

The *temperature* parameter indicates the initial 'number' to use for the random probability calculation, and *cooling* defines the delta of the temperature between iterations. The algorithm runs for `Math.ceil(temperature / cooling)` iterations.

### Remove overlaps

This strategy doesn't re-position labels to reduce overlaps, instead it removes overlapping labels. This is performed iteratively, with the labels that have the greatest area of overlap removed first.

*fc.layout*.**removeOverlaps**(*strategy*)

Constructs a removeOverlaps strategy, adapting the supplied *strategy* in order to remove overlaps after it has been executed.

## Text Label

This is a simple component that renders a label:

![d3fc label layout](textLabel.png)

This component uses the `layout-width` and `layout-height` properties of its parent element to set its own width and height. It also uses the `anchor-x` and `anchor-y` properties to place the circular anchor. These properties are all set by the label layout as described above.

*fc.layout*.**textLabel**()

Constructs a text label component.

*textLabel*.**labelPadding**(*number*)

Specifies the padding around the text.

*textLabel*.**value**(*accessor*)

Specifies the text rendered by this label as an accessor function.
BIN +176 KB d3fc-label-layout.png
Binary file not shown.
@@ -0,0 +1,7 @@
export { default as label } from './src/label';
export { default as textLabel } from './src/textLabel';
export { default as greedy } from './src/greedy';
export { default as annealing } from './src/annealing';
export { default as removeOverlaps } from './src/removeOverlaps';
export { default as boundingBox } from './src/boundingBox';
export { default as intersect } from './src/intersect';
@@ -0,0 +1,49 @@
{
"name": "d3fc-label-layout",
"version": "0.0.1",
"description": "A D3 layout that places labels avoiding overlaps, with strategies including simulated annealing, greedy and local.",
"license": "MIT",
"keywords": [
"d3",
"d3fc",
"label",
"layout"
],
"homepage": "https://github.com/ColinEberhardt/d3fc-label-layout",
"main": "build/d3fc-label-layout.js",
"jsnext:main": "index",
"repository": {
"type": "git",
"url": "https://github.com/ColinEberhardt/d3fc-label-layout"
},
"scripts": {
"bundle": "rimraf build && rollup -c && uglifyjs build/d3fc-label-layout.js --compress --mangle -o build/d3fc-label-layout.min.js",
"test": "npm run bundle && eslint index.js src/**/*.js test/**/*.js && jasmine JASMINE_CONFIG_PATH=test/support/jasmine.json",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"site": "npm run test && mkdir -p site/lib && cp build/d3fc-label-layout.js site/lib && cp node_modules/d3/d3.js site/lib && cp node_modules/d3fc-rebind/build/d3fc-rebind.js site/lib",
"dev": "npm run site && watch -p '{src,test}/**/*.js' -c 'npm run site'",
"deploy-site": "npm run site && bash ./site/deploy.sh"
},
"devDependencies": {
"babel-preset-es2015-rollup": "^1.1.1",
"d3fc": "^7.0.0",
"eslint": "^2.2.0",
"eslint-config-airbnb": "^6.0.2",
"eslint-config-standard": "^5.1.0",
"eslint-plugin-promise": "^1.1.0",
"eslint-plugin-standard": "^1.3.2",
"jasmine": "^2.4.1",
"js-combinatorics": "^0.5.0",
"jsdom": "^8.1.0",
"rimraf": "^2.5.2",
"rollup": "^0.25.4",
"rollup-plugin-babel": "^2.4.0",
"semantic-release": "^4.3.5",
"uglify-js": "^2.6.2",
"watch-cli": "^0.2.1"
},
"dependencies": {
"d3": "^3.5.16",
"d3fc-rebind": "^2.0.0"
}
}
@@ -0,0 +1,13 @@
import babel from 'rollup-plugin-babel';

export default {
entry: 'index.js',
moduleName: 'fc_layout',
format: 'umd',
plugins: [ babel() ],
dest: 'build/d3fc-label-layout.js',
globals: {
'd3': 'd3',
'd3fc-rebind': 'fc_rebind'
}
};

0 comments on commit 1ad2fb3

Please sign in to comment.
You can’t perform that action at this time.