UNPKG

datajunction-ui

Version:
1 lines 112 kB
{"version":3,"file":"index-BmlSMAOJ.cjs","sources":["../node_modules/recharts/es6/state/selectors/areaSelectors.js","../node_modules/recharts/es6/cartesian/Area.js","../node_modules/recharts/es6/chart/AreaChart.js","../src/app/pages/SystemMetricsExplorerPage/index.jsx"],"sourcesContent":["import { createSelector } from 'reselect';\nimport { computeArea } from '../../cartesian/Area';\nimport { selectAxisWithScale, selectStackGroups, selectTicksOfGraphicalItem, selectUnfilteredCartesianItems } from './axisSelectors';\nimport { selectChartLayout } from '../../context/chartLayoutContext';\nimport { selectChartDataWithIndexesIfNotInPanorama } from './dataSelectors';\nimport { getBandSizeOfAxis, getNormalizedStackId, isCategoricalAxis } from '../../util/ChartUtils';\nvar selectXAxisWithScale = (state, xAxisId, _yAxisId, isPanorama) => selectAxisWithScale(state, 'xAxis', xAxisId, isPanorama);\nvar selectXAxisTicks = (state, xAxisId, _yAxisId, isPanorama) => selectTicksOfGraphicalItem(state, 'xAxis', xAxisId, isPanorama);\nvar selectYAxisWithScale = (state, _xAxisId, yAxisId, isPanorama) => selectAxisWithScale(state, 'yAxis', yAxisId, isPanorama);\nvar selectYAxisTicks = (state, _xAxisId, yAxisId, isPanorama) => selectTicksOfGraphicalItem(state, 'yAxis', yAxisId, isPanorama);\nvar selectBandSize = createSelector([selectChartLayout, selectXAxisWithScale, selectYAxisWithScale, selectXAxisTicks, selectYAxisTicks], (layout, xAxis, yAxis, xAxisTicks, yAxisTicks) => {\n if (isCategoricalAxis(layout, 'xAxis')) {\n return getBandSizeOfAxis(xAxis, xAxisTicks, false);\n }\n return getBandSizeOfAxis(yAxis, yAxisTicks, false);\n});\nvar selectGraphicalItemStackedData = (state, xAxisId, yAxisId, isPanorama, areaSettings) => {\n var _stackGroups$stackId;\n var layout = selectChartLayout(state);\n var isXAxisCategorical = isCategoricalAxis(layout, 'xAxis');\n var stackGroups;\n if (isXAxisCategorical) {\n stackGroups = selectStackGroups(state, 'yAxis', yAxisId, isPanorama);\n } else {\n stackGroups = selectStackGroups(state, 'xAxis', xAxisId, isPanorama);\n }\n if (stackGroups == null) {\n return undefined;\n }\n var {\n dataKey,\n stackId\n } = areaSettings;\n if (stackId == null) {\n return undefined;\n }\n var groups = (_stackGroups$stackId = stackGroups[stackId]) === null || _stackGroups$stackId === void 0 ? void 0 : _stackGroups$stackId.stackedData;\n return groups === null || groups === void 0 ? void 0 : groups.find(v => v.key === dataKey);\n};\nvar pickAreaSettings = (_state, _xAxisId, _yAxisId, _isPanorama, areaSettings) => areaSettings;\n\n/*\n * There is a race condition problem because we read some data from props and some from the state.\n * The state is updated through a dispatch and is one render behind,\n * and so we have this weird one tick render where the displayedData in one selector have the old dataKey\n * but the new dataKey in another selector.\n *\n * A proper fix is to either move everything into the state, or read the dataKey always from props\n * - but this is a smaller change.\n */\nvar selectSynchronisedAreaSettings = createSelector([selectUnfilteredCartesianItems, pickAreaSettings], (graphicalItems, areaSettingsFromProps) => {\n if (graphicalItems.some(cgis => cgis.type === 'area' && areaSettingsFromProps.dataKey === cgis.dataKey && getNormalizedStackId(areaSettingsFromProps.stackId) === cgis.stackId && areaSettingsFromProps.data === cgis.data)) {\n /*\n * now, at least one of the areas has the same dataKey as the one in props.\n * Is this a perfect match? Maybe not because we could theoretically have two different Areas with the same dataKey\n * and the same stackId and the same data but still different areas, yes,\n * but the chances of that happening are ... lowish.\n *\n * A proper fix would be to store the areaSettings in a state too, and compare references directly instead of enumerating the properties.\n */\n return areaSettingsFromProps;\n }\n return undefined;\n});\nexport var selectArea = createSelector([selectChartLayout, selectXAxisWithScale, selectYAxisWithScale, selectXAxisTicks, selectYAxisTicks, selectGraphicalItemStackedData, selectChartDataWithIndexesIfNotInPanorama, selectBandSize, selectSynchronisedAreaSettings], (layout, xAxis, yAxis, xAxisTicks, yAxisTicks, stackedData, _ref, bandSize, areaSettings) => {\n var {\n chartData,\n dataStartIndex,\n dataEndIndex\n } = _ref;\n if (areaSettings == null || layout !== 'horizontal' && layout !== 'vertical' || xAxis == null || yAxis == null || xAxisTicks == null || yAxisTicks == null || xAxisTicks.length === 0 || yAxisTicks.length === 0 || bandSize == null) {\n return undefined;\n }\n var {\n data\n } = areaSettings;\n var displayedData;\n if (data && data.length > 0) {\n displayedData = data;\n } else {\n displayedData = chartData === null || chartData === void 0 ? void 0 : chartData.slice(dataStartIndex, dataEndIndex + 1);\n }\n if (displayedData == null) {\n return undefined;\n }\n\n // Where is this supposed to come from? No charts have that as a prop.\n var chartBaseValue = undefined;\n return computeArea({\n layout,\n xAxis,\n yAxis,\n xAxisTicks,\n yAxisTicks,\n dataStartIndex,\n areaSettings,\n stackedData,\n displayedData,\n chartBaseValue,\n bandSize\n });\n});","var _excluded = [\"layout\", \"type\", \"stroke\", \"connectNulls\", \"isRange\"],\n _excluded2 = [\"activeDot\", \"animationBegin\", \"animationDuration\", \"animationEasing\", \"connectNulls\", \"dot\", \"fill\", \"fillOpacity\", \"hide\", \"isAnimationActive\", \"legendType\", \"stroke\", \"xAxisId\", \"yAxisId\"];\nfunction _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }\nfunction _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nfunction _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\n// eslint-disable-next-line max-classes-per-file\nimport * as React from 'react';\nimport { PureComponent, useCallback, useMemo, useRef, useState } from 'react';\nimport { clsx } from 'clsx';\nimport { Curve } from '../shape/Curve';\nimport { Dot } from '../shape/Dot';\nimport { Layer } from '../container/Layer';\nimport { LabelList } from '../component/LabelList';\nimport { Global } from '../util/Global';\nimport { interpolate, isNan, isNullish, isNumber, uniqueId } from '../util/DataUtils';\nimport { getCateCoordinateOfLine, getTooltipNameProp, getValueByDataKey } from '../util/ChartUtils';\nimport { filterProps, isClipDot } from '../util/ReactUtils';\nimport { ActivePoints } from '../component/ActivePoints';\nimport { SetTooltipEntrySettings } from '../state/SetTooltipEntrySettings';\nimport { CartesianGraphicalItemContext } from '../context/CartesianGraphicalItemContext';\nimport { GraphicalItemClipPath, useNeedsClip } from './GraphicalItemClipPath';\nimport { selectArea } from '../state/selectors/areaSelectors';\nimport { useIsPanorama } from '../context/PanoramaContext';\nimport { useChartLayout, useOffset } from '../context/chartLayoutContext';\nimport { useChartName } from '../state/selectors/selectors';\nimport { SetLegendPayload } from '../state/SetLegendPayload';\nimport { useAppSelector } from '../state/hooks';\nimport { useAnimationId } from '../util/useAnimationId';\nimport { resolveDefaultProps } from '../util/resolveDefaultProps';\nimport { isWellBehavedNumber } from '../util/isWellBehavedNumber';\nimport { Animate } from '../animation/Animate';\n\n/**\n * Internal props, combination of external props + defaultProps + private Recharts state\n */\n\n/**\n * External props, intended for end users to fill in\n */\n\n/**\n * Because of naming conflict, we are forced to ignore certain (valid) SVG attributes.\n */\n\nfunction getLegendItemColor(stroke, fill) {\n return stroke && stroke !== 'none' ? stroke : fill;\n}\nvar computeLegendPayloadFromAreaData = props => {\n var {\n dataKey,\n name,\n stroke,\n fill,\n legendType,\n hide\n } = props;\n return [{\n inactive: hide,\n dataKey,\n type: legendType,\n color: getLegendItemColor(stroke, fill),\n value: getTooltipNameProp(name, dataKey),\n payload: props\n }];\n};\nfunction getTooltipEntrySettings(props) {\n var {\n dataKey,\n data,\n stroke,\n strokeWidth,\n fill,\n name,\n hide,\n unit\n } = props;\n return {\n dataDefinedOnItem: data,\n positions: undefined,\n settings: {\n stroke,\n strokeWidth,\n fill,\n dataKey,\n nameKey: undefined,\n name: getTooltipNameProp(name, dataKey),\n hide,\n type: props.tooltipType,\n color: getLegendItemColor(stroke, fill),\n unit\n }\n };\n}\nvar renderDotItem = (option, props) => {\n var dotItem;\n if (/*#__PURE__*/React.isValidElement(option)) {\n dotItem = /*#__PURE__*/React.cloneElement(option, props);\n } else if (typeof option === 'function') {\n dotItem = option(props);\n } else {\n var className = clsx('recharts-area-dot', typeof option !== 'boolean' ? option.className : '');\n dotItem = /*#__PURE__*/React.createElement(Dot, _extends({}, props, {\n className: className\n }));\n }\n return dotItem;\n};\nfunction shouldRenderDots(points, dot) {\n if (points == null) {\n return false;\n }\n if (dot) {\n return true;\n }\n return points.length === 1;\n}\nfunction Dots(_ref) {\n var {\n clipPathId,\n points,\n props\n } = _ref;\n var {\n needClip,\n dot,\n dataKey\n } = props;\n if (!shouldRenderDots(points, dot)) {\n return null;\n }\n var clipDot = isClipDot(dot);\n var areaProps = filterProps(props, false);\n var customDotProps = filterProps(dot, true);\n var dots = points.map((entry, i) => {\n var dotProps = _objectSpread(_objectSpread(_objectSpread({\n key: \"dot-\".concat(i),\n r: 3\n }, areaProps), customDotProps), {}, {\n index: i,\n cx: entry.x,\n cy: entry.y,\n dataKey,\n value: entry.value,\n payload: entry.payload,\n points\n });\n return renderDotItem(dot, dotProps);\n });\n var dotsProps = {\n clipPath: needClip ? \"url(#clipPath-\".concat(clipDot ? '' : 'dots-').concat(clipPathId, \")\") : undefined\n };\n return /*#__PURE__*/React.createElement(Layer, _extends({\n className: \"recharts-area-dots\"\n }, dotsProps), dots);\n}\nfunction StaticArea(_ref2) {\n var {\n points,\n baseLine,\n needClip,\n clipPathId,\n props,\n showLabels\n } = _ref2;\n var {\n layout,\n type,\n stroke,\n connectNulls,\n isRange\n } = props,\n others = _objectWithoutProperties(props, _excluded);\n return /*#__PURE__*/React.createElement(React.Fragment, null, (points === null || points === void 0 ? void 0 : points.length) > 1 && /*#__PURE__*/React.createElement(Layer, {\n clipPath: needClip ? \"url(#clipPath-\".concat(clipPathId, \")\") : undefined\n }, /*#__PURE__*/React.createElement(Curve, _extends({}, filterProps(others, true), {\n points: points,\n connectNulls: connectNulls,\n type: type,\n baseLine: baseLine,\n layout: layout,\n stroke: \"none\",\n className: \"recharts-area-area\"\n })), stroke !== 'none' && /*#__PURE__*/React.createElement(Curve, _extends({}, filterProps(props, false), {\n className: \"recharts-area-curve\",\n layout: layout,\n type: type,\n connectNulls: connectNulls,\n fill: \"none\",\n points: points\n })), stroke !== 'none' && isRange && /*#__PURE__*/React.createElement(Curve, _extends({}, filterProps(props, false), {\n className: \"recharts-area-curve\",\n layout: layout,\n type: type,\n connectNulls: connectNulls,\n fill: \"none\",\n points: baseLine\n }))), /*#__PURE__*/React.createElement(Dots, {\n points: points,\n props: props,\n clipPathId: clipPathId\n }), showLabels && LabelList.renderCallByParent(props, points));\n}\nfunction VerticalRect(_ref3) {\n var {\n alpha,\n baseLine,\n points,\n strokeWidth\n } = _ref3;\n var startY = points[0].y;\n var endY = points[points.length - 1].y;\n if (!isWellBehavedNumber(startY) || !isWellBehavedNumber(endY)) {\n return null;\n }\n var height = alpha * Math.abs(startY - endY);\n var maxX = Math.max(...points.map(entry => entry.x || 0));\n if (isNumber(baseLine)) {\n maxX = Math.max(baseLine, maxX);\n } else if (baseLine && Array.isArray(baseLine) && baseLine.length) {\n maxX = Math.max(...baseLine.map(entry => entry.x || 0), maxX);\n }\n if (isNumber(maxX)) {\n return /*#__PURE__*/React.createElement(\"rect\", {\n x: 0,\n y: startY < endY ? startY : startY - height,\n width: maxX + (strokeWidth ? parseInt(\"\".concat(strokeWidth), 10) : 1),\n height: Math.floor(height)\n });\n }\n return null;\n}\nfunction HorizontalRect(_ref4) {\n var {\n alpha,\n baseLine,\n points,\n strokeWidth\n } = _ref4;\n var startX = points[0].x;\n var endX = points[points.length - 1].x;\n if (!isWellBehavedNumber(startX) || !isWellBehavedNumber(endX)) {\n return null;\n }\n var width = alpha * Math.abs(startX - endX);\n var maxY = Math.max(...points.map(entry => entry.y || 0));\n if (isNumber(baseLine)) {\n maxY = Math.max(baseLine, maxY);\n } else if (baseLine && Array.isArray(baseLine) && baseLine.length) {\n maxY = Math.max(...baseLine.map(entry => entry.y || 0), maxY);\n }\n if (isNumber(maxY)) {\n return /*#__PURE__*/React.createElement(\"rect\", {\n x: startX < endX ? startX : startX - width,\n y: 0,\n width: width,\n height: Math.floor(maxY + (strokeWidth ? parseInt(\"\".concat(strokeWidth), 10) : 1))\n });\n }\n return null;\n}\nfunction ClipRect(_ref5) {\n var {\n alpha,\n layout,\n points,\n baseLine,\n strokeWidth\n } = _ref5;\n if (layout === 'vertical') {\n return /*#__PURE__*/React.createElement(VerticalRect, {\n alpha: alpha,\n points: points,\n baseLine: baseLine,\n strokeWidth: strokeWidth\n });\n }\n return /*#__PURE__*/React.createElement(HorizontalRect, {\n alpha: alpha,\n points: points,\n baseLine: baseLine,\n strokeWidth: strokeWidth\n });\n}\nfunction AreaWithAnimation(_ref6) {\n var {\n needClip,\n clipPathId,\n props,\n previousPointsRef,\n previousBaselineRef\n } = _ref6;\n var {\n points,\n baseLine,\n isAnimationActive,\n animationBegin,\n animationDuration,\n animationEasing,\n onAnimationStart,\n onAnimationEnd\n } = props;\n var animationId = useAnimationId(props, 'recharts-area-');\n var [isAnimating, setIsAnimating] = useState(true);\n var handleAnimationEnd = useCallback(() => {\n if (typeof onAnimationEnd === 'function') {\n onAnimationEnd();\n }\n setIsAnimating(false);\n }, [onAnimationEnd]);\n var handleAnimationStart = useCallback(() => {\n if (typeof onAnimationStart === 'function') {\n onAnimationStart();\n }\n setIsAnimating(true);\n }, [onAnimationStart]);\n var prevPoints = previousPointsRef.current;\n var prevBaseLine = previousBaselineRef.current;\n return /*#__PURE__*/React.createElement(Animate, {\n begin: animationBegin,\n duration: animationDuration,\n isActive: isAnimationActive,\n easing: animationEasing,\n from: {\n t: 0\n },\n to: {\n t: 1\n },\n onAnimationEnd: handleAnimationEnd,\n onAnimationStart: handleAnimationStart,\n key: animationId\n }, _ref7 => {\n var {\n t\n } = _ref7;\n if (prevPoints) {\n var prevPointsDiffFactor = prevPoints.length / points.length;\n var stepPoints =\n /*\n * Here it is important that at the very end of the animation, on the last frame,\n * we render the original points without any interpolation.\n * This is needed because the code above is checking for reference equality to decide if the animation should run\n * and if we create a new array instance (even if the numbers were the same)\n * then we would break animations.\n */\n t === 1 ? points : points.map((entry, index) => {\n var prevPointIndex = Math.floor(index * prevPointsDiffFactor);\n if (prevPoints[prevPointIndex]) {\n var prev = prevPoints[prevPointIndex];\n return _objectSpread(_objectSpread({}, entry), {}, {\n x: interpolate(prev.x, entry.x, t),\n y: interpolate(prev.y, entry.y, t)\n });\n }\n return entry;\n });\n var stepBaseLine;\n if (isNumber(baseLine)) {\n stepBaseLine = interpolate(prevBaseLine, baseLine, t);\n } else if (isNullish(baseLine) || isNan(baseLine)) {\n stepBaseLine = interpolate(prevBaseLine, 0, t);\n } else {\n stepBaseLine = baseLine.map((entry, index) => {\n var prevPointIndex = Math.floor(index * prevPointsDiffFactor);\n if (Array.isArray(prevBaseLine) && prevBaseLine[prevPointIndex]) {\n var prev = prevBaseLine[prevPointIndex];\n return _objectSpread(_objectSpread({}, entry), {}, {\n x: interpolate(prev.x, entry.x, t),\n y: interpolate(prev.y, entry.y, t)\n });\n }\n return entry;\n });\n }\n if (t > 0) {\n /*\n * We need to keep the refs in the parent component because we need to remember the last shape of the animation\n * even if AreaWithAnimation is unmounted as that happens when changing props.\n *\n * And we need to update the refs here because here is where the interpolation is computed.\n * Eslint doesn't like changing function arguments, but we need it so here is an eslint-disable.\n */\n // eslint-disable-next-line no-param-reassign\n previousPointsRef.current = stepPoints;\n // eslint-disable-next-line no-param-reassign\n previousBaselineRef.current = stepBaseLine;\n }\n return /*#__PURE__*/React.createElement(StaticArea, {\n points: stepPoints,\n baseLine: stepBaseLine,\n needClip: needClip,\n clipPathId: clipPathId,\n props: props,\n showLabels: !isAnimating\n });\n }\n if (t > 0) {\n // eslint-disable-next-line no-param-reassign\n previousPointsRef.current = points;\n // eslint-disable-next-line no-param-reassign\n previousBaselineRef.current = baseLine;\n }\n return /*#__PURE__*/React.createElement(Layer, null, /*#__PURE__*/React.createElement(\"defs\", null, /*#__PURE__*/React.createElement(\"clipPath\", {\n id: \"animationClipPath-\".concat(clipPathId)\n }, /*#__PURE__*/React.createElement(ClipRect, {\n alpha: t,\n points: points,\n baseLine: baseLine,\n layout: props.layout,\n strokeWidth: props.strokeWidth\n }))), /*#__PURE__*/React.createElement(Layer, {\n clipPath: \"url(#animationClipPath-\".concat(clipPathId, \")\")\n }, /*#__PURE__*/React.createElement(StaticArea, {\n points: points,\n baseLine: baseLine,\n needClip: needClip,\n clipPathId: clipPathId,\n props: props,\n showLabels: true\n })));\n });\n}\n\n/*\n * This components decides if the area should be animated or not.\n * It also holds the state of the animation.\n */\nfunction RenderArea(_ref8) {\n var {\n needClip,\n clipPathId,\n props\n } = _ref8;\n var {\n points,\n baseLine,\n isAnimationActive\n } = props;\n\n /*\n * These two must be refs, not state!\n * Because we want to store the most recent shape of the animation in case we have to interrupt the animation;\n * that happens when user initiates another animation before the current one finishes.\n *\n * If this was a useState, then every step in the animation would trigger a re-render.\n * So, useRef it is.\n */\n var previousPointsRef = useRef(null);\n var previousBaselineRef = useRef();\n var prevPoints = previousPointsRef.current;\n var prevBaseLine = previousBaselineRef.current;\n if (isAnimationActive &&\n /*\n * Here it's important that we unmount of AreaWithAnimation in case points are undefined\n * - this will make sure to interrupt the animation if it's running.\n * We still get to keep the last shape of the animation in the refs above.\n */\n points && points.length && (prevPoints !== points || prevBaseLine !== baseLine)) {\n return /*#__PURE__*/React.createElement(AreaWithAnimation, {\n needClip: needClip,\n clipPathId: clipPathId,\n props: props,\n previousPointsRef: previousPointsRef,\n previousBaselineRef: previousBaselineRef\n });\n }\n return /*#__PURE__*/React.createElement(StaticArea, {\n points: points,\n baseLine: baseLine,\n needClip: needClip,\n clipPathId: clipPathId,\n props: props,\n showLabels: true\n });\n}\nclass AreaWithState extends PureComponent {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"id\", uniqueId('recharts-area-'));\n }\n render() {\n var _filterProps;\n var {\n hide,\n dot,\n points,\n className,\n top,\n left,\n needClip,\n xAxisId,\n yAxisId,\n width,\n height,\n id,\n baseLine\n } = this.props;\n if (hide) {\n return null;\n }\n var layerClass = clsx('recharts-area', className);\n var clipPathId = isNullish(id) ? this.id : id;\n var {\n r = 3,\n strokeWidth = 2\n } = (_filterProps = filterProps(dot, false)) !== null && _filterProps !== void 0 ? _filterProps : {\n r: 3,\n strokeWidth: 2\n };\n var clipDot = isClipDot(dot);\n var dotSize = r * 2 + strokeWidth;\n return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Layer, {\n className: layerClass\n }, needClip && /*#__PURE__*/React.createElement(\"defs\", null, /*#__PURE__*/React.createElement(GraphicalItemClipPath, {\n clipPathId: clipPathId,\n xAxisId: xAxisId,\n yAxisId: yAxisId\n }), !clipDot && /*#__PURE__*/React.createElement(\"clipPath\", {\n id: \"clipPath-dots-\".concat(clipPathId)\n }, /*#__PURE__*/React.createElement(\"rect\", {\n x: left - dotSize / 2,\n y: top - dotSize / 2,\n width: width + dotSize,\n height: height + dotSize\n }))), /*#__PURE__*/React.createElement(RenderArea, {\n needClip: needClip,\n clipPathId: clipPathId,\n props: this.props\n })), /*#__PURE__*/React.createElement(ActivePoints, {\n points: points,\n mainColor: getLegendItemColor(this.props.stroke, this.props.fill),\n itemDataKey: this.props.dataKey,\n activeDot: this.props.activeDot\n }), this.props.isRange && Array.isArray(baseLine) && /*#__PURE__*/React.createElement(ActivePoints, {\n points: baseLine,\n mainColor: getLegendItemColor(this.props.stroke, this.props.fill),\n itemDataKey: this.props.dataKey,\n activeDot: this.props.activeDot\n }));\n }\n}\nvar defaultAreaProps = {\n activeDot: true,\n animationBegin: 0,\n animationDuration: 1500,\n animationEasing: 'ease',\n connectNulls: false,\n dot: false,\n fill: '#3182bd',\n fillOpacity: 0.6,\n hide: false,\n isAnimationActive: !Global.isSsr,\n legendType: 'line',\n stroke: '#3182bd',\n xAxisId: 0,\n yAxisId: 0\n};\nfunction AreaImpl(props) {\n var _useAppSelector;\n var _resolveDefaultProps = resolveDefaultProps(props, defaultAreaProps),\n {\n activeDot,\n animationBegin,\n animationDuration,\n animationEasing,\n connectNulls,\n dot,\n fill,\n fillOpacity,\n hide,\n isAnimationActive,\n legendType,\n stroke,\n xAxisId,\n yAxisId\n } = _resolveDefaultProps,\n everythingElse = _objectWithoutProperties(_resolveDefaultProps, _excluded2);\n var layout = useChartLayout();\n var chartName = useChartName();\n var {\n needClip\n } = useNeedsClip(xAxisId, yAxisId);\n var isPanorama = useIsPanorama();\n var areaSettings = useMemo(() => ({\n baseValue: props.baseValue,\n stackId: props.stackId,\n connectNulls,\n data: props.data,\n dataKey: props.dataKey\n }), [props.baseValue, props.stackId, connectNulls, props.data, props.dataKey]);\n var {\n points,\n isRange,\n baseLine\n } = (_useAppSelector = useAppSelector(state => selectArea(state, xAxisId, yAxisId, isPanorama, areaSettings))) !== null && _useAppSelector !== void 0 ? _useAppSelector : {};\n var {\n height,\n width,\n left,\n top\n } = useOffset();\n if (layout !== 'horizontal' && layout !== 'vertical') {\n // Can't render Area in an unsupported layout\n return null;\n }\n if (chartName !== 'AreaChart' && chartName !== 'ComposedChart') {\n // There is nothing stopping us from rendering Area in other charts, except for historical reasons. Do we want to allow that?\n return null;\n }\n\n /*\n * It is important to NOT have this condition here,\n * because we need the Animate inside to receive an empty state\n * so that it can properly reset its internal state and start a new animation.\n */\n // if (!points || !points.length) {\n // return null;\n // }\n\n return /*#__PURE__*/React.createElement(AreaWithState, _extends({}, everythingElse, {\n activeDot: activeDot,\n animationBegin: animationBegin,\n animationDuration: animationDuration,\n animationEasing: animationEasing,\n baseLine: baseLine,\n connectNulls: connectNulls,\n dot: dot,\n fill: fill,\n fillOpacity: fillOpacity,\n height: height,\n hide: hide,\n layout: layout,\n isAnimationActive: isAnimationActive,\n isRange: isRange,\n legendType: legendType,\n needClip: needClip,\n points: points,\n stroke: stroke,\n width: width,\n left: left,\n top: top,\n xAxisId: xAxisId,\n yAxisId: yAxisId\n }));\n}\nexport var getBaseValue = (layout, chartBaseValue, itemBaseValue, xAxis, yAxis) => {\n // The baseValue can be defined both on the AreaChart, and on the Area.\n // The value for the item takes precedence.\n var baseValue = itemBaseValue !== null && itemBaseValue !== void 0 ? itemBaseValue : chartBaseValue;\n if (isNumber(baseValue)) {\n return baseValue;\n }\n var numericAxis = layout === 'horizontal' ? yAxis : xAxis;\n // @ts-expect-error d3scale .domain() returns unknown, Math.max expects number\n var domain = numericAxis.scale.domain();\n if (numericAxis.type === 'number') {\n var domainMax = Math.max(domain[0], domain[1]);\n var domainMin = Math.min(domain[0], domain[1]);\n if (baseValue === 'dataMin') {\n return domainMin;\n }\n if (baseValue === 'dataMax') {\n return domainMax;\n }\n return domainMax < 0 ? domainMax : Math.max(Math.min(domain[0], domain[1]), 0);\n }\n if (baseValue === 'dataMin') {\n return domain[0];\n }\n if (baseValue === 'dataMax') {\n return domain[1];\n }\n return domain[0];\n};\nexport function computeArea(_ref9) {\n var {\n areaSettings: {\n connectNulls,\n baseValue: itemBaseValue,\n dataKey\n },\n stackedData,\n layout,\n chartBaseValue,\n xAxis,\n yAxis,\n displayedData,\n dataStartIndex,\n xAxisTicks,\n yAxisTicks,\n bandSize\n } = _ref9;\n var hasStack = stackedData && stackedData.length;\n var baseValue = getBaseValue(layout, chartBaseValue, itemBaseValue, xAxis, yAxis);\n var isHorizontalLayout = layout === 'horizontal';\n var isRange = false;\n var points = displayedData.map((entry, index) => {\n var value;\n if (hasStack) {\n value = stackedData[dataStartIndex + index];\n } else {\n value = getValueByDataKey(entry, dataKey);\n if (!Array.isArray(value)) {\n value = [baseValue, value];\n } else {\n isRange = true;\n }\n }\n var isBreakPoint = value[1] == null || hasStack && !connectNulls && getValueByDataKey(entry, dataKey) == null;\n if (isHorizontalLayout) {\n return {\n // @ts-expect-error getCateCoordinateOfLine expects chart data to be an object, we allow unknown\n x: getCateCoordinateOfLine({\n axis: xAxis,\n ticks: xAxisTicks,\n bandSize,\n entry,\n index\n }),\n y: isBreakPoint ? null : yAxis.scale(value[1]),\n value,\n payload: entry\n };\n }\n return {\n x: isBreakPoint ? null : xAxis.scale(value[1]),\n // @ts-expect-error getCateCoordinateOfLine expects chart data to be an object, we allow unknown\n y: getCateCoordinateOfLine({\n axis: yAxis,\n ticks: yAxisTicks,\n bandSize,\n entry,\n index\n }),\n value,\n payload: entry\n };\n });\n var baseLine;\n if (hasStack || isRange) {\n baseLine = points.map(entry => {\n var x = Array.isArray(entry.value) ? entry.value[0] : null;\n if (isHorizontalLayout) {\n return {\n x: entry.x,\n y: x != null && entry.y != null ? yAxis.scale(x) : null\n };\n }\n return {\n x: x != null ? xAxis.scale(x) : null,\n y: entry.y\n };\n });\n } else {\n baseLine = isHorizontalLayout ? yAxis.scale(baseValue) : xAxis.scale(baseValue);\n }\n return {\n points,\n baseLine,\n isRange\n };\n}\nexport class Area extends PureComponent {\n render() {\n // Report all props to Redux store first, before calling any hooks, to avoid circular dependencies.\n return /*#__PURE__*/React.createElement(CartesianGraphicalItemContext, {\n type: \"area\",\n data: this.props.data,\n dataKey: this.props.dataKey,\n xAxisId: this.props.xAxisId,\n yAxisId: this.props.yAxisId,\n zAxisId: 0,\n stackId: this.props.stackId,\n hide: this.props.hide,\n barSize: undefined\n }, /*#__PURE__*/React.createElement(SetLegendPayload, {\n legendPayload: computeLegendPayloadFromAreaData(this.props)\n }), /*#__PURE__*/React.createElement(SetTooltipEntrySettings, {\n fn: getTooltipEntrySettings,\n args: this.props\n }), /*#__PURE__*/React.createElement(AreaImpl, this.props));\n }\n}\n_defineProperty(Area, \"displayName\", 'Area');\n_defineProperty(Area, \"defaultProps\", defaultAreaProps);","import * as React from 'react';\nimport { forwardRef } from 'react';\nimport { arrayTooltipSearcher } from '../state/optionsSlice';\nimport { CartesianChart } from './CartesianChart';\nvar allowedTooltipTypes = ['axis'];\nexport var AreaChart = /*#__PURE__*/forwardRef((props, ref) => {\n return /*#__PURE__*/React.createElement(CartesianChart, {\n chartName: \"AreaChart\",\n defaultTooltipEventType: \"axis\",\n validateTooltipEventTypes: allowedTooltipTypes,\n tooltipPayloadSearcher: arrayTooltipSearcher,\n categoricalChartProps: props,\n ref: ref\n });\n});","import { memo, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport Select from 'react-select';\nimport {\n ResponsiveContainer,\n LineChart,\n Line,\n AreaChart,\n Area,\n BarChart,\n Bar,\n CartesianGrid,\n XAxis,\n YAxis,\n Tooltip,\n Legend,\n} from 'recharts';\nimport DJClientContext from '../../providers/djclient';\nimport { DataJunctionAPI } from '../../services/DJService';\nimport './styles.css';\n\nconst PALETTE = [\n '#3b51d6',\n '#00C49F',\n '#FFBB28',\n '#FF91A3',\n '#AA46BE',\n '#0088FE',\n '#FF8042',\n '#82ca9d',\n];\n\n// Brighter cousins of the canonical DJ node-type colors\n// (the canonical ones in src/styles/index.css are tuned for badges on a\n// white background — they're too desaturated for chart series).\nconst NODE_TYPE_COLORS = {\n source: '#22c55e', // bright emerald\n transform: '#3b82f6', // bright blue\n metric: '#f43f5e', // bright rose\n dimension: '#f59e0b', // bright amber\n cube: '#a855f7', // bright purple\n tag: '#8b5cf6', // bright violet\n};\n\nconst colorForSeries = (label, fallback) => {\n if (!label) return { color: fallback, opacity: 0.85 };\n const key = String(label).toLowerCase();\n if (NODE_TYPE_COLORS[key]) {\n return { color: NODE_TYPE_COLORS[key], opacity: 0.85 };\n }\n return { color: fallback, opacity: 0.85 };\n};\n\nconst OPERATORS = [\n { value: '=', label: '=' },\n { value: '!=', label: '≠' },\n { value: 'in', label: 'is one of' },\n { value: '>', label: '>' },\n { value: '<', label: '<' },\n { value: '>=', label: '≥' },\n { value: '<=', label: '≤' },\n];\n\nconst isTemporal = name =>\n /(_date|_week|_month|_quarter|_year|_day|dateint|created_at)/i.test(name);\n\nexport const typeIcon = type => {\n if (!type) return '#';\n const t = String(type).toLowerCase();\n if (/(bool)/.test(t)) return '✓';\n if (/(date|time)/.test(t)) return '📅';\n if (/(int|long|double|float|decimal|numeric|number)/.test(t)) return '123';\n if (/(string|varchar|char|text)/.test(t)) return 'Aa';\n if (/(list|array)/.test(t)) return '[ ]';\n return '#';\n};\n\n// Shared react-select styles so options inside groups are visibly indented\n// under their group header, and the header has a tight, distinct look.\nconst groupedSelectStyles = {\n groupHeading: base => ({\n ...base,\n fontSize: 10.5,\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n color: '#64748b',\n padding: '6px 12px 2px',\n }),\n option: (base, state) => ({\n ...base,\n paddingLeft: state.data && state.data.group ? 28 : base.paddingLeft,\n }),\n group: base => ({\n ...base,\n paddingTop: 4,\n paddingBottom: 4,\n }),\n};\n\n// Recharts tooltip that drops zero-valued series — when stacked breakdowns\n// have lots of empty cells (zero-filled for stacking), the default tooltip\n// fills the screen with \"X: 0\" rows that aren't useful.\nexport function NonZeroTooltip({ active, payload, label }) {\n if (!active || !payload || !payload.length) return null;\n const nonZero = payload.filter(p => p && p.value !== 0 && p.value != null);\n if (!nonZero.length) return null;\n return (\n <div\n style={{\n background: '#fff',\n border: '1px solid #e2e8f0',\n borderRadius: 6,\n padding: '8px 10px',\n fontSize: 12,\n boxShadow: '0 2px 8px rgba(0,0,0,0.08)',\n }}\n >\n <div style={{ fontWeight: 600, marginBottom: 4, color: '#1e293b' }}>\n {label}\n </div>\n {nonZero.map(p => (\n <div\n key={p.dataKey}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n color: '#334155',\n }}\n >\n <span\n style={{\n display: 'inline-block',\n width: 9,\n height: 9,\n background: p.color,\n borderRadius: 2,\n }}\n />\n <span>{p.name}</span>\n <span\n style={{ marginLeft: 'auto', fontVariantNumeric: 'tabular-nums' }}\n >\n {typeof p.value === 'number' ? p.value.toLocaleString() : p.value}\n </span>\n </div>\n ))}\n </div>\n );\n}\n\nconst isNumericLike = v =>\n typeof v === 'number' || (typeof v === 'string' && /^-?\\d+(\\.\\d+)?$/.test(v));\n\n// Recharts subtree, memoed so unrelated parent re-renders (e.g. URL syncs,\n// filter-input keystrokes) don't re-mount the chart's SVG paths.\nconst ChartView = memo(function ChartView({\n chartData,\n resolvedChartType,\n startAtZero,\n}) {\n const stacked = chartData.series.length > 1;\n const isLine = resolvedChartType === 'line';\n const isArea = resolvedChartType === 'area';\n const ChartCmp = isArea ? AreaChart : isLine ? LineChart : BarChart;\n return (\n <div className=\"sme-chart-body\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <ChartCmp\n data={chartData.data}\n margin={{ top: 10, right: 30, left: 10, bottom: 20 }}\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"#eee\" />\n <XAxis dataKey={chartData.xKey} tick={{ fontSize: 12 }} />\n <YAxis\n tick={{ fontSize: 12 }}\n domain={startAtZero ? [0, 'auto'] : ['auto', 'auto']}\n />\n <Tooltip\n content={<NonZeroTooltip />}\n isAnimationActive={false}\n cursor={{ fill: 'rgba(15, 23, 42, 0.04)' }}\n />\n {stacked ? <Legend /> : null}\n {chartData.series.map((s, i) => {\n const name = chartData.labels[s] || s;\n const { color, opacity } = colorForSeries(\n name,\n PALETTE[i % PALETTE.length],\n );\n if (isArea) {\n return (\n <Area\n key={s}\n type=\"monotone\"\n dataKey={s}\n name={name}\n stackId={stacked ? '1' : undefined}\n stroke={color}\n fill={color}\n fillOpacity={opacity}\n isAnimationActive={false}\n activeDot={false}\n />\n );\n }\n if (isLine) {\n return (\n <Line\n key={s}\n type=\"monotone\"\n dataKey={s}\n name={name}\n stroke={color}\n strokeWidth={2}\n dot={false}\n isAnimationActive={false}\n activeDot={false}\n />\n );\n }\n return (\n <Bar\n key={s}\n dataKey={s}\n name={name}\n fill={color}\n fillOpacity={opacity}\n stackId={stacked ? '1' : undefined}\n isAnimationActive={false}\n activeBar={false}\n />\n );\n })}\n </ChartCmp>\n </ResponsiveContainer>\n </div>\n );\n});\n\n// Parse URL search params into the explorer's initial state.\nfunction parseSearch(search) {\n const p = new URLSearchParams(search);\n const filters = p\n .getAll('filter')\n .map(raw => {\n const [dim = '', op = '=', ...rest] = raw.split('|');\n return { dim, op, value: rest.join('|') };\n })\n .filter(f => f.dim);\n return {\n metric: p.get('metric') || null,\n xAxis: p.get('x') || null,\n compareBy: (p.get('by') || '').split(',').filter(Boolean),\n filters,\n view: p.get('view') === 'table' ? 'table' : 'chart',\n chartType: ['line', 'area', 'bar', 'auto'].includes(p.get('chart'))\n ? p.get('chart')\n : 'auto',\n startAtZero: p.get('zero') === '1',\n };\n}\n\nexport function SystemMetricsExplorerPage() {\n const djClient = useContext(DJClientContext).DataJunctionAPI;\n const location = useLocation();\n const navigate = useNavigate();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const initial = useMemo(() => parseSearch(location.search), []);\n\n const [metricsList, setMetricsList] = useState([]);\n const [metricSearch, setMetricSearch] = useState('');\n const [dimSearch, setDimSearch] = useState('');\n\n const [selectedMetric, setSelectedMetric] = useState(initial.metric);\n const [availableDims, setAvailableDims] = useState([]); // [{name,type,path,label}]\n const [xAxisDim, setXAxisDim] = useState(null);\n const [compareBy, setCompareBy] = useState([]);\n const [filters, setFilters] = useState(initial.filters);\n\n // Pending selections from the URL that need to be resolved against\n // availableDims once they load.\n const pendingXAxis = useRef(initial.xAxis);\n const pendingCompareBy = useRef(initial.compareBy);\n\n const [rows, setRows] = useState(null);\n const [columns, setColumns] = useState([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n const [view, setView] = useState(initial.view);\n const [chartType, setChartType] = useState(initial.chartType);\n const [startAtZero, setStartAtZero] = useState(initial.startAtZero);\n\n // Load system metrics\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const list = await djClient.system.list();\n if (cancelled) return;\n if (!Array.isArray(list)) {\n setError(\n `Unexpected response from /system/metrics: ${JSON.stringify(\n list,\n ).slice(0, 200)}`,\n );\n setMetricsList([]);\n return;\n }\n // Endpoint may return either [\"name\", ...] (legacy) or\n // [{name, display_name, description}, ...] (new). Normalize.\n const normalized = list.map(item => {\n if (typeof item === 'string') {\n return {\n name: item,\n display_name: item,\n description: '',\n group: 'Other',\n subgroup: 'Other',\n suggestedCompareBy: [],\n };\n }\n const meta = item.custom_metadata || {};\n return {\n name: item.name,\n display_name: item.display_name || item.name,\n description: item.description || '',\n group: meta.group || 'Other',\n subgroup: meta.subgroup || 'Other',\n suggestedCompareBy: Array.isArray(meta.suggested_compare_by)\n ? meta.suggested_compare_by\n : [],\n };\n });\n setMetricsList(normalized);\n if (normalized.length && !selectedMetric)\n setSelectedMetric(normalized[0].name);\n } catch (e) {\n if (!cancelled) setError(String(e));\n }\n })();\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [djClient]);\n\n // Load dimensions for selected metric\n useEffect(() => {\n if (!selectedMetric) {\n setAvailableDims([]);\n return;\n }\n let cancelled = false;\n setXAxisDim(null);\n setCompareBy([]);\n setRows(null);\n setColumns([]);\n setError(null);\n (async () => {\n try {\n const dims = await djClient.commonDimensions([selectedMetric]);\n if (cancelled) return;\n const opts = (Array.isArray(dims) ? dims : []).map(d => {\n // d.name is e.g. \"system.dj.date.dateint[created_at]\". Pull the\n // role suffix out for the group header.\n const roleMatch = d.name.match(/\\[([^\\]]+)\\]$/);\n const role = roleMatch ? roleMatch[1] : null;\n const baseName = role\n ? d.name.slice(0, -roleMatch[0].length)\n : d.name;\n const segments = baseName.split('.');\n const attr = segments[segments.length - 1];\n const nodeDisplay =\n d.node_display_name || segments.slice(0, -1).join('.');\n const colDisplay = d.column_display_name || attr;\n const group = role ? `${nodeDisplay} [${role}]` : nodeDisplay;\n return {\n value: d.name,\n label: colDisplay,\n // Combined label used in search and in some narrow contexts.\n combinedLabel: `${group} · ${colDisplay}`,\n attr,\n nodeDisplay,\n colDisplay,\n role,\n group,\n path: d.path.join(' ▶ '),\n type: d.type,\n };\n });\n setAvailableDims(opts);\n\n // Apply pending URL state first if present; otherwise default to a\n // temporal X-axis. Pending state is one-shot — clear after applying.\n if (pendingXAxis.current) {\n const match = opts.find(o => o.value === pendingXAxis.current);\n if (match) setXAxisDim(match);\n pendingXAxis.current = null;\n } else {\n // Prefer a \"week\" temporal column; fall back to any temporal one.\n const weekly = opts.find(o => /_week\\b/i.test(o.value));\n const firstTemporal = weekly || opts.find(o => isTemporal(o.value));\n if (firstTemporal) setXAxisDim(firstTemporal);\n }\n if (pendingCompareBy.current && pendingCompareBy.current.length) {\n const matches = pendingCompareBy.current\n .map(v => opts.find(o => o.value === v))\n .filter(Boolean);\n if (matches.length) setCompareBy(matches);\n pendingCompareBy.current = null;\n } else {\n // Apply the metric's suggested compare-by defaults (from\n // custom_metadata.suggested_compare_by). Only kicks in when the\n // user has no pending compare-by from the URL.\n const metric = metricsList.find(m => m.name === selectedMetric);\n const suggested = (metric && metric.suggestedCompareBy) || [];\n if (suggested.length) {\n const matches = suggested\n .map(v => opts.find(o => o.value === v))\n .filter(Boolean);\n if (matches.length) setCompareBy(matches);\n }\n }\n } catch (e) {\n if (!cancelled) setError(String(e));\n }\n })();\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedMetric, djClient]);\n\n // Sync state back to URL so links are shareable.\n useEffect(() => {\n const p = new URLSearchParams();\n if (selectedMetric) p.set('metric', selectedMetric);\n if (xAxisDim) p.set('x', xAxisDim.value);\n if (compareBy.length) p.set('by', compareBy.map(d => d.value).join(','));\n filters.forEach(f => {\n if (f.dim && f.op) {\n p.append('filter', `${f.dim}|${f.op}|${f.value ?? ''}`);\n }\n });\n if (view !== 'chart') p.set('view', view);\n if (chartType !== 'auto') p.set('chart', chartType);\n if (startAtZero) p.set('zero', '1');\n const next = p.toString();\n const current = window.location.search.replace(/^\\?/, '');\n if (next !== current) {\n navigate(\n { pathname: window.location.pathname, search: next ? `?${next}` : '' },\n { replace: true },\n );\n }\n }, [\n selectedMetric,\n xAxisDim,\n compareBy,\n filters,\n view,\n chartType,\n startAtZero,\n navigate,\n ]);\n\n // Run query whenever the user changes inputs\n useEffect(() => {\n if (!selectedMetric) return;\n let cancelled = false;\n (async () => {\n setLoading(true);\n setError(null);\n try {\n const dimensions = [\n ...(xAxisDim ? [xAxisDim.value] : []),\n ...compareBy.map(d => d.value),\n ];\n const filterClauses = filters\n .filter(f => f.dim && f.op && f.value !== '')\n .map(f => {\n if (f.op === 'in') {\n const vals = f.value\n .split(',')\n .map(v => v.trim())\n .filter(Boolean)\n .map(v => (isNumericLike(v) ? v : `'${v.replace(/'/g, \"''\")}'`))\n .join(', ');\n return `${f.dim} IN (${vals})`;\n }\n const lhs = f.dim;\n const rhs = isNumericLike(f.value)\n ? f.value\n : `'${String(f.value).replace(/'/g, \"''\")}'`;\n return `${lhs} ${f.op} ${rhs}`;\n });\n\n const orderby = xAxisDim ? [xAxisDim.value] : [];\n\n const result = await djClient.querySystemMetric({\n metric: selectedMetric,\n dimensions,\n filters: filterClauses,\n orderby,\n });\n if (cancelled) return;\n // New compact shape: { columns: [...], rows: [[...], ...] }.\n // Rows are already aligned to columns, no per-cell envelope.\n setColumns(result.columns || []);\n setRows(result.rows || [])