Skip to content

Commit

Permalink
Merge pull request #4 from walterra/event-api
Browse files Browse the repository at this point in the history
add event click, mousever, and mouseleave API
  • Loading branch information
walterra authored Oct 25, 2018
2 parents b88613c + ec9aaf3 commit e51f727
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 23 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,41 @@ When called without `data` this triggers re-rendering the existing visualization

`data` is expected to be an array of event objects with fields matching either the expected defaults (`timestamp` and `text` attribute) or the provided mapping via `.mapping()`.

<a name="onEventClick" href="#onEventClick">#</a> vis.<b>onEventClick</b>(<i>function</i>)

Set a callback which is executed when the text or image is clicked.

```js
vis.onEventClick((d) => {
console.log('click', d);
alert(`
${d.text} | ${d.timestamp}
${JSON.stringify(d.attributes)}
`);
})
```

<a name="onEventMouseOver" href="#onEventMouseOver">#</a> vis.<b>onEventMouseOver</b>(<i>function</i>)

Set a callback which is executed when the mouse cursor is over text or image.

```js
vis.onEventMouseOver((d) => {
console.log('mouseover', d);
})
```

<a name="onEventMouseLeave" href="#onEventMouseLeave">#</a> vis.<b>onEventMouseLeave</b>(<i>function</i>)

Set a callback which is executed when the mouse cursor is leaving text or image.

```js
vis.onEventMouseLeave((d) => {
console.log('mouseleave', d);
})
```


## More

`d3-milestones` is also available as a visualization plugin for Kibana here: https://github.com/walterra/kibana-milestones-vis
120 changes: 120 additions & 0 deletions example/milestones-event_api.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Version Milestones with Event API</title>
<link rel="stylesheet" href="../build/d3-milestones.css">
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<style>
body {
font-family: sans-serif;
}

h1 {
font-size: 1.5em;
}

label {
font-size: .8em;
}
</style>
</head>

<body id="home">
<h1>Version Milestones with Event API</h1>
<p>The chart is response, try resizing the browser window. Use the checkboxes to trigger examples of the chart's features.</p>
<p>The chart implements click, mouseover, and mouseleave. Please click on an text or image to call the corresponding action. Or inspect the console to see the return of the event.</p>
<form>
<table><tbody>
<tr>
<td><label for="checkbox-optimize">Optimize Label Layout</label></td>
<td><input id="checkbox-optimize" type="checkbox" checked /></td>
</tr>
<tr>
<td><label for="select-groupby">Aggregate data by</label></td>
<td>
<select id="select-groupby">
<option value="second">Second</option>
<option value="minute">Minute</option>
<option value="hour">Hour</option>
<option value="day" selected>Day</option>
<option value="week">Week</option>
<option value="month">Month</option>
<option value="quarter">Quarter</option>
<option value="year">Year</option>
</select>
</td>
</td>
</table>
</form>
<div id="timeline"></div>
<script src="../build/d3-milestones.js"></script>
<script>
var data = [
{ "timestamp" : "2012-09-09T00:00", "detail":"v1.0.0" },
{ "timestamp" : "2012-09-10T00:00", "detail":"v1.0.1" },
{ "timestamp" : "2012-09-12T00:00", "detail":"v1.1.0" },
{ "timestamp" : "2012-09-15T00:00", "detail":"v1.1.1" },
{ "timestamp" : "2012-09-26T00:00", "detail":"v1.2.0" },
{ "timestamp" : "2012-10-10T00:00", "detail":"v2.0.0" },
{ "timestamp" : "2012-10-31T00:00", "detail":"v2.1.0" },
{ "timestamp" : "2012-11-07T00:00", "detail":"v2.2.0" },
{ "timestamp" : "2012-11-25T00:00", "detail":"v2.2.1" },
{ "timestamp" : "2012-12-01T00:00", "detail":"v2.3.0" },
{ "timestamp" : "2012-12-05T00:00", "detail":"v2.3.1" },
{ "timestamp" : "2013-01-03T00:00", "detail":"v3.0.0" },
{ "timestamp" : "2013-01-09T00:00", "detail":"v3.0.1" },
{ "timestamp" : "2013-01-16T00:00", "detail":"v3.1.0" },
{ "timestamp" : "2013-01-16T00:00", "detail":"v4.0.0" },
{ "timestamp" : "2013-01-17T00:00", "detail":"v4.0.1" },
{ "timestamp" : "2013-02-08T00:00", "detail":"v4.1.0" },
{ "timestamp" : "2013-03-06T00:00", "detail":"v4.1.1" },
{ "timestamp" : "2013-03-06T00:00", "detail":"v4.2.0" },
{ "timestamp" : "2013-03-09T00:00", "detail":"v4.3.0" },
{ "timestamp" : "2013-03-27T00:00", "detail":"v4.4.0" },
{ "timestamp" : "2013-04-13T00:00", "detail":"v4.5.0" },
{ "timestamp" : "2013-05-04T00:00", "detail":"v5.0.0" },
{ "timestamp" : "2013-07-06T00:00", "detail":"v5.0.1" },
{ "timestamp" : "2013-08-01T00:00", "detail":"v5.1.0" }
];

