diff --git a/demo/demo.js b/demo/demo.js index 4fb89fe58..0e4f4fcf9 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -2612,7 +2612,8 @@ var demos = { type: "line" }, subchart: { - show: "subchart()" + show: "subchart()", + showHandle: true } }, description: "Drag over subchart area to zoom main chart.
When is zoomed, try dragging zoom selection element or expand it dragging each edge(left/right)" diff --git a/src/ChartInternal/interactions/subchart.ts b/src/ChartInternal/interactions/subchart.ts index 46756e4a8..23bfbfcfe 100644 --- a/src/ChartInternal/interactions/subchart.ts +++ b/src/ChartInternal/interactions/subchart.ts @@ -20,14 +20,14 @@ export default { const $$ = this; const {config, scale, $el: {subchart}} = $$; const isRotated = config.axis_rotated; + let lastDomain; + let timeout; // set the brush - $$.brush = isRotated ? d3BrushY() : d3BrushX(); + $$.brush = ( + isRotated ? d3BrushY() : d3BrushX() + ).handleSize(5); - // set "brush" event - const brushHandler = () => { - $$.redrawForBrush(); - }; const getBrushSize = () => { const brush = $$.$el.svg.select(`.${CLASS.brush} .overlay`); const brushSize = {width: 0, height: 0}; @@ -40,18 +40,38 @@ export default { return brushSize[isRotated ? "width" : "height"]; }; - let lastDomain; - let timeout; + // bind brush event + $$.brush.on("start brush end", event => { + const {selection, target, type} = event; - $$.brush - .on("start", () => { + if (type === "start") { $$.state.inputType === "touch" && $$.hideTooltip(); - brushHandler(); - }) - .on("brush", brushHandler) - .on("end", () => { + } + + if (/(start|brush)/.test(type)) { + $$.redrawForBrush(); + } + + if (type === "end") { lastDomain = scale.x.orgDomain(); - }); + } + + // handle brush's handle position & visibility + if (target?.handle) { + if (selection === null) { + $$.brush.handle.attr("display", "none"); + } else { + $$.brush.handle.attr("display", null) + .attr("transform", (d, i) => { + const pos = isRotated ? + [33, selection[i] - (i === 0 ? 30 : 24)] : [selection[i], 3]; + + return `translate(${pos})`; + }); + } + } + }); + $$.brush.updateResize = function() { timeout && clearTimeout(timeout); @@ -149,11 +169,13 @@ export default { }); // Add extent rect for Brush - main.append("g") + const brush = main.append("g") .attr("clip-path", clipPath) .attr("class", CLASS.brush) .call($$.brush); + config.subchart_showHandle && $$.addBrushHandle(brush); + // ATTENTION: This must be called AFTER chart added // Add Axis axis.subX = main.append("g") @@ -163,6 +185,42 @@ export default { .style("visibility", config.subchart_axis_x_show ? visibility : "hidden"); }, + /** + * Add brush handle + * Enabled when: subchart.showHandle=true + * @param {d3Selection} brush Brush selection + * @private + */ + addBrushHandle(brush): void { + const $$ = this; + const {config} = $$; + const isRotated = config.axis_rotated; + const initRange = config.subchart_init_range; + const customHandleClass = "handle--custom"; + + // brush handle shape's path + const path = isRotated ? [ + "M 5.2491724,29.749209 a 6,6 0 0 0 -5.50000003,-6.5 H -5.7508276 a 6,6 0 0 0 -6.0000004,6.5 z m -5.00000003,-2 H -6.7508276 m 6.99999997,-2 H -6.7508276Z", + "M 5.2491724,23.249172 a 6,-6 0 0 1 -5.50000003,6.5 H -5.7508276 a 6,-6 0 0 1 -6.0000004,-6.5 z m -5.00000003,2 H -6.7508276 m 6.99999997,2 H -6.7508276Z" + ] : [ + "M 0 18 A 6 6 0 0 0 -6.5 23.5 V 29 A 6 6 0 0 0 0 35 Z M -2 23 V 30 M -4 23 V 30Z", + "M 0 18 A 6 6 0 0 1 6.5 23.5 V 29 A 6 6 0 0 1 0 35 Z M 2 23 V 30 M 4 23 V 30Z" + ]; + + + $$.brush.handle = brush.selectAll(`.${customHandleClass}`) + .data(isRotated ? + [{type: "n"}, {type: "s"}] : + [{type: "w"}, {type: "e"}] + ) + .enter() + .append("path") + .attr("class", customHandleClass) + .attr("cursor", `${isRotated ? "ns" : "ew"}-resize`) + .attr("d", d => path[+/[se]/.test(d.type)]) + .attr("display", initRange ? null : "none"); + }, + /** * Update sub chart * @param {object} targets $$.data.targets @@ -268,7 +326,6 @@ export default { $$.brush.getSelection(), initRange.map($$.scale.x) ); - // .call($$.brush.move, initRange.map($$.scale.x)); } } }, diff --git a/src/config/Options/interaction/subchart.ts b/src/config/Options/interaction/subchart.ts index e070218e3..675276570 100644 --- a/src/config/Options/interaction/subchart.ts +++ b/src/config/Options/interaction/subchart.ts @@ -16,6 +16,7 @@ export default { * @property {boolean} [subchart.show=false] Show sub chart on the bottom of the chart. * - **NOTE:** for ESM imports, needs to import 'subchart' exports and instantiate it by calling `subchart()`. * - `show: subchart()` + * @property {boolean} [subchart.showHandle=false] Show sub chart's handle. * @property {boolean} [subchart.axis.x.show=true] Show or hide x axis. * @property {boolean} [subchart.axis.x.tick.show=true] Show or hide x axis tick line. * @property {boolean} [subchart.axis.x.tick.text.show=true] Show or hide x axis tick text. @@ -27,6 +28,7 @@ export default { * @example * subchart: { * show: true, + * showHandle: true, * size: { * height: 20 * }, @@ -57,6 +59,7 @@ export default { * } */ subchart_show: false, + subchart_showHandle: false, subchart_size_height: 60, subchart_axis_x_show: true, subchart_axis_x_tick_show: true, diff --git a/test/interactions/subchart-spec.ts b/test/interactions/subchart-spec.ts index de5ec0204..81db03014 100644 --- a/test/interactions/subchart-spec.ts +++ b/test/interactions/subchart-spec.ts @@ -539,7 +539,59 @@ describe("SUBCHART", () => { }); }); - describe("", () => { - it - }) + describe("subchart handle", () => { + before(() => { + args = { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150] + ], + type: "line" + }, + subchart: { + show: true, + init: { + range: [0,0.5] + }, + showHandle: true + } + } + }); + + it("check the handlebar visiblity", () => { + const handlebar = chart.internal.brush.getSelection() + .selectAll(".handle--custom") + .each((d, i) => { + expect(d.type).to.be.equal(["w", "e"][i]); + }); + + const currDomain = chart.internal.scale.x.domain(); + + // when + util.doDrag(handlebar.node(), undefined, {clientX: 400, clientY: 100}, chart); + + expect(chart.internal.scale.x.domain()).to.be.not.deep.equal(currDomain); + }); + + it("set options axis.rotated=true", () => { + args.axis = { + rotated: true + }; + }); + + it("check the handlebar visiblity for rotated axis", () => { + const handlebar = chart.internal.brush.getSelection() + .selectAll(".handle--custom") + .each((d, i) => { + expect(d.type).to.be.equal(["n", "s"][i]); + }); + + const currDomain = chart.internal.scale.x.domain(); + + // when + util.doDrag(handlebar.node(), undefined, {clientX: 100, clientY: 0}, chart); + + expect(chart.internal.scale.x.domain()).to.be.not.deep.equal(currDomain); + }); + }); }); diff --git a/types/options.d.ts b/types/options.d.ts index 085bdb4cb..af8313534 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -1080,6 +1080,12 @@ export interface SubchartOptions { * Show sub chart on the bottom of the chart. */ show?: boolean; + + /** + * Show sub chart's handle. + */ + showHandle?: boolean; + size?: { /** * Change the height of the subchart.