UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

255 lines (243 loc) • 8.33 kB
import React from "react"; import { styled } from "styled-components"; const defaults = { barHeight: 30, titleWidth: 35, }; const StyledHorizontalStackedBar = styled.div ` h3, h6 { margin: 0; line-height: 1em; } h3 { margin-bottom: 1em; } h6 { font-size: 0.8em; padding: 0 0.5em 0.5em 0; width: 20%; text-align: right; color: #666; } figure { margin: 2em auto 2em auto; max-width: 1100px; position: relative; } .graphic { padding-left: 10px; } .row { display: flex; align-items: center; } .title { font-size: 0.9em; width: ${(props) => props.$titleWidth || defaults.titleWidth}%; padding-right: 5px; text-align: right; color: #666; display: flex; align-items: center; justify-content: right; } @keyframes expand { from { width: 0%; } to { width: ${(props) => props.$showTitle ? props.$titleWidth || defaults.titleWidth : 92}%; } } @media screen and (min-width: 768px) { @keyframes expand { from { width: 0%; } to { width: ${(props) => props.$showTitle ? props.$titleWidth || defaults.titleWidth : 92}%; } } } .chart { position: relative; overflow: visible; width: 0%; animation: expand 1.5s ease forwards; } .row + .row .chart { animation-delay: 0.2s; } .row + .row + .row .chart { animation-delay: 0.4s; } .block.yes { outline: 1px solid #999; } .block { display: flex; align-items: center; justify-content: center; height: ${(props) => props.$barHeight || defaults.barHeight}px; color: #333; font-size: 0.75em; float: left; background-color: #999; position: relative; overflow: hidden; opacity: 1; transition: opacity, 0.3s ease; cursor: pointer; } .block:hover { opacity: 0.65; } .x-axis { text-align: center; padding: 1em 0 0.5em; } .legend { margin: 0 auto; padding: 0; font-size: 0.9em; } .legend li { display: inline-block; padding: 0.25em 0.8em; line-height: 1em; } .legend li:before { content: ""; margin-right: 0.5em; display: inline-block; width: 8px; height: 8px; background-color: #334d5c; } .zero-marker { position: absolute; left: -1.5px; height: ${(props) => (props.$barHeight || defaults.barHeight) * 1.5}px; width: 1.5px; background-color: #aaa; top: -${(props) => (props.$barHeight || defaults.barHeight) * 0.25}px; } ${(props) => props.$rowTotals.map((total, index) => ` .row-${index} .total-label { position: absolute; left: ${total + 0.75}%; width: 100px; font-size: 0.9em; text-shadow: 0 0 2px #FFF, 0 0 2px #FFF, 0 0 2px #FFF, 0 0 2px #FFF, 0 0 2px #FFF, 0 0 2px #FFF, 0 0 2px #FFF, 0 0 2px #FFF; font-weight: bold; color: #666; height: ${props.$barHeight || defaults.barHeight}px; display: flex; align-items: center; } `)} ${(props) => props.$target ? ` .marker-label { position: absolute; ${props.$targetLabelPosition || "top"}: ${props.$targetLabelStyle === "normal" ? "-15" : "-12"}px; left: ${props.$target || 0}%; width: 100px; text-align: left; font-size: 0.7em; color: #767676; } .marker { position: absolute; left: ${props.$target}%; height: ${(props.$barHeight || defaults.barHeight) + 4}px; width: 3px; background-color: #000; opacity: 0.35; top: -2px; border-radius: 2px; } ` : ""} ${(props) => props.$blockGroupColors.map((blockGroupColor, index) => ` .legend li:nth-of-type(${index + 1}):before { background-color: ${blockGroupColor}; } `)} @media screen and (min-width: 768px) { h6 { padding: 0 0.5em 0.5em 0; width: 6em; float: left; } .block { font-size: 1em; } .legend { width: 100%; } } `; /** * Horizontal stacked bar chart component */ export const HorizontalStackedBar = ({ rows, rowConfigs, barHeight, titleWidth, showLegend = true, showTitle = true, showTotalLabel = true, showTargetLabel = true, targetLabelPosition = "top", targetLabelStyle = "normal", target, blockGroupNames, valueFormatter, targetValueFormatter, targetReachedColor, ...rest }) => { const numBlockGroups = rows[0].length; const blockGroupStyles = rest.blockGroupStyles && rest.blockGroupStyles.length >= numBlockGroups ? rest.blockGroupStyles : [ { backgroundColor: "blue" }, { backgroundColor: "green" }, { backgroundColor: "gray" }, ]; const rowTotals = rows.reduce((rowSumsSoFar, row) => { return [...rowSumsSoFar, sumRow(row)]; }, []); return (React.createElement(StyledHorizontalStackedBar, { "$rowTotals": rowTotals, "$target": target, "$barHeight": barHeight, "$showTitle": showTitle, "$titleWidth": titleWidth, "$blockGroupColors": blockGroupStyles .map((style) => style.backgroundColor) .slice(0, numBlockGroups), "$targetLabelPosition": targetLabelPosition, "$targetLabelStyle": targetLabelStyle }, React.createElement(React.Fragment, null, React.createElement("div", { className: "graphic" }, rows.map((row, rowNumber) => { const titleProp = rowConfigs[rowNumber].title; const titleValue = (() => { if (typeof titleProp === "function") { return titleProp; } else { return () => titleProp; } })(); const targetReached = target && rowTotals[rowNumber] >= target; return (React.createElement("div", { key: `row-${rowNumber}`, className: `row row-${rowNumber}` }, showTitle && (React.createElement("div", { className: "title" }, titleValue(rowTotals[rowNumber]))), React.createElement("div", { className: "chart" }, row.map((blockGroup, blockGroupNumber) => blockGroup.map((blockValue, blockNumber) => (React.createElement("span", { key: `${blockGroupNumber}${blockNumber}`, style: { width: `${blockValue}%`, ...blockGroupStyles[blockGroupNumber], ...(targetReached && targetReachedColor ? { backgroundColor: targetReachedColor } : {}), }, className: `block-group-${blockGroupNumber} block-${blockNumber} block` })))), React.createElement("div", { className: "zero-marker" }), target && (React.createElement(React.Fragment, null, React.createElement("div", { className: "marker" }), showTargetLabel && rowNumber === 0 && (React.createElement("div", { className: "marker-label" }, targetValueFormatter ? targetValueFormatter(target) : "Target")))), showTotalLabel && (React.createElement("div", { className: "total-label" }, valueFormatter ? valueFormatter(rowTotals[rowNumber]) : rowTotals[rowNumber]))))); })), showLegend && (React.createElement("div", { className: "x-axis" }, React.createElement("ul", { className: "legend" }, blockGroupNames .slice(0, numBlockGroups) .map((blockGroupName, blockGroupNameIndex) => (React.createElement("li", { key: blockGroupNameIndex }, blockGroupName))))))))); }; /** Sum row values */ const sumRow = (row) => row.reduce((rowSumSoFar, blockGroup) => rowSumSoFar + sumBlockGroup(blockGroup), 0); /** Sum block group values */ const sumBlockGroup = (group) => group.reduce((groupSumSoFar, blockValue) => groupSumSoFar + blockValue, 0); //# sourceMappingURL=HorizontalStackedBar.js.map