Add support for axis labels in plots
parent
b073c84b7e
commit
27da16305e
|
@ -1,6 +1,11 @@
|
|||
import React, {useRef, useLayoutEffect, SFC} from 'react'
|
||||
|
||||
import {PlotEnv, TICK_PADDING_RIGHT, TICK_PADDING_TOP} from 'src/minard'
|
||||
import {
|
||||
PlotEnv,
|
||||
TICK_PADDING_RIGHT,
|
||||
TICK_PADDING_TOP,
|
||||
PLOT_PADDING,
|
||||
} from 'src/minard'
|
||||
import {clearCanvas} from 'src/minard/utils/clearCanvas'
|
||||
|
||||
interface Props {
|
||||
|
@ -20,9 +25,13 @@ export const drawAxes = (
|
|||
const {
|
||||
width,
|
||||
height,
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
margins,
|
||||
xTicks,
|
||||
yTicks,
|
||||
xAxisLabel,
|
||||
yAxisLabel,
|
||||
baseLayer: {
|
||||
scales: {x: xScale, y: yScale},
|
||||
},
|
||||
|
@ -77,6 +86,31 @@ export const drawAxes = (
|
|||
|
||||
context.fillText(String(yTick), margins.left - TICK_PADDING_RIGHT, y)
|
||||
}
|
||||
|
||||
// Draw the x axis label
|
||||
if (xAxisLabel) {
|
||||
context.textAlign = 'center'
|
||||
context.textBaseline = 'bottom'
|
||||
context.fillText(
|
||||
xAxisLabel,
|
||||
margins.left + innerWidth / 2,
|
||||
height - PLOT_PADDING
|
||||
)
|
||||
}
|
||||
|
||||
// Draw the y axis label
|
||||
if (yAxisLabel) {
|
||||
const x = PLOT_PADDING
|
||||
const y = margins.top + innerHeight / 2
|
||||
|
||||
context.save()
|
||||
context.translate(x, y)
|
||||
context.rotate(-Math.PI / 2)
|
||||
context.textAlign = 'center'
|
||||
context.textBaseline = 'top'
|
||||
context.fillText(yAxisLabel, 0, 0)
|
||||
context.restore()
|
||||
}
|
||||
}
|
||||
|
||||
export const Axes: SFC<Props> = props => {
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
setTable,
|
||||
setControlledXDomain,
|
||||
setControlledYDomain,
|
||||
setXAxisLabel,
|
||||
setYAxisLabel,
|
||||
} from 'src/minard/utils/plotEnvActions'
|
||||
import {plotEnvReducer, INITIAL_PLOT_ENV} from 'src/minard/utils/plotEnvReducer'
|
||||
|
||||
|
@ -29,6 +31,8 @@ export interface Props {
|
|||
axesStroke?: string
|
||||
tickFont?: string
|
||||
tickFill?: string
|
||||
xAxisLabel?: string
|
||||
yAxisLabel?: string
|
||||
|
||||
// The x domain of the plot can be explicitly set. If this prop is passed,
|
||||
// then the component is operating in a "controlled" mode, where it always
|
||||
|
@ -53,6 +57,8 @@ export const Plot: SFC<Props> = ({
|
|||
axesStroke = '#31313d',
|
||||
tickFont = 'bold 10px Roboto',
|
||||
tickFill = '#8e91a1',
|
||||
xAxisLabel = '',
|
||||
yAxisLabel = '',
|
||||
xDomain = null,
|
||||
yDomain = null,
|
||||
}) => {
|
||||
|
@ -62,12 +68,16 @@ export const Plot: SFC<Props> = ({
|
|||
height,
|
||||
xDomain,
|
||||
yDomain,
|
||||
xAxisLabel,
|
||||
yAxisLabel,
|
||||
baseLayer: {...INITIAL_PLOT_ENV.baseLayer, table},
|
||||
})
|
||||
|
||||
useMountedEffect(() => dispatch(setTable(table)), [table])
|
||||
useMountedEffect(() => dispatch(setControlledXDomain(xDomain)), [xDomain])
|
||||
useMountedEffect(() => dispatch(setControlledYDomain(yDomain)), [yDomain])
|
||||
useMountedEffect(() => dispatch(setXAxisLabel(xAxisLabel)), [xAxisLabel])
|
||||
useMountedEffect(() => dispatch(setYAxisLabel(yAxisLabel)), [yAxisLabel])
|
||||
useMountedEffect(() => dispatch(setDimensions(width, height)), [
|
||||
width,
|
||||
height,
|
||||
|
|
|
@ -9,6 +9,8 @@ export const TICK_PADDING_TOP = 5
|
|||
export const TICK_CHAR_WIDTH = 7
|
||||
export const TICK_CHAR_HEIGHT = 10
|
||||
|
||||
export const AXIS_LABEL_PADDING_BOTTOM = 15
|
||||
|
||||
export {Plot} from 'src/minard/components/Plot'
|
||||
|
||||
export {
|
||||
|
@ -146,6 +148,8 @@ export interface PlotEnv {
|
|||
margins: Margins
|
||||
xTicks: number[]
|
||||
yTicks: number[]
|
||||
xAxisLabel: string
|
||||
yAxisLabel: string
|
||||
|
||||
// If the domains have been explicitly passed in to the `Plot` component,
|
||||
// they will be stored here. Scales and child layers use the `xDomain` and
|
||||
|
|
|
@ -8,6 +8,8 @@ export type PlotAction =
|
|||
| ResetAction
|
||||
| SetControlledXDomainAction
|
||||
| SetControlledYDomainAction
|
||||
| SetXAxisLabelAction
|
||||
| SetYAxisLabelAction
|
||||
|
||||
interface RegisterLayerAction {
|
||||
type: 'REGISTER_LAYER'
|
||||
|
@ -91,3 +93,23 @@ export const setControlledYDomain = (
|
|||
type: 'SET_CONTROLLED_Y_DOMAIN',
|
||||
payload: {yDomain},
|
||||
})
|
||||
|
||||
interface SetXAxisLabelAction {
|
||||
type: 'SET_X_AXIS_LABEL'
|
||||
payload: {xAxisLabel: string}
|
||||
}
|
||||
|
||||
export const setXAxisLabel = (xAxisLabel: string): SetXAxisLabelAction => ({
|
||||
type: 'SET_X_AXIS_LABEL',
|
||||
payload: {xAxisLabel},
|
||||
})
|
||||
|
||||
interface SetYAxisLabelAction {
|
||||
type: 'SET_Y_AXIS_LABEL'
|
||||
payload: {yAxisLabel: string}
|
||||
}
|
||||
|
||||
export const setYAxisLabel = (yAxisLabel: string): SetYAxisLabelAction => ({
|
||||
type: 'SET_Y_AXIS_LABEL',
|
||||
payload: {yAxisLabel},
|
||||
})
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
TICK_CHAR_HEIGHT,
|
||||
TICK_PADDING_RIGHT,
|
||||
TICK_PADDING_TOP,
|
||||
AXIS_LABEL_PADDING_BOTTOM,
|
||||
} from 'src/minard'
|
||||
import {PlotAction} from 'src/minard/utils/plotEnvActions'
|
||||
import {getGroupKey} from 'src/minard/utils/getGroupKey'
|
||||
|
@ -33,6 +34,8 @@ export const INITIAL_PLOT_ENV: PlotEnv = {
|
|||
},
|
||||
xTicks: [],
|
||||
yTicks: [],
|
||||
xAxisLabel: '',
|
||||
yAxisLabel: '',
|
||||
xDomain: null,
|
||||
yDomain: null,
|
||||
baseLayer: {
|
||||
|
@ -119,6 +122,26 @@ export const plotEnvReducer = (state: PlotEnv, action: PlotAction): PlotEnv =>
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
case 'SET_X_AXIS_LABEL': {
|
||||
const {xAxisLabel} = action.payload
|
||||
|
||||
draftState.xAxisLabel = xAxisLabel
|
||||
|
||||
setLayout(draftState)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case 'SET_Y_AXIS_LABEL': {
|
||||
const {yAxisLabel} = action.payload
|
||||
|
||||
draftState.yAxisLabel = yAxisLabel
|
||||
|
||||
setLayout(draftState)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -230,11 +253,20 @@ const setLayout = (draftState: PlotEnv): void => {
|
|||
const yTickWidth =
|
||||
Math.max(...draftState.yTicks.map(t => String(t).length)) * TICK_CHAR_WIDTH
|
||||
|
||||
const xAxisLabelHeight = draftState.xAxisLabel
|
||||
? TICK_CHAR_HEIGHT + AXIS_LABEL_PADDING_BOTTOM
|
||||
: 0
|
||||
|
||||
const yAxisLabelHeight = draftState.yAxisLabel
|
||||
? TICK_CHAR_HEIGHT + AXIS_LABEL_PADDING_BOTTOM
|
||||
: 0
|
||||
|
||||
const margins = {
|
||||
top: PLOT_PADDING,
|
||||
right: PLOT_PADDING,
|
||||
bottom: TICK_CHAR_HEIGHT + TICK_PADDING_TOP + PLOT_PADDING,
|
||||
left: yTickWidth + TICK_PADDING_RIGHT + PLOT_PADDING,
|
||||
bottom:
|
||||
TICK_CHAR_HEIGHT + TICK_PADDING_TOP + PLOT_PADDING + xAxisLabelHeight,
|
||||
left: yTickWidth + TICK_PADDING_RIGHT + PLOT_PADDING + yAxisLabelHeight,
|
||||
}
|
||||
|
||||
const innerWidth = width - margins.left - margins.right
|
||||
|
|
Loading…
Reference in New Issue