V

Vuer-Viz

vuer-vizv1.0.2

Publication-Quality Visualizations

Parallel Axis Chart Examples

Publication-quality parallel coordinates charts following matplotlib styling conventions. Visualize multivariate data with smooth curves, interactive highlighting, and support for both continuous and categorical axes.

Type Interface

import type { BaseChartProps } from '../../types';
import type { LegendLayout } from '../../components';

export type AxisType = 'continuous' | 'categorical';

export interface AxisDimension {
  /** Name of the dimension (displayed as axis label) */
  name: string;
  /** Type of axis: continuous (numeric) or categorical */
  type: AxisType;
  /** For continuous: min value. For categorical: undefined */
  min?: number;
  /** For continuous: max value. For categorical: undefined */
  max?: number;
  /** For categorical: ordered category values (max 10) */
  categories?: string[];
}

export interface ParallelDataSeries {
  /** Label for this data series */
  label: string;
  /** Values for each dimension (must match dimensions array length) */
  values: (number | string)[];
  /** Optional color override */
  color?: string;
  /** Optional line width override */
  lineWidth?: number;
  /** Optional opacity */
  opacity?: number;
}

export interface ParallelAxisProps extends BaseChartProps {
  /** Axis dimensions configuration (2-10 axes supported) */
  dimensions: AxisDimension[];
  /** Data series */
  series: ParallelDataSeries[];
  /** Chart title */
  title?: string;
  /** Show legend */
  legend?: boolean;
  /** Layout of the legend */
  layout?: LegendLayout;
  /** Custom axis spacing (default: auto-calculated) */
  axisSpacing?: number;
  /** Line opacity when not highlighted (default: 0.6) */
  defaultOpacity?: number;
  /** Line opacity when highlighted (default: 1.0) */
  highlightOpacity?: number;
  /** Line width when highlighted (default: 3x normal) */
  highlightLineWidth?: number;
  /** Layout position for hover value indicators relative to the chart: "above" (above chart), "below" (below chart), or "auto" (automatic) */
  hoverLayout?: "above" | "below" | "auto";
  /** Callback when hovering over a line */
  onHover?: (seriesIndex: number | null) => void;
  /** Currently selected series indices */
  selectedIndices?: number[];
  /** Callback when selection changes */
  onSelectionChange?: (selectedIndices: number[]) => void;
  /** Allow multiple selections (default: true) */
  multiSelect?: boolean;
  /** Children components (for overlays) */
  children?: React.ReactNode;
}

Iris Dataset

Classic Iris dataset visualization showing four continuous dimensions. Hover over lines to highlight individual data points.

Iris Dataset FeaturesSepal Length4.05.06.07.08.0Sepal Width2.02.83.54.35.0Petal Length1.02.54.05.57.0Petal Width0.00.81.52.33.0SetosaSetosa 2VersicolorVersicolor 2VirginicaVirginica 2
import { ParallelAxis } from '@vuer-ai/vuer-viz';

// Classic Iris dataset example
const dimensions = [
  { name: 'Sepal Length', type: 'continuous' as const, min: 4, max: 8 },
  { name: 'Sepal Width', type: 'continuous' as const, min: 2, max: 5 },
  { name: 'Petal Length', type: 'continuous' as const, min: 1, max: 7 },
  { name: 'Petal Width', type: 'continuous' as const, min: 0, max: 3 },
];

const series = [
  {
    label: 'Setosa',
    values: [5.1, 3.5, 1.4, 0.2],
    color: '#fa2525',
  },
  {
    label: 'Setosa 2',
    values: [4.9, 3.0, 1.4, 0.2],
    color: '#fa2525',
  },
  {
    label: 'Versicolor',
    values: [7.0, 3.2, 4.7, 1.4],
    color: '#1080dc',
  },
  {
    label: 'Versicolor 2',
    values: [6.4, 3.2, 4.5, 1.5],
    color: '#1080DC',
  },
  {
    label: 'Virginica',
    values: [6.3, 3.3, 6.0, 2.5],
    color: '#1080DC',
  },
  {
    label: 'Virginica 2',
    values: [5.8, 2.7, 5.1, 1.9],
    color: '#1080DC',
  },
];

