MDK Logo

Charts

Chart cards on the dashboard — line, timeline, and multi-series visualizations for mining operations

Chart components specialized for Bitcoin mining operations data visualization, including hashrate trends, power consumption, power mode timelines, and multi-container comparisons.

Prerequisites

ChartWrapper

Wrapper component for charts with built-in loading and empty state handling.

Import

import { ChartWrapper } from '@tetherto/mdk-react-devkit/foundation'

Props

PropStatusTypeDefaultDescription
childrenOptionalReactNodenoneChart content rendered when data is present
dataOptionalobject | arraynoneChart data for LineChart-style datasets; used by useChartDataCheck
datasetOptionalobject | arraynoneChart dataset for BarChart-style data; used by useChartDataCheck
isLoadingOptionalbooleanfalseWhen true, shows the loading overlay with default <Loader /> (animated dots) or customLoader, and hides chart children
showNoDataPlaceholderOptionalbooleantrueWhen true, shows empty placeholder when data/dataset has no values
customLoaderOptionalReactNode<Loader />Node shown in the loading overlay (default core Loader)
customNoDataMessageOptionalstring | ReactNodenoneCustom empty state message or component
minHeightOptionalnumber400Minimum height in pixels for empty state
loadingMinHeightOptionalnumberminHeightMinimum height in pixels for the loading overlay (default <Loader /> or customLoader)
classNameOptionalstringnoneRoot class name from the host app

Chart children (LineChart, BarChart) come from @tetherto/mdk-react-devkit/core.

import { LineChart, BarChart } from '@tetherto/mdk-react-devkit/core'

const CustomSpinner = () => <div className="my-spinner" aria-hidden />

Basic usage

<ChartWrapper
  data={chartData}
  isLoading={isLoading}
  minHeight={400}
>
  <LineChart data={chartData} />
</ChartWrapper>

With custom loader

<ChartWrapper
  data={chartData}
  isLoading={isLoading}
  customLoader={<CustomSpinner />}
  minHeight={300}
>
  <LineChart data={chartData} />
</ChartWrapper>

With custom empty message

Pass an empty dataset so ChartWrapper shows the empty placeholder instead of the chart. With non-empty dataset, children render and the custom message is not shown.

import { BarChart } from '@tetherto/mdk-react-devkit/core'

const emptyBarData = { labels: [], datasets: [] }

<ChartWrapper
  dataset={emptyBarData}
  isLoading={false}
  customNoDataMessage="No hashrate data available for this period"
  minHeight={300}
>
  <BarChart data={emptyBarData} />
</ChartWrapper>

States

The component handles three states automatically. Chart children are hidden while isLoading is true or when data / dataset is empty (and showNoDataPlaceholder is true):

  1. Loading: Shows default <Loader /> (animated dots) or customLoader
  2. No data: Shows EmptyState placeholder (or customNoDataMessage)
  3. Has data: Shows chart content

Styling

  • .mdk-chart-wrapper: Root element
  • .mdk-chart-wrapper__content: Chart content container
  • .mdk-chart-wrapper__content--hidden: Hidden content state
  • .mdk-chart-wrapper__empty: Empty state container
  • .mdk-chart-wrapper__loading: Loading state container

ConsumptionLineChart

Power consumption trend chart built on LineChartCard.

Import

import { ConsumptionLineChart } from '@tetherto/mdk-react-devkit/foundation'

Props

PropStatusTypeDefaultDescription
dataOptionalTailLogEntry[][]Consumption log entries for the line series
tagOptionalstring't-miner'Data tag that selects which aggregated power field to read
timelineOptionsOptionalTimelineOption[]noneRange buttons shown above the chart; omit to hide the selector
timelineOptionalstringnoneControlled active range; pair with onTimelineChange
defaultTimelineOptionalstringnoneInitial range when uncontrolled; falls back to 5m (or 1m when isOneMinEnabled)
onTimelineChangeOptionalfunctionnoneCalled with the new range value when the user changes timeline
powerAttributeOptionalstringnoneOverrides the derived power field name on each log entry
labelOptionalstringnoneLegend label (default: total miner consumption text)
rawConsumptionWOptionalnumber | stringnoneRaw consumption value for the detail legend
isOneMinEnabledOptionalbooleannoneWhen true, adds a 1m timeline option
totalTransformerConsumptionOptionalbooleannoneWhen true, uses transformer total consumption semantics
isDetailedOptionalbooleannoneWhen true, shows the detail legend with the current value
skipMinMaxAvgOptionalbooleanfalseWhen true, hides the min/max/avg footer row

