Skip to content

Commit

Permalink
Merge pull request #86 from redbearsam/feature/tooltip_refactor
Browse files Browse the repository at this point in the history
Refactored tooltips into cartesian-chart component
  • Loading branch information
matt-hooper authored Mar 12, 2019
2 parents 1807f26 + 4d10af7 commit c31da4e
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 22 deletions.
14 changes: 13 additions & 1 deletion packages/perspective-viewer-d3fc/src/js/charts/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {withGridLines} from "../gridlines/gridlines";
import chartSvgCartesian from "../d3fc/chart/svg/cartesian";
import {hardLimitZeroPadding} from "../d3fc/padding/hardLimitZero";
import zoomableChart from "../zoom/zoomableChart";
import nearbyTip from "../tooltip/nearbyTip";

function lineChart(container, settings) {
const data = splitData(settings, filterData(settings));
Expand All @@ -35,7 +36,8 @@ function lineChart(container, settings) {
.padUnit("percent");

const xScale = crossAxis.scale(settings);
const chart = chartSvgCartesian(xScale, mainAxis.scale(settings))
const yScale = mainAxis.scale(settings);
const chart = chartSvgCartesian(xScale, yScale)
.xDomain(crossAxis.domain(settings)(data))
.yDomain(mainAxis.domain(settings).paddingStrategy(paddingStrategy)(data))
.yOrient("left")
Expand All @@ -53,10 +55,20 @@ function lineChart(container, settings) {
.settings(settings)
.xScale(xScale);

const toolTip = nearbyTip()
.chart(chart)
.settings(settings)
.xScale(xScale)
.yScale(yScale)
.colour(colour)
.data(data);
container.call(toolTip);

// render
container.datum(data).call(zoomChart);
container.call(legend);
}

lineChart.plugin = {
type: "d3_y_line",
name: "[d3fc] Y Line Chart",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {filterDataByGroup} from "../legend/filter";
import {withCanvasGridLines} from "../gridlines/gridlines";
import {hardLimitZeroPadding} from "../d3fc/padding/hardLimitZero";
import zoomableChart from "../zoom/zoomableChart";
import nearbyTip from "../tooltip/nearbyTip";

function xyScatterCanvas(container, settings) {
const data = pointData(settings, filterDataByGroup(settings));
Expand Down Expand Up @@ -69,6 +70,18 @@ function xyScatterCanvas(container, settings) {
.yScale(yScale)
.canvas(true);

const toolTip = nearbyTip()
.chart(chart)
.settings(settings)
.canvas(true)
.xScale(xScale)
.xValueName("x")
.yValueName("y")
.yScale(yScale)
.colour(useGroupColours && colour)
.data(data);
container.call(toolTip);

// render
container.datum(data).call(zoomChart);
if (legend) container.call(legend);
Expand Down
32 changes: 32 additions & 0 deletions packages/perspective-viewer-d3fc/src/js/data/findBest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/******************************************************************************
*
* Copyright (c) 2017, the Perspective Authors.
*
* This file is part of the Perspective library, distributed under the terms of
* the Apache License 2.0. The full license can be found in the LICENSE file.
*
*/

export const findBestFromData = (array, valueFn, compareFn = Math.min) => {
const findBestFromArray = array => {
return array.reduce((best, v) => {
const thisValue = findBestFromItem(v, valueFn);
return thisValue && (!best || compareFn(best.value, thisValue.value) === thisValue.value) ? thisValue : best;
}, null);
};
const findBestFromItem = item => {
if (Array.isArray(item)) {
return findBestFromArray(item, valueFn);
}
const value = valueFn(item);
return value !== null
? {
item,
value
}
: null;
};

const bestItem = findBestFromArray(array, valueFn);
return bestItem ? bestItem.item : null;
};
158 changes: 158 additions & 0 deletions packages/perspective-viewer-d3fc/src/js/tooltip/nearbyTip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/******************************************************************************
*
* Copyright (c) 2017, the Perspective Authors.
*
* This file is part of the Perspective library, distributed under the terms of
* the Apache License 2.0. The full license can be found in the LICENSE file.
*
*/

import * as d3 from "d3";
import * as fc from "d3fc";
import {chainCallback} from "../utils/utils";
import {tooltip} from "./tooltip";
import {withOpacity} from "../series/seriesColours.js";
import {findBestFromData} from "../data/findBest";

export default () => {
let chart = null;
let settings = null;
let xScale = null;
let xCopy = null;
let yScale = null;
let yCopy = null;
let colour = null;
let canvas = false;
let data = null;
let xValueName = "crossValue";
let yValueName = "mainValue";

const showTooltip = tooltip().alwaysShow(true);

function nearbyTip(selection) {
const chartPlotArea = `d3fc-${canvas ? "canvas" : "svg"}.plot-area`;
if (xScale || yScale) {
const pointer = fc.pointer().on("point", event => {
const tooltipData = event.length ? [getClosestDataPoint(event[0])] : [];

renderTip(selection, tooltipData);
});

chainCallback(chart.decorate, sel => {
sel.select(chartPlotArea)
.on("measure.nearby-tip", () => {
if (xCopy) xCopy.range([0, d3.event.detail.width]);
if (yCopy) yCopy.range([0, d3.event.detail.height]);
})
.call(pointer);
});
}
}

const renderTip = (selection, tipData) => {
const tips = selection
.select("d3fc-svg.plot-area svg")
.selectAll("circle.nearbyTip")
.data(tipData);
tips.exit().remove();

tips.enter()
.append("circle")
.attr("class", "nearbyTip")
.attr("r", 10)
.merge(tips)
.attr("transform", d => `translate(${xScale(d[xValueName])},${yScale(d[yValueName])})`)
.style("stroke", "none")
.style("fill", d => colour && withOpacity(colour(d.key)));

showTooltip(tips, settings);
};

const getClosestDataPoint = pos => {
return findBestFromData(
data,
v => {
if (v[yValueName] === undefined || v[yValueName] === null || v[xValueName] === undefined || v[xValueName] === null) return null;

return Math.sqrt(Math.pow(xScale(v[xValueName]) - pos.x, 2) + Math.pow(yScale(v[yValueName]) - pos.y, 2));
},
Math.min
);
};

nearbyTip.chart = (...args) => {
if (!args.length) {
return chart;
}
chart = args[0];
return nearbyTip;
};

nearbyTip.settings = (...args) => {
if (!args.length) {
return settings;
}
settings = args[0];
return nearbyTip;
};

nearbyTip.xScale = (...args) => {
if (!args.length) {
return xScale;
}
xScale = args[0];
xCopy = xScale ? xScale.copy() : null;
return nearbyTip;
};

nearbyTip.yScale = (...args) => {
if (!args.length) {
return yScale;
}
yScale = args[0];
yCopy = yScale ? yScale.copy() : null;
return nearbyTip;
};

nearbyTip.colour = (...args) => {
if (!args.length) {
return colour;
}
colour = args[0];
return nearbyTip;
};

nearbyTip.canvas = (...args) => {
if (!args.length) {
return canvas;
}
canvas = args[0];
return nearbyTip;
};

nearbyTip.data = (...args) => {
if (!args.length) {
return data;
}
data = args[0];
return nearbyTip;
};

nearbyTip.xValueName = (...args) => {
if (!args.length) {
return xValueName;
}
xValueName = args[0];
return nearbyTip;
};

nearbyTip.yValueName = (...args) => {
if (!args.length) {
return yValueName;
}
yValueName = args[0];
return nearbyTip;
};

return nearbyTip;
};
57 changes: 40 additions & 17 deletions packages/perspective-viewer-d3fc/src/js/tooltip/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,38 @@ import {generateHtmlDefault} from "./generateHTML";

export const tooltip = () => {
let generateHtml = generateHtmlDefault;
let alwaysShow = false;
let tooltipDiv = null;

const _tooltip = (selection, settings) => {
const node = selection.node();

if (!node || !node.isConnected) return;
if (!node || !node.isConnected) {
hideTooltip(tooltipDiv);
return;
}

const container = select(getChartElement(node).getContainer());
const tooltipDiv = getTooltipDiv(container);
selection
// .filter(d => d.baseValue !== d.mainValue)
.on("mouseover", function(data) {
generateHtml(tooltipDiv, data, settings);
showTooltip(container.node(), this, tooltipDiv);
select(this).style("opacity", "0.7");
})
.on("mouseout", function() {
hideTooltip(tooltipDiv);
select(this).style("opacity", "1");
});
tooltipDiv = getTooltipDiv(container);

const showTip = (data, i, nodes) => {
generateHtml(tooltipDiv, data, settings);
showTooltip(container.node(), nodes[i], tooltipDiv);
select(nodes[i]).style("opacity", "0.7");
};
const hideTip = (data, i, nodes) => {
hideTooltip(tooltipDiv);
if (nodes) select(nodes[i]).style("opacity", "1");
};

if (alwaysShow) {
selection.each(showTip);
} else {
selection
// .filter(d => d.baseValue !== d.mainValue)
.on("mouseover", showTip)
.on("mouseout", hideTip);
}
};

_tooltip.generateHtml = (...args) => {
Expand All @@ -35,6 +48,14 @@ export const tooltip = () => {
return _tooltip;
};

_tooltip.alwaysShow = (...args) => {
if (!args.length) {
return alwaysShow;
}
alwaysShow = args[0];
return _tooltip;
};

return _tooltip;
};

Expand Down Expand Up @@ -81,8 +102,10 @@ function shiftIfOverflowingChartArea(tooltipDiv, containerRect, left, top) {
}

function hideTooltip(tooltipDiv) {
tooltipDiv
.transition()
.duration(500)
.style("opacity", 0);
if (tooltipDiv) {
tooltipDiv
.transition()
.duration(500)
.style("opacity", 0);
}
}
17 changes: 17 additions & 0 deletions packages/perspective-viewer-d3fc/src/js/utils/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/******************************************************************************
*
* Copyright (c) 2017, the Perspective Authors.
*
* This file is part of the Perspective library, distributed under the terms of
* the Apache License 2.0. The full license can be found in the LICENSE file.
*
*/

export function areArraysEqualSimple(arr1, arr2) {
return JSON.stringify(arr1) === JSON.stringify(arr2);
}
Expand All @@ -6,3 +15,11 @@ export function getOrCreateElement(container, selector, createCallback) {
let element = container.select(selector);
return element.size() > 0 ? element : createCallback();
}

export const chainCallback = (property, fn) => {
const oldFn = property();
property((...args) => {
if (oldFn) oldFn(...args);
fn(...args);
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import * as d3 from "d3";
import {getOrCreateElement} from "../utils/utils";
import template from "../../html/zoom-controls.html";
import {chainCallback} from "../utils/utils";

export default () => {
let chart = null;
Expand Down Expand Up @@ -45,7 +46,7 @@ export default () => {
});
});

chart.decorate(sel => {
chainCallback(chart.decorate, sel => {
if (!bound) {
bound = true;
// add the zoom interaction on the enter selection
Expand Down
Loading

0 comments on commit c31da4e

Please sign in to comment.