Interactions
Veltix provides powerful interaction capabilities that enable dynamic data exploration and component communication.
Overview
Interaction features in Veltix include:
- Component Linking: Connect components for coordinated interactions
- Data Filtering: Filter data based on user interactions
- Drill-down Analysis: Navigate from summary to detailed views
- Cross-chart Highlighting: Highlight related data across multiple charts
- Custom Interactions: Build custom interaction patterns
Component Linking
Basic Component Links
// Define component links
const componentLinks = [
{
source: 'sales-chart',
target: 'region-table',
type: 'filter',
field: 'region'
},
{
source: 'time-filter',
target: 'all-charts',
type: 'time-range',
field: 'date'
}
];
// Using component links
<Dashboard
components={components}
links={componentLinks}
onInteraction={handleInteraction}
/>Link Types
// Different link types
const linkTypes = {
filter: {
type: 'filter',
field: 'category',
operator: 'equals'
},
highlight: {
type: 'highlight',
field: 'value',
color: '#ff6b6b'
},
drillDown: {
type: 'drill-down',
target: 'detail-view',
field: 'id'
},
crossFilter: {
type: 'cross-filter',
fields: ['region', 'product'],
operator: 'and'
}
};Data Filtering
Interactive Filters
// Filter component
function FilterComponent({ data, onFilterChange }) {
const [filters, setFilters] = useState({});
const handleFilterChange = (field, value) => {
const newFilters = { ...filters, [field]: value };
setFilters(newFilters);
onFilterChange(newFilters);
};
return (
<div className="filter-panel">
<Select
label="Region"
options={getUniqueValues(data, 'region')}
onChange={(value) => handleFilterChange('region', value)}
/>
<Select
label="Product"
options={getUniqueValues(data, 'product')}
onChange={(value) => handleFilterChange('product', value)}
/>
<DateRangePicker
label="Date Range"
onChange={(range) => handleFilterChange('dateRange', range)}
/>
</div>
);
}
// Using filters
function FilteredDashboard() {
const [filteredData, setFilteredData] = useState(originalData);
const handleFilterChange = (filters) => {
const filtered = applyFilters(originalData, filters);
setFilteredData(filtered);
};
return (
<div>
<FilterComponent
data={originalData}
onFilterChange={handleFilterChange}
/>
<BarChart data={filteredData} />
<LineChart data={filteredData} />
</div>
);
}Advanced Filtering
// Advanced filter implementation
const useAdvancedFiltering = (data, filters) => {
const filteredData = useMemo(() => {
return data.filter(item => {
return Object.entries(filters).every(([field, filter]) => {
if (!filter.value) return true;
switch (filter.operator) {
case 'equals':
return item[field] === filter.value;
case 'contains':
return item[field].includes(filter.value);
case 'greaterThan':
return item[field] > filter.value;
case 'lessThan':
return item[field] < filter.value;
case 'between':
return item[field] >= filter.min && item[field] <= filter.max;
case 'in':
return filter.value.includes(item[field]);
default:
return true;
}
});
});
}, [data, filters]);
return filteredData;
};Drill-down Analysis
Hierarchical Navigation
// Drill-down component
function DrillDownChart({ data, hierarchy }) {
const [currentLevel, setCurrentLevel] = useState(0);
const [drillPath, setDrillPath] = useState([]);
const handleDrillDown = (item) => {
const newPath = [...drillPath, item];
setDrillPath(newPath);
setCurrentLevel(currentLevel + 1);
};
const handleDrillUp = () => {
const newPath = drillPath.slice(0, -1);
setDrillPath(newPath);
setCurrentLevel(currentLevel - 1);
};
const currentData = getDataAtLevel(data, drillPath);
return (
<div>
<div className="drill-navigation">
<button onClick={handleDrillUp} disabled={currentLevel === 0}>
← Back
</button>
<span>Level: {currentLevel}</span>
<span>Path: {drillPath.map(p => p.name).join(' > ')}</span>
</div>
<BarChart
data={currentData}
onClick={handleDrillDown}
drillable={true}
/>
</div>
);
}Multi-level Drill-down
// Multi-level drill-down configuration
const drillDownConfig = {
levels: [
{
name: 'Country',
field: 'country',
chart: 'BarChart'
},
{
name: 'Region',
field: 'region',
chart: 'BarChart'
},
{
name: 'City',
field: 'city',
chart: 'BarChart'
},
{
name: 'Store',
field: 'store',
chart: 'TableChart'
}
],
breadcrumb: true,
maxLevels: 4
};
// Using multi-level drill-down
<DrillDownChart
data={salesData}
config={drillDownConfig}
onDrillComplete={handleDrillComplete}
/>Cross-chart Highlighting
Highlight Coordination
// Cross-chart highlighting
const useCrossChartHighlighting = () => {
const [highlightedItems, setHighlightedItems] = useState([]);
const [highlightSource, setHighlightSource] = useState(null);
const highlightItems = useCallback((items, source) => {
setHighlightedItems(items);
setHighlightSource(source);
}, []);
const clearHighlight = useCallback(() => {
setHighlightedItems([]);
setHighlightSource(null);
}, []);
return {
highlightedItems,
highlightSource,
highlightItems,
clearHighlight
};
};
// Using cross-chart highlighting
function HighlightedDashboard() {
const { highlightedItems, highlightItems, clearHighlight } = useCrossChartHighlighting();
return (
<div>
<BarChart
data={salesData}
highlightedItems={highlightedItems}
onItemHover={(item) => highlightItems([item], 'sales-chart')}
onItemLeave={clearHighlight}
/>
<PieChart
data={categoryData}
highlightedItems={highlightedItems}
onItemHover={(item) => highlightItems([item], 'category-chart')}
onItemLeave={clearHighlight}
/>
</div>
);
}Highlight Styles
// Highlight style configuration
const highlightStyles = {
default: {
opacity: 0.3,
stroke: '#666',
strokeWidth: 1
},
highlighted: {
opacity: 1,
stroke: '#ff6b6b',
strokeWidth: 2,
shadow: '0 0 10px rgba(255, 107, 107, 0.5)'
},
selected: {
opacity: 1,
stroke: '#4ecdc4',
strokeWidth: 3,
shadow: '0 0 15px rgba(78, 205, 196, 0.7)'
}
};
// Applying highlight styles
<BarChart
data={data}
highlightStyles={highlightStyles}
highlightedItems={highlightedItems}
/>Custom Interactions
Event Handlers
// Custom interaction handlers
const useCustomInteractions = () => {
const [interactionState, setInteractionState] = useState({});
const handleClick = useCallback((event, data) => {
console.log('Chart clicked:', data);
setInteractionState(prev => ({
...prev,
lastClicked: data,
clickCount: (prev.clickCount || 0) + 1
}));
}, []);
const handleHover = useCallback((event, data) => {
setInteractionState(prev => ({
...prev,
hoveredItem: data
}));
}, []);
const handleSelection = useCallback((event, selection) => {
setInteractionState(prev => ({
...prev,
selectedItems: selection
}));
}, []);
return {
interactionState,
handleClick,
handleHover,
handleSelection
};
};
// Using custom interactions
function InteractiveChart({ data }) {
const { interactionState, handleClick, handleHover, handleSelection } = useCustomInteractions();
return (
<div>
<BarChart
data={data}
onClick={handleClick}
onHover={handleHover}
onSelection={handleSelection}
/>
<div className="interaction-info">
<p>Clicked: {interactionState.lastClicked?.name}</p>
<p>Hovered: {interactionState.hoveredItem?.name}</p>
<p>Selected: {interactionState.selectedItems?.length || 0} items</p>
</div>
</div>
);
}Custom Interaction Patterns
// Custom interaction pattern
const useInteractionPattern = (pattern) => {
const [patternState, setPatternState] = useState({});
const executePattern = useCallback((trigger, data) => {
switch (pattern.type) {
case 'cascade':
return executeCascadePattern(pattern, trigger, data);
case 'synchronize':
return executeSynchronizePattern(pattern, trigger, data);
case 'conditional':
return executeConditionalPattern(pattern, trigger, data);
default:
return null;
}
}, [pattern]);
return { patternState, executePattern };
};
// Cascade interaction pattern
const cascadePattern = {
type: 'cascade',
steps: [
{
trigger: 'click',
action: 'filter',
target: 'all-charts',
field: 'category'
},
{
trigger: 'filter',
action: 'highlight',
target: 'related-charts',
field: 'value'
},
{
trigger: 'highlight',
action: 'update-summary',
target: 'summary-panel'
}
]
};Interaction Configuration
Global Interaction Settings
// Global interaction configuration
const interactionConfig = {
enabled: true,
mode: 'cross-chart', // 'single-chart', 'cross-chart', 'custom'
highlightOnHover: true,
selectOnClick: true,
enableDrillDown: true,
enableCrossFiltering: true,
animation: {
duration: 300,
easing: 'ease-in-out'
},
styles: {
highlight: {
color: '#ff6b6b',
opacity: 0.8
},
selection: {
color: '#4ecdc4',
opacity: 0.9
}
}
};
// Apply global configuration
<Dashboard
components={components}
interactionConfig={interactionConfig}
/>Component-specific Interactions
// Component-specific interaction settings
const componentInteractions = {
'sales-chart': {
clickable: true,
hoverable: true,
selectable: true,
drillable: true,
onInteraction: (event, data) => {
console.log('Sales chart interaction:', event, data);
}
},
'region-table': {
clickable: true,
hoverable: false,
selectable: true,
drillable: false,
onInteraction: (event, data) => {
console.log('Region table interaction:', event, data);
}
}
};Performance Optimization
Interaction Performance
// Optimized interaction handling
const useOptimizedInteractions = (data, config) => {
const [interactionState, setInteractionState] = useState({});
const interactionRef = useRef({});
const handleInteraction = useCallback((event, data) => {
// Debounce frequent interactions
if (interactionRef.current.timeout) {
clearTimeout(interactionRef.current.timeout);
}
interactionRef.current.timeout = setTimeout(() => {
setInteractionState(prev => ({
...prev,
[event.type]: data,
timestamp: Date.now()
}));
}, config.debounce || 100);
}, [config.debounce]);
// Cleanup on unmount
useEffect(() => {
return () => {
if (interactionRef.current.timeout) {
clearTimeout(interactionRef.current.timeout);
}
};
}, []);
return { interactionState, handleInteraction };
};Memory Management
// Memory-efficient interaction state
const useInteractionState = (maxHistory = 10) => {
const [history, setHistory] = useState([]);
const [currentState, setCurrentState] = useState({});
const addToHistory = useCallback((interaction) => {
setHistory(prev => {
const newHistory = [...prev, interaction];
// Keep only the latest interactions
return newHistory.slice(-maxHistory);
});
}, [maxHistory]);
const clearHistory = useCallback(() => {
setHistory([]);
}, []);
return {
history,
currentState,
addToHistory,
clearHistory,
setCurrentState
};
};Best Practices
1. Interaction Design
- Keep interactions intuitive and discoverable
- Provide visual feedback for all interactions
- Use consistent interaction patterns
- Consider accessibility requirements
2. Performance
- Debounce frequent interactions
- Limit interaction history
- Optimize data filtering
- Use efficient event handling
3. User Experience
- Provide clear interaction feedback
- Include interaction instructions
- Support keyboard navigation
- Maintain interaction state
4. Accessibility
- Support screen readers
- Provide keyboard shortcuts
- Include ARIA labels
- Ensure color contrast
5. Testing
- Test all interaction paths
- Verify cross-browser compatibility
- Test with different data sizes
- Validate interaction performance
Troubleshooting
Common Issues
Interactions not working
- Check interaction configuration
- Verify event handlers
- Test component connectivity
- Review console errors
Performance issues
- Implement interaction debouncing
- Optimize data filtering
- Limit interaction history
- Monitor memory usage
Cross-chart issues
- Verify component linking
- Check data consistency
- Test interaction propagation
- Review highlight styles
Last updated on