diff --git a/src/js/charts/cb-chart-grid/chart-grid-config.js b/src/js/charts/cb-chart-grid/chart-grid-config.js index 411255b3..b3b503a1 100644 --- a/src/js/charts/cb-chart-grid/chart-grid-config.js +++ b/src/js/charts/cb-chart-grid/chart-grid-config.js @@ -10,6 +10,7 @@ var ChartConfig = require("../ChartConfig"); * @static * @memberof chart_grid_config * @property {Nem|number} afterTitle - Distance btwn top of title and top of legend or chart +* @property {Nem|number} afterSub - Distance btwn top of sub and top of legend or chart * @property {Nem|number} afterLegend - Distance btwn top of legend and top of chart * @property {Nem|number} blockerRectOffset - Distance btwn text of axis and its background blocker * @property {Nem|number} paddingBerBar - Space btwn two bars in a bar grid @@ -24,7 +25,9 @@ var ChartConfig = require("../ChartConfig"); * @property {object} padding - Distances btwn inner chart elements and container */ var display = { - afterTitle: "1.25em", // distance between top of title and top of legend or chart + afterTitle: "1.2em", // distance between top of title and top of sub, legend or chart + afterSub: "1.2em", // distance between top of sub and top of legend or chart + afterTopMeta: "0.2em", afterLegend: "0.5em", // distance between top of legend and top of chart blockerRectOffset: 6, // distance between text and background blocker rect paddingPerBar: "0.7em", // extra space around bars @@ -119,6 +122,7 @@ var defaultProps = { id: null, chartType: "chartgrid", title: "", + sub: "", source: "", credit: "Made with Chartbuilder", size: "auto" diff --git a/src/js/charts/cb-chart-grid/chart-grid-dimensions.js b/src/js/charts/cb-chart-grid/chart-grid-dimensions.js index 64242e39..13a56a19 100644 --- a/src/js/charts/cb-chart-grid/chart-grid-dimensions.js +++ b/src/js/charts/cb-chart-grid/chart-grid-dimensions.js @@ -27,6 +27,12 @@ function chartGridDimensions(width, opts) { if (model.metadata.title.length > 0 && opts.showMetadata) { height += opts.displayConfig.afterTitle; + height += (opts.displayConfig.afterTopMeta || 0); + + if (model.metadata.sub.length > 0) { + height += opts.displayConfig.afterSub; + } + } else if (!opts.showMetadata) { height -= opts.displayConfig.padding.bottom; } diff --git a/src/js/charts/cb-xy/xy-config.js b/src/js/charts/cb-xy/xy-config.js index 956efa1d..0e031f48 100644 --- a/src/js/charts/cb-xy/xy-config.js +++ b/src/js/charts/cb-xy/xy-config.js @@ -15,7 +15,8 @@ var now = new Date(); * @property {Nem|number} labelTextMargin - Horiz distance btwn label rect and text * @property {Nem|number} labelRowHeight - Vert distance btwn rows of labels * items with colors the appropriate indexed CSS class -* @property {Nem|number} afterTitle - Distance btwn top of title and top of legend or chart +* @property {Nem|number} afterTitle - Distance btwn top of title and top of legend, sub or chart +* @property {Nem|number} afterSub - Distance btwn top of sub and top of legend or chart * @property {Nem|number} afterLegend - Distance btwn top of legend and top of chart * @property {Nem|number} blockerRectOffset - Distance btwn text of axis and its background blocker * @property {Nem|number} columnPaddingCoefficient - Distance relative to @@ -36,7 +37,9 @@ var display = { labelXMargin: "0.6em", labelTextMargin: "0.3em", labelRowHeight: "1.2em", - afterTitle: "1.6em", + afterTitle: "1.2em", + afterSub: "1.2em", + afterTopMeta: "0.4em", afterLegend: "1.6em", blockerRectOffset: "0.3em", columnPaddingCoefficient: 0.3, @@ -138,6 +141,7 @@ var defaultProps = { metadata: { chartType: 'xy', title: "", + sub: "", source: "", credit: "Made with Chartbuilder", size: "auto" diff --git a/src/js/components/ChartMetadata.jsx b/src/js/components/ChartMetadata.jsx index 918c55ca..c81676b8 100644 --- a/src/js/components/ChartMetadata.jsx +++ b/src/js/components/ChartMetadata.jsx @@ -1,5 +1,5 @@ // Component that handles global metadata, ie data that is universal regardless -// of chart type. Eg title, source, credit, size. +// of chart type. Eg title, sub, source, credit, size. var React = require("react"); var PropTypes = React.PropTypes; @@ -41,6 +41,7 @@ var chart_sizes = [ var text_input_values = [ { name: "title", content: "Title", isRequired: true }, + { name: "sub", content: "Subtitle" }, { name: "credit", content: "Credit" }, { name: "source", content: "Source" } ]; @@ -63,6 +64,7 @@ var ChartMetadata = React.createClass({ size: PropTypes.string.isRequired, source: PropTypes.string, credit: PropTypes.string, + sub: PropTypes.string, title: PropTypes.string }), stepNumber: PropTypes.string, @@ -99,13 +101,16 @@ var ChartMetadata = React.createClass({ onChange={this._handleMetadataUpdate} isRequired={textInput.isRequired} /> - }, this); + }, this) + .filter(function(chart_meta_text){ + return chart_meta_text.key == "sub" ? (metadata.title.length > 0) : true; + }); return (

{this.props.stepNumber} - Set title, source, credit and size + Set title, subtitle, source, credit and size

{textInputs} {this.props.additionalComponents} diff --git a/src/js/components/RendererWrapper.jsx b/src/js/components/RendererWrapper.jsx index db9008c5..8edcbe75 100644 --- a/src/js/components/RendererWrapper.jsx +++ b/src/js/components/RendererWrapper.jsx @@ -107,6 +107,10 @@ var RendererWrapper = React.createClass({ chartProps = _chartProps; } var state = assign({}, { chartProps: chartProps }, size_calcs); + + // if there's no afterTopMeta use afterTitle + state.chartConfig.display.afterTopMeta = state.chartConfig.display.afterTopMeta || 0; + this.setState(state); }, @@ -247,6 +251,7 @@ var RendererWrapper = React.createClass({ var margin = this.state.chartConfig.display.margin; var metadataSvg = []; var title; + var sub; var translate = { top: margin.top, @@ -267,6 +272,20 @@ var RendererWrapper = React.createClass({ /> ); metadataSvg.push(title); + + // only display a subtitle if there's a title + if (metadata.sub && metadata.sub !== "") { + sub = ( + + ); + metadataSvg.push(sub); + } } metadataSvg.push( @@ -319,4 +338,4 @@ var RendererWrapper = React.createClass({ }); -module.exports = RendererWrapper; +module.exports = RendererWrapper; \ No newline at end of file diff --git a/src/js/components/chart-grid/ChartGridBars.jsx b/src/js/components/chart-grid/ChartGridBars.jsx index 80521ce4..fd17a1d9 100644 --- a/src/js/components/chart-grid/ChartGridBars.jsx +++ b/src/js/components/chart-grid/ChartGridBars.jsx @@ -93,9 +93,12 @@ var ChartGridBars = React.createClass({ /* Height of each grid block */ dimensionsPerGrid.height = (dimensions.height) / chartProps._grid.rows; - if (this.props.hasTitle) { - extraPadding.top = extraPadding.top + displayConfig.afterTitle; - dimensionsPerGrid.height -= displayConfig.afterTitle; + if (this.props.metadata.hasBoth) { + extraPadding.top = extraPadding.top + displayConfig.afterTitle + displayConfig.afterSub + displayConfig.afterTopMeta; + dimensionsPerGrid.height -= displayConfig.afterTitle + displayConfig.afterSub + displayConfig.afterTopMeta; + } else if (this.props.metadata.hasTitle) { + extraPadding.top = extraPadding.top + displayConfig.afterTitle + displayConfig.afterTopMeta; + dimensionsPerGrid.height -= displayConfig.afterTitle + displayConfig.afterTopMeta; } diff --git a/src/js/components/chart-grid/ChartGridRenderer.jsx b/src/js/components/chart-grid/ChartGridRenderer.jsx index 91f16467..31b4fa9d 100644 --- a/src/js/components/chart-grid/ChartGridRenderer.jsx +++ b/src/js/components/chart-grid/ChartGridRenderer.jsx @@ -71,8 +71,29 @@ var ChartGridRenderer = React.createClass({ var chartProps = update(_chartProps, { $merge: { scale: scale }}); - /* Pass a boolean that detects whether there is a title */ - var hasTitle = (this.props.metadata.title.length > 0 && this.props.showMetadata); + /* Pass a boolean that detects whether there is a title and sub*/ + this.props.metadata.hasTitle = this.props.metadata.title.length > 0 && this.props.showMetadata; + this.props.metadata.hasBoth = this.props.metadata.hasTitle && this.props.metadata.sub && this.props.metadata.sub.length > 0; + + /* Choose between grid of bars and grid of XY, and transfer all props to + * relevant component + */ + if (this.props.chartProps._grid.type == "bar") { + gridTypeRenderer = ( + + ); + } else { + gridTypeRenderer = ( + + ); + } + /* Choose between grid of bars and grid of XY, and transfer all props to * relevant component @@ -82,7 +103,6 @@ var ChartGridRenderer = React.createClass({ ); } else { @@ -90,7 +110,6 @@ var ChartGridRenderer = React.createClass({ ); } diff --git a/src/js/components/chart-grid/ChartGridXY.jsx b/src/js/components/chart-grid/ChartGridXY.jsx index 96f971f1..75333387 100644 --- a/src/js/components/chart-grid/ChartGridXY.jsx +++ b/src/js/components/chart-grid/ChartGridXY.jsx @@ -94,8 +94,11 @@ var ChartGridXY = React.createClass({ var dimensions = clone(this.props.dimensions); - if (this.props.hasTitle) { - extraPadding.top = extraPadding.top + displayConfig.afterTitle; + + if (this.props.metadata.hasBoth) { + extraPadding.top = extraPadding.top + displayConfig.afterTitle + displayConfig.afterSub + displayConfig.afterTopMeta; + } else if (this.props.metadata.hasTitle) { + extraPadding.top = extraPadding.top + displayConfig.afterTitle + displayConfig.afterTopMeta; } /* Divide total width by number of grids, also subtracting the spade to be @@ -360,7 +363,7 @@ function drawXYChartGrid(el, state) { .left(function(y) { y.key("value") .domain(chartProps.scale.primaryScale.domain) - .range([this.height - this.padding.bottom, this.padding.top + displayConfig.afterTitle]); + .range([this.height - this.padding.bottom, this.padding.top + displayConfig.afterTitle + displayConfig.afterTopMeta]); }) .chartAreaOnTop(false) .mixout("rightAxis") diff --git a/src/js/components/chart-xy/XYRenderer.jsx b/src/js/components/chart-xy/XYRenderer.jsx index 1e737939..69dde55d 100644 --- a/src/js/components/chart-xy/XYRenderer.jsx +++ b/src/js/components/chart-xy/XYRenderer.jsx @@ -154,9 +154,11 @@ var XYRenderer = React.createClass({ // apply `chartSettings` to data var dataWithSettings = this._applySettingsToData(_chartProps); - // compute margin based on existence of labels and title, based on default + // compute margin based on existence of labels and titles, based on default // margin set in config var labels = _chartProps._annotations.labels; + var hasTitle = (this.props.metadata.title.length > 0 && this.props.showMetadata); + var hasBoth = hasTitle && this.props.metadata.sub.length > 0; // compute the max tick width for each scale each(scaleNames, function(scaleKey) { @@ -218,6 +220,7 @@ var XYRenderer = React.createClass({ chartProps={_chartProps} allLabelsDragged={allLabelsDragged} hasTitle={hasTitle} + hasBoth={hasBoth} yOffset={yOffset} displayConfig={this.props.displayConfig} styleConfig={this.props.styleConfig} @@ -239,6 +242,7 @@ var XYRenderer = React.createClass({ chartAreaDimensions={chartAreaDimensions} data={dataWithSettings} hasTitle={hasTitle} + hasBoth={hasBoth} yOffset={yOffset} scale={scale} editable={this.props.editable} @@ -260,6 +264,7 @@ var XYRenderer = React.createClass({ * propTypes: { * chartProps: PropTypes.object.isRequired, * hasTitle: PropTypes.bool.isRequired, + * hasBoth: PropTypes.bool.isRequired, * displayConfig: PropTypes.object.isRequired, * styleConfig: PropTypes.object.isRequired, * data: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -282,6 +287,7 @@ var XYChart = React.createClass({ propTypes: { chartProps: PropTypes.object.isRequired, hasTitle: PropTypes.bool.isRequired, + hasBoth: PropTypes.bool.isRequired, yOffset: PropTypes.number.isRequired, displayConfig: PropTypes.object.isRequired, styleConfig: PropTypes.object.isRequired, @@ -329,6 +335,21 @@ var XYChart = React.createClass({ return false; }, + componentWillReceiveProps: function(nextProps) { + var yOffset; + if (nextProps.hasBoth) { + yOffset = nextProps.displayConfig.margin.top + nextProps.displayConfig.afterTitle + nextProps.displayConfig.afterSub + nextProps.displayConfig.afterTopMeta; + } else if (nextProps.hasTitle) { + yOffset = nextProps.displayConfig.margin.top + nextProps.displayConfig.afterTitle + nextProps.displayConfig.afterTopMeta; + } else { + yOffset = nextProps.displayConfig.margin.top; + } + + this.setState({ + yOffset: yOffset + }); + }, + _getChartState: function(props) { // Generate and return the state needed to draw the chart. This is what will // passed to the d4/d3 draw function. @@ -380,6 +401,7 @@ var XYChart = React.createClass({ * propTypes: { * chartProps: PropTypes.object.isRequired, * hasTitle: PropTypes.bool.isRequired, + * hasBoth: PropTypes.bool.isRequired, * displayConfig: PropTypes.object.isRequired, * styleConfig: PropTypes.object.isRequired, * data: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -404,6 +426,7 @@ var XYLabels = React.createClass({ chartProps: PropTypes.object.isRequired, editable: PropTypes.bool, hasTitle: PropTypes.bool.isRequired, + hasBoth: PropTypes.bool.isRequired, displayConfig: PropTypes.object.isRequired, styleConfig: PropTypes.object.isRequired, data: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -430,6 +453,17 @@ var XYLabels = React.createClass({ mixins: [ DateScaleMixin, NumericScaleMixin ], componentWillReceiveProps: function(nextProps) { + // Determine how far down vertically the labels should be placed, depending + // on presence (or not) of a title + var yOffset; + if (nextProps.hasBoth) { + yOffset = nextProps.displayConfig.margin.top + nextProps.displayConfig.afterTitle + nextProps.displayConfig.afterSub + nextProps.displayConfig.afterTopMeta; + } else if (nextProps.hasTitle) { + yOffset = nextProps.displayConfig.margin.top + nextProps.displayConfig.afterTitle + nextProps.displayConfig.afterTopMeta; + } else { + yOffset = nextProps.displayConfig.margin.top; + } + /* * We use this XYLabels component's state to save locations of undragged * labels. Dragged labels are saved to the parent store so that they can be @@ -894,9 +928,11 @@ function computePadding(props) { var displayConfig = props.displayConfig; var _top = (props.labelYMax * props.chartAreaDimensions.height) + displayConfig.afterLegend; - if (props.hasTitle) { - _top += displayConfig.afterTitle; - } + if (props.hasBoth) { + _top += displayConfig.afterTitle + displayConfig.afterSub + displayConfig.afterTopMeta; + } else if (props.hasTitle) { + _top += displayConfig.afterTitle + displayConfig.afterTopMeta; + } // Reduce top padding if all labels or dragged or there is only one series, // meaning no label will be shown diff --git a/src/styl/chart-renderer.styl b/src/styl/chart-renderer.styl index 6ebcb091..454df18b 100644 --- a/src/styl/chart-renderer.styl +++ b/src/styl/chart-renderer.styl @@ -144,6 +144,10 @@ svg fill $color-body-text font-family $font-sans font-size $em_size + &.svg-text-sub + fill $color-chart-axis-text + font-family $font-sans-light + font-size $em_size .svg-source-pipe stroke $color-chart-meta