"use strict";
import { GraphContent } from "../../core";
import { getDefaultValue } from "../../core/BaseConfig";
import constants from "../../helpers/constants";
import {
prepareLabelShapeItem,
removeLabelShapeItem
} from "../../helpers/label";
import { removeLegendItem, reflowLegend } from "../../helpers/legend";
import styles from "../../helpers/styles";
import utils from "../../helpers/utils";
import BarConfig from "./BarConfig";
import { removeAxisInfoRowLabels } from "./helpers/axisInfoRowHelpers";
import {
clear,
draw,
prepareLegendItems,
processDataPoints,
setGroupName,
drawDataBars
} from "./helpers/creationHelpers";
import { processGoalLines, translateRegion } from "./helpers/goalLineHelpers";
import { clickHandler, hoverHandler } from "./helpers/legendHelpers";
import { scaleBandAxis, setBarOffsets } from "./helpers/resizeHelpers";
import {
clearSelectionDatum,
updateSelectionBars
} from "./helpers/selectionHelpers";
import {
translateBarGraph,
translateTextLabel
} from "./helpers/translateHelpers";
/**
* Calculates the min and max values for Y Axis or Y2 Axis
*
* @private
* @param {Array} values - Datapoint values
* @param {string} axis - y or y2
* @returns {object} - Contains min and max values for the data points
*/
const calculateValuesRange = (values, axis = constants.Y_AXIS) => {
const min = Math.min(...values.map((i) => i.y));
const max = Math.max(...values.map((i) => i.y));
return {
[axis]: {
min: min < 0 ? min : 0,
max: max > 0 ? max : 0
}
};
};
/**
* Data point sets can be loaded using this function.
* Load function validates, clones and stores the input onto a config object.
*
* @private
* @param {object} inputJSON - Input JSON provided by the consumer
* @returns {object} BarConfig config object containing consumer data
*/
const loadInput = (inputJSON) =>
new BarConfig().setInput(inputJSON).validateInput().clone().getConfig();
/**
* Initializes the necessary Bar constructor objects
*
* @private
* @param {Bar} control - Bar instance
* @returns {Bar} Bar instance
*/
const initConfig = (control) => {
control.config = {};
control.bandScale = {
x0: {},
x1: {}
};
control.dataTarget = {};
control.valuesRange = {};
return control;
};
/**
* A bar graph is a graph used to represent numerical values of data by
* height or length of lines or rectangles of equal width
*
* Lifecycle functions include:
* * Load
* * Generate
* * Unload
* * Destroy
*
* @module Bar
* @class Bar
*/
class Bar extends GraphContent {
/**
* @class
* @param {BarConfig} input - Input JSON instance created using GraphConfig
*/
constructor(input) {
super();
initConfig(this);
this.config = loadInput(input);
this.type = "Bar";
this.config.yAxis = getDefaultValue(
this.config.yAxis,
constants.Y_AXIS
);
this.config.axisPadding = false;
this.valuesRange = calculateValuesRange(
this.config.values,
this.config.yAxis
);
}
/**
* @inheritdoc
*/
load(graph) {
setGroupName(this.config, graph.content);
scaleBandAxis(this.bandScale, graph.config, graph.content);
this.dataTarget = processDataPoints(graph.config, this.config);
draw(
graph.scale,
this.bandScale,
graph.config,
graph.svg,
this.dataTarget
);
updateSelectionBars(
this.dataTarget.internalValuesSubset,
graph.svg,
graph.config
);
prepareLegendItems(
graph.config,
{
clickHandler: clickHandler(
graph,
this,
graph.config,
graph.svg
),
hoverHandler: hoverHandler(graph.config.shownTargets, graph.svg)
},
this.dataTarget,
graph.legendSVG
);
prepareLabelShapeItem(
graph.config,
this.dataTarget,
graph.axesLabelShapeGroup[this.config.yAxis]
);
return this;
}
/**
* @inheritdoc
*/
unload(graph) {
clear(graph.svg, this.dataTarget.key);
removeLegendItem(graph.legendSVG, this.dataTarget);
removeLabelShapeItem(
graph.axesLabelShapeGroup[this.config.yAxis],
this.dataTarget,
graph.config
);
removeAxisInfoRowLabels(
graph.svg.select(`.${styles.axisInfoRow}`),
this.dataTarget.key
);
clearSelectionDatum(graph.svg, this.dataTarget.key);
initConfig(this);
return this;
}
/**
* @inheritdoc
*/
resize(graph) {
scaleBandAxis(this.bandScale, graph.config, graph.content);
setBarOffsets(
graph.content,
graph.contentConfig,
this,
this.bandScale,
graph.config
);
translateBarGraph(
graph.scale,
this.bandScale,
graph.svg,
this.dataTarget,
graph.config
);
if (utils.notEmpty(this.dataTarget.axisInfoRow)) {
translateTextLabel(
this.bandScale,
graph.scale,
graph.config,
graph.svg,
this.dataTarget.axisInfoRow,
this.dataTarget
);
}
if (utils.notEmpty(this.dataTarget.regions)) {
processGoalLines(
graph.scale,
this.bandScale,
graph.config,
this.dataTarget,
this.config.yAxis
);
translateRegion(
graph.scale,
graph.config,
graph.svg.selectAll(
`rect[aria-describedby=region_${this.dataTarget.key}]`
)
);
}
return this;
}
/**
* @inheritdoc
*/
reflow(graph, graphData) {
const eventHandlers = {
clickHandler: clickHandler(graph, this, graph.config, graph.svg),
hoverHandler: hoverHandler(graph.config.shownTargets, graph.svg)
};
this.config.values = graphData.values;
this.dataTarget = processDataPoints(graph.config, this.config);
const position = graph.config.shownTargets.lastIndexOf(graphData.key);
if (position > -1) {
graph.config.shownTargets.splice(position, 1);
}
reflowLegend(
graph.legendSVG,
graph.content.filter((bar) => bar.config.key === graphData.key)[0]
.config,
graph,
eventHandlers
);
const tickValues = graph.config.axis.x.ticks.values.map((d) => ({
x: d,
valueSubsetArray: []
}));
if (utils.isEmpty(graphData.values)) {
removeLabelShapeItem(
graph.axesLabelShapeGroup[this.config.yAxis],
this.dataTarget,
graph.config
);
}
scaleBandAxis(this.bandScale, graph.config, graph.content);
const barSelectionGroup = graph.svg
.select(`.${styles.barSelectionGroup}`)
.selectAll(`.${styles.taskBarSelection}`)
.data(tickValues);
barSelectionGroup
.enter()
.append("rect")
.attr("aria-hidden", true)
.classed(styles.taskBarSelection, true)
.attr(
"aria-describedby",
(value) => `bar-selector-${tickValues.indexOf(value)}`
)
.attr("rx", 3)
.attr("ry", 3);
barSelectionGroup
.exit()
.transition()
.call(
constants.d3Transition(
graph.config.settingsDictionary.transition
)
)
.remove();
updateSelectionBars(
this.dataTarget.internalValuesSubset,
graph.svg,
graph.config
);
const currentBarsPath = graph.svg
.select(`g[aria-describedby="${graphData.key}"]`)
.select(`[class="${styles.currentBarsGroup}"]`)
.data([this.dataTarget]);
const bars = currentBarsPath
.selectAll(`.${styles.bar}`)
.data(this.dataTarget.internalValuesSubset);
bars.exit().remove();
const barsContent = currentBarsPath
.selectAll(`.${styles.bar} > rect`)
.data(this.dataTarget.internalValuesSubset);
drawDataBars(
graph.scale,
this.bandScale,
graph.config,
graph.svg,
barsContent.enter(),
this.dataTarget.regions,
this.dataTarget.axisInfoRow
);
this.resize(graph);
this.valuesRange = calculateValuesRange(
this.config.values,
this.config.yAxis
);
this.resize(graph);
}
/**
* @inheritdoc
*/
redraw(graph) {
clear(graph.svg, this.dataTarget.key);
removeAxisInfoRowLabels(
graph.svg.select(`.${styles.axisInfoRow}`),
this.dataTarget.key
);
draw(
graph.scale,
this.bandScale,
graph.config,
graph.svg,
this.dataTarget
);
return this;
}
}
export default Bar;