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

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

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