intro tutorial work in progress

This commit is contained in:
Ansis Brammanis
2013-03-25 13:22:37 -04:00
parent 7ef2e65c4d
commit 73bf7b108a
15 changed files with 734 additions and 5 deletions
+2 -1
View File
@@ -91,6 +91,7 @@ fs.writeFileSync('data/data.js', 'iD.data = ' + JSON.stringify({
// Push changes from data/core.yaml into data/locales.js
var core = YAML.load(fs.readFileSync('data/core.yaml', 'utf8'));
var presets = YAML.load(fs.readFileSync('data/presets.yaml', 'utf8'));
var en = _.merge(core, presets);
var intro = YAML.load(fs.readFileSync('data/intro.yaml', 'utf8'));
var en = _.merge(_.merge(core, presets), intro);
var out = 'locale.en = ' + JSON.stringify(en.en, null, 4) + ';';
fs.writeFileSync('data/locales.js', fs.readFileSync('data/locales.js', 'utf8').replace(/locale.en =[^;]*;/, out));
+47
View File
@@ -0,0 +1,47 @@
.intro-nav-wrap {
position: absolute;
left: 30px;
right: 500px;
bottom: 30px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.8);
z-index: 1001;
}
.intro-nav-wrap button.step {
padding: 20px;
padding-bottom: 40px;
}
.intro-nav-wrap button.step.finished {
background: #74C574;
}
.intro-nav-wrap button.step h3 {
font-weight: normal;
}
.curtain-tooltip .tooltip-inner {
font-size: 14px;
font-weight: normal;
}
.curtain-tooltip .tooltip-inner .bold {
font-weight: bold;
display: block;
}
/*
.curtain-tooltip .tooltip-inner {
border-radius: 4px;
font-size: 14px;
border: 4px solid #7092ff;
}
.curtain-tooltip.right .tooltip-arrow {
border-right-color: #7092ff;
}
.curtain-tooltip.bottom .tooltip-arrow {
border-bottom-color: #7092ff;
}
.curtain-tooltip.left .tooltip-arrow {
border-left-color: #7092ff;
}
*/
+35
View File
@@ -0,0 +1,35 @@
en:
intro:
navigation:
drag: "The main map area shows OpenStreetMap data on top of background imagery, by default Bing. You can navigate by dragging and scrolling, just like any web map. **Drag the map!**"
select: "Features can be represented in different ways, using points, lines or areas. They can all be selected by clicking on them. **Click on the point to select it.**"
header: "The header shows us that this point is a Town Hall."
pane: "The main pane shows the feature's attributes such as its name and address."
selectstreet: "**Select West Michigan Avenue!**"
headerstreet: "It's a residential road, the most common type of road."
points:
add: "Points can be used to represent features like shops, restaurants and monuments. They mark a specific spot and tell us what is there. **Click the Point button to add a new point to the map.**"
place: "Click to place the point on the map. The point we are adding is a Cafe. **Place the point at the corner of This Street and That Street**"
search: "There many different features that can be represented by points. **Search for *Cafe* **"
choose: "**Choose *Cafe* from the grid.**"
describe: "The point is now marked as a cafe. Using the feature editor we can add more information about the feature. **Add a name and address**"
close: "The feature editor can be closed by clicking on the close button, or anywhere on the map. **Close the feature editor**"
selectanother: "Often points will already exist, but have mistakes or be incomplete. We can edit exist points. **Select the point for *Someting**"
fixname: "Change the name to *Something Else* **"
selectthird: "We only want to have one representation of a feature. We can see that there are two points for *Some Place*. **Select one of the points.**"
delete: "There menu that shows around the point contains various operations, including delete. **Delete the point.**"
areas:
add: "Areas are a more detailed way to represent features. They give us information on the boundaries of a feature, which is especially useful for larger features. Areas can be used for most features points can be used for, and are often preferred. **Click the Area button to add a new area.**"
corner: "To start drawing an area, click to place a node on one of the corners. **Place the starting node on one of the corners of the playground.**"
place: "Click to mark the corners tracing the boundary of the area. Finish the area by clicking on the starting point. **Draw an area for the basketball field.**"
search: "**Search for *Playground*.**"
choose: "**Choose *Playground from the grid.**"
lines:
add: "Lines are used to represent features such as roads, railways and rivers. **Click the Line button to add a new line.**"
start: "**Click on the end of the road to start drawing the line.**"
intersect: "Rods, and many other lines, are part of a larger network. It is important for these lines to be connected properly so that routing applications can work. **Click on the other road, to create an intersection connecting the two lines.**"
finish: "Just like with areas, click on the last point to end a line. **Finish drawing the road.**"
road: "**Select *Road* from the grid**"
residential: "There are different types of roads, the most common of which is Residential. **Choose the *Residential* road type**"
describe: "**Name the road**"
File diff suppressed because one or more lines are too long
+38 -3
View File
@@ -172,10 +172,7 @@ locale.en = {
"no_documentation_key": "There is no documentation available for this key",
"show_more": "Show More",
"new_tag": "New tag",
"edit_tags": "Edit tags",
"okay": "Okay",
"view_on_osm": "View on OSM",
"name": "Name",
"editing_feature": "Editing {feature}",
"additional": "Additional tags",
"choose": "Select feature type",
@@ -1438,6 +1435,44 @@ locale.en = {
"terms": ""
}
}
},
"intro": {
"navigation": {
"drag": "The main map area shows OpenStreetMap data on top of background imagery, by default Bing. You can navigate by dragging and scrolling, just like any web map. **Drag the map!**",
"select": "Features can be represented in different ways, using points, lines or areas. They can all be selected by clicking on them. **Click on the point to select it.**",
"header": "The header shows us that this point is a Town Hall.",
"pane": "The main pane shows the feature's attributes such as its name and address.",
"selectstreet": "**Select West Michigan Avenue!**",
"headerstreet": "It's a residential road, the most common type of road."
},
"points": {
"add": "Points can be used to represent features like shops, restaurants and monuments. They mark a specific spot and tell us what is there. **Click the Point button to add a new point to the map.**",
"place": "Click to place the point on the map. The point we are adding is a Cafe. **Place the point at the corner of This Street and That Street**",
"search": "There many different features that can be represented by points. **Search for *Cafe* **",
"choose": "**Choose *Cafe* from the grid.**",
"describe": "The point is now marked as a cafe. Using the feature editor we can add more information about the feature. **Add a name and address**",
"close": "The feature editor can be closed by clicking on the close button, or anywhere on the map. **Close the feature editor**",
"selectanother": "Often points will already exist, but have mistakes or be incomplete. We can edit exist points. **Select the point for *Someting**",
"fixname": "Change the name to *Something Else* **",
"selectthird": "We only want to have one representation of a feature. We can see that there are two points for *Some Place*. **Select one of the points.**",
"delete": "There menu that shows around the point contains various operations, including delete. **Delete the point.**"
},
"areas": {
"add": "Areas are a more detailed way to represent features. They give us information on the boundaries of a feature, which is especially useful for larger features. Areas can be used for most features points can be used for, and are often preferred. **Click the Area button to add a new area.**",
"corner": "To start drawing an area, click to place a node on one of the corners. **Place the starting node on one of the corners of the playground.**",
"place": "Click to mark the corners tracing the boundary of the area. Finish the area by clicking on the starting point. **Draw an area for the basketball field.**",
"search": "**Search for *Playground*.**",
"choose": "**Choose *Playground from the grid.**"
},
"lines": {
"add": "Lines are used to represent features such as roads, railways and rivers. **Click the Line button to add a new line.**",
"start": "**Click on the end of the road to start drawing the line.**",
"intersect": "Rods, and many other lines, are part of a larger network. It is important for these lines to be connected properly so that routing applications can work. **Click on the other road, to create an intersection connecting the two lines.**",
"finish": "Just like with areas, click on the last point to end a line. **Finish drawing the road.**",
"road": "**Select *Road* from the grid**",
"residential": "There are different types of roads, the most common of which is Residential. **Choose the *Residential* road type**",
"describe": "**Name the road**"
}
}
};
locale.zh = {
+9
View File
@@ -6,6 +6,7 @@
<link rel='stylesheet' href='css/reset.css'>
<link rel='stylesheet' href='css/map.css'>
<link rel='stylesheet' href='css/app.css'>
<link rel='stylesheet' href='css/intro.css'>
<link rel='stylesheet' href='css/feature-icons.css'>
<!-- mobile devices -->
@@ -24,6 +25,7 @@
<script src='js/lib/d3.size.js'></script>
<script src='js/lib/d3.trigger.js'></script>
<script src='js/lib/d3.keybinding.js'></script>
<script src='js/lib/d3.curtain.js'></script>
<script src='js/lib/d3.one.js'></script>
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
@@ -57,6 +59,7 @@
<script src="js/id/svg/labels.js"></script>
<script src="js/id/ui.js"></script>
<script src='js/id/ui/intro.js'></script>
<script src='js/id/ui/attribution.js'></script>
<script src='js/id/ui/radial_menu.js'></script>
<script src='js/id/ui/inspector.js'></script>
@@ -99,6 +102,11 @@
<script src='js/id/ui/preset/radio.js'></script>
<script src='js/id/ui/preset/textarea.js'></script>
<script src='js/id/ui/intro/navigation.js'></script>
<script src='js/id/ui/intro/point.js'></script>
<script src='js/id/ui/intro/area.js'></script>
<script src='js/id/ui/intro/line.js'></script>
<script src='js/id/actions.js'></script>
<script src="js/id/actions/add_midpoint.js"></script>
<script src='js/id/actions/add_entity.js'></script>
@@ -175,6 +183,7 @@
<script src='js/lib/locale.js'></script>
<script src='data/locales.js'></script>
<script src='data/introGraph.js'></script>
</head>
<body>
<div id='iD'></div>
+10 -1
View File
@@ -13,7 +13,8 @@ iD.Connection = function(context) {
memberStr = 'member',
nodeStr = 'node',
wayStr = 'way',
relationStr = 'relation';
relationStr = 'relation',
off;
connection.changesetUrl = function(changesetId) {
return url + '/browse/changeset/' + changesetId;
@@ -227,6 +228,9 @@ iD.Connection = function(context) {
function abortRequest(i) { i.abort(); }
connection.loadTiles = function(projection, dimensions) {
if (off) return;
var scaleExtent = [16, 16],
s = projection.scale(),
tiles = d3.geo.tile()
@@ -294,6 +298,11 @@ iD.Connection = function(context) {
return connection;
};
connection.toggle = function(_) {
off = !_;
return connection;
};
connection.user = function(_) {
if (!arguments.length) return user;
user = _;
+1
View File
@@ -26,6 +26,7 @@ window.iD = function () {
// the connection requires .storage() to be available on calling.
var connection = iD.Connection(context)
.toggle(false)
.keys(iD.data.keys);
connection.on('load.context', function loadContext(err, result) {
+2
View File
@@ -145,6 +145,8 @@ iD.ui = function(context) {
context.container()
.call(iD.ui.Splash(context))
.call(iD.ui.Restore(context));
d3.select(document.body).call(iD.ui.intro(context));
};
};
+83
View File
@@ -0,0 +1,83 @@
iD.ui.intro = function(context) {
var step;
function intro(selection) {
// Load semi-real data used in intro
context.history().reset();
context.history().merge(iD.Graph().load(JSON.parse(iD.introGraph)).entities);
curtain = d3.curtain();
selection.call(curtain);
var steps = ['navigation', 'point', 'area', 'line'].map(function(step, i) {
var s = iD.ui.intro[step](context, curtain)
.on('done', function() {
entered.filter(function(d) {
return d.name === s.name;
}).classed('finished', true);
enter(steps[i + 1]);
});
return s;
});
var navwrap = selection.append('div').attr('class', 'intro-nav-wrap');
var entered = navwrap.append('div')
.attr('class', 'col12 button-wrap joined')
.selectAll('button.step')
.data(steps)
.enter().append('button')
.attr('class', 'step col2')
.on('click', function(d) {
enter(d);
});
entered.append('h3').text(function(d) { return d.name; });
enter(steps[0]);
function enter (newStep) {
if (step) {
step.exit();
}
step = newStep;
step.enter();
entered.classed('active', function(d) {
return d.name === step.name;
});
}
}
return intro;
};
iD.ui.intro.pointBox = function(point) {
return {
left: point[0] - 30,
top: point[1] - 50,
width: 60,
height: 70
};
};
iD.ui.intro.pad = function(box, padding) {
if (box instanceof Array) {
console.log("array");
box = {
left: box[0],
top: box[1],
width: 0,
height: 0
};
}
box.left -= padding;
box.top -= padding;
box.width += 2 * padding;
box.height += 2 * padding;
return box;
};
+83
View File
@@ -0,0 +1,83 @@
iD.ui.intro.area = function(context, curtain) {
var event = d3.dispatch('done'),
timeouts = [];
var step = {
name: 'Areas'
};
step.enter = function() {
var playground = [-85.63552, 41.94159],
corner = [-85.63565411045074, 41.9417715536927];
context.map().centerZoom(playground, 19);
curtain.reveal('button.add-area', 'bottom', t('intro.areas.add'));
context.on('enter.intro', addArea);
function addArea(mode) {
if (mode.id !== 'add-area') return;
context.on('enter.intro', drawArea);
var padding = 120 * Math.pow(2, context.map().zoom() - 19);
var pointBox = iD.ui.intro.pad(context.projection(corner), padding);
curtain.reveal(pointBox, 'right', t('intro.areas.corner'));
context.map().on('move.intro', function() {
padding = 120 * Math.pow(2, context.map().zoom() - 19);
pointBox = iD.ui.intro.pad(context.projection(corner), padding);
curtain.reveal(pointBox, 'right', t('intro.areas.corner'), 0);
});
}
function drawArea(mode) {
if (mode.id !== 'draw-area') return;
context.on('enter.intro', enterSelect);
var padding = 150 * Math.pow(2, context.map().zoom() - 19);
var pointBox = iD.ui.intro.pad(context.projection(playground), padding);
curtain.reveal(pointBox, 'right', t('intro.areas.place'));
context.map().on('move.intro', function() {
padding = 150 * Math.pow(2, context.map().zoom() - 19);
pointBox = iD.ui.intro.pad(context.projection(playground), padding);
curtain.reveal(pointBox, 'right', t('intro.areas.place'), 0);
});
}
function enterSelect(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
context.on('enter.intro', null);
setTimeout(function() {
curtain.reveal('.preset-grid-search', 'left', t('intro.areas.search'));
d3.select('.preset-grid-search').on('keyup.intro', keySearch);
}, 500);
}
function keySearch() {
var first = d3.select('.grid-button-wrap:first-child');
if (first.datum().id === 'leisure/playground') {
curtain.reveal(first.select('.grid-entry').node(), 'left', t('intro.areas.choose'));
d3.selection.prototype.one.call(context.history(), 'change.intro', selectedPreset);
d3.select('.preset-grid-search').on('keyup.intro', null);
}
}
function selectedPreset() {
curtain.reveal('.pane', 'left', 'Add a name and edit some details. Click x to exit');
context.on('exit.intro', event.done);
}
};
step.exit = function() {
context.on('enter.intro', null);
context.on('exit.intro', null);
};
return d3.rebind(step, event, 'on');
};
+113
View File
@@ -0,0 +1,113 @@
iD.ui.intro.line = function(context, curtain) {
var event = d3.dispatch('done');
var step = {
name: 'Lines'
};
function one(target, e, f) {
d3.selection.prototype.one.call(target, e, f);
}
step.enter = function() {
var centroid = [-85.62830, 41.95699];
var midpoint = [-85.62975395449628, 41.95787501510204];
var start = [-85.6297754121684, 41.9583158176903];
var intersection = [-85.62974496187628, 41.95742515554585];
context.map().centerZoom(start, 19);
console.log("here");
curtain.reveal('button.add-line', 'bottom', t('intro.lines.add'));
context.on('enter.intro', addLine);
function addLine(mode) {
if (mode.id !== 'add-line') return;
context.on('enter.intro', drawLine);
var padding = 150 * Math.pow(2, context.map().zoom() - 19);
var pointBox = iD.ui.intro.pad(context.projection(start), padding);
curtain.reveal(pointBox, 'right', t('intro.lines.start'));
context.map().on('move.intro', function() {
padding = 150 * Math.pow(2, context.map().zoom() - 19);
pointBox = iD.ui.intro.pad(context.projection(start), padding);
curtain.reveal(pointBox, 'right', t('intro.lines.start'), 0);
});
}
function drawLine (mode) {
if (mode.id !== 'draw-line') return;
context.on('enter.intro', null);
context.history().on('change.intro', addIntersection);
var padding = 300 * Math.pow(2, context.map().zoom() - 19);
var pointBox = iD.ui.intro.pad(context.projection(midpoint), padding);
curtain.reveal(pointBox, 'right', t('intro.lines.intersect'));
context.map().on('move.intro', function() {
padding = 300 * Math.pow(2, context.map().zoom() - 19);
pointBox = iD.ui.intro.pad(context.projection(midpoint), padding);
curtain.reveal(pointBox, 'right', t('intro.lines.intersect'), 0);
});
}
function addIntersection(changes) {
if ( _.any(changes.created(), function(d) {
return d.type === 'node' && context.graph().parentWays(d).length > 1;
})) {
context.history().on('change.intro', null);
context.on('enter.intro', enterSelect);
var padding = 900 * Math.pow(2, context.map().zoom() - 19);
var pointBox = iD.ui.intro.pad(context.projection(centroid), padding);
curtain.reveal(pointBox, 'right', t('intro.lines.finish'));
context.map().on('move.intro', function() {
padding = 900 * Math.pow(2, context.map().zoom() - 19);
pointBox = iD.ui.intro.pad(context.projection(centroid), padding);
curtain.reveal(pointBox, 'right', t('intro.lines.finish'), 0);
});
}
}
function enterSelect(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
context.on('enter.intro', null);
setTimeout(function() {
var road = d3.select('.preset-grid .grid-entry').filter(function(d) {
return d.id === 'Road';
});
curtain.reveal(road.node(), 'left', t('intro.lines.road'));
road.one('click.intro', roadCategory);
}, 500);
}
function roadCategory() {
window.setTimeout(function() {
var grid = d3.select('.subgrid');
curtain.reveal(grid.node(), '', t('intro.lines.residential'));
grid.selectAll('.grid-entry').filter(function(d) {
return d.id === 'highway/residential';
}).one('click.intro', roadDetails);
}, 200);
}
function roadDetails() {
curtain.reveal('.pane', '', t('intro.lines.describe'));
context.on('exit.intro', event.done);
}
};
step.exit = function() {
context.on('enter.intro', null);
context.on('exit.intro', null);
curtain.hide();
};
return d3.rebind(step, event, 'on');
};
+85
View File
@@ -0,0 +1,85 @@
iD.ui.intro.navigation = function(context, curtain) {
var event = d3.dispatch('done'),
timeouts = [];
var step = {
name: 'Navigation'
};
function set(f, t) {
timeouts.push(window.setTimeout(f, t));
}
/*
* Steps:
* Drag map
* Select poi
* Show editor header
* Show editor pane
* Select road
* Show header
*/
step.enter = function() {
var map = {
left: 0,
top: 60,
width: window.innerWidth - 400,
height: window.innerHeight - 200
};
context.map().centerZoom([-85.63591, 41.94285], 19);
curtain.reveal(map, 'right', t('intro.navigation.drag'));
context.map().on('move.intro', _.debounce(function() {
context.map().on('move.intro', null);
townhall();
context.on('enter.intro', inspectTownHall);
}, 400));
function townhall() {
var hall = d3.select('.node.tag-amenity-townhall');
var box = iD.ui.intro.pointBox(context.projection(hall.datum().loc));
curtain.reveal(box, 'right', t('intro.navigation.select'));
context.map().on('move.intro', function() {
var hall = d3.select('.node.tag-amenity-townhall');
var box = iD.ui.intro.pointBox(context.projection(hall.datum().loc));
curtain.reveal(box, 'right', t('intro.navigation.select'), 0);
});
}
function primaryRoad() {
curtain.reveal('.tag-highway-primary', 'right', t('intro.navigation.selectstreet'));
context.on('enter.intro', inspectRoad);
}
function inspectTownHall(mode) {
if (mode.id !== 'select') return;
context.on('enter.intro', null);
context.map().on('move.intro', null);
set(curtain.getReveal('.header', 'left', t('intro.navigation.header')), 700);
set(curtain.getReveal('.tag-wrap', 'left', t('intro.navigation.pane')), 4000);
set(primaryRoad, 7001);
}
function inspectRoad(mode) {
if (mode.id !== 'select') return;
context.on('enter.intro', null);
set(curtain.getReveal('.header', 'left', t('intro.navigation.headerstreet')), 700);
set(event.done, 4000);
}
};
step.exit = function() {
context.map().on('move.intro', null);
context.on('enter.intro', null);
timeouts.forEach(window.clearTimeout);
};
return d3.rebind(step, event, 'on');
};
+67
View File
@@ -0,0 +1,67 @@
iD.ui.intro.point = function(context, curtain) {
var event = d3.dispatch('done'),
timeouts = [];
var step = {
name: 'Points'
};
function setTimeout(f, t) {
timeouts.push(window.setTimeout(f, t));
}
step.enter = function() {
context.map().centerZoom([-85.63279, 41.94394], 19);
curtain.reveal('button.add-point', 'bottom', t('intro.points.add'));
var corner = [-85.632481,41.944094];
context.on('enter.intro', addPoint);
function addPoint(mode) {
if (mode.id !== 'add-point') return;
context.on('enter.intro', enterSelect);
var pointBox = iD.ui.intro.pad(context.projection(corner), 150);
curtain.reveal(pointBox, 'right', t('intro.points.place'));
context.map().on('move.intro', function() {
pointBox = iD.ui.intro.pad(context.projection(corner), 150);
curtain.reveal(pointBox, 'right', t('intro.points.place'), 0);
});
}
function enterSelect(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
context.on('enter.intro', null);
setTimeout(function() {
curtain.reveal('.preset-grid-search', 'left', t('intro.points.search'));
d3.select('.preset-grid-search').one('keydown.intro', keySearch);
}, 500);
}
function keySearch() {
curtain.reveal('button.grid-entry', 'left', t('intro.points.choose'));
d3.selection.prototype.one.call(context.history(), 'change.intro', selectedPreset);
}
function selectedPreset() {
curtain.reveal('.pane', 'left', 'Add a name and edit some details. Click x to exit');
context.on('exit.intro', event.done);
}
};
step.exit = function() {
timeouts.forEach(window.clearTimeout);
context.on('exit.intro', null);
context.on('enter.intro', null);
context.map().on('move.intro', null);
};
return d3.rebind(step, event, 'on');
};
+158
View File
@@ -0,0 +1,158 @@
// Tooltips and svg mask used to highlight certain features
d3.curtain = function() {
var event = d3.dispatch(),
tooltip,
mask;
function curtain(selection) {
var surface = selection.append('svg')
.style({
'z-index': 1000,
'pointer-events': 'none',
'position': 'absolute',
'top': 0,
'left': 0,
'right': 0,
'bottom': 0
});
tooltip = selection.append('div')
.attr('class', 'tooltip')
.style('z-index', 1002);
tooltip.append('div').attr('class', 'tooltip-arrow');
tooltip.append('div').attr('class', 'tooltip-inner');
var defs = surface.append('defs');
mask = defs.append('mask')
.attr('id', 'mask');
mask.append('rect')
.attr({
x: 0,
y: 0,
width: window.innerWidth,
height: window.innerHeight
})
.style({
'fill': 'white'
});
d3.select(window).on('resize.curtain', function() {
var size = {
width: window.innerWidth,
height: window.innerHeight
};
mask.attr(size);
darkness.attr(size);
});
var darkness = surface.append('rect')
.attr({
x: 0,
y: 0,
width: window.innerWidth,
height: window.innerHeight,
'mask': "url(#mask)"
})
.style({
'fill-opacity': 0.7,
'fill': '#222'
});
}
function elementRect(elem) {
var ret = elem.getBoundingClientRect();
return ret;
}
curtain.getReveal = function(box, side, text) {
return function() {
curtain.reveal(box, side, text);
};
};
curtain.hide = function() {
curtain.cut();
tooltip.classed('in', false);
};
curtain.reveal = function(box, side, text, duration) {
if (typeof box === 'string') box = elementRect(d3.select(box).node());
if (box.getBoundingClientRect) box = elementRect(box);
var pos;
curtain.cut(box, duration);
if (box.top + box.height < Math.min(200, box.width + box.left)) {
side = 'bottom';
pos = [box.left, box.top + box.height];
} else if (box.left + box.width + 300 < window.innerWidth) {
side = 'right';
pos = [box.left + box.width, Math.max(box.top, 10)];
} else if (box.left > 300) {
side = 'left';
pos = [box.left - 200, Math.max(box.top, 10)];
} else {
side = 'bottom';
pos = [box.left, box.top + box.height];
}
// pseudo markdown bold text hack
var parts = text.split('**');
var html = parts[0];
if (parts[1]) html += '<span class="bold">' + parts[1] + '</span>';
tooltip.attr('class', 'curtain-tooltip tooltip in ' + side)
.style('top', pos[1] + 'px')
.style('left', pos[0] + 'px')
.select('.tooltip-inner')
.html(html);
};
curtain.cut = function(data, duration) {
data = data ? [data] : [];
var cutouts = mask.selectAll('.cutout')
.data(data);
var entered = cutouts.enter()
.append('rect')
.attr({
left: function(d) { return d.left + d.width / 2; },
top: function(d) { return d.top + d.height/ 2; },
width: function(d) { return 0; },
height: function(d) { return 0; },
fill: 'black',
'class': 'cutout'
})
.style('fill-opacity', 0);
var all = mask.selectAll('.cutout');
(duration === 0 ? all : all.transition().duration(duration || 600))
.style('fill-opacity', 1)
.attr('x', function(d) { return d.left; })
.attr('y', function(d) { return d.top; })
.attr('width', function(d) { return d.width; })
.attr('height', function(d) { return d.height; });
cutouts.exit().transition()
.duration(500)
.style('fill-opacity', 0)
.attr('x', function(d) { return d.left + d.width / 2; })
.attr('y', function(d) { return d.top + d.height / 2; })
.attr('width', 0)
.attr('height', 0)
.remove();
};
return d3.rebind(curtain, event, 'on');
};