diff --git a/spec/api/api.zoom-spec.js b/spec/api/api.zoom-spec.js index 97dc5a9d3..08302af0d 100644 --- a/spec/api/api.zoom-spec.js +++ b/spec/api/api.zoom-spec.js @@ -190,25 +190,23 @@ describe("API zoom", function() { }); it("should be zoomed properly", done => { - const target = [3, 5]; - const internal = chart.internal; - const main = internal.main; + const rectlist = chart.$.main.selectAll(`.${CLASS.eventRect}`).nodes(); + const rect = []; - const bars = main.select(`.${CLASS.chartBars}`).node(); - const rects = main.select(`.${CLASS.eventRects}`).node(); - const rectlist = main.selectAll(`.${CLASS.eventRect}`).nodes(); - const orgWidth = bars.getBoundingClientRect().width; - const rectWidth = internal.getEventRectWidth(); - - chart.zoom(target); + // when + chart.zoom([3, 5]); setTimeout(() => { - rectlist.forEach(v => { - expect(parseFloat(d3.select(v).attr("width"))).to.be.equal(rectWidth); - }); + rectlist.forEach(function(el, i) { + const x = +el.getAttribute("x"); + const width = +el.getAttribute("width"); + + if (i > 0) { + expect(rect[i - 1]).to.be.equal(x); + } - expect(bars.getBoundingClientRect().width/orgWidth).to.be.above(2.5); - expect(rects.getBoundingClientRect().width/orgWidth).to.be.above(2.5); + rect.push(x + width); + }); done(); }, 500) diff --git a/spec/interactions/interaction-spec.js b/spec/interactions/interaction-spec.js index 3bb1e7008..26c87f756 100644 --- a/spec/interactions/interaction-spec.js +++ b/spec/interactions/interaction-spec.js @@ -38,7 +38,7 @@ describe("INTERACTION", () => { const lefts = [69, 130, 198, 403]; const widths = [61, 68, 205, 197.5]; - chart.internal.main.selectAll(`.${CLASS.eventRect}`).each(function(d, i) { + chart.$.main.selectAll(`.${CLASS.eventRect}`).each(function (d, i) { const box = d3.select(this).node().getBoundingClientRect(); expect(box.left).to.be.closeTo(lefts[i], 10); @@ -62,11 +62,11 @@ describe("INTERACTION", () => { }); it("should have 1 event rects properly", () => { - const eventRects = chart.internal.main.selectAll(`.${CLASS.eventRect}`); + const eventRects = chart.$.main.selectAll(`.${CLASS.eventRect}`); expect(eventRects.size()).to.be.equal(1); - eventRects.each(function() { + eventRects.each(function () { const box = d3.select(this).node().getBoundingClientRect(); expect(box.left).to.be.closeTo(30.5, 10); @@ -92,7 +92,7 @@ describe("INTERACTION", () => { const lefts = [33.5, 185.5, 348, 497.5]; const widths = [152, 162.5, 149.5, 138.5]; - chart.internal.main.selectAll(`.${CLASS.eventRect}`).each(function (d, i) { + chart.$.main.selectAll(`.${CLASS.eventRect}`).each(function (d, i) { const box = d3.select(this).node().getBoundingClientRect(); expect(box.left).to.be.closeTo(lefts[i], 10); @@ -115,19 +115,48 @@ describe("INTERACTION", () => { }); it("should have 1 event rects properly", () => { - const eventRects = chart.internal.main.selectAll(`.${CLASS.eventRect}`); + const eventRects = chart.$.main.selectAll(`.${CLASS.eventRect}`); expect(eventRects.size()).to.be.equal(1); - eventRects.each(function() { + eventRects.each(function () { const box = d3.select(this).node().getBoundingClientRect(); expect(box.left).to.be.closeTo(30.5, 10); expect(box.width).to.be.closeTo(608, 10); }); }); + + describe("indexed", () => { + before(() => { + args = { + data: { + columns: [ + ["data", 10, 20, 30, 40, 50] + ] + } + }; + }); + + it("rect elements should be positioned without gaps", () => { + const rect = []; + + chart.$.main.selectAll(`.${CLASS.eventRect}`).each(function(d, i) { + const x = +this.getAttribute("x"); + const width = +this.getAttribute("width"); + + if (i > 0) { + expect(rect[i - 1]).to.be.equal(x); + } + + rect.push(x + width); + }); + }); + }); }); + }); + describe("Different interactions", () => { describe("check for data.onclick", () => { let clicked = false; let data; @@ -153,7 +182,7 @@ describe("INTERACTION", () => { }); it("check for data click for line", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}-0`).node(); const circle = main.select(`.${CLASS.circles}-data1 circle`).node().getBBox(); @@ -173,7 +202,7 @@ describe("INTERACTION", () => { }); it("check for data click for rectangle data point", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}`).node(); const circle = main.select(`.${CLASS.circles}-data1 rect`).node().getBBox(); @@ -196,7 +225,7 @@ describe("INTERACTION", () => { }); it("check for data click for polygon data point", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}`).node(); const circle = main.select(`.${CLASS.circles}-data2 use`).node().getBBox(); @@ -218,7 +247,7 @@ describe("INTERACTION", () => { }); it("check for data click for area", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}-0`).node(); const circle = main.select(`.${CLASS.circles}-data1 circle`).node().getBBox(); @@ -238,7 +267,7 @@ describe("INTERACTION", () => { }); it("check for data click for scatter", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}`).node(); const circle = main.select(`.${CLASS.circles}-data2 circle`).node().getBBox(); @@ -258,7 +287,7 @@ describe("INTERACTION", () => { }); it("check for data click for bubble", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}`).node(); const circle = main.select(`.${CLASS.circles}-data2 circle`).node().getBBox(); const delta = 50; @@ -279,7 +308,7 @@ describe("INTERACTION", () => { }); it("check for data click for bar", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRect}.${CLASS.eventRect}-0`).node(); const path = main.select(`.${CLASS.bars}-data1 path`).node().getBBox(); @@ -299,7 +328,7 @@ describe("INTERACTION", () => { }); it("check for data click for pie", () => { - const main = chart.internal.main; + const main = chart.$.main; const path = main.select(`.${CLASS.arcs}-data1 path`).node(); const box = path.getBBox(); @@ -319,7 +348,7 @@ describe("INTERACTION", () => { }); it("check for data click for gauge", () => { - const main = chart.internal.main; + const main = chart.$.main; const path = main.select(`.${CLASS.arcs}-data1 path`).node(); const box = path.getBBox(); @@ -358,7 +387,7 @@ describe("INTERACTION", () => { }); it("check for data click for multiple xs", () => { - const main = chart.internal.main; + const main = chart.$.main; const rect = main.select(`.${CLASS.eventRects}.${CLASS.eventRectsMultiple} rect`).node(); const circle = main.select(`.${CLASS.circles}.${CLASS.circles}-data1 circle`).node().getBBox(); diff --git a/src/interactions/interaction.js b/src/interactions/interaction.js index 49a127171..3a73dfa39 100644 --- a/src/interactions/interaction.js +++ b/src/interactions/interaction.js @@ -209,7 +209,10 @@ extend(ChartInternal.prototype, { let rectW; let rectX; - if (($$.isCustomX() || $$.isTimeSeries()) && !$$.isCategorized()) { + if ($$.isCategorized()) { + rectW = $$.getEventRectWidth(); + rectX = d => xScale(d.x) - (rectW / 2); + } else { // update index for x that is used by prevX and nextX $$.updateXs(); @@ -249,9 +252,6 @@ extend(ChartInternal.prototype, { return (xScale(thisX) + xScale(prevX)) / 2; }; - } else { - rectW = $$.getEventRectWidth(); - rectX = d => xScale(d.x) - (rectW / 2); } x = isRotated ? 0 : rectX; diff --git a/src/internals/ChartInternal.js b/src/internals/ChartInternal.js index 607a744b0..92e238fae 100644 --- a/src/internals/ChartInternal.js +++ b/src/internals/ChartInternal.js @@ -59,6 +59,7 @@ export default class ChartInternal { init() { const $$ = this; const config = $$.config; + let convertedData; $$.initParams(); @@ -71,14 +72,16 @@ export default class ChartInternal { $$.initWithData ); } else if (config.data_json) { - $$.initWithData($$.convertJsonToData(config.data_json, config.data_keys)); + convertedData = $$.convertJsonToData(config.data_json, config.data_keys); } else if (config.data_rows) { - $$.initWithData($$.convertRowsToData(config.data_rows)); + convertedData = $$.convertRowsToData(config.data_rows); } else if (config.data_columns) { - $$.initWithData($$.convertColumnsToData(config.data_columns)); + convertedData = $$.convertColumnsToData(config.data_columns); } else { throw Error("url or json or rows or columns is required."); } + + convertedData && $$.initWithData(convertedData); } initParams() { @@ -119,7 +122,7 @@ export default class ChartInternal { $$.dataTimeFormat = config.data_xLocaltime ? d3TimeParse : d3UtcParse; $$.axisTimeFormat = config.axis_x_localtime ? d3TimeFormat : d3UtcFormat; - $$.defaultAxisTimeFormat = function(d) { + $$.defaultAxisTimeFormat = d => { const specifier = (d.getMilliseconds() && ".%L") || (d.getSeconds() && ".:%S") || (d.getMinutes() && "%I:%M") || @@ -138,20 +141,17 @@ export default class ChartInternal { $$.defocusedTargetIds = []; $$.xOrient = isRotated ? "left" : "bottom"; - $$.yOrient = isRotated ? (config.axis_y_inner ? "top" : "bottom") : (config.axis_y_inner ? "right" : "left"); - $$.y2Orient = isRotated ? (config.axis_y2_inner ? "bottom" : "top") : (config.axis_y2_inner ? "left" : "right"); - $$.subXOrient = isRotated ? "left" : "bottom"; + $$.isLegendRight = config.legend_position === "right"; $$.isLegendInset = config.legend_position === "inset"; $$.isLegendTop = config.legend_inset_anchor === "top-left" || config.legend_inset_anchor === "top-right"; - $$.isLegendLeft = config.legend_inset_anchor === "top-left" || config.legend_inset_anchor === "bottom-left"; @@ -160,9 +160,7 @@ export default class ChartInternal { $$.legendItemHeight = 0; $$.currentMaxTickWidths = { - x: 0, - y: 0, - y2: 0 + x: 0, y: 0, y2: 0 }; $$.rotated_padding_left = 30; @@ -427,13 +425,10 @@ export default class ChartInternal { if (type === "grid") { el.each(function() { const g = d3Select(this); + const [x1, x2, y1, y2] = ["x1", "x2", "y1", "y2"] + .map(v => Math.ceil(g.attr(v))); - g.attr({ - "x1": Math.ceil(g.attr("x1")), - "x2": Math.ceil(g.attr("x2")), - "y1": Math.ceil(g.attr("y1")), - "y2": Math.ceil(g.attr("y2")) - }); + g.attr({x1, x2, y1, y2}); }); } } @@ -441,11 +436,13 @@ export default class ChartInternal { updateSizes() { const $$ = this; const config = $$.config; + const isRotated = config.axis_rotated; + const hasArc = $$.hasArcType(); + const legendHeight = $$.legend ? $$.getLegendHeight() : 0; const legendWidth = $$.legend ? $$.getLegendWidth() : 0; const legendHeightForBottom = $$.isLegendRight || $$.isLegendInset ? 0 : legendHeight; - const hasArc = $$.hasArcType(); - const xAxisHeight = config.axis_rotated || hasArc ? 0 : $$.getHorizontalAxisHeight("x"); + const xAxisHeight = isRotated || hasArc ? 0 : $$.getHorizontalAxisHeight("x"); const subchartHeight = config.subchart_show && !hasArc ? (config.subchart_size_height + xAxisHeight) : 0; @@ -453,7 +450,7 @@ export default class ChartInternal { $$.currentHeight = $$.getCurrentHeight(); // for main - $$.margin = config.axis_rotated ? { + $$.margin = isRotated ? { top: $$.getHorizontalAxisHeight("y2") + $$.getCurrentPaddingTop(), right: hasArc ? 0 : $$.getCurrentPaddingRight(), bottom: $$.getHorizontalAxisHeight("y") + legendHeightForBottom + $$.getCurrentPaddingBottom(), @@ -466,7 +463,7 @@ export default class ChartInternal { }; // for subchart - $$.margin2 = config.axis_rotated ? { + $$.margin2 = isRotated ? { top: $$.margin.top, right: NaN, bottom: 20 + legendHeightForBottom, @@ -501,10 +498,10 @@ export default class ChartInternal { $$.height = 0; } - $$.width2 = config.axis_rotated ? + $$.width2 = isRotated ? $$.margin.left - $$.rotated_padding_left - $$.rotated_padding_right : $$.width; - $$.height2 = config.axis_rotated ? + $$.height2 = isRotated ? $$.height : $$.currentHeight - $$.margin2.top - $$.margin2.bottom; if ($$.width2 < 0) { $$.width2 = 0; } @@ -571,6 +568,36 @@ export default class ChartInternal { .style("opacity", "1"); } + getWithOption(options) { + const withOptions = { + Y: true, + SubChart: true, + Transition: true, + EventRect: true, + Dimension: true, + TrimXDomain: true, + Transform: false, + UpdateXDomain: false, + UpdateOrgXDomain: false, + Legend: false, + UpdateXAxis: "UpdateXDomain", + TransitionForExit: "Transition", + TransitionForAxis: "Transition" + }; + + Object.keys(withOptions).forEach(key => { + let defVal = withOptions[key]; + + if (isString(defVal)) { + defVal = withOptions[defVal]; + } + + withOptions[key] = getOption(options, `with${key}`, defVal); + }); + + return withOptions; + } + redraw(options = {}, transitionsValue) { const $$ = this; const main = $$.main; @@ -590,23 +617,10 @@ export default class ChartInternal { let intervalForCulling; let xDomainForZoom; - const withY = getOption(options, "withY", true); - const withSubchart = getOption(options, "withSubchart", true); - const withTransition = getOption(options, "withTransition", true); - const withTransform = getOption(options, "withTransform", false); - const withUpdateXDomain = getOption(options, "withUpdateXDomain", false); - const withUpdateOrgXDomain = getOption(options, "withUpdateOrgXDomain", false); - const withTrimXDomain = getOption(options, "withTrimXDomain", true); - const withUpdateXAxis = getOption(options, "withUpdateXAxis", withUpdateXDomain); - const withLegend = getOption(options, "withLegend", false); - const withEventRect = getOption(options, "withEventRect", true); - const withDimension = getOption(options, "withDimension", true); - const withTransitionForExit = getOption(options, "withTransitionForExit", withTransition); - const withTransitionForAxis = getOption(options, "withTransitionForAxis", withTransition); - - const duration = withTransition ? config.transition_duration : 0; - const durationForExit = withTransitionForExit ? duration : 0; - const durationForAxis = withTransitionForAxis ? duration : 0; + const wth = $$.getWithOption(options); + const duration = wth.Transition ? config.transition_duration : 0; + const durationForExit = wth.TransitionForExit ? duration : 0; + const durationForAxis = wth.TransitionForAxis ? duration : 0; const transitions = transitionsValue || $$.axis.generateTransitions(durationForAxis); @@ -614,9 +628,9 @@ export default class ChartInternal { $$.inputType === "touch" && $$.hideTooltip(); // update legend and transform each g - if (withLegend && config.legend_show && !config.legend_contents_bindto) { + if (wth.Legend && config.legend_show && !config.legend_contents_bindto) { $$.updateLegend($$.mapToIds($$.data.targets), options, transitions); - } else if (withDimension) { + } else if (wth.Dimension) { // need to update dimension (e.g. axis.y.tick.values) because y tick values should change // no need to update axis in it because they will be updated in redraw() $$.updateDimension(true); @@ -628,7 +642,7 @@ export default class ChartInternal { } if (targetsToShow.length) { - $$.updateXDomain(targetsToShow, withUpdateXDomain, withUpdateOrgXDomain, withTrimXDomain); + $$.updateXDomain(targetsToShow, wth.UpdateXDomain, wth.UpdateOrgXDomain, wth.TrimXDomain); if (!config.axis_x_tick_values) { tickValues = $$.axis.updateXAxisTickValues(targetsToShow); @@ -668,10 +682,10 @@ export default class ChartInternal { $$.axis.redraw(transitions, hideAxis); // Update axis label - $$.axis.updateLabels(withTransition); + $$.axis.updateLabels(wth.Transition); // show/hide if manual culling needed - if ((withUpdateXDomain || withUpdateXAxis) && targetsToShow.length) { + if ((wth.UpdateXDomain || wth.UpdateXAxis) && targetsToShow.length) { if (config.axis_x_tick_culling && tickValues) { for (let i = 1; i < tickValues.length; i++) { if (tickValues.length / i < config.axis_x_tick_culling_max) { @@ -705,7 +719,7 @@ export default class ChartInternal { const yForText = $$.generateXYForText(areaIndices, barIndices, lineIndices, false); // Update sub domain - if (withY) { + if (wth.Y) { $$.subY.domain($$.getYDomain(targetsToShow, "y")); $$.subY2.domain($$.getYDomain(targetsToShow, "y2")); } @@ -742,7 +756,7 @@ export default class ChartInternal { $$.redrawTitle && $$.redrawTitle(); // arc - $$.redrawArc && $$.redrawArc(duration, durationForExit, withTransform); + $$.redrawArc && $$.redrawArc(duration, durationForExit, wth.Transform); // radar hasRadar && $$.redrawRadar(); @@ -751,7 +765,7 @@ export default class ChartInternal { config.subchart_show && $$.redrawSubchart && $$.redrawSubchart( - withSubchart, + wth.Subchart, transitions, duration, durationForExit, @@ -767,7 +781,7 @@ export default class ChartInternal { .remove(); // event rects will redrawn when flow called - if (config.interaction_enabled && !options.flow && withEventRect) { + if (config.interaction_enabled && !options.flow && wth.EventRect) { $$.redrawEventRect(); $$.bindZoomEvent(); } diff --git a/src/internals/util.js b/src/internals/util.js index 75f2df744..5121fd3cd 100644 --- a/src/internals/util.js +++ b/src/internals/util.js @@ -43,6 +43,7 @@ const isObject = obj => obj && !obj.nodeType && isObjectType(obj) && !isArray(ob const getOption = (options, key, defaultValue) => ( isDefined(options[key]) ? options[key] : defaultValue ); + const hasValue = (dict, value) => { let found = false; @@ -69,46 +70,25 @@ const getRectSegList = path => { * seg0 ---------- seg3 * */ const bbox = path.getBBox(); - const list = []; - - // seg0 - list.push({ - x: bbox.x, - y: bbox.y + bbox.height - }); - - // seg1 - list.push({ - x: bbox.x, - y: bbox.y - }); - - // seg2 - list.push({ - x: bbox.x + bbox.width, - y: bbox.y - }); - - // seg3 - list.push({ - x: bbox.x + bbox.width, - y: bbox.y + bbox.height - }); - - return list; + const [x, y, width, height] = [bbox.x, bbox.y, bbox.width, bbox.height]; + + return [ + {x, y: y + height}, // seg0 + {x, y}, // seg1 + {x: x + width, y}, // seg2 + {x: x + width, y: y + height} // seg3 + ]; }; const getPathBox = path => { const box = path.getBoundingClientRect(); + const [width, height] = [box.width, box.height]; const items = getRectSegList(path); - const minX = items[0].x; - const minY = Math.min(items[0].y, items[1].y); + const x = items[0].x; + const y = Math.min(items[0].y, items[1].y); return { - x: minX, - y: minY, - width: box.width, - height: box.height, + x, y, width, height }; };