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