Basic usage

<ConsumptionLineChart
  data={consumptionLogs}
  timelineOptions={timelineOptions}
  defaultTimeline="5m"
/>

Controlled timeline

const [timeline, setTimeline] = useState('5m')

const handleTimelineChange = (value: string) => setTimeline(value)

<ConsumptionLineChart
  data={consumptionLogs}
  timelineOptions={timelineOptions}
  timeline={timeline}
  onTimelineChange={handleTimelineChange}
/>

ContainerCharts

Container-level chart dashboard with combination selector and multiple chart types.

Import

import { ContainerCharts } from '@tetherto/mdk-react-devkit/foundation'

Props

PropStatusTypeDefaultDescription
featureEnabledOptionalbooleantrueWhen false, shows disabledMessage instead of charts
combinationsRequired{ value: string; label: string }[]noneSelect options (value / label pairs)
chartRawDataOptionalChartEntry[]nullRaw time-series rows; shape depends on selected container type
selectedCombinationOptionalstring | nullnoneControlled selected combinations[].value
defaultSelectedCombinationOptionalstring | nullnullInitial selection when uncontrolled
onSelectedCombinationChangeOptionalfunctionnoneCalled with the new combination value (or null) when the user changes selection
isLoadingCombinationsOptionalbooleanfalseWhen true, shows loading state on the combination selector
isLoadingChartsOptionalbooleanfalseWhen true, shows loading state on the chart grid
disabledMessageOptionalstring'Container Charts feature is not enabled'Message when featureEnabled is false
titleOptionalstring'Container Charts'Section heading
getDatasetBorderColorOptionalfunctionnoneResolver for dataset border colors; receives dataset metadata from the builder

ChartEntry type

type EntryData = {
  [key: string]: number | null | undefined
}

type ChartEntry = {
  ts: number | string
  container_specific_stats_group_aggr: Record<string, EntryData | null | undefined>
}

Each row’s container_specific_stats_group_aggr keys use complete container type ids (for example container-bd-d40-m30, container-as-hk3). Stat field names inside each entry depend on the container family.

Bitdeer (container-bd-… keys) — field names per chart block:

Chart blockStat field pattern
Liquid Temp Hhot_temp_c_w_{n}_group
Liquid Temp Lcold_temp_c_w_{n}_group
Oil Tempcold_temp_c_{n}_group (no _w_)
Pressuretank{n}_bar_group

Hydro (container-as-hk3): supply_liquid_temp_group, supply_liquid_pressure_group.

Combination value strings must contain the real prefix patterns the component detects (bd-…, as-hk3…, as-immersion…, mbt-…). Values such as bitmain_hydro_b2 (underscores) do not match isAntspaceHydro() and show the wrong chart blocks.

const chartData: ChartEntry[] = [
  {
    ts: 1700000000,
    container_specific_stats_group_aggr: {
      'container-bd-d40-m30': {
        hot_temp_c_w_1_group: 52,
        hot_temp_c_w_2_group: 54,
        cold_temp_c_w_1_group: 38,
        cold_temp_c_w_2_group: 39,
        cold_temp_c_1_group: 36,
        cold_temp_c_2_group: 37,
        tank1_bar_group: 2.4,
        tank2_bar_group: 2.5,
      },
      'container-as-hk3': {
        supply_liquid_temp_group: 44,
        supply_liquid_pressure_group: 2.8,
      },
    },
  },
]

Basic usage

<ContainerCharts
  combinations={[
    { value: 'bd-d40-m30_wm-m30sp', label: 'Bitdeer M30SP' },
    { value: 'as-hk3_am-s19xp_h', label: 'Bitmain Hydro S19XP' },
  ]}
  defaultSelectedCombination="bd-d40-m30_wm-m30sp"
  chartRawData={chartData}
  onSelectedCombinationChange={setSelected}
/>

Container type charts

Charts displayed vary by container type:

  • Bitdeer: Liquid Temp H, Liquid Temp L, Oil Temp, Pressure
  • Bitmain Hydro: Liquid Temp L, Pressure
  • Bitmain Immersion: Liquid Temp L, Oil Temp
  • MicroBT: Liquid Temp L, Pressure

