Skip to content

Commit

Permalink
feat: create options for tick rotation (carbon-design-system#770)
Browse files Browse the repository at this point in the history
* feat: create options for tick rotation

- new enums and configuration
- control x-axis tick rotation depending on configuration

* refactor: change TickRotations DEPENDING to AUTO

* refactor: avoid extra calculation for shouldRotateTicks

* refactor: limit top/bottom axis tick rotation to top/bottom zoom bar only

* refactor: tick rotation don't depend on zoom
  • Loading branch information
hlyang397 authored Sep 11, 2020
1 parent 1f1a8e3 commit f2608ac
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 70 deletions.
118 changes: 49 additions & 69 deletions packages/core/src/components/axes/axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { Tools } from "../../tools";
import { ChartModel } from "../../model";
import { DOMUtils } from "../../services";
import { TickRotations } from "../../interfaces/enums";
import * as Configuration from "../../configuration";
import {
computeTimeIntervalName,
Expand All @@ -29,9 +30,6 @@ export class Axis extends Component {
scale: any;
scaleType: ScaleTypes;

// Track whether zoom domain needs to update in a render
zoomDomainChanging = false;

constructor(model: ChartModel, services: any, configs?: any) {
super(model, services, configs);

Expand All @@ -40,31 +38,8 @@ export class Axis extends Component {
}

this.margins = this.configs.margins;
this.init();
}

init() {
this.services.events.addEventListener(
Events.ZoomBar.SELECTION_START,
this.handleZoomBarSelectionStart
);
this.services.events.addEventListener(
Events.ZoomBar.SELECTION_END,
this.handleZoomBarSelectionEnd
);
}

handleZoomBarSelectionStart = () => {
this.zoomDomainChanging = true;
};

handleZoomBarSelectionEnd = () => {
this.zoomDomainChanging = false;
// need another update after zoom bar selection is completed
// to make sure the tick rotation is calculated correctly
this.services.events.dispatchEvent(Events.Model.UPDATE);
};

render(animate = true) {
const { position: axisPosition } = this.configs;
const options = this.model.getOptions();
Expand Down Expand Up @@ -439,41 +414,56 @@ export class Axis extends Component {
axisPosition === AxisPositions.BOTTOM ||
axisPosition === AxisPositions.TOP
) {
let rotateTicks = false;

// If we're dealing with a discrete scale type
// We're able to grab the spacing between the ticks
if (scale.step) {
const textNodes = invisibleAxisRef
.selectAll("g.tick text")
.nodes();

// If any ticks are any larger than the scale step size
rotateTicks = textNodes.some(
(textNode) =>
DOMUtils.getSVGElementSize(textNode, { useBBox: true })
.width >= scale.step()
);
} else {
// When dealing with a continuous scale
// We need to calculate an estimated size of the ticks
const minTickSize =
Tools.getProperty(
axisOptions,
"ticks",
"rotateIfSmallerThan"
) || Configuration.axis.ticks.rotateIfSmallerThan;
const ticksNumber = isTimeScaleType
? axis.tickValues().length
: scale.ticks().length;
const estimatedTickSize = width / ticksNumber / 2;
rotateTicks = isTimeScaleType
? estimatedTickSize < minTickSize * 2 // datetime tick could be very long
: estimatedTickSize < minTickSize;
let shouldRotateTicks = false;
// user could decide if tick rotation is required during zoom domain changing
const tickRotation = Tools.getProperty(
axisOptions,
"ticks",
"rotation"
);

if (tickRotation === TickRotations.ALWAYS) {
shouldRotateTicks = true;
} else if (tickRotation === TickRotations.NEVER) {
shouldRotateTicks = false;
} else if (!tickRotation || tickRotation === TickRotations.AUTO) {
// if the option is not set or set to AUTO

// depending on if tick rotation is necessary by calculating space
// If we're dealing with a discrete scale type
// We're able to grab the spacing between the ticks
if (scale.step) {
const textNodes = invisibleAxisRef
.selectAll("g.tick text")
.nodes();

// If any ticks are any larger than the scale step size
shouldRotateTicks = textNodes.some(
(textNode) =>
DOMUtils.getSVGElementSize(textNode, {
useBBox: true
}).width >= scale.step()
);
} else {
// When dealing with a continuous scale
// We need to calculate an estimated size of the ticks
const minTickSize =
Tools.getProperty(
axisOptions,
"ticks",
"rotateIfSmallerThan"
) || Configuration.axis.ticks.rotateIfSmallerThan;
const ticksNumber = isTimeScaleType
? axis.tickValues().length
: scale.ticks().length;
const estimatedTickSize = width / ticksNumber / 2;
shouldRotateTicks = isTimeScaleType
? estimatedTickSize < minTickSize * 2 // datetime tick could be very long
: estimatedTickSize < minTickSize;
}
}

// always rotate ticks if zoomDomain is changing to avoid rotation flips during zoomDomain changing
if (rotateTicks || this.zoomDomainChanging) {
if (shouldRotateTicks) {
if (!isNumberOfTicksProvided) {
axis.ticks(
this.getNumberOfFittingTicks(
Expand Down Expand Up @@ -690,15 +680,5 @@ export class Axis extends Component {
.on("mouseover", null)
.on("mousemove", null)
.on("mouseout", null);

// Remove zoom bar event listeners
this.services.events.removeEventListener(
Events.ZoomBar.SELECTION_START,
this.handleZoomBarSelectionStart
);
this.services.events.removeEventListener(
Events.ZoomBar.SELECTION_END,
this.handleZoomBarSelectionEnd
);
}
}
6 changes: 5 additions & 1 deletion packages/core/src/interfaces/axis-scales.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ScaleTypes } from "./enums";
import { ScaleTypes, TickRotations } from "./enums";
import { AxisDomain } from "d3";
import { Locale } from "date-fns";
import { ThresholdOptions } from "./components";
Expand Down Expand Up @@ -74,6 +74,10 @@ export interface AxisOptions {
* before getting rotated (in pixels)
*/
rotateIfSmallerThan?: number;
/**
* when to rotate ticks
*/
rotation?: TickRotations;
/**
* function to format the ticks
*/
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/interfaces/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,12 @@ export enum Statuses {
WARNING = "warning",
DANGER = "danger"
}

/**
* enum of axis ticks rotation
*/
export enum TickRotations {
ALWAYS = "always",
AUTO = "auto",
NEVER = "never"
}

0 comments on commit f2608ac

Please sign in to comment.