var timeline = milestones('#timeline')
.mapping({
timestamp: 'timestamp',
text: 'detail'
})
.onEventClick((d) => {
console.log('click', d);
alert(`
${d.text} | ${d.timestamp}
${JSON.stringify(d.attributes)}
`);
})
.onEventMouseOver((d) => {
console.log('mouseover', d);
})
.onEventMouseLeave((d) => {
console.log('mouseleave', d);
});

var checkboxOptimize = document.getElementById('checkbox-optimize');
var selectGroupBy = document.getElementById('select-groupby');

function update() {
timeline
.parseTime('%Y-%m-%dT%H:%M')
.aggregateBy(selectGroupBy.options[selectGroupBy.selectedIndex].value)
.optimize(checkboxOptimize.checked)
.render(data);
}

checkboxOptimize.onclick = update;
selectGroupBy.onchange = update;

update();

</script>
</body>
</html>
130 changes: 107 additions & 23 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import * as dom from 'd3-selection';
import * as scale from 'd3-scale';
import { ascending } from 'd3-array';
import { extent } from 'd3-array';
import { max } from 'd3-array';
import { ascending, extent, max } from 'd3-array';
import { nest } from 'd3-collection';
import { timeFormat as d3TimeFormat } from 'd3-time-format';
import { timeParse as d3TimeParse } from 'd3-time-format';
import { isoParse } from 'd3-time-format';
import { isoParse, timeFormat as d3TimeFormat, timeParse as d3TimeParse } from 'd3-time-format';
import api from './_api';

const cssPrefix = 'milestones';
Expand All @@ -19,6 +15,7 @@ const cssLastClass = cssLabelClass + '-last';
const cssAboveClass = cssLabelClass + '-above';
const cssTextClass = cssLabelClass + '__text';
const cssTitleClass = cssTextClass + '__title';
const cssEventClass = cssTextClass + '__event';

const labelMaxWidth = 180;
const labelRightMargin = 6;
Expand Down Expand Up @@ -78,6 +75,42 @@ export default function milestones(selector) {
}
setUseLabels(true);

// set callback for event mouseover
let callBackMouseOver;
function setEventMouseOverCallback(callback) {
callBackMouseOver = callback;
}
function eventMouseOver(d) {
if (typeof callBackMouseOver === 'function') {
callBackMouseOver(d);
}
return d;
}

// set callback for event mouseleave
let callBackMouseLeave;
function setEventMouseLeaveCallback(callback) {
callBackMouseLeave = callback;
}
function eventMouseLeave(d) {
if (typeof callBackMouseOver === 'function') {
callBackMouseLeave(d);
}
return d;
}

