diff --git a/demo/demo.js b/demo/demo.js index a764f6ac3..f7b877184 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -1374,6 +1374,37 @@ var demos = { } } }, + YAxisTickCulling: { + options: { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150, 250, 30, 200, 100, 400, 150, 250, 30, 200, 100, 400, 150, 250, 200, 100, 400, 150, 250], + ["data2", 130, 100, 200, 250, 250, 150, 230, 300, 200, 300, 250, 150, 330, 100, 200, 100, 350, 50, 100, 200, 300, 250, 150] + ], + axes: { + data2: "y2" + }, + types: { + data1: "bar" + } + }, + axis: { + y: { + tick: { + culling: { + max: 3 + } + } + }, + y2: { + show: true, + tick: { + culling: true + } + } + } + } + }, YAxisTickFormat: { options: { data: { diff --git a/spec/internals/axis-spec.js b/spec/internals/axis-spec.js index d950d887b..6957f7a6b 100644 --- a/spec/internals/axis-spec.js +++ b/spec/internals/axis-spec.js @@ -1528,4 +1528,71 @@ describe("AXIS", function() { ); }); }); + + describe("Axes tick culling", () => { + before(() => { + args = { + data: { + columns: [ + ["data1", 30, 200, 100, 400, 150, 250, 30, 200, 100, 400, 150, 250, 30, 200, 100, 400, 150, 250, 200, 100, 400, 150, 250], + ["data2", 30, 200, 100, 400, 150, 250, 30, 200, 100, 400, 150, 250, 30, 200, 100, 400, 150, 250, 200, 100, 400, 150, 250] + ], + axes: { + data2: "y2" + } + }, + axis: { + x: { + tick: { + culling: { + max: 4 + } + } + }, + y: { + tick: { + culling: { + max: 3 + } + } + }, + y2: { + show: true, + tick: { + culling: true + } + } + } + }; + }); + + const checkTickValues = () => { + const expected = { + x: [0, 6, 12, 18], + y: [0, 200, 400], + y2: [0, 100, 200, 300, 400] + }; + + ["x", "y", "y2"].forEach(v => { + const data = chart.internal.axes[v] + .selectAll(".tick text").filter(function() { + return this.style.display === "block"; + }).data(); + + expect(data).to.be.deep.equal(expected[v]); + }); + } + + it("check tick values are culled", () => { + checkTickValues(); + }); + + it("set options axis.rotated=true", () => { + args.axis.rotated = true; + }); + + it("check tick values are culled when axis is rotated", () => { + checkTickValues(); + }); + }); }); diff --git a/src/axis/Axis.js b/src/axis/Axis.js index c9056fdd7..57d63321e 100644 --- a/src/axis/Axis.js +++ b/src/axis/Axis.js @@ -9,7 +9,7 @@ import { axisRight as d3AxisRight } from "d3-axis"; import CLASS from "../config/classes"; -import {capitalize, isArray, isFunction, isString, isValue, isEmpty, isNumber, isObjectType} from "../internals/util"; +import {capitalize, isArray, isFunction, isString, isValue, isEmpty, isNumber, isObjectType, sortValue} from "../internals/util"; import AxisRenderer from "./AxisRenderer"; const isHorizontal = ($$, forHorizontal) => { @@ -665,4 +665,116 @@ export default class Axis { this.updateAxes(); } + + /** + * Redraw axis + * @param {Object} targetsToShow targets data to be shown + * @param {Object} wth + * @param {Ojbect} transitions + * @param {Object} flow + * @private + */ + redrawAxis(targetsToShow, wth, transitions, flow, isInit) { + const $$ = this.owner; + const config = $$.config; + const hasZoom = !!$$.zoomScale; + let xDomainForZoom; + + if (!hasZoom && $$.isCategorized() && targetsToShow.length === 0) { + $$.x.domain([0, $$.axes.x.selectAll(".tick").size()]); + } + + if ($$.x && targetsToShow.length) { + !hasZoom && + $$.updateXDomain(targetsToShow, wth.UpdateXDomain, wth.UpdateOrgXDomain, wth.TrimXDomain); + + if (!config.axis_x_tick_values) { + this.updateXAxisTickValues(targetsToShow); + } + } else if ($$.xAxis) { + $$.xAxis.tickValues([]); + $$.subXAxis.tickValues([]); + } + + if (config.zoom_rescale && !flow) { + xDomainForZoom = $$.x.orgDomain(); + } + + ["y", "y2"].forEach(key => { + const axis = $$[key]; + + if (axis) { + const tickValues = config[`axis_${key}_tick_values`]; + const tickCount = config[`axis_${key}_tick_count`]; + + axis.domain($$.getYDomain(targetsToShow, key, xDomainForZoom)); + + if (!tickValues && tickCount) { + const domain = axis.domain(); + + $$[`${key}Axis`].tickValues( + this.generateTickValues( + domain, + domain.every(v => v === 0) ? 1 : tickCount, + $$.isTimeSeriesY() + ) + ); + } + } + }); + + // axes + this.redraw(transitions, $$.hasArcType(), isInit); + + // Update axis label + this.updateLabels(wth.Transition); + + // show/hide if manual culling needed + if ((wth.UpdateXDomain || wth.UpdateXAxis) && targetsToShow.length) { + this.setCulling(); + } + + // Update sub domain + if (wth.Y) { + $$.subY && $$.subY.domain($$.getYDomain(targetsToShow, "y")); + $$.subY2 && $$.subY2.domain($$.getYDomain(targetsToShow, "y2")); + } + } + + /** + * Set manual culling + * @private + */ + setCulling() { + const $$ = this.owner; + const config = $$.config; + + ["x", "y", "y2"].forEach(type => { + const axis = $$.axes[type]; + const toCull = config[`axis_${type}_tick_culling`]; + + if (axis && toCull) { + const tickText = axis.selectAll(".tick text"); + const tickValues = sortValue(tickText.data()); + const tickSize = tickValues.length; + const cullingMax = config[`axis_${type}_tick_culling_max`]; + let intervalForCulling; + + if (tickSize) { + for (let i = 1; i < tickSize; i++) { + if (tickSize / i < cullingMax) { + intervalForCulling = i; + break; + } + } + + tickText.each(function(d) { + this.style.display = tickValues.indexOf(d) % intervalForCulling ? "none" : "block"; + }); + } else { + tickText.style("display", "block"); + } + } + }); + } } diff --git a/src/config/Options.js b/src/config/Options.js index e46fb23ba..611ca0352 100644 --- a/src/config/Options.js +++ b/src/config/Options.js @@ -2166,6 +2166,44 @@ export default class Options { */ axis_y_tick_format: undefined, + /** + * Setting for culling ticks.

+ * If true is set, the ticks will be culled, then only limitted tick text will be shown. This option does not hide the tick lines. If false is set, all of ticks will be shown.

+ * We can change the number of ticks to be shown by axis.y.tick.culling.max. + * @name axis․y․tick․culling + * @memberof Options + * @type {Boolean} + * @default false + * @example + * axis: { + * y: { + * tick: { + * culling: false + * } + * } + * } + */ + axis_y_tick_culling: false, + + /** + * The number of tick texts will be adjusted to less than this value. + * @name axis․y․tick․culling․max + * @memberof Options + * @type {Number} + * @default 5 + * @example + * axis: { + * y: { + * tick: { + * culling: { + * max: 5 + * } + * } + * } + * } + */ + axis_y_tick_culling_max: 5, + /** * Show y axis outer tick. * @name axis․y․tick․outer @@ -2511,6 +2549,44 @@ export default class Options { */ axis_y2_tick_format: undefined, + /** + * Setting for culling ticks.

+ * If true is set, the ticks will be culled, then only limitted tick text will be shown. This option does not hide the tick lines. If false is set, all of ticks will be shown.

+ * We can change the number of ticks to be shown by axis.y.tick.culling.max. + * @name axis․y2․tick․culling + * @memberof Options + * @type {Boolean} + * @default false + * @example + * axis: { + * y2: { + * tick: { + * culling: false + * } + * } + * } + */ + axis_y2_tick_culling: false, + + /** + * The number of tick texts will be adjusted to less than this value. + * @name axis․y2․tick․culling․max + * @memberof Options + * @type {Number} + * @default 5 + * @example + * axis: { + * y2: { + * tick: { + * culling: { + * max: 5 + * } + * } + * } + * } + */ + axis_y2_tick_culling_max: 5, + /** * Show or hide y2 axis outer tick. * @name axis․y2․tick․outer diff --git a/src/internals/ChartInternal.js b/src/internals/ChartInternal.js index a1f65f21f..d665c4174 100644 --- a/src/internals/ChartInternal.js +++ b/src/internals/ChartInternal.js @@ -596,7 +596,7 @@ export default class ChartInternal { // update axis // @TODO: Make 'init' state to be accessible everywhere not passing as argument. - $$.redrawAxis(targetsToShow, wth, transitions, flow, initializing); + $$.axis.redrawAxis(targetsToShow, wth, transitions, flow, initializing); // update data's index value to be alinged with the x Axis $$.updateDataIndexByX(); @@ -658,100 +658,6 @@ export default class ChartInternal { $$.callPluginHook("$redraw", options, duration); } - /** - * Redraw axis - * @param {Object} targetsToShow targets data to be shown - * @param {Object} wth - * @param {Ojbect} transitions - * @param {Object} flow - * @private - */ - redrawAxis(targetsToShow, wth, transitions, flow, isInit) { - const $$ = this; - const config = $$.config; - const hasArcType = $$.hasArcType(); - const hasZoom = !!$$.zoomScale; - let tickValues; - let intervalForCulling; - let xDomainForZoom; - - if (!hasZoom && $$.isCategorized() && targetsToShow.length === 0) { - $$.x.domain([0, $$.axes.x.selectAll(".tick").size()]); - } - - if ($$.x && targetsToShow.length) { - !hasZoom && - $$.updateXDomain(targetsToShow, wth.UpdateXDomain, wth.UpdateOrgXDomain, wth.TrimXDomain); - - if (!config.axis_x_tick_values) { - tickValues = $$.axis.updateXAxisTickValues(targetsToShow); - } - } else if ($$.xAxis) { - $$.xAxis.tickValues([]); - $$.subXAxis.tickValues([]); - } - - if (config.zoom_rescale && !flow) { - xDomainForZoom = $$.x.orgDomain(); - } - - ["y", "y2"].forEach(key => { - const axis = $$[key]; - - if (axis) { - const tickValues = config[`axis_${key}_tick_values`]; - const tickCount = config[`axis_${key}_tick_count`]; - - axis.domain($$.getYDomain(targetsToShow, key, xDomainForZoom)); - - if (!tickValues && tickCount) { - const domain = axis.domain(); - - $$[`${key}Axis`].tickValues( - $$.axis.generateTickValues( - domain, - domain.every(v => v === 0) ? 1 : tickCount, - $$.isTimeSeriesY() - ) - ); - } - } - }); - - // axes - $$.axis.redraw(transitions, hasArcType, isInit); - - // Update axis label - $$.axis.updateLabels(wth.Transition); - - // show/hide if manual culling needed - 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) { - intervalForCulling = i; - break; - } - } - - $$.svg.selectAll(`.${CLASS.axisX} .tick text`).each(function(d) { - const index = tickValues.indexOf(d); - - index >= 0 && - d3Select(this).style("display", index % intervalForCulling ? "none" : "block"); - }); - } else { - $$.svg.selectAll(`.${CLASS.axisX} .tick text`).style("display", "block"); - } - } - - // Update sub domain - if (wth.Y) { - $$.subY && $$.subY.domain($$.getYDomain(targetsToShow, "y")); - $$.subY2 && $$.subY2.domain($$.getYDomain(targetsToShow, "y2")); - } - } - /** * Generate redraw list * @param {Object} targetsToShow targets data to be shown diff --git a/src/internals/util.js b/src/internals/util.js index acd5379c3..f82bfa70b 100644 --- a/src/internals/util.js +++ b/src/internals/util.js @@ -281,7 +281,7 @@ const sortValue = (data, isAsc = true) => { if (data[0] instanceof Date) { fn = isAsc ? (a, b) => a - b : (a, b) => b - a; } else { - if (isAsc && data.every(Number)) { + if (isAsc && !data.every(isNaN)) { fn = (a, b) => a - b; } else if (!isAsc) { fn = (a, b) => (a > b && -1) || (a < b && 1) || (a === b && 0); diff --git a/types/axis.d.ts b/types/axis.d.ts index ac91fa218..8c9714901 100644 --- a/types/axis.d.ts +++ b/types/axis.d.ts @@ -287,6 +287,18 @@ export interface YTickConfiguration { */ format?(x: number): string; + /** + * Setting for culling ticks. + * If true is set, the ticks will be culled, then only limitted tick text will be shown. + * This option does not hide the tick lines. If false is set, all of ticks will be shown. + */ + culling?: boolean | { + /** + * The number of tick texts will be adjusted to less than this value. + */ + max?: number; + }; + text?: { /** * Set the x Axis tick text's position relatively its original position