Styling

  • .mdk-container-charts: Root element
  • .mdk-container-charts__title: Section title
  • .mdk-container-charts__select-row: Combination selector row
  • .mdk-container-charts__select-label: Selector label
  • .mdk-container-charts__select: Select dropdown
  • .mdk-container-charts__layout: Charts grid layout
  • .mdk-container-charts__chart-block: Individual chart container
  • .mdk-container-charts__chart-title: Chart block title
  • .mdk-container-charts__loading: Loading state container

HashRateLineChartSelector

Hashrate over time visualization with timeline selector and real-time data support. Switches between a plain hashrate layout and a pool-overlay layout based on hasPoolLine.

All props are optional.

Import

import { HashRateLineChartSelector } from '@tetherto/mdk-react-devkit/foundation'

Behaviour

HashRateLineChartSelector renders one of two layouts based on hasPoolLine:

  • hasPoolLine={false} (default) — a single hashrate line driven by data and realtimeHashrateData, with timeline range selector and a highlighted current-value readout.
  • hasPoolLine={true} — a multi-series chart that overlays the miner's hashrate against per-pool breakdowns, driven by minerTailLogData and minerPoolDataRaw.

Pass the props for the layout you want; props for the other layout have no effect.

Layout

PropStatusTypeDefaultDescription
hasPoolLineOptionalbooleanfalseWhen true, renders HashRateLineChartWithPool; when false, plain hashrate line

Plain-layout props

Used when hasPoolLine is false.

PropStatusTypeDefaultDescription
dataOptionalHashRateLogEntry[][]Historical hashrate log entries for the primary line
realtimeHashrateDataOptionalHashRateLogEntrynoneLatest sample merged into the series for the current-value readout
fixedTimezoneOptionalstringnoneIANA timezone for axis tick formatting
heightOptionalnumbernoneChart height in pixels
loadingOptionalbooleannoneWhen true, shows loading state and hides the footer readout

Pool overlay layout props

Used when hasPoolLine is true.

PropStatusTypeDefaultDescription
minerTailLogDataOptionalHashRateLogEntry[][]Miner hashrate samples (flat array or array of arrays)
minerPoolDataRawOptionalMinerPoolDataItem[][]Per-pool hashrate samples overlaid on the miner line
isMinerTailLogLoadingOptionalbooleanfalseWhen true, initial-load state for the miner series
isMinerTailLogFetchingOptionalbooleanfalseWhen true, background refresh indicator for the miner series
isMinerpoolInitialLoadingOptionalbooleanfalseWhen true, initial-load state for the pool overlay series

Shared props

PropStatusTypeDefaultDescription
isOneMinEnabledOptionalbooleannoneWhen true, adds a 1m option to the timeline range selector in both layouts

HashRateLogEntry type

type HashRateLogEntry = {
  ts: number
  hashrate_mhs_1m_sum_aggr?: number
  [key: string]: unknown
}

MinerPoolDataItem type

type PoolStat = {
  poolType: string
  hashrate: number
}

type MinerPoolDataItem = {
  stats: PoolStat[]
  ts: number | string
}

Basic usage

<HashRateLineChartSelector
  data={hashrateHistory}
  realtimeHashrateData={currentHashrate}
  isOneMinEnabled
  height={400}
  loading={isLoading}
/>

With fixed timezone

<HashRateLineChartSelector
  data={hashrateHistory}
  fixedTimezone="America/New_York"
  height={400}
  loading={false}
/>

With pool overlay

<HashRateLineChartSelector
  hasPoolLine
  minerTailLogData={tailLog}
  isMinerTailLogLoading={isInitialLoading}
  isMinerTailLogFetching={isRefetching}
  minerPoolDataRaw={poolSamples}
  isMinerpoolInitialLoading={isPoolInitialLoading}
  isOneMinEnabled
/>

Features

  • Timeline range selector: 5m, 15m, 1h, 6h, 24h, 7d (plus 1m when isOneMinEnabled)
  • Legend toggle: show/hide individual datasets
  • Min/Max/Avg readout derived from the visible series
  • Highlighted current-value readout (plain layout)
  • Pool type breakdown overlay (pool overlay layout)

Styling