export default function IrisDatasetExample() {
  return (
    <ParallelAxis
      dimensions={dimensions}
      series={series}
      title="Iris Dataset Features"
      width={700}
      legend
      hoverLayout="above"
    />
  );
}

Event Handling & Selection API

ParallelAxis supports interactive hover and selection events, allowing you to build rich data exploration interfaces.

Hover Events

Move your mouse over lines to highlight them with increased opacity and a thicker border:

  • Value indicators appear near the legend showing current values for all dimensions
  • Configure position with hoverLayout prop: "above" (above chart), "below" (below chart), or "auto" (automatic)
  • Use onHover callback to receive hover events: (seriesIndex: number | null) => void

Selection Events

Click on lines to select/deselect experiments:

  • Controlled via selectedIndices prop: array of selected series indices
  • Triggered via onSelectionChange callback: (selectedIndices: number[]) => void
  • multiSelect prop enables/disables multiple selection (default: true)
  • Selected lines maintain highlight styling with increased opacity
  • Unselected lines fade to 20% opacity when selections exist
  • Useful for filtering, comparison, and detailed inspection workflows

Basic Selection Example

import { useState } from 'react';
import { ParallelAxis } from '@vuer-ai/vuer-viz';

function MyChart() {
  const [selectedIndices, setSelectedIndices] = useState<number[]>([]);

  return (
    <>
      <ParallelAxis
        dimensions={dimensions}
        series={series}
        selectedIndices={selectedIndices}
        onSelectionChange={setSelectedIndices}
        multiSelect={true} // Allow multiple selections
      />

      {/* Display selected items */}
      <div>
        Selected: {selectedIndices.map(i => series[i].label).join(', ')}
      </div>
    </>
  );
}

Single Selection Mode

Set multiSelect={false} to allow only one selection at a time:

<ParallelAxis
  dimensions={dimensions}
  series={series}
  selectedIndices={selectedIndices}
  onSelectionChange={setSelectedIndices}
  multiSelect={false} // Only one selection allowed
/>

Programmatic Selection

You can programmatically control selections:

// Select top performers
const selectTopN = (n: number) => {
  const sorted = series
    .map((s, i) => ({ index: i, score: s.values[4] as number }))
    .sort((a, b) => b.score - a.score)
    .slice(0, n)
    .map(item => item.index);
  setSelectedIndices(sorted);
};

// Clear all selections
const clearSelection = () => setSelectedIndices([]);

// Select all
const selectAll = () => setSelectedIndices(series.map((_, i) => i));

Selection with Table Example

Interactive selection example showing how to use selectedIndices and onSelectionChange to build data exploration interfaces. Click lines to select/deselect experiments and view details in the table below.

Hyperparameter Sweep (Click lines to select)Learning Rate0.00.00.00.00.0Batch Size3264128256Hidden Dim128.0224.0320.0416.0512.0Num Layers2345Reward500.0612.5725.0837.5950.0

Selected Experiments (0)

ConfigLearning RateBatch SizeHidden DimNum LayersReward
this table is empty
import { ParallelAxis } from '@vuer-ai/vuer-viz';
import { useState } from 'react';

// Hyperparameter sweep example data
const dimensions = [
  { name: 'Learning Rate', type: 'continuous' as const, min: 0.0001, max: 0.01 },
  { name: 'Batch Size', type: 'categorical' as const, categories: ['32', '64', '128', '256'] },
  { name: 'Hidden Dim', type: 'continuous' as const, min: 128, max: 512 },
  { name: 'Num Layers', type: 'categorical' as const, categories: ['2', '3', '4', '5'] },
  { name: 'Reward', type: 'continuous' as const, min: 500, max: 950 },
];

const series = [
  {
    label: 'config-0',
    values: [0.001, '64', 256, '3', 850],
    color: '#fa2525',
  },
  {
    label: 'config-1',
    values: [0.0001, '128', 512, '4', 920],
    color: '#1080dc',
  },
  {
    label: 'config-2',
    values: [0.01, '32', 128, '2', 650],
    color: '#23aaff',
  },
  {
    label: 'config-3',
    values: [0.005, '256', 384, '3', 780],
    color: '#ff9500',
  },
  {
    label: 'config-4',
    values: [0.0003, '64', 256, '4', 890],
    color: '#34c759',
  },
  {
    label: 'config-5',
    values: [0.002, '128', 192, '3', 720],
    color: '#af52de',
  },
  {
    label: 'config-6',
    values: [0.0005, '256', 448, '5', 860],
    color: '#ff2d55',
  },
];

