From 6106c6e39706ba5a3c7d71a43f6666b8faf19bb8 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 25 Oct 2018 18:13:10 +0200 Subject: [PATCH] Refactor event API to recover from layout regressions. --- src/main.js | 123 +++++++++++++++++++++------------------- src/styles/styles.less | 6 +- test/event-test.js | 58 +++++++++++++++++++ test/milestones-test.js | 2 + 4 files changed, 126 insertions(+), 63 deletions(-) create mode 100644 test/event-test.js diff --git a/src/main.js b/src/main.js index 174dab5..340eefe 100644 --- a/src/main.js +++ b/src/main.js @@ -82,6 +82,7 @@ export default function milestones(selector) { } function eventMouseOver(d) { if (typeof callBackMouseOver === 'function') { + dom.select(this).classed('milestones__group__label__text__event--hover', true); callBackMouseOver(d); } return d; @@ -94,6 +95,7 @@ export default function milestones(selector) { } function eventMouseLeave(d) { if (typeof callBackMouseOver === 'function') { + dom.select(this).classed('milestones__group__label__text__event--hover', false); callBackMouseLeave(d); } return d; @@ -336,72 +338,75 @@ 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 = '' + labelFormat(aggregateFormatParse(d.key)) + ''; - // 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 ''; - // } - // return t; - // }); - - // (above) ? lines.push(group) : lines.unshift(group); - - // return lines.join('
'); - // }); - - // 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)); + }) + .each(function(d) { + const above = isAbove(d.index); + + const element = dom.select(this); + element.empty(); + + if (!above) { + element.append('span') + .classed(cssTitleClass, true) + .text(labelFormat(aggregateFormatParse(d.key))); + element.append('br'); } - return ''; - }); - // add textEnter event - textEnter.selectAll('.' + cssEventClass) - .data(d => { - return d.values.map(v => { - return { + d.values.map((v, i) => { + if (i > 0) { + element.append('br'); + } + + const t = v[mapping.text]; + let item; + // 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) { + item = element.append('img') + .classed('milestones-label', true) + .classed('milestones-image-label', true) + .attr('height', '100') + .attr('src', t); + } else { + item = element.append('span') + .classed('milestones-label', true) + .classed('milestones-text-label', true) + .text(t); + } + + item.datum({ text: v[mapping.text], timestamp: v[mapping.timestamp], attributes: v, // original value of an object passed to the milestone - }; + }); + + if ( + typeof callbackClick === 'function' + || typeof callBackMouseLeave === 'function' + || typeof callBackMouseOver === 'function' + ) { + item.classed(cssEventClass, true); + } + + if (typeof callbackClick === 'function') { + item.on('click', eventClick); + } + + if (typeof callBackMouseLeave === 'function') { + item.on('mouseleave', eventMouseLeave); + } + + if (typeof callBackMouseOver === 'function') { + item.on('mouseover', eventMouseOver); + } }); - }) - .enter() - .append('div') - .on('click', eventClick) - .on('mouseleave', eventMouseLeave) - .on('mouseover', eventMouseOver) - .attr('class', cssEventClass) - .attr('data-date', d => d.timestamp) - .html(d => { - if (['jpg', 'jpeg', 'gif', 'png'].indexOf(d.text.split('.').pop()) > -1) { - return ''; - } - 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)); + + if (above) { + element.append('br'); + element.append('span') + .classed(cssTitleClass, true) + .text(labelFormat(aggregateFormatParse(d.key))); } - return ''; }); const textMerge = text.merge(textEnter); diff --git a/src/styles/styles.less b/src/styles/styles.less index adeefde..da54981 100644 --- a/src/styles/styles.less +++ b/src/styles/styles.less @@ -71,11 +71,9 @@ white-space: nowrap; } .milestones__group__label__text__event { - color: #000; - padding: 5px 0; cursor: pointer; } - .milestones__group__label__text__event:hover { + .milestones__group__label__text__event--hover { background: #efefef; color: #313131; - } \ No newline at end of file + } diff --git a/test/event-test.js b/test/event-test.js new file mode 100644 index 0000000..924a5e0 --- /dev/null +++ b/test/event-test.js @@ -0,0 +1,58 @@ +import tape from 'tape'; +import milestones from '../src/main'; +import * as d3 from 'd3-selection'; + +tape('should render a minimal milestones chart with attached events', t => { + document.body.insertAdjacentHTML( + 'afterbegin', + '
' + ); + + const 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' } + ]; + + const timeline = milestones('#wrapper') + .onEventClick((d) => { + t.equal(d.text, 'v1.0.0', 'click event text should match label text'); + }) + .onEventMouseOver((d) => { + t.equal(d.text, 'v1.0.0', 'mouseover event text should match label text'); + }) + .onEventMouseLeave((d) => { + t.equal(d.text, 'v1.0.0', 'mouseover event text should match label text'); + }) + .mapping({ + timestamp: 'timestamp', + text: 'detail' + }); + + timeline + .parseTime('%Y-%m-%dT%H:%M') + .aggregateBy('second') + .optimize(true) + .render(data); + + t.plan(3); + + d3.select('#wrapper .milestones-text-label').each(function (d, i) { + var onClickFunc = d3.select(this).on('click'); + onClickFunc.apply(this, [d, i]); + }); + + d3.select('#wrapper .milestones-text-label').each(function (d, i) { + var onClickFunc = d3.select(this).on('mouseover'); + onClickFunc.apply(this, [d, i]); + }); + + d3.select('#wrapper .milestones-text-label').each(function (d, i) { + var onClickFunc = d3.select(this).on('mouseleave'); + onClickFunc.apply(this, [d, i]); + }); + + t.end(); + + document.body.innerHTML = ''; +}); diff --git a/test/milestones-test.js b/test/milestones-test.js index 2a4238c..ff3fba3 100644 --- a/test/milestones-test.js +++ b/test/milestones-test.js @@ -33,4 +33,6 @@ tape('should render a minimal milestones chart', t => { t.equal(d3.selectAll('#wrapper .milestones .milestones__group').size(), 3, 'should render 3 .milestones__group elements'); t.end(); + + document.body.innerHTML = ''; });