'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const index = require('./index-9f97b469.js'); const chartState = require('./chart-state-0a988421.js'); const debounce = require('./debounce-7d86d183.js'); const groupConfigUtils = require('./group-config-utils-07ac5e65.js'); const uniqueId = require('./unique-id-292f555d.js'); const array = require('./array-490698ca.js'); function max(values, valueof) { let max; if (valueof === undefined) { for (const value of values) { if (value != null && (max < value || (max === undefined && value >= value))) { max = value; } } } else { let index = -1; for (let value of values) { if ((value = valueof(value, ++index, values)) != null && (max < value || (max === undefined && value >= value))) { max = value; } } } return max; } function sum(values, valueof) { let sum = 0; if (valueof === undefined) { for (let value of values) { if (value = +value) { sum += value; } } } else { let index = -1; for (let value of values) { if (value = +valueof(value, ++index, values)) { sum += value; } } } return sum; } function none$1(series, order) { if (!((n = series.length) > 1)) return; for (var i = 1, j, s0, s1 = series[order[0]], n, m = s1.length; i < n; ++i) { s0 = s1, s1 = series[order[i]]; for (j = 0; j < m; ++j) { s1[j][1] += s1[j][0] = isNaN(s0[j][1]) ? s0[j][0] : s0[j][1]; } } } function none(series) { var n = series.length, o = new Array(n); while (--n >= 0) o[n] = n; return o; } function stackValue(d, key) { return d[key]; } function stackSeries(key) { const series = []; series.key = key; return series; } function stack() { var keys = chartState.constant([]), order = none, offset = none$1, value = stackValue; function stack(data) { var sz = Array.from(keys.apply(this, arguments), stackSeries), i, n = sz.length, j = -1, oz; for (const d of data) { for (i = 0, ++j; i < n; ++i) { (sz[i][j] = [0, +value(d, sz[i].key, j, data)]).data = d; } } for (i = 0, oz = array.array(order(sz)); i < n; ++i) { sz[oz[i]].index = i; } offset(sz, oz); return sz; } stack.keys = function(_) { return arguments.length ? (keys = typeof _ === "function" ? _ : chartState.constant(Array.from(_)), stack) : keys; }; stack.value = function(_) { return arguments.length ? (value = typeof _ === "function" ? _ : chartState.constant(+_), stack) : value; }; stack.order = function(_) { return arguments.length ? (order = _ == null ? none : typeof _ === "function" ? _ : chartState.constant(Array.from(_)), stack) : order; }; stack.offset = function(_) { return arguments.length ? (offset = _ == null ? none$1 : _, stack) : offset; }; return stack; } var __rest = (undefined && undefined.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; class BarChartSvgBars { constructor(selection) { this.selection = selection; } addTooltipHandler(tooltipElement, xScale, yScale, xOffset, yOffset, tooltipTextFormatter) { if (!tooltipElement) { this.selection.on("touchmove mousemove touchstart", null); this.selection.on("touchend mouseleave", null); return this; } groupConfigUtils.select(tooltipElement).on("touchend mouseleave", function () { tooltipElement.open = false; }); this.selection.on("touchmove mousemove touchstart", function (event, d) { event.preventDefault(); // reopen to trigger popperjs placement tooltipElement.open = false; tooltipElement.open = true; const _a = d.data, { groupKey } = _a, values = __rest(_a, ["groupKey"]); const x = xScale(groupKey) + xOffset; const y = Object.values(values).reduce((s, c) => s + c) / 2; const selectedValue = d[1] - d[0]; groupConfigUtils.select(tooltipElement) .style("position", "absolute") .style("display", "block") .style("left", x + "px") .style("top", yScale(y) + yOffset + "px") .select('[slot="tip"]') .html(groupConfigUtils.transform(tooltipTextFormatter, { key: d.data.groupKey, value: selectedValue, values: values, hoveredGroupKey: d.dataGroupKey, })); }); this.selection.on("touchend mouseleave", function (event) { if (event.relatedTarget && event.relatedTarget.closest("uic-tooltip")) { return; } tooltipElement.open = false; }); return this; } updateInBarLabels(xScale, yScale, yFormatter, transitionDurationMs) { const inBarLabels = this.selection .selectAll(".uic-bar-chart__in-bar-label") .data((d) => [d]); const enteringBarLabels = inBarLabels .enter() .append("text") .attr("class", "uic-bar-chart__in-bar-label") .attr("fill", "black") .attr("dominant-baseline", "middle") .attr("text-anchor", "middle") .attr("filter", "url(#uic-bar-chart__in-bar-label-background)") .attr("style", "opacity: 0") .call((s) => BarChartSvgBars.positionInBarLabels(s, xScale, yScale)); inBarLabels .transition() .duration(transitionDurationMs) .call((s) => BarChartSvgBars.positionInBarLabels(s, xScale, yScale)); inBarLabels.exit().remove(); const exisitingInBarLabels = enteringBarLabels .merge(inBarLabels) .text((d) => groupConfigUtils.transform(yFormatter, d[1] - d[0])); const maxBarLength = exisitingInBarLabels .nodes() .map((n) => n.getComputedTextLength()) .reduce((max, x) => (x > max ? x : max)); exisitingInBarLabels .transition() .delay(transitionDurationMs) .duration(transitionDurationMs / 5) .attr("style", (d) => `opacity: ${maxBarLength < xScale.bandwidth() * 0.8 && yScale(d[0]) - yScale(d[1]) > 50 ? "1" : "0"}`); } static positionInBarLabels(selection, xScale, yScale) { selection .attr("x", (d) => (xScale(d.data.groupKey.toString()) || 0) + xScale.bandwidth() / 2) .attr("y", function (d) { return yScale(d[1] + (d[0] - d[1]) / 2); }); } } class BarChartSvgDataGroup { constructor(selection) { this.selection = selection; } updateBars(xScale, yScale, transitionDurationMs) { const bars = this.selection .selectAll(".uic-bar-chart__bar-item") .classed("uic-bar-chart__bar-item", true) .data((data) => data.map((d) => (Object.assign(Object.assign({}, d), { dataGroupKey: data.key })))); const enteringBars = bars .enter() .append("g") .classed("uic-bar-chart__bar-item", true); bars.exit().remove(); enteringBars .append("rect") .call((s) => BarChartSvgDataGroup.positionBars(s, xScale, yScale)); const existingBars = enteringBars.merge(bars); const barsRects = existingBars.select("rect"); barsRects .transition() .duration(transitionDurationMs) .call((s) => BarChartSvgDataGroup.positionBars(s, xScale, yScale)); return new BarChartSvgBars(existingBars); } static positionBars(selection, xScale, yScale) { selection .attr("x", (d) => xScale(d.data.groupKey.toString()) || 0) .attr("y", (d) => yScale(d[1])) .attr("height", (d) => Math.max(0, yScale(d[0]) - yScale(d[1]))) .attr("width", xScale.bandwidth()); } } class BarChartSvgChartArea { constructor(svg, margin) { this.selection = svg .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); } getXAxisArea() { return this.xAxisArea; } getYAxisArea() { return this.yAxisArea; } appendYAxis() { this.yAxisArea = this.selection .append("g") .attr("class", "uic-bar-chart__y-axis"); return this.yAxisArea; } appendXAxis(parentHeight, margin) { this.xAxisArea = this.selection .append("g") .attr("class", "uic-bar-chart__x-axis") .attr("transform", `translate(0,${parentHeight - margin.bottom - margin.top})`); return this.xAxisArea; } appendDataGroupArea() { this.selection.append("g").attr("class", "uic-bar-chart"); this.selection .append("g") .attr("class", "uic-bar-chart__target-values"); } updateChartArea(margin, yAxisLabelOffset, maxYTickLabelWidth) { this.selection.attr("transform", `translate(${margin.left + yAxisLabelOffset + maxYTickLabelWidth}, ${margin.top})`); return this; } updateYAxis(yScale, yTicks, yFormatter, xAxisWidth, transitionDurationMs) { this.selection .select(".uic-bar-chart__y-axis") .transition() .duration(transitionDurationMs) .call(BarChartSvgChartArea.yAxis(yScale, yTicks, yFormatter, xAxisWidth)); return this; } updateXAxis(xScale, parentHeight, maxXTickLabelWidth, labelRotationDegrees, additionalXTickLabelSpace, margin, xFormatter, transitionDurationMs) { const xAxis = this.selection.select(".uic-bar-chart__x-axis"); xAxis .transition() .duration(transitionDurationMs) .attr("transform", `translate(0, ${parentHeight - margin.bottom - margin.top - additionalXTickLabelSpace})`) .call(BarChartSvgChartArea.xAxis(xScale, xFormatter)); // x axis label direction const text = xAxis.selectAll("text"); if (maxXTickLabelWidth > xScale.bandwidth() * 1.1) { // instead of shrinking the chart we may crop the labels if they get too long, maybe use textPath with diagonal line instead as it crops the text automatically // see https://stackoverflow.com/questions/9241315/trimming-text-to-a-given-pixel-width-in-svg, https://bl.ocks.org/armollica/90ff1d55f7a8fdbf42500558e1c6d1f5 text.style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") .attr("transform", `rotate(-${labelRotationDegrees})`); } else { text.style("text-anchor", "inherit") .attr("dy", "0.71em") .attr("dx", null) .attr("transform", null); } return this; } updateTargetValues(groupedData, targetValues, xScale, yScale, transitionDurationMs) { const orderedTargetValues = Object.keys(groupedData) .map((g) => ({ key: g, target: targetValues[g] })) .filter((d) => d.target !== undefined); const targetValueElements = this.selection .select(".uic-bar-chart__target-values") .selectAll(".uic-bar-chart__target-value") .data(orderedTargetValues, (d) => d != null ? `uic-bar-chart__target-value--${d.key}` : undefined); targetValueElements .enter() .append("line") .attr("class", "uic-bar-chart__target-value") .call((s) => BarChartSvgChartArea.positionTargetValueLines(s, xScale, yScale)); targetValueElements .transition() .duration(transitionDurationMs) .call((s) => BarChartSvgChartArea.positionTargetValueLines(s, xScale, yScale)); targetValueElements.exit().remove(); return this; } updateDataGroups(groupedData, groupConfig) { const dataGroups = this.selection .select(".uic-bar-chart") .selectAll(".uic-bar-chart__datagroup") .data(groupedData, function (d) { return d != null ? `uic-bar-chart__datagroup--${d.key}` : this.id; }); const enteringDataGroups = dataGroups.enter().append("g"); const exisitingDataGroups = enteringDataGroups .merge(dataGroups) .attr("id", (s) => `uic-bar-chart__datagroup--${s.key}`) .attr("class", (s) => `uic-bar-chart__datagroup ${groupConfig[s.key] != undefined ? groupConfig[s.key].cssClass : ""}`); dataGroups.exit().remove(); return new BarChartSvgDataGroup(exisitingDataGroups); } static xAxis(xScale, xFromatter) { return groupConfigUtils.axisBottom(xScale) .tickSizeOuter(0) .tickFormat((val) => groupConfigUtils.transform(xFromatter, val)); } static yAxis(yScale, yTicks, yFormatter, width) { return groupConfigUtils.axisLeft(yScale) .ticks(yTicks) .tickFormat((val) => groupConfigUtils.transform(yFormatter, val).toString()) .tickSize(-width); } static positionTargetValueLines(selection, xScale, yScale) { selection .attr("data-key", (d) => d.key) .attr("x1", (d) => xScale(d.key) - xScale.bandwidth() * 0.05) .attr("x2", (d) => xScale(d.key) + xScale.bandwidth() * 1.05) .attr("y1", (d) => yScale(d.target)) .attr("y2", (d) => yScale(d.target)); } } class BarChartSvg { constructor(id, parentWidth, parentHeight) { this.selection = groupConfigUtils.select(`#${id}`) .append("svg") .attr("width", parentWidth) .attr("height", parentHeight); } show() { this.selection.style("visibility", "inherit"); return this; } hide() { this.selection.style("visibility", "hidden"); return this; } appendInBarLabelBackgroundFilter() { const filter = this.selection .append("defs") .append("filter") .attr("x", "-0.1") .attr("y", "-0.1") .attr("width", "1.2") .attr("height", "1.2") .attr("id", "uic-bar-chart__in-bar-label-background"); filter .append("feFlood") .attr("flood-color", "white") .attr("flood-opacity", "0.4"); filter .append("feComposite") .attr("in", "SourceGraphic") .attr("operator", "xor"); // avoid blurry text, only show cut-out return filter; } appendYAxisLabelArea() { return this.selection .append("g") .attr("class", "uic-bar-chart__y-axis-label"); } appendChartArea(margin) { this.chartArea = new BarChartSvgChartArea(this.selection, margin); return this.chartArea; } updateDimensions(parentWidth, parentHeight, transitionDurationMs) { this.selection .transition() .duration(transitionDurationMs) .attr("width", parentWidth) .attr("height", parentHeight); return this; } updateYAxisLabel(yAxisLabelData, margin, parentHeight, transitionDurationMs) { const yAxisLabel = this.selection .select(".uic-bar-chart__y-axis-label") .selectAll("text") .data(yAxisLabelData); yAxisLabel .enter() .append("text") .attr("text-anchor", "middle") .attr("y", margin.left) .attr("x", -parentHeight / 2) .attr("transform", "rotate(-90)") .attr("dy", "1em") .text((d) => d); yAxisLabel .text((d) => d) .transition() .duration(transitionDurationMs) .attr("y", margin.left) .attr("x", -parentHeight / 2); yAxisLabel.exit().remove(); return this; } } const barChartCss = ".uic-bar-chart{font-size:var(--uic-charts-bar-chart-general-font-size, 0.8rem);display:flex;align-items:center;justify-content:center;position:relative;color:var(--uic-charts-bar-chart-text-color, black)}.uic-bar-chart__x-axis .tick line{display:none}.uic-bar-chart__x-axis text{font-weight:var(--uic-charts-bar-chart-x-axis-label-font-weight, bold);font-size:var(--uic-charts-bar-chart-x-axis-label-font-size, 0.8rem)}.uic-bar-chart__y-axis path{display:none}.uic-bar-chart__y-axis .tick line{stroke:var(--uic-charts-bar-chart-tick-line-color, #eee)}.uic-bar-chart__y-axis text{font-weight:var(--uic-charts-bar-chart-y-axis-tick-font-weight, normal);font-size:var(--uic-charts-bar-chart-y-axis-tick-font-size, 0.8rem)}.uic-bar-chart__y-axis-label{font-weight:var(--uic-charts-bar-chart-y-axis-label-font-weight, normal);font-size:var(--uic-charts-bar-chart-y-axis-label-font-size, 0.8rem)}.uic-bar-chart__target-value{stroke:var(--uic-charts-bar-chart-target-value-stroke-color, white);stroke-dasharray:var(--uic-charts-bar-chart-target-value-stroke-dasharray, 5, 3);filter:var(--uic-charts-bar-chart-target-value-filter, drop-shadow(1px 1px 2px black));stroke-width:var(--uic-charts-bar-chart-target-value-stroke-width, 2)}.uic-bar-chart__datagroup{fill:var(--uic-charts-data-group-color, black)}.uic-bar-chart__tooltip-color-indicator{width:1em;height:1em;margin:0 0.5em 0 0;display:inline-block;vertical-align:middle;background-color:var(--uic-charts-data-group-color, black)}.uic-bar-chart__in-bar-label{font-size:var(--uic-charts-bar-chart-in-bar-label-font-size, 0.625rem);font-weight:var(--uic-charts-bar-chart-in-bar-label-font-weight, bold)}.uic-bar-chart__loading_indicator{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%;text-align:center;vertical-align:middle;background:var(--uic-charts-bar-chart-loading-overlay-color, white)}.uic-bar-chart uic-loader{position:absolute;display:flex;align-items:center;justify-content:center;width:max(188px, 25%);text-align:center;vertical-align:middle}.uic-bar-chart__no-data-indicator{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%}"; let BarChart = class { constructor(hostRef) { index.registerInstance(this, hostRef); /** Target values for each x axis entry of `data` */ this.targetValues = {}; /** Number of ticks (entries) on the Y axis. * In some cases d3 overwrites this value and chooses automatically * the best, depending on the current width and height of the chart. */ this.yTicks = 6; /** Whether tooltip should be show on hover/touchmove */ this.disableTooltip = false; /** Tooltip text formatter. Gets the x axis key and values for all data points at that location as input. * There's a default formatter which sorts by value, uses x and y formatters and adds a color dot per group by setting the group css class */ this.tooltipFormatter = this.defaultTooltipFormatter.bind(this); /** Customize the margin. You don't have to change them, labels won't be truncated regardless of these values */ this.margin = { top: 20, right: 20, bottom: 20, left: 20 }; /** Can be used to define a custom bar chart root class for styling purposes.*/ this.barChartRootClass = ""; /** State in which the bar chart is currently in. */ this.state = chartState.ChartState.SUCCESS; /** Text for displaying when no data is available. */ this.noDataText = "No data available"; /** Set the transition duration. As long as the transitions do not cause any issues, keep the default */ this.transitionDurationMs = 500; /** id attribute for this chart. A unique id will be generated if this is empty */ this.chartId = ""; this.debouncedUpdateSvg = debounce.debounce(() => this.updateSvg(), 100); this.yAxisLabelOffset = 30; this.xAxisLabelRotationDegrees = 65; } onResize() { this.debouncedUpdateSvg(); } componentDidLoad() { this.initSvg(); } componentWillLoad() { const { chartId } = this; this.chartId = chartId || uniqueId.getUniqueId(); } componentDidRender() { this.updateSvg(); } initSvg() { const { width: parentWidth, height: parentHeight, } = this.el.parentElement.getBoundingClientRect(); this.svg = new BarChartSvg(this.chartId, parentWidth, parentHeight); // define static svg filter for in bar label background this.svg.appendInBarLabelBackgroundFilter(); const chartArea = this.svg.appendChartArea(this.margin); chartArea.appendYAxis(); chartArea.appendXAxis(parentHeight, this.margin); chartArea.appendDataGroupArea(); this.svg.appendYAxisLabelArea(); this.updateSvg(); } updateSvg() { if (this.svg === undefined) return; if (this.state !== chartState.ChartState.NODATA && this.data !== undefined && this.data !== null && Object.keys(this.data).length > 0) { this.svg.show(); this.updateChart(); } else { this.svg.hide(); } } updateChart() { const { width: parentWidth, height: parentHeight, } = this.el.parentElement.getBoundingClientRect(); const yAxisLabelOffset = BarChart.hasYAxisLabel(this.yAxisLabel) ? this.yAxisLabelOffset : 0; const yAxisLabelData = BarChart.hasYAxisLabel(this.yAxisLabel) ? [this.yAxisLabel] : []; const maxYValue = +max(BarChart.groupDataValues(this.data).map((k) => sum(k))); const maxYTickLabelWidth = BarChart.maxYTickLabelWidth(this.svg.chartArea.getYAxisArea(), maxYValue, this.yFormatter); const xAxisWidth = BarChart.xScaleWidth(parentWidth, this.margin, maxYTickLabelWidth, yAxisLabelOffset); const xScale = BarChart.xScale(this.data, xAxisWidth); const maxXTickLabelWidth = BarChart.maxXTickLabelWidth(this.svg.chartArea.getXAxisArea(), this.data, this.xFormatter); const additionalXTickLabelSpace = maxXTickLabelWidth > xScale.bandwidth() * 1.1 ? maxXTickLabelWidth * Math.sin(this.xAxisLabelRotationDegrees * (Math.PI / 180)) : 0; const yScale = BarChart.yScale(maxYValue, parentHeight - this.margin.bottom - this.margin.top - additionalXTickLabelSpace); this.svg .updateDimensions(parentWidth, parentHeight, this.transitionDurationMs) .updateYAxisLabel(yAxisLabelData, this.margin, parentHeight, this.transitionDurationMs) .chartArea.updateChartArea(this.margin, yAxisLabelOffset, maxYTickLabelWidth) .updateYAxis(yScale, this.yTicks, this.yFormatter, xAxisWidth, this.transitionDurationMs) .updateXAxis(xScale, parentHeight, maxXTickLabelWidth, this.xAxisLabelRotationDegrees, additionalXTickLabelSpace, this.margin, this.xFormatter, this.transitionDurationMs) .updateTargetValues(this.data, this.targetValues, xScale, yScale, this.transitionDurationMs) .updateDataGroups(BarChart.stackData(this.data), this.groupConfig) .updateBars(xScale, yScale, this.transitionDurationMs) .addTooltipHandler(this.tooltip, xScale, yScale, this.margin.left + yAxisLabelOffset + maxYTickLabelWidth + xScale.bandwidth() / 2, this.margin.top, this.tooltipFormatter) .updateInBarLabels(xScale, yScale, this.yFormatter, this.transitionDurationMs); } /** Default tooltip formatter, sorts by value, uses x and y formatters and adds a color dot per group by setting the group css class */ defaultTooltipFormatter(d) { const formattedXKey = groupConfigUtils.transform(this.xFormatter, d.key); const dataGroupsAndValues = Object.entries(d.values) .reverse() .reduce((s, [itemGroupKey, value]) => { const cssClass = groupConfigUtils.getCssClassForKey(this.groupConfig, itemGroupKey); const dataGroup = groupConfigUtils.getLabelForKey(this.groupConfig, itemGroupKey); const formattedYValue = groupConfigUtils.transform(this.yFormatter, value); return (s + `