From 6b4001c15d329674bf3c848acf30b362e8650e17 Mon Sep 17 00:00:00 2001 From: Julien Castelain Date: Tue, 13 Feb 2018 15:57:31 +0100 Subject: [PATCH 1/3] feat(legend): Add shapes to legend Fix #269 --- src/config/classes.js | 1 + src/internals/legend.js | 113 +++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/config/classes.js b/src/config/classes.js index 19b74b152..a4f559a67 100644 --- a/src/config/classes.js +++ b/src/config/classes.js @@ -76,6 +76,7 @@ export default { legendItem: "bb-legend-item", legendItemEvent: "bb-legend-item-event", legendItemTile: "bb-legend-item-tile", + legendItemPoint: "bb-legend-item-point", legendItemHidden: "bb-legend-item-hidden", legendItemFocused: "bb-legend-item-focused", dragarea: "bb-dragarea", diff --git a/src/internals/legend.js b/src/internals/legend.js index 31e04f49c..7eccc4422 100644 --- a/src/internals/legend.js +++ b/src/internals/legend.js @@ -4,11 +4,12 @@ */ import { select as d3Select, - event as d3Event + event as d3Event, + namespaces as d3Namespaces } from "d3"; import ChartInternal from "./ChartInternal"; import CLASS from "../config/classes"; -import {extend, isDefined, getOption, isEmpty, isFunction} from "./util"; +import {extend, isDefined, getOption, isEmpty, isFunction, notEmpty} from "./util"; extend(ChartInternal.prototype, { /** @@ -466,6 +467,7 @@ extend(ChartInternal.prototype, { } }; + if ($$.isLegendInset) { step = config.legend_inset_step ? config.legend_inset_step : targetIdz.length; $$.updateLegendStep(step); @@ -513,15 +515,47 @@ extend(ChartInternal.prototype, { .attr("x", $$.isLegendRight || $$.isLegendInset ? xForLegendRect : -200) .attr("y", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendRect); - l.append("line") - .attr("class", CLASS.legendItemTile) - .style("stroke", $$.color) - .style("pointer-events", "none") - .attr("x1", $$.isLegendRight || $$.isLegendInset ? x1ForLegendTile : -200) - .attr("y1", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) - .attr("x2", $$.isLegendRight || $$.isLegendInset ? x2ForLegendTile : -200) - .attr("y2", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) - .attr("stroke-width", config.legend_item_tile_height); + const hasCustomPoints = $$.config.point_pattern.length; + + if (!hasCustomPoints) { + l.append("line") + .attr("class", CLASS.legendItemTile) + .style("stroke", $$.color) + .style("pointer-events", "none") + .attr("x1", $$.isLegendRight || $$.isLegendInset ? x1ForLegendTile : -200) + .attr("y1", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) + .attr("x2", $$.isLegendRight || $$.isLegendInset ? x2ForLegendTile : -200) + .attr("y2", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) + .attr("stroke-width", config.legend_item_tile_height); + } else { + const ids = []; + + l.append(d => { + const pattern = notEmpty(config.point_pattern) ? config.point_pattern : [config.point_type]; + + if (ids.indexOf(d) === -1) { + ids.push(d); + } + let point = pattern[ids.indexOf(d) % pattern.length]; + + if (point === "rectangle") { + point = "rect"; + } + + const nodeType = $$.hasValidPointType(point) ? point : "use"; + + return document.createElementNS(d3Namespaces.svg, nodeType); + }) + .attr("class", CLASS.legendItemPoint) + .style("fill", d => $$.color(d)) + .style("pointer-events", "none") + .attr("href", (data, idx, selection) => { + const node = selection[idx]; + const nodeName = node.nodeName.toLowerCase(); + + return nodeName === "use" ? `#${$$.datetimeId}-point-${data}` : undefined; + }); + } // Set background for inset legend background = $$.legend.select(`.${CLASS.legendBackground} rect`); @@ -552,15 +586,56 @@ extend(ChartInternal.prototype, { .attr("x", xForLegendRect) .attr("y", yForLegendRect); - const tiles = $$.legend.selectAll(`line.${CLASS.legendItemTile}`) - .data(targetIdz); - (withTransition ? tiles.transition() : tiles) - .style("stroke", $$.color) - .attr("x1", x1ForLegendTile) - .attr("y1", yForLegendTile) - .attr("x2", x2ForLegendTile) - .attr("y2", yForLegendTile); + if (!hasCustomPoints) { + const tiles = $$.legend.selectAll(`line.${CLASS.legendItemTile}`) + .data(targetIdz); + + + (withTransition ? tiles.transition() : tiles) + .style("stroke", $$.color) + .attr("x1", x1ForLegendTile) + .attr("y1", yForLegendTile) + .attr("x2", x2ForLegendTile) + .attr("y2", yForLegendTile); + } else { + const tiles = $$.legend.selectAll(`.${CLASS.legendItemPoint}`) + .data(targetIdz); + + (withTransition ? tiles.transition() : tiles) + .each((data, idx, selection) => { + const node = selection[idx]; + const nodeName = node.nodeName.toLowerCase(); + const d3Node = d3Select(node); + let x = "x"; + let y = "y"; + let yOffset = 2; + let radius; + let width; + let height; + + if (nodeName === "circle") { + x = "cx"; + y = "cy"; + radius = $$.config.point_r; + yOffset = -($$.config.point_r); + } + + if (nodeName === "rect") { + width = $$.config.point_r * 2; + height = $$.config.point_r * 2; + yOffset = 0; + } + + + d3Node + .attr(x, x1ForLegendTile) + .attr(y, d => yForLegendTile(d) - yOffset) + .attr("r", radius) + .attr("width", width) + .attr("height", height); + }); + } if (background) { (withTransition ? background.transition() : background) From 9ea7c1494b52121a0f446ff9c948982b531305b2 Mon Sep 17 00:00:00 2001 From: Julien Castelain Date: Fri, 16 Feb 2018 13:35:39 +0100 Subject: [PATCH 2/3] feat(legend): Add test for custom points --- spec/internals/legend-spec.js | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/spec/internals/legend-spec.js b/spec/internals/legend-spec.js index 843c458a4..e6951bc64 100644 --- a/spec/internals/legend-spec.js +++ b/spec/internals/legend-spec.js @@ -345,4 +345,42 @@ describe("LEGEND", () => { expect(items.size()).to.be.equal(1); }); }); + + describe("when using custom points", () => { + + before(() => { + args = { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150, 250], + ["data2", 130, 100, 200, 100, 250, 150], + ["data3", 60, 190, 320, 520, 20, 300], + ["data4", 80, 20, 250, 320, 180, 50] + ] + }, + point: { + pattern: [ + "circle", + "rectangle", + "" + ] + } + }; + }); + + it("should render custom points in legend", () => { + const nodes = chart.internal.svg.selectAll(".bb-legend-item .bb-legend-item-point"); + + nodes.each((data, idx, selection) => { + const node = selection[idx]; + const nodeName = node.nodeName.toLowerCase(); + const expected = (idx === 0 || idx === 3) ? + "circle" : (idx === 1) ? "rect" : (idx === 2) ? "use" : ""; + + expect(nodeName).to.be.equal(expected); + }); + + expect(nodes.size()).to.be.equal(4); + }); + }); }); From 18d99be627eaceee3f49fca3aa2f89aacf719bc7 Mon Sep 17 00:00:00 2001 From: Julien Castelain Date: Mon, 19 Feb 2018 10:36:41 +0100 Subject: [PATCH 3/3] feat(legend): Add `legend.usePoint` option --- spec/internals/legend-spec.js | 3 ++ src/config/Options.js | 2 ++ src/internals/legend.js | 56 +++++++++++++++++------------------ 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/spec/internals/legend-spec.js b/spec/internals/legend-spec.js index e6951bc64..4d6cdf9d5 100644 --- a/spec/internals/legend-spec.js +++ b/spec/internals/legend-spec.js @@ -358,6 +358,9 @@ describe("LEGEND", () => { ["data4", 80, 20, 250, 320, 180, 50] ] }, + legend: { + usePoint: true + }, point: { pattern: [ "circle", diff --git a/src/config/Options.js b/src/config/Options.js index 081c8b58c..9a50a5354 100644 --- a/src/config/Options.js +++ b/src/config/Options.js @@ -1063,6 +1063,7 @@ export default class Options { * @property {Function} [legend.item.onout=undefined] Set mouse/touch out event handler to the legend item. * @property {Number} [legend.item.tile.width=10] Set width of item tile element * @property {Number} [legend.item.tile.height=10] Set height of item tile element + * @property {Boolean} [legend.usePoint=false] Whether to use custom points in legend. * @example * legend: { * show: true, @@ -1121,6 +1122,7 @@ export default class Options { legend_padding: 0, legend_item_tile_width: 10, legend_item_tile_height: 10, + legend_usePoint: false, /** * Switch x and y axis position. diff --git a/src/internals/legend.js b/src/internals/legend.js index 7eccc4422..ede4b3aac 100644 --- a/src/internals/legend.js +++ b/src/internals/legend.js @@ -515,19 +515,9 @@ extend(ChartInternal.prototype, { .attr("x", $$.isLegendRight || $$.isLegendInset ? xForLegendRect : -200) .attr("y", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendRect); - const hasCustomPoints = $$.config.point_pattern.length; + const usePoint = $$.config.legend_usePoint; - if (!hasCustomPoints) { - l.append("line") - .attr("class", CLASS.legendItemTile) - .style("stroke", $$.color) - .style("pointer-events", "none") - .attr("x1", $$.isLegendRight || $$.isLegendInset ? x1ForLegendTile : -200) - .attr("y1", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) - .attr("x2", $$.isLegendRight || $$.isLegendInset ? x2ForLegendTile : -200) - .attr("y2", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) - .attr("stroke-width", config.legend_item_tile_height); - } else { + if (usePoint) { const ids = []; l.append(d => { @@ -555,6 +545,16 @@ extend(ChartInternal.prototype, { return nodeName === "use" ? `#${$$.datetimeId}-point-${data}` : undefined; }); + } else { + l.append("line") + .attr("class", CLASS.legendItemTile) + .style("stroke", $$.color) + .style("pointer-events", "none") + .attr("x1", $$.isLegendRight || $$.isLegendInset ? x1ForLegendTile : -200) + .attr("y1", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) + .attr("x2", $$.isLegendRight || $$.isLegendInset ? x2ForLegendTile : -200) + .attr("y2", $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) + .attr("stroke-width", config.legend_item_tile_height); } // Set background for inset legend @@ -587,18 +587,7 @@ extend(ChartInternal.prototype, { .attr("y", yForLegendRect); - if (!hasCustomPoints) { - const tiles = $$.legend.selectAll(`line.${CLASS.legendItemTile}`) - .data(targetIdz); - - - (withTransition ? tiles.transition() : tiles) - .style("stroke", $$.color) - .attr("x1", x1ForLegendTile) - .attr("y1", yForLegendTile) - .attr("x2", x2ForLegendTile) - .attr("y2", yForLegendTile); - } else { + if (usePoint) { const tiles = $$.legend.selectAll(`.${CLASS.legendItemPoint}`) .data(targetIdz); @@ -609,7 +598,7 @@ extend(ChartInternal.prototype, { const d3Node = d3Select(node); let x = "x"; let y = "y"; - let yOffset = 2; + let yOffset = 2.5; let radius; let width; let height; @@ -617,13 +606,13 @@ extend(ChartInternal.prototype, { if (nodeName === "circle") { x = "cx"; y = "cy"; - radius = $$.config.point_r; + radius = $$.config.point_r + ($$.config.point_r * 0.2); yOffset = -($$.config.point_r); } if (nodeName === "rect") { - width = $$.config.point_r * 2; - height = $$.config.point_r * 2; + width = $$.config.point_r * 2.5; + height = $$.config.point_r * 2.5; yOffset = 0; } @@ -635,6 +624,17 @@ extend(ChartInternal.prototype, { .attr("width", width) .attr("height", height); }); + } else { + const tiles = $$.legend.selectAll(`line.${CLASS.legendItemTile}`) + .data(targetIdz); + + + (withTransition ? tiles.transition() : tiles) + .style("stroke", $$.color) + .attr("x1", x1ForLegendTile) + .attr("y1", yForLegendTile) + .attr("x2", x2ForLegendTile) + .attr("y2", yForLegendTile); } if (background) {