Skip to content

Commit

Permalink
feat(axis): improve axis styles and flexibility
Browse files Browse the repository at this point in the history
- add visibility to ticks, labels, line and overall axis
- add style overrides for each axis that override theme axis styles
- improve padding controls for axis titles, labels and ticks
- add tick label offsets to tweek final position of tick labels
  • Loading branch information
nickofthyme committed Jul 13, 2020
1 parent 7d91d8f commit 8e4ccb8
Show file tree
Hide file tree
Showing 36 changed files with 793 additions and 330 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = {
'@typescript-eslint/no-unsafe-return': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/restrict-template-expressions': 1,
'@typescript-eslint/restrict-plus-operands': 1,
'@typescript-eslint/restrict-plus-operands': 0, // rule is broken
'@typescript-eslint/no-unsafe-call': 1,
'@typescript-eslint/unbound-method': 1,
'unicorn/consistent-function-scoping': 1,
Expand Down
12 changes: 6 additions & 6 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,18 @@ export const Axis: React.FunctionComponent<SpecRequired & SpecOptionals>;
// @public (undocumented)
export interface AxisConfig {
// (undocumented)
axisLineStyle: StrokeStyle;
axisLine: StrokeStyle;
// (undocumented)
axisTitleStyle: TextStyle;
axisTitle: TextStyle;
// (undocumented)
gridLineStyle: {
gridLine: {
horizontal: GridLineConfig;
vertical: GridLineConfig;
};
// (undocumented)
tickLabelStyle: TextStyle;
tickLabel: TextStyle;
// (undocumented)
tickLineStyle: TickStyle;
tickLine: TickStyle;
}

// @public (undocumented)
Expand All @@ -141,8 +141,8 @@ export type AxisId = string;
export interface AxisSpec extends Spec {
// (undocumented)
chartType: typeof ChartTypes.XYAxis;
gridLine?: GridLineConfig;
domain?: YDomainRange;
gridLineStyle?: GridLineConfig;
groupId: GroupId;
hide: boolean;
id: AxisId;
Expand Down
60 changes: 41 additions & 19 deletions src/chart_types/xy_chart/renderer/canvas/axes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import { withContext } from '../../../../../renderers/canvas';
import { Dimensions } from '../../../../../utils/dimensions';
import { AxisId } from '../../../../../utils/ids';
import { AxisConfig } from '../../../../../utils/themes/theme';
import { AxisStyle } from '../../../../../utils/themes/theme';
import { getSpecsById } from '../../../state/utils/spec';
import { AxisTick, AxisTicksDimensions } from '../../../utils/axis_utils';
import { AxisTick, AxisTicksDimensions, shouldShowTicks } from '../../../utils/axis_utils';
import { AxisSpec } from '../../../utils/specs';
import { renderDebugRect } from '../utils/debug';
import { renderLine } from './line';
Expand All @@ -32,7 +32,7 @@ import { renderTitle } from './title';

/** @internal */
export interface AxisProps {
axisConfig: AxisConfig;
axisStyle: AxisStyle;
axisSpec: AxisSpec;
axisTicksDimensions: AxisTicksDimensions;
axisPosition: Dimensions;
Expand All @@ -47,27 +47,41 @@ export interface AxesProps {
axesSpecs: AxisSpec[];
axesTicksDimensions: Map<AxisId, AxisTicksDimensions>;
axesPositions: Map<AxisId, Dimensions>;
axisStyle: AxisConfig;
axesStyles: Map<string, AxisStyle | null>;
sharedAxesStyle: AxisStyle;
debug: boolean;
chartDimensions: Dimensions;
}

/** @internal */
export function renderAxes(ctx: CanvasRenderingContext2D, props: AxesProps) {
const { axesVisibleTicks, axesSpecs, axesTicksDimensions, axesPositions, axisStyle, debug, chartDimensions } = props;
const {
axesVisibleTicks,
axesSpecs,
axesTicksDimensions,
axesPositions,
axesStyles,
sharedAxesStyle,
debug,
chartDimensions,
} = props;
axesVisibleTicks.forEach((ticks, axisId) => {
const axisSpec = getSpecsById<AxisSpec>(axesSpecs, axisId);
const axisTicksDimensions = axesTicksDimensions.get(axisId);
const axisPosition = axesPositions.get(axisId);
if (!ticks || !axisSpec || !axisTicksDimensions || !axisPosition) {

if (!ticks || !axisSpec || !axisTicksDimensions || !axisPosition || axisSpec.hide) {
return;
}

const axisStyle = axesStyles.get(axisSpec.id) ?? sharedAxesStyle;

renderAxis(ctx, {
axisSpec,
axisTicksDimensions,
axisPosition,
ticks,
axisConfig: axisStyle,
axisStyle,
debug,
chartDimensions,
});
Expand All @@ -76,7 +90,8 @@ export function renderAxes(ctx: CanvasRenderingContext2D, props: AxesProps) {

function renderAxis(ctx: CanvasRenderingContext2D, props: AxisProps) {
withContext(ctx, (ctx) => {
const { ticks, axisPosition, debug } = props;
const { ticks, axisPosition, debug, axisStyle, axisSpec } = props;
const showTicks = shouldShowTicks(axisStyle.tickLine, axisSpec.hide);
ctx.translate(axisPosition.left, axisPosition.top);
if (debug) {
renderDebugRect(ctx, {
Expand All @@ -90,18 +105,25 @@ function renderAxis(ctx: CanvasRenderingContext2D, props: AxisProps) {
withContext(ctx, (ctx) => {
renderLine(ctx, props);
});
withContext(ctx, (ctx) => {
ticks.forEach((tick) => {
renderTick(ctx, tick, props);
});
});
withContext(ctx, (ctx) => {
ticks
.filter((tick) => tick.label !== null)
.forEach((tick) => {
renderTickLabel(ctx, tick, props);

if (showTicks) {
withContext(ctx, (ctx) => {
ticks.forEach((tick) => {
renderTick(ctx, tick, props);
});
});
});
}

if (props.axisStyle.tickLabel.visible) {
withContext(ctx, (ctx) => {
ticks
.filter((tick) => tick.label !== null)
.forEach((tick) => {
renderTickLabel(ctx, tick, showTicks, props);
});
});
}

withContext(ctx, (ctx) => {
renderTitle(ctx, props);
});
Expand Down
19 changes: 11 additions & 8 deletions src/chart_types/xy_chart/renderer/canvas/axes/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ import { Position } from '../../../../../utils/commons';
import { isVerticalAxis } from '../../../utils/axis_type_utils';

/** @internal */
export function renderLine(ctx: CanvasRenderingContext2D, props: AxisProps) {
const {
axisSpec: { position },
axisPosition,
axisConfig: { axisLineStyle },
} = props;
export function renderLine(ctx: CanvasRenderingContext2D, {
axisSpec: { position },
axisPosition,
axisStyle: { axisLine },
}: AxisProps) {
if (!axisLine.visible) {
return;
}

const lineProps: number[] = [];
if (isVerticalAxis(position)) {
lineProps[0] = position === Position.Left ? axisPosition.width : 0;
Expand All @@ -43,7 +46,7 @@ export function renderLine(ctx: CanvasRenderingContext2D, props: AxisProps) {
ctx.beginPath();
ctx.moveTo(lineProps[0], lineProps[1]);
ctx.lineTo(lineProps[2], lineProps[3]);
ctx.strokeStyle = axisLineStyle.stroke;
ctx.lineWidth = axisLineStyle.strokeWidth;
ctx.strokeStyle = axisLine.stroke;
ctx.lineWidth = axisLine.strokeWidth;
ctx.stroke();
}
13 changes: 5 additions & 8 deletions src/chart_types/xy_chart/renderer/canvas/axes/tick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,19 @@ import { TickStyle } from '../../../../../utils/themes/theme';
import { stringToRGB } from '../../../../partition_chart/layout/utils/color_library_wrappers';
import { isVerticalAxis } from '../../../utils/axis_type_utils';
import { AxisTick } from '../../../utils/axis_utils';
import { renderLine, MIN_STROKE_WIDTH } from '../primitives/line';
import { renderLine } from '../primitives/line';

/** @internal */
export function renderTick(ctx: CanvasRenderingContext2D, tick: AxisTick, props: AxisProps) {
const {
axisSpec: { tickSize, position },
axisSpec: { position },
axisPosition,
axisConfig: { tickLineStyle },
axisStyle: { tickLine },
} = props;
if (!tickLineStyle.visible || tickLineStyle.strokeWidth < MIN_STROKE_WIDTH) {
return;
}
if (isVerticalAxis(position)) {
renderVerticalTick(ctx, position, axisPosition.width, tickSize, tick.position, tickLineStyle);
renderVerticalTick(ctx, position, axisPosition.width, tickLine.size, tick.position, tickLine);
} else {
renderHorizontalTick(ctx, position, axisPosition.height, tickSize, tick.position, tickLineStyle);
renderHorizontalTick(ctx, position, axisPosition.height, tickLine.size, tick.position, tickLine);
}
}

Expand Down
73 changes: 54 additions & 19 deletions src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,55 @@ import { renderText } from '../primitives/text';
import { renderDebugRectCenterRotated } from '../utils/debug';

/** @internal */
export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, props: AxisProps) {
export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, showTicks: boolean, props: AxisProps) {
/**
* padding is already computed through width
* and bbox_calculator using tickLabelPadding
* set padding to 0 to avoid conflict
*/
const labelStyle = {
...props.axisConfig.tickLabelStyle,
padding: 0,
};
const labelStyle = props.axisStyle.tickLabel;

const {
axisSpec: { tickSize, tickPadding, position },
axisSpec: { position },
axisTicksDimensions,
axisPosition,
debug,
} = props;
const {
rotation: tickLabelRotation,
alignment,
offset,
} = props.axisStyle.tickLabel;

const tickLabelRotation = props.axisSpec.tickLabelRotation || 0;

const tickLabelProps = getTickLabelProps(
tickLabelRotation,
tickSize,
tickPadding,
const {
maxLabelBboxWidth,
maxLabelBboxHeight,
maxLabelTextWidth,
maxLabelTextHeight,
} = axisTicksDimensions;
const {
x,
y,
offsetX,
offsetY,
textOffsetX,
textOffsetY,
align,
verticalAlign,
} = getTickLabelProps(
props.axisStyle,
tick.position,
position,
axisPosition,
axisTicksDimensions,
showTicks,
offset,
alignment,
);

const { maxLabelTextWidth, maxLabelTextHeight } = axisTicksDimensions;

const { x, y, offsetX, offsetY, align, verticalAlign } = tickLabelProps;

if (debug) {
// full text container
renderDebugRectCenterRotated(
ctx,
{
Expand All @@ -76,6 +90,25 @@ export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, p
undefined,
tickLabelRotation,
);
// rotated text container
if (![0, -90, 90, 180].includes(tickLabelRotation)) {
renderDebugRectCenterRotated(
ctx,
{
x: x + offsetX,
y: y + offsetY,
},
{
x: x + offsetX,
y: y + offsetY,
height: maxLabelBboxHeight,
width: maxLabelBboxWidth,
},
undefined,
undefined,
0,
);
}
}
const font: Font = {
fontFamily: labelStyle.fontFamily,
Expand All @@ -86,13 +119,11 @@ export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, p
textOpacity: 1,
};
withContext(ctx, (ctx) => {
const textOffsetX = tickLabelRotation === 0 ? 0 : offsetX;
const textOffsetY = tickLabelRotation === 0 ? 0 : offsetY;
renderText(
ctx,
{
x: x + textOffsetX,
y: y + textOffsetY,
x: x + offsetX,
y: y + offsetY,
},
tick.label,
{
Expand All @@ -103,6 +134,10 @@ export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, p
baseline: verticalAlign as CanvasTextBaseline,
},
tickLabelRotation,
{
x: textOffsetX,
y: textOffsetY,
},
);
});
}
Loading

0 comments on commit 8e4ccb8

Please sign in to comment.