Built on ChartContainer and LineChart from @tetherto/mdk-react-devkit/core. The pool-overlay layout adds a .mdk-hash-rate-line-chart-with-pool root wrapper. See core chart styling for customization options.

LineChartCard

Composable card with timeline selector, legend, line chart, and stats footer.

Import

import { LineChartCard } from '@tetherto/mdk-react-devkit/foundation'

Props

PropStatusTypeDefaultDescription
dataOptionalLineChartCardDatanonePre-processed chart payload passed to the inner line chart
rawDataOptionalunknownnoneRaw API payload; use with dataAdapter instead of data
dataAdapterOptionalfunctionnoneMaps rawData to LineChartCardData
timelineOptionsOptionalTimelineOption[]noneTimeline range buttons
timelineOptionalstringnoneControlled active timeline value
defaultTimelineOptionalstring'5m'Initial timeline when uncontrolled
onTimelineChangeOptionalfunctionnoneCalled when the user selects a new timeline range
titleOptionalstringnoneChart title in the card header
detailLegendsOptionalbooleanfalseWhen true, shows detailed legends below the title
isLoadingOptionalbooleanfalseWhen true, shows loading state
shouldResetZoomOptionalbooleantrueWhen true, resets chart zoom when the timeline changes
minHeightOptionalnumber350Minimum chart body height in pixels
classNameOptionalstringnoneRoot class name from the host app

Basic usage

<LineChartCard
  title="Temperature"
  data={chartData}
  timelineOptions={[
    { label: '5m', value: '5m' },
    { label: '1h', value: '1h' },
    { label: '24h', value: '24h' },
  ]}
  defaultTimeline="1h"
/>

With data adapter

<LineChartCard
  title="Power Consumption"
  rawData={rawStats}
  dataAdapter={(raw) => processStatsToChartData(raw)}
  timelineOptions={timelineOptions}
  defaultTimeline="24h"
  isLoading={isLoading}
/>

Styling

  • .mdk-line-chart-card: Root element
  • .mdk-line-chart-card__legends: Detail legends container

PowerModeTimelineChart

Visualizes power mode changes over time using TimelineChart.

Mining-specific wrapper around TimelineChart. Use TimelineChart directly when you supply custom row labels and segment data.

Import

import { PowerModeTimelineChart } from '@tetherto/mdk-react-devkit/foundation'

Props

PropStatusTypeDefaultDescription
dataOptionalPowerModeTimelineEntry[][]Historical power mode segments for TimelineChart
dataUpdatesOptionalPowerModeTimelineEntry[][]New segments merged into the chart when updates arrive
timezoneOptionalstring'UTC'IANA timezone for segment timestamps
titleOptionalstring'Power Mode Timeline'Chart title passed to the inner timeline
isLoadingOptionalbooleanfalseWhen true, shows loading state inside the chart card

PowerModeTimelineEntry type

type PowerModeTimelineEntry = {
  ts?: number
  power_mode_group_aggr?: Record<string, string>
  status_group_aggr?: Record<string, string>
}

Basic usage

<PowerModeTimelineChart
  data={powerModeHistory}
  timezone="America/New_York"
  isLoading={isLoading}
/>

With real-time updates

<PowerModeTimelineChart
  data={powerModeHistory}
  dataUpdates={realtimeUpdates}
  timezone={userTimezone}
/>

TimelineChart

Horizontal timeline visualization for time-based events like power mode changes.

Gantt-like timeline for multiple rows. labels lists row names (Y axis). Each dataset groups colored segments; each segment's y must match one of labels, and x is [startMs, endMs].

Import

import { TimelineChart, emptyTimelineChartData } from '@tetherto/mdk-react-devkit/foundation'
import type { TimelineChartData } from '@tetherto/mdk-react-devkit/foundation'

Empty initial state

Use emptyTimelineChartData ({ labels: [], datasets: [] }) for the first render before live timeline data arrives. Pair with isLoading while fetching if needed.

<TimelineChart
  initialData={emptyTimelineChartData}
  title="Miner state"
  isLoading={isLoading}
/>

Props

