import React, { useMemo } from 'react'; import useDimensions from 'react-cool-dimensions'; // Definitions import { ErrorManagement, NumberOrDateRange, NumberOrTimeDelta, NumberOrTimeDeltaRange, NumberRange, } from '../../types/general'; import { OrientationOptions } from '../../types/plots'; import ControlsHeader from '../typography/ControlsHeader'; // Mid-level controls import SelectedRangeControl from './SelectedRangeControl'; import BinWidthControl from './BinWidthControl'; import AxisRangeControl from './AxisRangeControl'; // Local Components import Button from '../widgets/Button'; import ButtonGroup from '../widgets/ButtonGroup'; import Notification from '../widgets/Notification'; import OpacitySlider from '../widgets/OpacitySlider'; import OrientationToggle from '../widgets/OrientationToggle'; import { Toggle } from '@veupathdb/coreui'; import { NumberRangeInput } from '../widgets/NumberAndDateRangeInputs'; import LabelledGroup from '../widgets/LabelledGroup'; import { ColorDescriptor } from '@veupathdb/coreui/dist/components/theming/types'; import { blue } from '@veupathdb/coreui/dist/definitions/colors'; /** * Props for histogram controls. * * The presence or absence of an optional callback will * determine if that control is displayed. */ export type HistogramControlsProps = { /** Label for control panel. Optional. */ label?: string; /** Currently selected bar layout. Optional */ barLayout?: string; /** Function to invoke when barlayout changes. */ onBarLayoutChange?: (layout: 'overlay' | 'stack') => void; /** Whether or not to display the plot legend. */ displayLegend?: boolean; /** Action to take on display legend change. */ toggleDisplayLegend?: (displayLegend: boolean) => void; /** Whether or not to display the additionally controls that * may be provided by the charting library used to generate the plot. * For example, Plot.ly controls.*/ displayLibraryControls?: boolean; /** Action to take on display library controls change. */ toggleLibraryControls?: (displayLegend: boolean) => void; /** Current histogram opacity. */ opacity?: number; /** Function to invoke when opacity changes. */ onOpacityChange?: (opacity: number) => void; /** The current orientation of the plot. */ orientation?: OrientationOptions; /** Function to invoke when orientation changes. */ toggleOrientation?: (orientation: string) => void; /** Type of x-variable 'number' or 'date' */ valueType?: 'number' | 'date'; /** Available unit options by which to bin data. */ availableUnits?: Array; /** The currently selected binWidth unit. */ selectedUnit?: string; /** Function to invoke when the selected bin unit changes. */ onSelectedUnitChange?: (unit: string) => void; /** The current binWidth */ binWidth?: NumberOrTimeDelta; /** Function to invoke when bin width changes. */ onBinWidthChange?: (newBinWidth: NumberOrTimeDelta) => void; /** The acceptable range of binWidthValues. */ binWidthRange?: NumberOrTimeDeltaRange; /** The step to take when adjusting binWidth */ binWidthStep?: number; /** A range to highlight by means of opacity. Optional */ selectedRange?: NumberOrDateRange; // TO DO: handle DateRange too /** function to call upon selecting a range (in independent axis). Optional */ onSelectedRangeChange?: (newRange?: NumberOrDateRange) => void; /** Min and max allowed values for the selected range. Optional */ selectedRangeBounds?: NumberOrDateRange; // TO DO: handle DateRange too /** Additional styles for controls container. Optional */ containerStyles?: React.CSSProperties; /** Color to use as an accent in the control panel. */ accentColor?: ColorDescriptor; /** Attributes and methdods for error management. */ errorManagement?: ErrorManagement; // add y-axis controls /** Whether or not to show y-axis log scale. */ dependentAxisLogScale?: boolean; /** Action to take on y-axis log scale change. */ toggleDependentAxisLogScale?: (dependentAxisLogScale: boolean) => void; /** Whether or not to set y-axis min/max range. */ dependentAxisRange?: NumberRange; /** Action to take on y-axis min/max range change. */ onDependentAxisRangeChange?: (newRange?: NumberRange) => void; /** Whether or not to display y-axis absolute Relative. */ dependentAxisMode?: 'absolute' | 'relative'; /** Action to take on display legend change. */ onDependentAxisModeChange?: (newMode: 'absolute' | 'relative') => void; /** Action to reset dependent axis range. */ onDependentAxisSettingsReset?: () => void; /** Whether or not to set x-axis min/max range. */ independentAxisRange?: NumberOrDateRange; /** Action to take on x-axis min/max range change. */ onIndependentAxisRangeChange?: (newRange?: NumberOrDateRange) => void; /** Action to reset independent axis range. */ onIndependentAxisSettingsReset?: () => void; /** Action to Reset all to defaults. */ onResetAll?: () => void; }; /** * A histogram controls panel. * * If you prefer a different layout or composition, you can * contruct you own control panel by using the various * widgets contained here. */ export default function HistogramControls({ label, binWidth, binWidthStep, binWidthRange, onBinWidthChange, displayLegend, toggleDisplayLegend, displayLibraryControls, toggleLibraryControls, barLayout, onBarLayoutChange, opacity, onOpacityChange, orientation, toggleOrientation, valueType, availableUnits, selectedUnit, onSelectedUnitChange, selectedRange, onSelectedRangeChange, selectedRangeBounds, // add y-axis controls dependentAxisLogScale, toggleDependentAxisLogScale, dependentAxisRange, onDependentAxisRangeChange, dependentAxisMode, onDependentAxisModeChange, onDependentAxisSettingsReset, // add x-axis/independent axis controls: axis range and range reset independentAxisRange, onIndependentAxisRangeChange, onIndependentAxisSettingsReset, // add reset all onResetAll, containerStyles = {}, accentColor = { hue: blue, level: 400 }, errorManagement, }: HistogramControlsProps) { const accentColorStr = accentColor.hue[accentColor.level]; const { ref, width } = useDimensions(); const errorStacks = useMemo(() => { if (errorManagement == null) return []; return errorManagement.errors.reduce< Array<{ error: Error; occurences: number }> >((accumulatedValue, currentValue) => { const existingErrorStack = accumulatedValue.find( (stack) => stack.error.message === currentValue.message ); if (existingErrorStack) { existingErrorStack.occurences++; return [...accumulatedValue]; } else { return [...accumulatedValue, { error: currentValue, occurences: 1 }]; } }, []); }, [errorManagement?.errors]); return (
{orientation && toggleOrientation && ( )} {barLayout && onBarLayoutChange && ( )}
500 ? '2fr 2fr 1fr' : width > 300 ? '1fr 1fr' : '1fr', // marginTop: '0.9375em', // marginRight: '0.9375em', columnGap: '1.5625em', rowGap: '0.3125em', }} > {opacity != null && onOpacityChange && ( )}
{displayLegend != null && toggleDisplayLegend && ( )} {displayLibraryControls != null && toggleLibraryControls && ( )}
{toggleDependentAxisLogScale && dependentAxisLogScale != null && ( )} {onDependentAxisRangeChange && ( { onDependentAxisRangeChange(newRange as NumberRange); }} allowPartialRange={false} /> )} {dependentAxisMode && onDependentAxisModeChange && ( )} {onDependentAxisSettingsReset && (
); }