Skip to content

Commit

Permalink
Merge pull request #98 from redbearsam/feature/legend_draggable_and_k…
Browse files Browse the repository at this point in the history
…eys_cropped

legend draggable and keys cropped
  • Loading branch information
redbearsam authored Mar 20, 2019
2 parents f8e1145 + f5c6631 commit fd1814b
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 4 deletions.
6 changes: 6 additions & 0 deletions packages/perspective-viewer-d3fc/src/js/legend/legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import scrollableLegend from "./scrollableLegend";
import {withoutOpacity} from "../series/seriesColors";
import {getChartElement} from "../plugin/root";
import {getOrCreateElement} from "../utils/utils";
import {draggableComponent} from "./styling/draggableComponent";

const scrollColorLegend = scrollableLegend(
d3Legend
Expand Down Expand Up @@ -45,6 +46,7 @@ function legendComponent(scrollLegend, scaleModifier) {
let settings = {};
let scale = null;
let color = null;
let draggable = draggableComponent();

function legend(container) {
if (scale) {
Expand Down Expand Up @@ -72,8 +74,11 @@ function legendComponent(scrollLegend, scaleModifier) {
// render the legend
legendSelection
.attr("class", "legend-container")
.attr("borderbox-on-hover", true)
.style("z-index", "2")
.call(scrollLegend);

draggable(legendSelection);
}
}

Expand All @@ -86,6 +91,7 @@ function legendComponent(scrollLegend, scaleModifier) {
.selectAll("g.cell");

cells.classed("hidden", isHidden);
cells.append("title").html(d => d);

if (color) {
cells
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as d3Legend from "d3-svg-legend";
import {rebindAll} from "d3fc";
import {areArraysEqualSimple, getOrCreateElement} from "../utils/utils";
import legendControlsTemplate from "../../html/legend-controls.html";
import {cropCellContents} from "./styling/cropCellContents";

export default fromLegend => {
const legend = fromLegend || d3Legend.legendColor();
Expand All @@ -33,6 +34,7 @@ export default fromLegend => {
const render = selection => {
renderControls(selection);
renderLegend(selection);
cropCellContents(selection);
};

const renderControls = selection => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/******************************************************************************
*
* 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 {isElementOverflowing} from "../../utils/utils";

export function cropCellContents(legendDiv) {
const legendCells = legendDiv.select("g.legendCells");
const legendDivRect = legendDiv.node().getBoundingClientRect();

if (!isElementOverflowing(legendDivRect, legendCells.node().getBoundingClientRect())) {
return;
}

const svg = legendDiv.select(".legend");

legendCells.selectAll(".label").text((d, i, nodes) => {
const cell = nodes[i];
if (isElementOverflowing(legendDivRect, cell.getBoundingClientRect())) {
const cutoffCharIndex = getCutoffCharacterIndex(cell, svg, legendDivRect);
return `${d.substring(0, cutoffCharIndex - 3)}...`;
} else {
return d;
}
});
}

function getCutoffCharacterIndex(cell, svg, legendDivRect) {
const cellRect = cell.getBoundingClientRect();
const cutoffCoord = svg.node().createSVGPoint();
// Sometimes the svg point can _just_ miss a character, so we fuzz it.
const fuzzyFactor = 3;
cutoffCoord.x = legendDivRect.right - cellRect.left - fuzzyFactor;
cutoffCoord.y = 0;
return cell.getCharNumAtPosition(cutoffCoord);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/******************************************************************************
*
* 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 {isElementOverflowing} from "../../utils/utils";
import {getChartElement} from "../../plugin/root";

const margin = 10;
const resizeForDraggingEvent = "resize.for-dragging";

export function draggableComponent() {
let pinned = true;

const draggable = element => {
const node = element.node();
node.style.cursor = "move";

const drag = d3.drag().on("drag", function() {
const [offsetX, offsetY] = enforceContainerBoundaries(this, d3.event.dx, d3.event.dy);
this.style.left = `${this.offsetLeft + offsetX}px`;
this.style.top = `${this.offsetTop + offsetY}px`;

const element = d3.select(this);
if (isNodeInTopRight(node)) {
pinned = pinNodeToTopRight(node);
return;
}

pinned = unpinNodeFromTopRight(node, element, pinned);
});

element.call(drag);
};

return draggable;
}

function unpinNodeFromTopRight(node, element, pinned) {
if (pinned !== false) {
// Default behaviour for the legend is to remain pinned to the top right hand corner with a specific margin.
// Once the legend has moved we cannot continue to use that css based approach.
d3.select(window).on(resizeForDraggingEvent, function() {
const [offsetX, offsetY] = enforceContainerBoundaries(node, 0, 0);
node.style.left = `${node.offsetLeft + offsetX}px`;
node.style.top = `${node.offsetTop + offsetY}px`;
});
}
return false;
}

function pinNodeToTopRight(node) {
d3.select(window).on(resizeForDraggingEvent, null);
node.style.left = "auto";
return true;
}

function isNodeInTopRight(node) {
const nodeRect = node.getBoundingClientRect();
const containerRect = d3
.select(getChartElement(node).getContainer())
.node()
.getBoundingClientRect();

const fuzz = 5;

return nodeRect.right + margin + fuzz >= containerRect.right && nodeRect.top - margin - fuzz <= containerRect.top;
}

function enforceContainerBoundaries(innerNode, offsetX, offsetY) {
const chartNodeRect = d3
.select(getChartElement(innerNode).getContainer())
.node()
.getBoundingClientRect();

const legendNodeRect = innerNode.getBoundingClientRect();

const draggedLegendNodeRect = {
top: legendNodeRect.top + offsetY - margin,
right: legendNodeRect.right + offsetX + margin,
bottom: legendNodeRect.bottom + offsetY + margin,
left: legendNodeRect.left + offsetX - margin
};

const adjustedOffsets = {x: offsetX, y: offsetY};
const boundaries = [{edge: "right", dimension: "x"}, {edge: "left", dimension: "x"}, {edge: "top", dimension: "y"}, {edge: "bottom", dimension: "y"}];

boundaries.forEach(bound => {
if (isElementOverflowing(chartNodeRect, draggedLegendNodeRect, bound.edge)) {
const adjustment = draggedLegendNodeRect[bound.edge] - chartNodeRect[bound.edge];
adjustedOffsets[bound.dimension] = adjustedOffsets[bound.dimension] - adjustment;
}
});

return [adjustedOffsets.x, adjustedOffsets.y];
}
6 changes: 3 additions & 3 deletions packages/perspective-viewer-d3fc/src/js/tooltip/tooltip.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {select} from "d3";
import {getChartElement} from "../plugin/root";
import {getOrCreateElement} from "../utils/utils";
import {getOrCreateElement, isElementOverflowing} from "../utils/utils";
import tooltipTemplate from "../../html/tooltip.html";
import {generateHtml} from "./generateHTML";

Expand Down Expand Up @@ -87,12 +87,12 @@ function showTooltip(containerNode, barNode, tooltipDiv) {
function shiftIfOverflowingChartArea(tooltipDiv, containerRect, left, top) {
const tooltipDivRect = tooltipDiv.node().getBoundingClientRect();

if (containerRect.right < tooltipDivRect.right) {
if (isElementOverflowing(containerRect, tooltipDivRect)) {
const leftAdjust = tooltipDivRect.right - containerRect.right;
tooltipDiv.style("left", `${left - leftAdjust}px`);
}

if (containerRect.bottom < tooltipDivRect.bottom) {
if (isElementOverflowing(containerRect, tooltipDivRect, "bottom")) {
const topAdjust = tooltipDivRect.bottom - containerRect.bottom;
tooltipDiv.style("top", `${top - topAdjust}px`);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/perspective-viewer-d3fc/src/js/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ export function getOrCreateElement(container, selector, createCallback) {
let element = container.select(selector);
return element.size() > 0 ? element : createCallback();
}

export function isElementOverflowing(containerRect, innerElementRect, direction = "right") {
if (direction === "right" || direction === "bottom") {
return containerRect[direction] < innerElementRect[direction] ? true : false;
}

if (direction === "left" || direction === "top") {
return containerRect[direction] > innerElementRect[direction] ? true : false;
}

throw `Direction being checked for overflow is invalid: ${direction}`;
}
14 changes: 13 additions & 1 deletion packages/perspective-viewer-d3fc/src/less/chart.less
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@
top: 15px;
right: 15px;
left: auto;
max-width: 150px;
width: 150px;

&[borderbox-on-hover="true"] {
&:hover {
box-shadow: 2px 2px 6px #000;
}
}

& .legend {
font-size: 8pt;
Expand All @@ -115,6 +121,12 @@
fill: var(--d3fc-legend-text, rgb(51, 51, 51));
}

& .label {
&:hover {
fill: rgb(34,160,206);
}
}

& #legend-axis path {
stroke: var(--d3fc-legend-text, rgb(51, 51, 51));
}
Expand Down

0 comments on commit fd1814b

Please sign in to comment.