PropStatusTypeDefaultDescription
initialDataRequiredTimelineChartDatanoneRow labels and segment datasets shown on first render
newDataOptionalTimelineChartDatanoneExtra labels and datasets merged when skipUpdates is false
skipUpdatesOptionalbooleanfalseWhen true, ignores newData
rangeOptionalChartRangenoneVisible time window (min / max as Date or epoch ms)
axisTitleTextOptionalAxisTitleText{ x: 'Time', y: '' }Axis title strings
isLoadingOptionalbooleanfalseShows loading state inside ChartContainer
titleOptionalstringnoneChart title passed to ChartContainer
heightOptionalnumber | stringnoneRoot height (CSS length or pixels)

TimelineChartData type

See also Timeline chart types.

type TimelineChartDataPoint = {
  x: [number, number] // segment start and end (epoch ms)
  y: string | undefined // must match an entry in labels
}

type TimelineChartDataset = {
  label: string
  data: TimelineChartDataPoint[]
  borderColor?: string[]
  backgroundColor?: string[]
  color?: string
}

type TimelineChartData = {
  labels: string[]
  datasets: TimelineChartDataset[]
}

type ChartRange = {
  min: Date | number
  max: Date | number
}

Basic usage

const start = Date.now() - 2 * 60 * 60 * 1000

<TimelineChart
  initialData={{
    labels: ['Task A', 'Task B', 'Task C'],
    datasets: [
      {
        label: 'In Progress',
        color: '#22afff',
        data: [
          { x: [start, start + 30 * 60 * 1000], y: 'Task A' },
          { x: [start + 20 * 60 * 1000, start + 50 * 60 * 1000], y: 'Task B' },
        ],
      },
      {
        label: 'Completed',
        color: '#72f59e',
        data: [{ x: [start + 30 * 60 * 1000, start + 60 * 60 * 1000], y: 'Task A' }],
      },
    ],
  }}
  title="Project timeline"
  axisTitleText={{ x: 'Time', y: 'Tasks' }}
/>

With newData updates

<TimelineChart
  initialData={baseline}
  newData={streamingUpdate}
  skipUpdates={false}
/>

When newData arrives, labels are unioned and datasets are appended. Set skipUpdates to keep only initialData.

Styling

  • .mdk-timeline-chart: Root element
  • .mdk-timeline-chart__loading: Loading container
  • .mdk-timeline-chart__legend, .mdk-timeline-chart__legend-item, .mdk-timeline-chart__legend-color, .mdk-timeline-chart__legend-label: Legend row
  • .mdk-timeline-chart__visualization: Scrollable chart body

WidgetTopRow

Dashboard header row displaying title, power consumption, and alarm indicators.

Import

import { WidgetTopRow } from '@tetherto/mdk-react-devkit/foundation'

Props

PropStatusTypeDefaultDescription
titleRequiredstringnoneWidget title shown in the header row
powerOptionalnumbernonePower consumption value; formatted with unit when set
unitOptionalstringnoneUnit suffix for power (for example kW)
statsErrorMessageOptionalstring | ErrorWithTimestamp[]noneReplaces the power readout with - and a formatErrors tooltip
alarmsOptionalobjectnoneMap keyed by liquidAlarms, leakageAlarms, pressureAlarms, otherAlarms; values are Alert arrays
classNameOptionalstringnoneRoot class name from the host app

Composition

WidgetTopRow renders the title, then four fixed alarm slots in order (liquid, leakage, pressure, other), then the power readout. Each slot reads the matching alarms key from the table below; when the array is missing or empty, that slot is not shown. Tooltip text lists each Alert with timestamp and description.

Slot labelalarms keyIcon role
LiquidliquidAlarmsTemperature / liquid alarm
LeakageleakageAlarmsFluid / leakage alarm
PressurepressureAlarmsPressure alarm
OtherotherAlarmsOther alarm

When power and unit are set and statsErrorMessage is unset, the power label shows formatNumber(unitToKilo(power)) followed by the unit in a nested <span>.

Basic usage

<WidgetTopRow
  title="Container A1"
  power={125000}
  unit="kW"
/>

More examples

Styling

  • .mdk-widget-top-row: Root element
  • .mdk-widget-top-row__inner: Inner container
  • .mdk-widget-top-row__title: Title text
  • .mdk-widget-top-row__alarm-info-icon: Alarm slot icon (tooltip trigger)
  • .mdk-widget-top-row__alarm-info-title: Alarm tooltip heading ({slot} issues)
  • .mdk-widget-top-row__power: Power display or error placeholder

On this page