export default function SelectionWithTableExample() {
  const [selectedIndices, setSelectedIndices] = useState<number[]>([]);

  // Get selected experiments
  const selectedExperiments = selectedIndices.map(i => series[i]);

  return (
    <div className="space-y-6">
      {/* Parallel Coordinates Chart */}
      <div>
        <ParallelAxis
          dimensions={dimensions}
          series={series}
          title="Hyperparameter Sweep (Click lines to select)"
          width={800}
          height={300}
          legend={false}
          hoverLayout="below"
          selectedIndices={selectedIndices}
          onSelectionChange={setSelectedIndices}
          multiSelect={true}
        />
      </div>

      {/* Selected Experiments Table */}
      <div>
        <h3 className="text-lg font-semibold mb-3">
          Selected Experiments ({selectedExperiments.length})
        </h3>

        <div className="overflow-x-auto">
          <table className="min-w-full border border-gray-300 dark:border-gray-700">
            <thead className="bg-gray-100 dark:bg-gray-800">
              <tr>
                <th className="px-4 py-2 text-left border-b border-gray-300 dark:border-gray-700">
                  Config
                </th>
                <th className="px-4 py-2 text-left border-b border-gray-300 dark:border-gray-700">
                  Learning Rate
                </th>
                <th className="px-4 py-2 text-left border-b border-gray-300 dark:border-gray-700">
                  Batch Size
                </th>
                <th className="px-4 py-2 text-left border-b border-gray-300 dark:border-gray-700">
                  Hidden Dim
                </th>
                <th className="px-4 py-2 text-left border-b border-gray-300 dark:border-gray-700">
                  Num Layers
                </th>
                <th className="px-4 py-2 text-left border-b border-gray-300 dark:border-gray-700">
                  Reward
                </th>
              </tr>
            </thead>
            <tbody>
              {selectedExperiments.length === 0 ? (
                <tr>
                  <td
                    colSpan={6}
                    className="px-4 py-8 text-center text-gray-400 dark:text-gray-600 italic"
                  >
                    this table is empty
                  </td>
                </tr>
              ) : (
                selectedExperiments.map((exp, idx) => (
                  <tr
                    key={exp.label}
                    className={idx % 2 === 0 ? 'bg-white dark:bg-gray-900' : 'bg-gray-50 dark:bg-gray-800'}
                  >
                    <td className="px-4 py-2 border-b border-gray-200 dark:border-gray-700 font-mono">
                      <span
                        className="inline-block w-3 h-3 rounded-full mr-2"
                        style={{ backgroundColor: exp.color }}
                      />
                      {exp.label}
                    </td>
                    <td className="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
                      {exp.values[0]}
                    </td>
                    <td className="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
                      {exp.values[1]}
                    </td>
                    <td className="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
                      {exp.values[2]}
                    </td>
                    <td className="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
                      {exp.values[3]}
                    </td>
                    <td className="px-4 py-2 border-b border-gray-200 dark:border-gray-700 font-semibold">
                      {exp.values[4]}
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </div>

      {/* Controls */}
      <div className="flex gap-3 text-sm">
        <button
          onClick={() => setSelectedIndices([])}
          className="px-3 py-1.5 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded"
          disabled={selectedExperiments.length === 0}
        >
          Clear Selection
        </button>
        <button
          onClick={() => setSelectedIndices(series.map((_, i) => i))}
          className="px-3 py-1.5 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded"
        >
          Select All
        </button>
        <button
          onClick={() => {
            // Select top 3 by reward
            const sorted = series
              .map((s, i) => ({ index: i, reward: s.values[4] as number }))
              .sort((a, b) => b.reward - a.reward)
              .slice(0, 3)
              .map(item => item.index);
            setSelectedIndices(sorted);
          }}
          className="px-3 py-1.5 bg-blue-500 hover:bg-blue-600 text-white rounded"
        >
          Select Top 3
        </button>
      </div>
    </div>
  );
}

Categorical Data

Demonstration of mixed continuous and categorical axes. Categories are ordered and can have up to 10 values per axis.

Product ComparisonSizeSmallMediumLargePrice0.025.050.075.0100.0QualityLowMediumHighRating0.01.32.53.85.0Product AProduct BProduct CProduct D
import { ParallelAxis } from '@vuer-ai/vuer-viz';

// Example with categorical axes
const dimensions = [
  {
    name: 'Size',
    type: 'categorical' as const,
    categories: ['Small', 'Medium', 'Large'],
  },
  { name: 'Price', type: 'continuous' as const, min: 0, max: 100 },
  {
    name: 'Quality',
    type: 'categorical' as const,
    categories: ['Low', 'Medium', 'High'],
  },
  { name: 'Rating', type: 'continuous' as const, min: 0, max: 5 },
];

const series = [
  {
    label: 'Product A',
    values: ['Small', 25, 'High', 4.5],
    color: '#f12e2e',
  },
  {
    label: 'Product B',
    values: ['Large', 75, 'Medium', 3.8],
    color: '#15c6b9',
  },
  {
    label: 'Product C',
    values: ['Medium', 50, 'High', 4.2],
    color: '#0ea589',
  },
  {
    label: 'Product D',
    values: ['Large', 90, 'Low', 2.5],
    color: '#facc17',
  },
];

export default function CategoricalDataExample() {
  return (
    <ParallelAxis
      dimensions={dimensions}
      series={series}
      title="Product Comparison"
      width={700}
      legend
      hoverLayout="auto"
    />
  );
}

Weather Patterns

Seasonal weather data comparison across multiple metrics using smooth curves.

Seasonal Weather PatternsTemperature (°C)-10.02.515.027.540.0Humidity (%)0.025.050.075.0100.0Wind Speed (km/h)0.012.525.037.550.0Precipitation (mm)0.025.050.075.0100.0SpringSummerAutumnWinter
import { ParallelAxis } from '@vuer-ai/vuer-viz';

// Weather data across different metrics
const dimensions = [
  { name: 'Temperature (°C)', type: 'continuous' as const, min: -10, max: 40 },
  { name: 'Humidity (%)', type: 'continuous' as const, min: 0, max: 100 },
  { name: 'Wind Speed (km/h)', type: 'continuous' as const, min: 0, max: 50 },
  { name: 'Precipitation (mm)', type: 'continuous' as const, min: 0, max: 100 },
];

const series = [
  {
    label: 'Spring',
    values: [15, 60, 15, 40],
    color: '#1dc186',
  },
  {
    label: 'Summer',
    values: [30, 50, 10, 10],
    color: '#ed7420',
  },
  {
    label: 'Autumn',
    values: [12, 70, 20, 60],
    color: '#e6251b',
  },
  {
    label: 'Winter',
    values: [-5, 80, 25, 50],
    color: '#1769ec',
  },
];

export default function WeatherPatternsExample() {
  return (
    <ParallelAxis
      dimensions={dimensions}
      series={series}
      title="Seasonal Weather Patterns"
      width={700}
      legend
      hoverLayout="below"
    />
  );
}

Features

  • Matplotlib-style appearance with Times New Roman font
  • Smooth curves using Catmull-Rom splines
  • Interactive hover highlighting with thicker lines
  • Support for continuous and categorical axes
  • Maximum 10 categories per categorical axis
  • Automatic value normalization
  • Prevents text selection during interaction
  • Customizable colors, opacity, and line widths
  • Legend support with color coding
  • Export-ready at 325x220px (or custom dimensions)

Axis Interaction

  • Click Axis: Click on any axis line or label to toggle between categorical and continuous types
    • State persists across page refreshes using localStorage
    • Each axis configuration is indexed by its name (low, high, vtype)
    • Categorical mode extracts unique values and sorts them
    • Cannot switch to continuous if values contain non-numeric data
  • Text Selection: Prevented during mouse movement for smooth interaction
  • Lines automatically fade when another line is highlighted or selected

State Management

The ParallelAxis component uses Jotai for state management. Each axis configuration is stored in an atom indexed by the axis name, persisting:

  • vtype: Type of axis (continuous or categorical)
  • low: Minimum value for continuous axes
  • high: Maximum value for continuous axes
  • categories: Category values for categorical axes

You can access and modify the axis configuration state using the exported axisConfigsAtom from @vuer-ai/vuer-viz.


← Back to all charts