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