diff --git a/package.json b/package.json
index 6fc67abb..b3ba0be6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@netdata/charts",
- "version": "4.4.8",
+ "version": "4.5.0",
"description": "Netdata frontend SDK and chart utilities",
"main": "dist/index.js",
"module": "dist/es6/index.js",
diff --git a/src/agentv2.stories.js b/src/agentv2.stories.js
index 78c906b8..ba69a136 100644
--- a/src/agentv2.stories.js
+++ b/src/agentv2.stories.js
@@ -7,6 +7,7 @@ import NumberComponent from "@/components/number"
import D3pieComponent from "@/components/d3pie"
import BarsComponent from "@/components/bars"
import GroupBoxes from "@/components/groupBoxes"
+import Table from "@/components/table"
import makeDefaultSDK from "./makeDefaultSDK"
export const Chart = ({ nodesScope, contextScope, contexts, host, theme, singleDimension }) => {
@@ -27,6 +28,25 @@ export const Chart = ({ nodesScope, contextScope, contexts, host, theme, singleD
sdk.appendChild(chart)
+ const chart7 = sdk.makeChart({
+ attributes: {
+ id: "control",
+ selectedContexts: [contexts],
+ nodesScope: [nodesScope],
+ contextScope: ["disk.io", "disk.ops", "disk.await", "disk.util"],
+ host: host,
+ aggregationMethod: "avg",
+ agent: true,
+ syncHover: true,
+ groupingMethod: "average",
+ chartLibrary: "table",
+ groupBy: ["label", "dimension", "context", "node"],
+ groupByLabel: ["device"],
+ },
+ })
+
+ sdk.appendChild(chart7)
+
const chart2 = sdk.makeChart({
attributes: {
selectedContexts: [contexts],
@@ -171,6 +191,7 @@ export const Chart = ({ nodesScope, contextScope, contexts, host, theme, singleD
+
)
@@ -186,7 +207,7 @@ export default {
contexts: "*",
singleDimension: "*",
theme: "default",
- host: "http://10.10.10.20:19999/api/v2",
+ host: "http://10.20.4.200:19999/api/v2",
},
argTypes: {
host: {
diff --git a/src/chartLibraries/table/index.js b/src/chartLibraries/table/index.js
new file mode 100644
index 00000000..f6d7dfb7
--- /dev/null
+++ b/src/chartLibraries/table/index.js
@@ -0,0 +1,77 @@
+import makeChartUI from "@/sdk/makeChartUI"
+import { unregister } from "@/helpers/makeListeners"
+import makeResizeObserver from "@/helpers/makeResizeObserver"
+
+export default (sdk, chart) => {
+ const chartUI = makeChartUI(sdk, chart)
+ let listeners
+ let resizeObserver
+ let prevMin
+ let prevMax
+
+ const mount = element => {
+ chartUI.mount(element)
+
+ resizeObserver = makeResizeObserver(
+ element,
+ () => chartUI.trigger("resize"),
+ () => chartUI.trigger("resize")
+ )
+
+ const { loaded } = chart.getAttributes()
+
+ listeners = unregister(
+ chart.onAttributeChange("hoverX", render),
+ !loaded && chart.onceAttributeChange("loaded", render)
+ )
+
+ render()
+ }
+
+ const render = () => {
+ chartUI.render()
+
+ const { hoverX, loaded } = chart.getAttributes()
+
+ if (!loaded) return
+
+ const { data } = chart.getPayload()
+
+ const row = hoverX ? chart.getClosestRow(hoverX[0]) : data.length - 1
+
+ const rowData = data[row]
+ if (!Array.isArray(rowData)) return
+
+ chartUI.render()
+ const min = chart.getAttribute("min")
+ const max = chart.getAttribute("max")
+
+ if (min !== prevMin || max !== prevMax) {
+ chartUI.sdk.trigger("yAxisChange", chart, min, max)
+ }
+
+ prevMin = min
+ prevMax = max
+
+ chartUI.trigger("rendered")
+ }
+
+ const unmount = () => {
+ if (listeners) listeners()
+
+ if (resizeObserver) resizeObserver()
+
+ chartUI.unmount()
+ prevMin = null
+ prevMax = null
+ }
+
+ const instance = {
+ ...chartUI,
+ mount,
+ unmount,
+ render,
+ }
+
+ return instance
+}
diff --git a/src/components/line/dimensions/color.js b/src/components/line/dimensions/color.js
index 6923a01d..e24b3e5a 100644
--- a/src/components/line/dimensions/color.js
+++ b/src/components/line/dimensions/color.js
@@ -54,9 +54,9 @@ export const BaseColorBar = ({
)
}
-export const ColorBar = ({ id, valueKey, ...rest }) => {
+export const ColorBar = ({ id, partIndex, valueKey, ...rest }) => {
const chart = useChart()
- const bg = valueKey === "arp" ? "anomalyTextLite" : chart.selectDimensionColor(id)
+ const bg = valueKey === "arp" ? "anomalyTextLite" : chart.selectDimensionColor(id, partIndex)
const min = valueKey === "arp" ? 0 : chart.getAttribute("min")
const max = valueKey === "arp" ? 100 : chart.getAttribute("max")
@@ -80,9 +80,9 @@ export const ColorBar = ({ id, valueKey, ...rest }) => {
)
}
-const ColorValue = ({ id, ...rest }) => {
+const ColorValue = ({ id, partIndex, ...rest }) => {
const chart = useChart()
- const bg = chart.selectDimensionColor(id)
+ const bg = chart.selectDimensionColor(id, partIndex)
if (!bg) return null
diff --git a/src/components/line/dimensions/name.js b/src/components/line/dimensions/name.js
index da74c92c..7769b532 100644
--- a/src/components/line/dimensions/name.js
+++ b/src/components/line/dimensions/name.js
@@ -17,9 +17,9 @@ export const Name = memo(
))
)
-const Container = ({ id, ...rest }) => {
+const Container = ({ id, partIndex, ...rest }) => {
const chart = useChart()
- const name = chart.getDimensionName(id)
+ const name = chart.getDimensionName(id, partIndex)
return {name}
}
diff --git a/src/components/table/columns.js b/src/components/table/columns.js
new file mode 100644
index 00000000..cc129dac
--- /dev/null
+++ b/src/components/table/columns.js
@@ -0,0 +1,268 @@
+import React from "react"
+import { Flex, TextSmall, TextMicro } from "@netdata/netdata-ui"
+import styled from "styled-components"
+import Color, { ColorBar } from "@/components/line/dimensions/color"
+import Name from "@/components/line/dimensions/name"
+import Units from "@/components/line/dimensions/units"
+import Value, { Value as ValuePart } from "@/components/line/dimensions/value"
+import { useChart, useAttributeValue, useVisibleDimensionId } from "@/components/provider"
+import Label from "@/components/filterToolbox/label"
+import { rowFlavours } from "@/components/line/popover/dimensions"
+
+const ColorBackground = styled(ColorBar).attrs({
+ position: "absolute",
+ top: 1,
+ left: 2,
+ backgroundOpacity: 0.4,
+ round: 0.5,
+})``
+
+const rowValueKeys = {
+ ANOMALY_RATE: "arp",
+ default: "value",
+}
+
+const metricsByValue = {
+ dimension: "dimensions",
+ node: "nodes",
+ instance: "instances",
+ label: "labels",
+ value: "values",
+ default: "values",
+}
+
+const emptyArray = []
+
+export const labelColumn = ({ fallbackExpandKey, partIndex, header = "Name" } = {}) => ({
+ id: `label${header || ""}${partIndex || ""}`,
+ header: () => {header},
+ size: 100,
+ minSize: 45,
+ cell: ({
+ row: {
+ original: { ids },
+ depth = 0,
+ getCanExpand,
+ getToggleExpandedHandler,
+ getIsExpanded,
+ },
+ }) => {
+ const [, row] = useAttributeValue("hoverX") || emptyArray
+ const rowFlavour = rowFlavours[row] || rowFlavours.default
+
+ const chart = useChart()
+ const visible = ids.some(chart.isDimensionVisible)
+
+ const [firstId] = ids
+
+ return (
+
+
+ {visible && (
+
+
+
+ )}
+
+
+ {getCanExpand() && (
+
+ )
+ },
+})
+
+const ValueOnDot = ({ children, fractionDigits = 0, ...rest }) => {
+ const [first, last] = children.toString().split(".")
+
+ return (
+
+
+ {first}
+
+ {typeof last !== "undefined" && .}
+
+ {last}
+
+
+ )
+}
+
+export const valueColumn = ({ context = "Dimensions", dimension = "Value" }) => ({
+ id: `value${context}${dimension}`,
+ header: () => {
+ return (
+
+ {dimension}
+
+ )
+ },
+ size: 80,
+ minSize: 45,
+ cell: ({
+ row: {
+ original: { key, ids, contextGroups },
+ depth = 0,
+ getCanExpand,
+ getToggleExpandedHandler,
+ getIsExpanded,
+ },
+ }) => {
+ const id = (contextGroups?.[context]?.[dimension] || ids).find(id => id.includes(key))
+ const visible = useVisibleDimensionId(id)
+
+ const chart = useChart()
+ const fractionDigits = chart.getAttribute("unitsConversionFractionDigits")
+
+ return (
+
+ )
+ },
+ sortingFn: "basic",
+})
+
+// export const anomalyColumn = ({ period, objKey }) => ({
+// id: objKey ? `${objKey}-arp` : "arp",
+// header: AR %,
+// size: 45,
+// minSize: 45,
+// cell: ({
+// row: { original: id, depth = 0, getCanExpand, getToggleExpandedHandler, getIsExpanded },
+// }) => {
+// const visible = useVisibleDimensionId(id)
+
+// return (
+//
+// )
+// },
+// sortingFn: "basic",
+// })
+
+// export const minColumn = ({ period, objKey }) => ({
+// id: objKey ? `${objKey}-min` : "min",
+// header: (
+//
+// Min
+//
+// ),
+// size: 45,
+// minSize: 45,
+// cell: ({
+// row: { original: id, depth = 0, getCanExpand, getToggleExpandedHandler, getIsExpanded },
+// }) => {
+// const visible = useVisibleDimensionId(id)
+
+// return (
+//
+// )
+// },
+// sortingFn: "basic",
+// })
+
+// export const avgColumn = ({ period, objKey }) => ({
+// id: objKey ? `${objKey}-avg` : "avg",
+// header: (
+//
+// Avg
+//
+// ),
+// size: 45,
+// minSize: 45,
+// cell: ({
+// row: { original: id, depth = 0, getCanExpand, getToggleExpandedHandler, getIsExpanded },
+// }) => {
+// const visible = useVisibleDimensionId(id)
+
+// return (
+//
+// )
+// },
+// sortingFn: "basic",
+// })
+
+// export const maxColumn = ({ period, objKey }) => ({
+// id: objKey ? `${objKey}-max` : "max",
+// header: (
+//
+// Max
+//
+// ),
+// size: 45,
+// minSize: 45,
+// cell: ({
+// row: { original: id, depth = 0, getCanExpand, getToggleExpandedHandler, getIsExpanded },
+// }) => {
+// const visible = useVisibleDimensionId(id)
+
+// return (
+//
+// )
+// },
+// sortingFn: "basic",
+// })
diff --git a/src/components/table/index.js b/src/components/table/index.js
new file mode 100644
index 00000000..355d8d14
--- /dev/null
+++ b/src/components/table/index.js
@@ -0,0 +1,169 @@
+import React, { forwardRef, useMemo } from "react"
+import groupBy from "lodash/groupBy"
+import isEmpty from "lodash/isEmpty"
+import { Table, TextSmall } from "@netdata/netdata-ui"
+import ChartContainer from "@/components/chartContainer"
+import { useChart, useDimensionIds, useAttributeValue } from "@/components/provider"
+import withChart from "@/components/hocs/withChart"
+import { ChartWrapper } from "@/components/hocs/withTile"
+import { uppercase } from "@/helpers/objectTransform"
+import FontSizer from "@/components/helpers/fontSizer"
+import { labelColumn, valueColumn, anomalyColumn, minColumn, avgColumn, maxColumn } from "./columns"
+
+const keepoutRegex = ".*"
+const keepRegex = "(" + keepoutRegex + ")"
+
+const useColumns = (options = {}) => {
+ const { period, dimensionIds, groups, labels, rowGroups, contextGroups } = options
+
+ const hover = useAttributeValue("hoverX")
+
+ return useMemo(() => {
+ return [
+ {
+ id: "Instance",
+ header: () => {
+ console.log(1)
+ return "Instance"
+ },
+ columns: labels.map(label =>
+ labelColumn({ header: uppercase(label), partIndex: groups.findIndex(gi => gi === label) })
+ ),
+ },
+ ...Object.keys(contextGroups).map(context => {
+ return {
+ id: `Context-${context}`,
+ header: () => {
+ console.log(2, context)
+ return context
+ },
+ columns: Object.keys(contextGroups[context]).map(dimension =>
+ valueColumn({ context, dimension, ...options })
+ ),
+ }
+ }),
+ ]
+ }, [period, !!hover, dimensionIds])
+}
+
+const columnAttrs = ["context", "dimension"]
+
+const groupByColumn = (result, ids, groups, attrs = columnAttrs) => {
+ const [attr, ...restAttrs] = attrs
+ const groupByRegex = new RegExp(
+ groups.reduce((s, g) => {
+ s = s + (s ? "," : "")
+
+ if (attr === g) {
+ s = s + keepRegex
+ } else {
+ s = s + keepoutRegex
+ }
+
+ return s
+ }, "")
+ )
+
+ if (isEmpty(result)) {
+ result = groupBy(ids, value => {
+ const [, ...matches] = value.match(groupByRegex)
+ return matches.join(",")
+ })
+ } else {
+ Object.keys(result).forEach(key => {
+ result[key] = groupBy(result[key], value => {
+ const [, ...matches] = value.match(groupByRegex)
+ return matches.join(",")
+ })
+ })
+ }
+
+ if (!restAttrs.length) return result
+
+ return groupByColumn(result, ids, groups, restAttrs)
+}
+
+const Dimensions = () => {
+ const dimensionIds = useDimensionIds()
+
+ const tab = useAttributeValue("weightsTab")
+
+ const chart = useChart()
+ const groups = chart.getDimensionGroups()
+ const tableColumns = chart.getAttribute("tableColumns")
+
+ const [rowGroups, contextGroups, labels] = useMemo(() => {
+ let forRows = []
+
+ let groupByRegex = new RegExp(
+ groups.reduce((s, g) => {
+ s = s + (s ? "," : "")
+
+ if (tableColumns.includes(g)) {
+ s = s + keepoutRegex
+ } else {
+ s = s + keepRegex
+ forRows.push(g)
+ }
+
+ return s
+ }, "")
+ )
+
+ const baseGroup = groupBy(dimensionIds, value => {
+ const [, ...matches] = value.match(groupByRegex)
+ return matches.join(",")
+ })
+
+ let contextAndDimensionsGroup = groupByColumn({}, dimensionIds, groups, tableColumns)
+
+ return [baseGroup, contextAndDimensionsGroup, forRows]
+ }, [dimensionIds])
+
+ const columns = useColumns({
+ period: tab,
+ groups,
+ dimensionIds,
+ labels,
+ rowGroups,
+ contextGroups,
+ })
+
+ return (
+ ({ key: g, ids: rowGroups[g], contextGroups }))}
+ // onRowSelected={onItemClick}
+ // onSearch={noop}
+ // meta={meta}
+ // sortBy={sortBy}
+ // rowSelection={rowSelection}
+ // onSortingChange={onSortByChange}
+ // expanded={expanded}
+ // onExpandedChange={onExpandedChange}
+ // enableSubRowSelection={enableSubRowSelection}
+ width="100%"
+ // bulkActions={bulkActions}
+ // rowActions={rowActions}
+ />
+ )
+}
+
+export const TableChart = forwardRef(({ uiName, ...rest }, ref) => (
+
+
+
+
+
+))
+
+export default withChart(TableChart, { tile: true })
diff --git a/src/components/toolbox/chartType.js b/src/components/toolbox/chartType.js
index 2b7204db..7a89af5d 100644
--- a/src/components/toolbox/chartType.js
+++ b/src/components/toolbox/chartType.js
@@ -11,6 +11,7 @@ import easypieChart from "@netdata/netdata-ui/dist/components/icon/assets/chart_
import gaugeChart from "@netdata/netdata-ui/dist/components/icon/assets/chart_gauge.svg"
import d3pieChart from "@netdata/netdata-ui/dist/components/icon/assets/chart_pie.svg"
import valueChart from "@netdata/netdata-ui/dist/components/icon/assets/value.svg"
+import tableChart from "@netdata/netdata-ui/dist/components/icon/assets/chart_bars.svg"
import groupBoxesChart from "@netdata/netdata-ui/dist/components/icon/assets/container.svg"
import Icon, { Button } from "@/components/icon"
import { useChart, useAttributeValue } from "@/components/provider"
@@ -60,6 +61,13 @@ const useItems = chart =>
svg: barChart,
"data-track": chart.track("chartType-multiBar"),
},
+ {
+ value: "table",
+ label: "Table",
+ icon: ,
+ svg: tableChart,
+ "data-track": chart.track("chartType-tableBar"),
+ },
{
value: "heatmap",
label: "Heatmap",
diff --git a/src/makeDefaultSDK.js b/src/makeDefaultSDK.js
index 7bdecfd8..39a586e8 100644
--- a/src/makeDefaultSDK.js
+++ b/src/makeDefaultSDK.js
@@ -5,6 +5,7 @@ import number from "./chartLibraries/number"
import d3pie from "./chartLibraries/d3pie"
import bars from "./chartLibraries/bars"
import groupBoxes from "./chartLibraries/groupBoxes"
+import table from "./chartLibraries/table"
import makeSDK from "./sdk"
import unitConversion from "./sdk/plugins/unitConversion"
import hover from "./sdk/plugins/hover"
@@ -19,7 +20,7 @@ const minutes15 = 15 * 60
export default ({ attributes, ...options } = {}) =>
makeSDK({
- ui: { dygraph, easypiechart, gauge, groupBoxes, number, d3pie, bars },
+ ui: { dygraph, easypiechart, gauge, groupBoxes, number, d3pie, bars, table },
plugins: {
// order matters
move,
diff --git a/src/sdk/makeChart/api/fetchCloudData.js b/src/sdk/makeChart/api/fetchCloudData.js
index 8c35a497..10d317d9 100644
--- a/src/sdk/makeChart/api/fetchCloudData.js
+++ b/src/sdk/makeChart/api/fetchCloudData.js
@@ -37,8 +37,8 @@ const getPayload = chart => {
Array.isArray(selectedContexts) && selectedContexts.length
? selectedContexts
: context
- ? [context]
- : wildcardArray,
+ ? [context]
+ : wildcardArray,
nodes: Array.isArray(selectedNodes) && selectedNodes.length ? selectedNodes : wildcardArray,
instances:
Array.isArray(selectedInstances) && selectedInstances.length
diff --git a/src/sdk/makeChart/filters/makeControllers.js b/src/sdk/makeChart/filters/makeControllers.js
index 8900691f..03ef501e 100644
--- a/src/sdk/makeChart/filters/makeControllers.js
+++ b/src/sdk/makeChart/filters/makeControllers.js
@@ -115,6 +115,7 @@ export default chart => {
d3pie: true,
bars: true,
groupBoxes: true,
+ table: true,
}
const updateChartTypeAttribute = selected => {
diff --git a/src/sdk/makeChart/makeDimensions.js b/src/sdk/makeChart/makeDimensions.js
index ce8862c2..b409ffc8 100644
--- a/src/sdk/makeChart/makeDimensions.js
+++ b/src/sdk/makeChart/makeDimensions.js
@@ -220,7 +220,7 @@ export default (chart, sdk) => {
return memKey
}
- chart.selectDimensionColor = (id = "selected") => {
+ chart.selectDimensionColor = (id = "selected", partIndex) => {
const key = getMemKey()
const colorsAttr = chart.getAttribute("colors")
const sparkline = chart.isSparkline()
@@ -229,6 +229,8 @@ export default (chart, sdk) => {
const isSelected = id === "selected"
id = !id || isSelected ? chart.getAttribute("selectedDimensions")[0] : id
+ if (!isNaN(partIndex)) id = id.split(",")?.[partIndex] || id
+
const color =
isSelected && colorsAttr?.length
? colorsAttr[0]
@@ -238,14 +240,18 @@ export default (chart, sdk) => {
return typeof color === "string" ? color : color[index]
}
- chart.getDimensionName = id => {
+ chart.getDimensionName = (id, partIndex) => {
const viewDimensions = chart.getAttribute("viewDimensions")
if (!viewDimensions?.names) return ""
- if (isHeatmap(chart)) return withoutPrefix(viewDimensions.names[dimensionsById[id]])
+ let dimName = viewDimensions.names[dimensionsById[id]]
+
+ if (!isNaN(partIndex)) dimName = dimName.split(",")?.[partIndex] || dimName
- return viewDimensions.names[dimensionsById[id]]
+ if (isHeatmap(chart)) return withoutPrefix(dimName)
+
+ return dimName
}
chart.getDimensionPriority = id => {
@@ -256,6 +262,14 @@ export default (chart, sdk) => {
return viewDimensions.priorities[dimensionsById[id]]
}
+ chart.getDimensionGroups = () => {
+ const viewDimensions = chart.getAttribute("viewDimensions")
+
+ if (!viewDimensions?.grouped_by?.length) return chart.getAttribute("groupBy")
+
+ return viewDimensions.grouped_by
+ }
+
chart.getRowDimensionValue = (
id,
pointData,