// set callback for event click
let callbackClick;
function setEventClickCallback(callback) {
callbackClick = callback;
}
function eventClick(d) {
if (typeof callbackClick === 'function') {
callbackClick(d);
}
return d;
}

// second, minute, hour, day, month, quarter, year
const aggregateFormats = {
second: '%Y-%m-%d %H:%M:%S',
Expand Down Expand Up @@ -303,24 +336,72 @@ export default function milestones(selector) {
const labelRightMargin = 6;
const finalWidth = Math.min(labelMaxWidth, (Math.max(0, availableWidth - labelRightMargin)));
return finalWidth + 'px';
});
// .html(d => {
// const above = isAbove(d.index);

// const group = '<span class="' + cssTitleClass + '">' + labelFormat(aggregateFormatParse(d.key)) + '</span>';
// const lines = d.values.map(d => {
// const t = d[mapping.text];
// // test if text is an image filename,
// // if so return an image tag with the filename as the source
// if (['jpg', 'jpeg', 'gif', 'png'].indexOf(t.split('.').pop()) > -1) {
// return '<img class="milestones-image-label" src="' + t + '" height="100" />';
// }
// return t;
// });

// (above) ? lines.push(group) : lines.unshift(group);

// return lines.join('<br />');
// });

// delete textEnter children on chart update
textEnter.selectAll('*').remove();

// add textEnter title if above
textEnter.append('div')
.attr('class', cssTitleClass)
.text(d => {
if (!isAbove(d.index)) {
return labelFormat(aggregateFormatParse(d.key));
}
return '';
});

// add textEnter event
textEnter.selectAll('.' + cssEventClass)
.data(d => {
return d.values.map(v => {
return {
text: v[mapping.text],
timestamp: v[mapping.timestamp],
attributes: v, // original value of an object passed to the milestone
};
});
})
.enter()
.append('div')
.on('click', eventClick)
.on('mouseleave', eventMouseLeave)
.on('mouseover', eventMouseOver)
.attr('class', cssEventClass)
.attr('data-date', d => d.timestamp)
.html(d => {
const above = isAbove(d.index);

const group = '<span class="' + cssTitleClass + '">' + labelFormat(aggregateFormatParse(d.key)) + '</span>';
const lines = d.values.map(d => {
const t = d[mapping.text];
// test if text is an image filename,
// if so return an image tag with the filename as the source
if (['jpg', 'jpeg', 'gif', 'png'].indexOf(t.split('.').pop()) > -1) {
return '<img class="milestones-image-label" src="' + t + '" height="100" />';
}
return t;
});

(above) ? lines.push(group) : lines.unshift(group);

return lines.join('<br />');
if (['jpg', 'jpeg', 'gif', 'png'].indexOf(d.text.split('.').pop()) > -1) {
return '<img class="milestones-image-label" src="' + d.text + '" height="100" />';
}
return d.text;
});

// add textEnter title if bottom
textEnter.append('div')
.attr('class', cssTitleClass)
.text(d => {
if (isAbove(d.index)) {
return labelFormat(aggregateFormatParse(d.key));
}
return '';
});

const textMerge = text.merge(textEnter);
Expand Down Expand Up @@ -437,6 +518,9 @@ export default function milestones(selector) {
labelFormat: setLabelFormat,
useLabels: setUseLabels,
range: setRange,
render: render
render: render,
onEventClick: setEventClickCallback,
onEventMouseLeave: setEventMouseLeaveCallback,
onEventMouseOver: setEventMouseOverCallback,
});
}
10 changes: 10 additions & 0 deletions src/styles/styles.less
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,15 @@
.milestones__group__label__text__title {
color: #000;
font-weight: bold;
font-size: 11px;
white-space: nowrap;
}
.milestones__group__label__text__event {
color: #000;
padding: 5px 0;
cursor: pointer;
}
.milestones__group__label__text__event:hover {
background: #efefef;
color: #313131;
}

0 comments on commit e51f727

Please sign in to comment.