Skip to content

Commit

Permalink
feat(axis): Intent to ship axis.x.inverted
Browse files Browse the repository at this point in the history
Implement inversion option for x axis

Close #3137
  • Loading branch information
netil committed Mar 28, 2023
1 parent 6d0c492 commit e193c0f
Show file tree
Hide file tree
Showing 18 changed files with 416 additions and 37 deletions.
81 changes: 81 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,87 @@ var demos = {
}
}
],
InvertedAxis: [
{
options: {
title: {
text: "inverted timeseries x Aaxis"
},
data: {
x: "x",
columns: [
["x", "2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-06"],
["data1", 30, 200, 100, 400, 150]
],
type: "line"
},
axis: {
x: {
inverted: true,
type: "timeseries",
tick: {
format: "%Y-%m-%d"
}
}
}
}
},
{
options: {
title: {
text: "inverted indexed x & y Aaxis"
},
data: {
columns: [
["data1", 30, 200, 100, 400, 150],
["data2", 330, 150, 210, 330, 270]
],
types: {
data1: "bar",
data2: "area-spline"
}
},
axis: {
x: {
inverted: true
},
y: {
inverted: true
}
}
}
},
{
options: {
title: {
text: "inverted category x Aaxis"
},
data: {
x: "x",
columns: [
["x", "Type A", "Type B", "Type C", "Type D", "Type E"],
["data1", 130, 300, 200, 300, 260],
["data2", 30, 200, 100, 400, 150]
],
type: "line",
axes: {
data1: "y",
data2: "y2"
}
},
axis: {
x: {
inverted: true,
type: "category"
},
y2: {
show: true,
inverted: true
}
}
}
}
],
LogScales: {
options: {
data: {
Expand Down
21 changes: 18 additions & 3 deletions src/Chart/api/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@ import {extend, getMinMax, isDefined, isObject, parseDate} from "../../module/ut
* @param {Array} domain Target domain value
* @param {Array} current Current zoom domain value
* @param {Array} range Zoom range value
* @param {boolean} isInverted Whether the axis is inverted or not
* @returns {boolean}
* @private
*/
function withinRange(domain: (number|Date)[], current, range: number[]): boolean {
function withinRange(
domain: (number|Date)[], current, range: number[], isInverted = false
): boolean {
const [min, max] = range;

return domain.every((v, i) => (
i === 0 ? (v >= min) : (v <= max)
i === 0 ? (
isInverted ? v <= min : v >= min
) : (
isInverted ? v >= max : v <= max
)
) && !(domain.every((v, i) => v === current[i])));
}

Expand Down Expand Up @@ -45,6 +52,7 @@ const zoom = function(domainValue?: (Date|number|string)[]): (Date|number)[]|und
const $$ = this.internal;
const {$el, axis, config, org, scale} = $$;
const isRotated = config.axis_rotated;
const isInverted = config.axis_x_inverted;
const isCategorized = axis.isCategorized();
let domain = domainValue;

Expand All @@ -53,7 +61,14 @@ const zoom = function(domainValue?: (Date|number|string)[]): (Date|number)[]|und
domain = domain.map(x => parseDate.bind($$)(x));
}

if (withinRange(domain as (number|Date)[], $$.getZoomDomain(true), $$.getZoomDomain())) {
const isWithinRange = withinRange(
domain as (number|Date)[],
$$.getZoomDomain(true),
$$.getZoomDomain(),
isInverted
);

if (isWithinRange) {
if (isCategorized) {
domain = domain.map((v, i) => Number(v) + (i === 0 ? 0 : 1));
}
Expand Down
1 change: 1 addition & 0 deletions src/ChartInternal/Axis/Axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class Axis {
owner: $$
}, isX && {
isCategory,
isInverted: config.axis_x_inverted,
tickMultiline: config.axis_x_tick_multiline,
tickWidth: config.axis_x_tick_width,
tickTitle: isCategory && config.axis_x_tick_tooltip && $$.api.categories(),
Expand Down
9 changes: 7 additions & 2 deletions src/ChartInternal/Axis/AxisRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,13 @@ export default class AxisRenderer {

if (!tickWidth || tickWidth <= 0) {
tickWidth = isLeftRight ? 95 : (
params.isCategory ?
(Math.ceil(scale(ticks[1]) - scale(ticks[0])) - 12) : 110
params.isCategory ? (
Math.ceil(
params.isInverted ?
scale(ticks[0]) - scale(ticks[1]) :
scale(ticks[1]) - scale(ticks[0])
) - 12
) : 110
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/ChartInternal/ChartInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ export default class ChartInternal {

// Set domains for each scale
if (x) {
x.domain(sortValue($$.getXDomain($$.data.targets)));
x.domain(sortValue($$.getXDomain($$.data.targets), !config.axis_x_inverted));
subX.domain(x.domain());

// Save original x domain for zoom update
Expand Down
3 changes: 2 additions & 1 deletion src/ChartInternal/data/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,15 @@ export default {
getMaxDataCountTarget() {
let target = this.filterTargetsToShow() || [];
const length = target.length;
const isInverted = this.config.axis_x_inverted;

if (length > 1) {
target = target.map(t => t.values)
.reduce((a, b) => a.concat(b))
.map(v => v.x);

target = sortValue(getUnique(target))
.map((x, index) => ({x, index}));
.map((x, index, array) => ({x, index: isInverted ? array.length - index - 1 : index}));
} else if (length) {
target = target[0].values.concat();
}
Expand Down
5 changes: 3 additions & 2 deletions src/ChartInternal/interactions/eventrect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default {
const $$ = this;
const {config, state, $el} = $$;
const isMultipleX = $$.isMultipleX();
const isInverted = config.axis_x_inverted;

if ($el.eventRect) {
$$.updateEventRect($el.eventRect, true);
Expand Down Expand Up @@ -66,8 +67,8 @@ export default {
// Set data and update eventReceiver.data
const xAxisTickValues = $$.getMaxDataCountTarget();

if (!config.data_xSort) {
xAxisTickValues.sort((a, b) => a.x - b.x);
if (!config.data_xSort || isInverted) {
xAxisTickValues.sort((a, b) => (isInverted ? b.x - a.x : a.x - b.x));
}

// update data's index value to be alinged with the x Axis
Expand Down
5 changes: 3 additions & 2 deletions src/ChartInternal/interactions/subchart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ export default {
"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"}] :
Expand Down Expand Up @@ -335,7 +334,9 @@ export default {
*/
redrawForBrush() {
const $$ = this;
const {config: {subchart_onbrush: onBrush, zoom_rescale: withY}, scale} = $$;
const {config: {
subchart_onbrush: onBrush, zoom_rescale: withY
}, scale} = $$;

$$.redraw({
withTransition: false,
Expand Down
13 changes: 10 additions & 3 deletions src/ChartInternal/interactions/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default {
$$.state.xTickOffset = $$.axis.x.tickOffset();
}

scale.zoom = $$.getCustomizedScale(newScale);
scale.zoom = $$.getCustomizedXScale(newScale);
$$.axis.x.scale(scale.zoom);

if (rescale) {
Expand Down Expand Up @@ -256,8 +256,15 @@ export default {
const xDomain = subX.domain();
const delta = 0.015; // arbitrary value

const isfullyShown = (zoomDomain[0] <= xDomain[0] || (zoomDomain[0] - delta) <= xDomain[0]) &&
(xDomain[1] <= zoomDomain[1] || xDomain[1] <= (zoomDomain[1] - delta));
const isfullyShown = $$.config.axis_x_inverted ? (
zoomDomain[0] >= xDomain[0] || (zoomDomain[0] + delta) >= xDomain[0]
) && (
xDomain[1] >= zoomDomain[1] || xDomain[1] >= (zoomDomain[1] + delta)
) : (
zoomDomain[0] <= xDomain[0] || (zoomDomain[0] - delta) <= xDomain[0]
) && (
xDomain[1] <= zoomDomain[1] || xDomain[1] <= (zoomDomain[1] - delta)
);

// check if the zoomed chart is fully shown, then reset scale when zoom is out as initial
if (force || isfullyShown) {
Expand Down
25 changes: 17 additions & 8 deletions src/ChartInternal/internals/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ export default {
* @returns {Array} x Axis domain
* @private
*/
getXDomain(targets?: IData[]): (Date|number)[] {
getXDomain(targets: IData[]): (Date|number)[] {
const $$ = this;
const {axis, scale: {x}} = $$;
const {axis, config, scale: {x}} = $$;
const isInverted = config.axis_x_inverted;
const domain = [
$$.getXDomainMinMax(targets, "min"),
$$.getXDomainMinMax(targets, "max")
Expand Down Expand Up @@ -309,7 +310,7 @@ export default {
}
}

return [min, max];
return isInverted ? [max, min] : [min, max];
},

updateXDomain(targets, withUpdateXDomain, withUpdateOrgXDomain, withTrim, domain) {
Expand All @@ -318,7 +319,7 @@ export default {
const zoomEnabled = config.zoom_enabled;

if (withUpdateOrgXDomain) {
x.domain(domain || sortValue($$.getXDomain(targets)));
x.domain(domain || sortValue($$.getXDomain(targets), !config.axis_x_inverted));
org.xDomain = x.domain();

zoomEnabled && $$.zoom.updateScaleExtent();
Expand All @@ -341,16 +342,24 @@ export default {
return x.domain();
},

/**
* Trim x domain when given domain surpasses the range
* @param {Array} domain Domain value
* @returns {Array} Trimed domain if given domain is out of range
* @private
*/
trimXDomain(domain) {
const zoomDomain = this.getZoomDomain();
const $$ = this;
const isInverted = $$.config.axis_x_inverted;
const zoomDomain = $$.getZoomDomain();
const [min, max] = zoomDomain;

if (domain[0] <= min) {
domain[1] = +domain[1] + (min - domain[0]);
if (isInverted ? domain[0] >= min : domain[0] <= min) {
domain[0] = min;
domain[1] = +domain[1] + (min - domain[0]);
}

if (max <= domain[1]) {
if (isInverted ? domain[1] <= max : domain[1] >= max) {
domain[0] = +domain[0] - (domain[1] - max);
domain[1] = max;
}
Expand Down
1 change: 0 additions & 1 deletion src/ChartInternal/internals/redraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export default {
}
});


// circles for select
$el.text && main.selectAll(`.${$SELECT.selectedCircles}`)
.filter($$.isBarType.bind($$))
Expand Down
11 changes: 7 additions & 4 deletions src/ChartInternal/internals/scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default {
const $$ = this;
const scale = $$.scale.zoom || getScale($$.axis.getAxisType("x"), min, max);

return $$.getCustomizedScale(
return $$.getCustomizedXScale(
domain ? scale.domain(domain) : scale,
offset
);
Expand Down Expand Up @@ -89,15 +89,16 @@ export default {
},

/**
* Get customized scale
* Get customized x axis scale
* @param {d3.scaleLinear|d3.scaleTime} scaleValue Scale function
* @param {Function} offsetValue Offset getter to be sum
* @returns {Function} Scale function
* @private
*/
getCustomizedScale(scaleValue: Function | any, offsetValue): Function {
getCustomizedXScale(scaleValue: Function | any, offsetValue): Function {
const $$ = this;
const offset = offsetValue || (() => $$.axis.x.tickOffset());
const isInverted = $$.config.axis_x_inverted;
const scale = function(d, raw) {
const v = scaleValue(d) + offset();

Expand All @@ -120,7 +121,9 @@ export default {
if (!arguments.length) {
domain = this.orgDomain();

return [domain[0], domain[1] + 1];
return isInverted ?
[domain[0] + 1, domain[1]] :
[domain[0], domain[1] + 1];
}

scaleValue.domain(domain);
Expand Down
7 changes: 6 additions & 1 deletion src/ChartInternal/internals/size.axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ export default {
},

getEventRectWidth(): number {
return Math.max(0, this.axis.x.tickInterval());
const $$ = this;
const {config, axis} = $$;
const isInverted = config.axis_x_inverted;
const tickInterval = axis.x.tickInterval();

return Math.max(0, isInverted ? Math.abs(tickInterval) : tickInterval);
},

/**
Expand Down
17 changes: 17 additions & 0 deletions src/config/Options/axis/x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,23 @@ export default {
*/
axis_x_min: <number|undefined> undefined,

/**
* Change the direction of x axis.<br><br>
* If true set, the direction will be `right -> left`.
* @name axis鈥鈥nverted
* @memberof Options
* @type {boolean}
* @default false
* @see [Demo](https://naver.github.io/billboard.js/demo/#Axis.InvertedAxis)
* @example
* axis: {
* x: {
* inverted: true
* }
* }
*/
axis_x_inverted: false,

/**
* Set padding for x axis.<br><br>
* If this option is set, the range of x axis will increase/decrease according to the values.
Expand Down
Loading

0 comments on commit e193c0f

Please sign in to comment.