Merge pull request #9 from foretold-app/feature/1088

Tooltips
This commit is contained in:
Ozzie Gooen 2020-02-27 11:43:19 +00:00 committed by GitHub
commit 9c0a0b708f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 19 deletions

View File

@ -86,6 +86,21 @@ module Styles = {
selector(".lollipops-x-axis .domain", [display(`none)]), selector(".lollipops-x-axis .domain", [display(`none)]),
selector(".lollipops-x-axis .tick line", [display(`none)]), selector(".lollipops-x-axis .tick line", [display(`none)]),
selector(".lollipops-x-axis .tick text", [display(`none)]), selector(".lollipops-x-axis .tick text", [display(`none)]),
selector(
".lollipop-tooltip",
[
position(`absolute),
textAlign(`center),
padding(px(2)),
backgroundColor(hex("bfcad4")),
borderRadius(px(3)),
],
),
selector(
".lollipops-circle-mouseover",
[SVG.fill(hex("ffa500")), SVG.stroke(`hex("fff"))],
),
selector(".lollipops-line-mouseover", [SVG.stroke(`hex("ffa500"))]),
]); ]);
}; };

View File

@ -1,7 +1,11 @@
const _ = require('lodash'); const _ = require('lodash');
const d3 = require('d3'); const d3 = require('d3');
const moment = require('moment'); const moment = require('moment');
require('./styles.css');
/**
* @todo: To rename as "DistPlotD3".
*/
export class CdfChartD3 { export class CdfChartD3 {
constructor() { constructor() {
@ -93,11 +97,11 @@ export class CdfChartD3 {
// Add svg. // Add svg.
this.svg = this._container this.svg = this._container
.createObject({ tag: 'svg', selector: 'svg-chart-container' }) .createObject({ tag: 'svg', selector: 'svg-chart-container' })
.attr('width', "100%") .attr('width', '100%')
.attr('height', this.attrs.svgHeight) .attr('height', this.attrs.svgHeight)
.attr('pointer-events', 'none'); .attr('pointer-events', 'none');
// Add container "g" (empty) element. // Add container 'g' (empty) element.
this.chart = this.svg this.chart = this.svg
.createObject({ tag: 'g', selector: 'chart' }) .createObject({ tag: 'g', selector: 'chart' })
.attr( .attr(
@ -171,13 +175,13 @@ export class CdfChartD3 {
.ticks(3) .ticks(3)
.tickFormat(d => { .tickFormat(d => {
if (Math.abs(d) < 1) { if (Math.abs(d) < 1) {
return d3.format(".2")(d); return d3.format('.2')(d);
} else if (xMin > 1000 && xMax < 3000) { } else if (xMin > 1000 && xMax < 3000) {
// Condition which identifies years; 2019, 2020, 2021. // Condition which identifies years; 2019, 2020, 2021.
return d3.format(".0")(d); return d3.format('.0')(d);
} else { } else {
const prefix = d3.formatPrefix(".0", d); const prefix = d3.formatPrefix('.0', d);
return prefix(d).replace("G", "B"); return prefix(d).replace('G', 'B');
} }
}); });
} }
@ -314,32 +318,77 @@ export class CdfChartD3 {
.domain([yMinDomain, yMaxDomain]) .domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0]); .range([this.calc.chartHeight, 0]);
// Adds "g" for an y-axis. // Adds 'g' for an y-axis.
this.chart.append('g') this.chart.append('g')
.attr('class', 'lollipops-y-axis') .attr('class', 'lollipops-y-axis')
.attr('transform', `translate(${this.calc.chartWidth}, 0)`) .attr('transform', `translate(${this.calc.chartWidth}, 0)`)
.call(d3.axisLeft(yScale)); .call(d3.axisLeft(yScale));
function showTooltip(d) {
d3.select('#lollipops-line-' + d.id)
.classed('lollipops-line-mouseover', true);
d3.select('#lollipops-circle-' + d.id)
.classed('lollipops-circle-mouseover', true)
.attr('r', 6);
tooltip.transition()
.style('opacity', .9);
tooltip.html(`X: ${d.x}, Y: ${d.y}`)
.style('left', (distributionChart.xScale(d.x) + 60) + 'px')
.style('top', yScale(d.y) + 'px');
}
function hideTooltip(d) {
d3.select('#lollipops-line-' + d.id)
.classed('lollipops-line-mouseover', false);
d3.select('#lollipops-circle-' + d.id)
.classed('lollipops-circle-mouseover', false)
.attr('r', 4);
tooltip.transition()
.style('opacity', 0);
}
// Lines. // Lines.
this.chart.selectAll('lollipops-line') this.chart.selectAll('lollipops-line')
.data(data) .data(data)
.enter() .enter()
.append('line') .append('line')
.attr('class', 'lollipops-line') .attr('class', 'lollipops-line')
.attr('id', d => 'lollipops-line-' + d.id)
.attr('x1', d => distributionChart.xScale(d.x)) .attr('x1', d => distributionChart.xScale(d.x))
.attr('x2', d => distributionChart.xScale(d.x)) .attr('x2', d => distributionChart.xScale(d.x))
.attr('y1', d => yScale(d.y)) .attr('y1', d => yScale(d.y))
.attr('y2', yScale(0)); .attr('y2', yScale(0));
// Define the div for the tooltip
const tooltip = this._container.append('div')
.attr('class', 'lollipop-tooltip')
.style('opacity', 0);
// Circles. // Circles.
this.chart.selectAll('lollipops-circle') this.chart.selectAll('lollipops-circle')
.data(data) .data(data)
.enter() .enter()
.append('circle') .append('circle')
.attr('class', 'lollipops-circle') .attr('class', 'lollipops-circle')
.attr('id', d => 'lollipops-circle-' + d.id)
.attr('cx', d => distributionChart.xScale(d.x)) .attr('cx', d => distributionChart.xScale(d.x))
.attr('cy', d => yScale(d.y)) .attr('cy', d => yScale(d.y))
.attr('r', '4'); .attr('r', '4');
// Rectangles.
this.chart.selectAll('lollipops-rectangle')
.data(data)
.enter()
.append('rect')
.attr('width', 30)
.attr('height', this.calc.chartHeight)
.attr('x', d => distributionChart.xScale(d.x) - 15)
.attr('y', 0)
.attr('opacity', 0)
.attr('pointer-events', 'all')
.on('mouseover', showTooltip)
.on('mouseout', hideTooltip)
;
} }
/** /**
@ -347,7 +396,7 @@ export class CdfChartD3 {
* @returns {string} * @returns {string}
*/ */
formatDates(ts) { formatDates(ts) {
return moment(ts).format("MMMM Do YYYY"); return moment(ts).format('MMMM Do YYYY');
} }
/** /**
@ -356,23 +405,23 @@ export class CdfChartD3 {
*/ */
getTimeTicksByStr(unit) { getTimeTicksByStr(unit) {
switch (unit) { switch (unit) {
case "months": case 'months':
return d3.timeMonth.every(4); return d3.timeMonth.every(4);
case "quarters": case 'quarters':
return d3.timeMonth.every(3); return d3.timeMonth.every(3);
case "hours": case 'hours':
return d3.timeHour.every(10); return d3.timeHour.every(10);
case "days": case 'days':
return d3.timeDay.every(7); return d3.timeDay.every(7);
case "seconds": case 'seconds':
return d3.timeSecond.every(10); return d3.timeSecond.every(10);
case "years": case 'years':
return d3.timeYear.every(10); return d3.timeYear.every(10);
case "minutes": case 'minutes':
return d3.timeMinute.every(10); return d3.timeMinute.every(10);
case "weeks": case 'weeks':
return d3.timeWeek.every(10); return d3.timeWeek.every(10);
case "milliseconds": case 'milliseconds':
return d3.timeMillisecond.every(10); return d3.timeMillisecond.every(10);
default: default:
return d3.timeYear.every(10); return d3.timeYear.every(10);
@ -390,7 +439,7 @@ export class CdfChartD3 {
const len = data.xs.length; const len = data.xs.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
dt.push({ x: data.xs[i], y: data.ys[i] }); dt.push({ x: data.xs[i], y: data.ys[i], id: i });
} }
return dt; return dt;

View File

@ -14,6 +14,7 @@ function getRandomInt(min, max) {
} }
/** /**
* @todo: To rename as "DistPlotReact".
* @param props * @param props
* @returns {*} * @returns {*}
* @constructor * @constructor

View File

@ -0,0 +1,10 @@
.lollipops-line-mouseover {
stroke-dasharray: 4;
animation: dash 2s linear infinite;
}
@keyframes dash {
to {
stroke-dashoffset: 1000;
}
}