diff --git a/README.md b/README.md index 9e25b9e..01809fc 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,148 @@ -# python-ooui -Port of ooui.js to Python +# Python OOUI -## Testing +Python OOUI (Open Object User Interface) is a Python port of ooui.js, providing powerful tools for data visualization and processing. -```shell +## Features + +- **Graph Processing**: Create line charts, bar charts, pie charts, and indicators +- **Tree Views**: Handle structured data with advanced filtering and conditional formatting +- **Data Helpers**: Utilities for domain parsing, condition evaluation, and data aggregation +- **XML Parsing**: Parse XML-based graph and tree definitions +- **Cross-Platform**: Compatible with Python 2.7+ and Python 3.x + +## Quick Start + +### Installation + +```bash +pip install ooui +``` + +### Basic Usage + +```python +from ooui.graph import parse_graph +from ooui.tree import parse_tree + +# Create a line chart +graph_xml = ''' + + + + +''' +graph = parse_graph(graph_xml) + +# Create a tree view +tree_xml = ''' + + + + + +''' +tree = parse_tree(tree_xml) +``` + +## Documentation + +📚 **[Complete Documentation](docs/index.md)** - Start here for comprehensive guides and examples + +### Quick Links + +- **[Installation Guide](docs/installation.md)** - Detailed setup instructions +- **[Usage Guide](docs/usage.md)** - Core concepts and basic usage +- **[API Reference](docs/api-reference.md)** - Complete API documentation +- **[Examples](docs/examples.md)** - Practical code examples +- **[Advanced Usage](docs/advanced-usage.md)** - Complex scenarios and advanced features + +## Key Components + +### Graph Processing +```python +from ooui.graph import parse_graph + +# Support for multiple chart types +graph = parse_graph(xml_definition) +result = graph.process(data, fields) +``` + +### Tree Views +```python +from ooui.tree import parse_tree + +# Structured data with conditional formatting +tree = parse_tree(xml_definition) +conditional_fields = tree.fields_in_conditions +``` + +### Condition Evaluation +```python +from ooui.helpers import ConditionParser + +parser = ConditionParser("red:amount < 100;green:amount >= 100") +result = parser.eval({'amount': 150}) # Returns "green" +``` + +### Domain Parsing +```python +from ooui.helpers import Domain + +domain = Domain("[('active', '=', True), ('age', '>', 18)]") +parsed = domain.parse({'user_id': 42}) +``` + +## Development + +### Setting Up Development Environment + +```bash +# Clone the repository +git clone https://github.com/gisce/python-ooui.git +cd python-ooui + +# Install in development mode pip install -e . + +# Install development dependencies pip install -r requirements-dev.txt +``` + +### Running Tests + +```bash +# Run all tests mamba + +# Tests use mamba (BDD testing framework) +# See spec/ directory for test specifications ``` + +### Project Structure + +``` +ooui/ +├── graph/ # Graph processing (charts, indicators) +├── tree/ # Tree view processing +└── helpers/ # Utilities (conditions, domain, dates, etc.) +``` + +## Contributing + +Contributions are welcome! Please: + +1. Check the [documentation](docs/) to understand the project +2. Review existing [examples](docs/examples.md) and [API reference](docs/api-reference.md) +3. Follow the existing code style and patterns +4. Add tests for new functionality +5. Update documentation as needed + +## License + +MIT License - see LICENSE file for details. + +## About + +Developed by [GISCE](https://gisce.net) for the GISCE-ERP project. + +For more information, visit the [complete documentation](docs/index.md). diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md new file mode 100644 index 0000000..977577f --- /dev/null +++ b/docs/advanced-usage.md @@ -0,0 +1,873 @@ +# Advanced Usage + +This guide covers advanced features and complex usage scenarios of Python OOUI. + +## Advanced Graph Processing + +### Custom Graph Types and Extensions + +While Python OOUI comes with built-in graph types, you can understand and work with the underlying system: + +```python +from ooui.graph import GRAPH_TYPES, parse_graph +from ooui.graph.base import Graph + +# Check available graph types +print("Available graph types:", list(GRAPH_TYPES.keys())) + +# Understand the graph type mapping +for graph_type, graph_class in GRAPH_TYPES.items(): + print(f"{graph_type} -> {graph_class.__name__}") +``` + +### Complex Multi-Axis Charts + +```python +# Advanced chart with multiple Y-axes and grouping +complex_chart_xml = ''' + + + + + + + +''' + +chart = parse_graph(complex_chart_xml) + +# Advanced data with multiple dimensions +complex_data = [ + { + 'date': '2023-01-01', 'revenue': 10000, 'profit_margin': 0.15, + 'customer_count': 50, 'sales_rep': 'Alice', 'region': 'North' + }, + { + 'date': '2023-01-01', 'revenue': 8000, 'profit_margin': 0.18, + 'customer_count': 35, 'sales_rep': 'Bob', 'region': 'South' + }, + # ... more data +] + +# Process with advanced options +options = { + 'group_by_date': True, + 'aggregate_function': 'sum', + 'time_granularity': 'month' +} + +result = chart.process(complex_data, { + 'date': {'type': 'date'}, + 'revenue': {'type': 'float'}, + 'profit_margin': {'type': 'float'}, + 'customer_count': {'type': 'integer'}, + 'sales_rep': {'type': 'char'} +}, options) +``` + +### Dynamic Graph Configuration + +```python +class DynamicGraphBuilder: + """Build graphs dynamically based on configuration.""" + + def __init__(self): + self.graph_templates = { + 'time_series': ''' + + + + + ''', + 'comparison': ''' + + + + + ''', + 'distribution': ''' + + + + + ''' + } + + def build_graph(self, graph_type, config): + """Build a graph from configuration.""" + if graph_type not in self.graph_templates: + raise ValueError(f"Unknown graph type: {graph_type}") + + template = self.graph_templates[graph_type] + xml = template.format(**config) + + return parse_graph(xml) + + def build_dashboard(self, dashboard_config): + """Build multiple graphs for a dashboard.""" + graphs = {} + + for graph_name, graph_spec in dashboard_config.items(): + graph_type = graph_spec.pop('type') + graphs[graph_name] = self.build_graph(graph_type, graph_spec) + + return graphs + +# Usage +builder = DynamicGraphBuilder() + +dashboard_config = { + 'sales_trend': { + 'type': 'time_series', + 'title': 'Sales Trend', + 'time_field': 'date', + 'value_field': 'amount', + 'operator': 'sum' + }, + 'product_comparison': { + 'type': 'comparison', + 'title': 'Product Performance', + 'category_field': 'product', + 'value_field': 'revenue', + 'operator': 'sum' + }, + 'regional_distribution': { + 'type': 'distribution', + 'title': 'Regional Sales Distribution', + 'category_field': 'region', + 'value_field': 'sales', + 'operator': 'sum' + } +} + +dashboard_graphs = builder.build_dashboard(dashboard_config) +``` + +## Advanced Tree Processing + +### Dynamic Tree Configuration with Complex Conditions + +```python +class AdvancedTreeProcessor: + """Advanced tree processing with dynamic conditions.""" + + def __init__(self): + self.condition_builders = { + 'status_colors': self._build_status_colors, + 'priority_colors': self._build_priority_colors, + 'threshold_colors': self._build_threshold_colors, + 'multi_condition': self._build_multi_condition + } + + def _build_status_colors(self, config): + """Build status-based color conditions.""" + status_map = config.get('status_map', {}) + conditions = [] + + for status, color in status_map.items(): + conditions.append(f"{color}:status=='{status}'") + + return ';'.join(conditions) + + def _build_priority_colors(self, config): + """Build priority-based color conditions.""" + priority_colors = config.get('priority_colors', {}) + conditions = [] + + for priority, color in priority_colors.items(): + conditions.append(f"{color}:priority=='{priority}'") + + return ';'.join(conditions) + + def _build_threshold_colors(self, config): + """Build threshold-based color conditions.""" + field = config.get('field') + thresholds = config.get('thresholds', []) + + conditions = [] + for threshold in thresholds: + operator = threshold.get('operator', '>=') + value = threshold['value'] + color = threshold['color'] + conditions.append(f"{color}:{field} {operator} {value}") + + return ';'.join(conditions) + + def _build_multi_condition(self, config): + """Build complex multi-field conditions.""" + rules = config.get('rules', []) + conditions = [] + + for rule in rules: + result = rule['result'] + condition_parts = [] + + for condition in rule['conditions']: + field = condition['field'] + operator = condition['operator'] + value = condition['value'] + + if isinstance(value, str): + condition_parts.append(f"{field} {operator} '{value}'") + else: + condition_parts.append(f"{field} {operator} {value}") + + logical_op = rule.get('logical_operator', 'and') + full_condition = f" {logical_op} ".join(condition_parts) + conditions.append(f"{result}:{full_condition}") + + return ';'.join(conditions) + + def build_tree(self, fields, condition_config=None): + """Build a tree with dynamic conditions.""" + field_elements = [] + for field in fields: + if isinstance(field, str): + field_elements.append(f'') + else: + field_elements.append(f'') + + fields_xml = '\n '.join(field_elements) + + tree_attrs = ['string="Dynamic Tree"'] + + if condition_config: + for condition_type, config in condition_config.items(): + if condition_type in self.condition_builders: + condition_string = self.condition_builders[condition_type](config) + tree_attrs.append(f'{condition_type}="{condition_string}"') + + attrs_str = ' '.join(tree_attrs) + + xml = f''' + + {fields_xml} + + ''' + + return parse_tree(xml) + +# Usage +processor = AdvancedTreeProcessor() + +# Complex condition configuration +condition_config = { + 'colors': { + 'rules': [ + { + 'result': 'red', + 'conditions': [ + {'field': 'status', 'operator': '==', 'value': 'overdue'}, + {'field': 'amount', 'operator': '>', 'value': 1000} + ], + 'logical_operator': 'and' + }, + { + 'result': 'orange', + 'conditions': [ + {'field': 'priority', 'operator': '==', 'value': 'high'}, + {'field': 'days_remaining', 'operator': '<=', 'value': 3} + ], + 'logical_operator': 'and' + } + ] + } +} + +fields = [ + 'name', + 'status', + 'priority', + 'amount', + 'days_remaining' +] + +advanced_tree = processor.build_tree(fields, {'colors': condition_config['colors']}) +``` + +## Advanced Condition Processing + +### Custom Condition Evaluators + +```python +from ooui.helpers.conditions import ConditionParser +import re +from datetime import datetime, timedelta + +class AdvancedConditionParser(ConditionParser): + """Extended condition parser with custom functions.""" + + def __init__(self, condition): + super().__init__(condition) + + # Add custom functions + self.functions.update({ + 'days_ago': self._days_ago, + 'in_range': self._in_range, + 'matches': self._matches_regex, + 'calculate_age': self._calculate_age, + }) + + def _days_ago(self, days): + """Calculate date N days ago.""" + return (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') + + def _in_range(self, value, min_val, max_val): + """Check if value is in range.""" + return min_val <= value <= max_val + + def _matches_regex(self, text, pattern): + """Check if text matches regex pattern.""" + return bool(re.match(pattern, str(text))) + + def _calculate_age(self, birth_date): + """Calculate age from birth date.""" + if isinstance(birth_date, str): + birth = datetime.strptime(birth_date, '%Y-%m-%d') + else: + birth = birth_date + + today = datetime.now() + return (today - birth).days // 365 + +# Usage +advanced_conditions = """ +urgent:days_remaining <= 1 and priority == 'high'; +warning:in_range(score, 60, 79) and department == 'sales'; +elderly:calculate_age(birth_date) >= 65; +valid_email:matches(email, r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$'); +recent:created_date >= days_ago(30) +""" + +parser = AdvancedConditionParser(advanced_conditions) + +# Test data +test_data = { + 'days_remaining': 0, + 'priority': 'high', + 'score': 75, + 'department': 'sales', + 'birth_date': '1950-01-01', + 'email': 'user@example.com', + 'created_date': '2023-10-01' +} + +result = parser.eval(test_data) +print(f"Condition result: {result}") +``` + +### Condition Caching and Performance + +```python +from functools import lru_cache +from ooui.helpers.conditions import ConditionParser + +class CachedConditionParser: + """Condition parser with caching for better performance.""" + + def __init__(self, max_cache_size=1000): + self.max_cache_size = max_cache_size + self._parsers = {} + + @lru_cache(maxsize=1000) + def _get_parser(self, condition_string): + """Get cached parser for condition string.""" + return ConditionParser(condition_string) + + def eval_condition(self, condition_string, values): + """Evaluate condition with caching.""" + parser = self._get_parser(condition_string) + return parser.eval(values) + + def bulk_evaluate(self, condition_string, value_list): + """Evaluate condition against multiple value sets.""" + parser = self._get_parser(condition_string) + results = [] + + for values in value_list: + result = parser.eval(values) + results.append(result) + + return results + + def clear_cache(self): + """Clear the condition parser cache.""" + self._get_parser.cache_clear() + +# Usage for high-performance scenarios +cached_parser = CachedConditionParser() + +# Evaluate same condition against many records +condition = "red:amount < 1000;orange:amount < 5000;green:amount >= 5000" +records = [ + {'amount': 500}, + {'amount': 2500}, + {'amount': 7500}, + # ... thousands more records +] + +results = cached_parser.bulk_evaluate(condition, records) +print(f"Processed {len(results)} records") +``` + +## Advanced Domain Processing + +### Complex Domain Builder + +```python +from ooui.helpers.domain import Domain +import json + +class DomainBuilder: + """Build complex domains programmatically.""" + + def __init__(self): + self.operators = { + 'eq': '=', + 'ne': '!=', + 'gt': '>', + 'gte': '>=', + 'lt': '<', + 'lte': '<=', + 'like': 'like', + 'ilike': 'ilike', + 'in': 'in', + 'not_in': 'not in' + } + + def build_filter(self, field, operator, value): + """Build a single filter clause.""" + op = self.operators.get(operator, operator) + return (field, op, value) + + def build_and_domain(self, filters): + """Build AND domain from filter list.""" + domain = [] + for filter_spec in filters: + if isinstance(filter_spec, dict): + filter_clause = self.build_filter( + filter_spec['field'], + filter_spec['operator'], + filter_spec['value'] + ) + else: + filter_clause = filter_spec + domain.append(filter_clause) + return domain + + def build_or_domain(self, filter_groups): + """Build OR domain from filter groups.""" + domain = [] + + for i, group in enumerate(filter_groups): + if i > 0: + domain.append('|') + + if isinstance(group, list): + domain.extend(self.build_and_domain(group)) + else: + domain.append(group) + + return domain + + def build_complex_domain(self, domain_spec): + """Build complex nested domain.""" + if domain_spec['type'] == 'and': + return self.build_and_domain(domain_spec['filters']) + elif domain_spec['type'] == 'or': + return self.build_or_domain(domain_spec['groups']) + else: + raise ValueError(f"Unknown domain type: {domain_spec['type']}") + +# Usage +builder = DomainBuilder() + +# Complex search criteria +search_criteria = { + 'type': 'and', + 'filters': [ + {'field': 'active', 'operator': 'eq', 'value': True}, + {'field': 'created_date', 'operator': 'gte', 'value': '2023-01-01'}, + { + 'type': 'or', + 'groups': [ + [ + {'field': 'type', 'operator': 'eq', 'value': 'premium'}, + {'field': 'amount', 'operator': 'gte', 'value': 1000} + ], + [ + {'field': 'priority', 'operator': 'eq', 'value': 'high'} + ] + ] + } + ] +} + +complex_domain = builder.build_complex_domain(search_criteria) +domain_obj = Domain(str(complex_domain)) +parsed = domain_obj.parse() +print("Complex domain:", json.dumps(parsed, indent=2)) +``` + +### Dynamic Domain Evaluation + +```python +class DynamicDomainEvaluator: + """Evaluate domains against data sets.""" + + def __init__(self): + self.type_converters = { + 'date': self._convert_date, + 'datetime': self._convert_datetime, + 'int': int, + 'float': float, + 'bool': bool, + 'str': str + } + + def _convert_date(self, value): + """Convert string to date.""" + if isinstance(value, str): + return datetime.strptime(value, '%Y-%m-%d').date() + return value + + def _convert_datetime(self, value): + """Convert string to datetime.""" + if isinstance(value, str): + return datetime.strptime(value, '%Y-%m-%d %H:%M:%S') + return value + + def evaluate_record(self, record, domain_filters, field_types=None): + """Check if record matches domain filters.""" + if field_types is None: + field_types = {} + + for domain_filter in domain_filters: + if isinstance(domain_filter, str): + continue # Skip logical operators + + field, operator, expected = domain_filter + + if field not in record: + return False + + actual = record[field] + + # Type conversion if needed + if field in field_types: + field_type = field_types[field] + if field_type in self.type_converters: + converter = self.type_converters[field_type] + try: + actual = converter(actual) + if not isinstance(expected, type(actual)): + expected = converter(expected) + except (ValueError, TypeError): + return False + + # Evaluate condition + if not self._evaluate_condition(actual, operator, expected): + return False + + return True + + def _evaluate_condition(self, actual, operator, expected): + """Evaluate single condition.""" + try: + if operator == '=': + return actual == expected + elif operator == '!=': + return actual != expected + elif operator == '>': + return actual > expected + elif operator == '>=': + return actual >= expected + elif operator == '<': + return actual < expected + elif operator == '<=': + return actual <= expected + elif operator == 'like': + return str(expected).lower() in str(actual).lower() + elif operator == 'ilike': + return str(expected).lower() in str(actual).lower() + elif operator == 'in': + return actual in expected + elif operator == 'not in': + return actual not in expected + else: + return False + except (TypeError, ValueError): + return False + + def filter_dataset(self, dataset, domain_string, field_types=None): + """Filter entire dataset using domain.""" + domain = Domain(domain_string) + domain_filters = domain.parse() + + filtered = [] + for record in dataset: + if self.evaluate_record(record, domain_filters, field_types): + filtered.append(record) + + return filtered + +# Usage +evaluator = DynamicDomainEvaluator() + +# Sample dataset +dataset = [ + {'id': 1, 'name': 'Alice', 'age': 30, 'active': True, 'created': '2023-01-15'}, + {'id': 2, 'name': 'Bob', 'age': 25, 'active': False, 'created': '2023-02-20'}, + {'id': 3, 'name': 'Charlie', 'age': 35, 'active': True, 'created': '2023-03-10'}, +] + +# Complex filter +domain_string = "[('active', '=', True), ('age', '>', 28), ('created', '>=', '2023-02-01')]" +field_types = { + 'age': 'int', + 'active': 'bool', + 'created': 'date' +} + +filtered_data = evaluator.filter_dataset(dataset, domain_string, field_types) +print("Filtered data:", filtered_data) +``` + +## Performance Optimization + +### Batch Processing + +```python +class BatchProcessor: + """Process large datasets in batches.""" + + def __init__(self, batch_size=1000): + self.batch_size = batch_size + + def process_graphs_in_batches(self, graph, dataset, fields): + """Process graph data in batches for memory efficiency.""" + results = [] + + for i in range(0, len(dataset), self.batch_size): + batch = dataset[i:i + self.batch_size] + batch_result = graph.process(batch, fields) + results.append(batch_result) + + # Combine results + return self._combine_graph_results(results) + + def _combine_graph_results(self, results): + """Combine batch results into single result.""" + if not results: + return None + + # Implementation depends on graph type and result structure + combined = results[0] + + for result in results[1:]: + # Merge logic here - depends on specific result format + pass + + return combined + + def process_conditions_in_batches(self, condition_parser, dataset): + """Evaluate conditions in batches.""" + results = [] + + for i in range(0, len(dataset), self.batch_size): + batch = dataset[i:i + self.batch_size] + batch_results = [] + + for record in batch: + result = condition_parser.eval(record) + batch_results.append(result) + + results.extend(batch_results) + + return results + +# Memory-efficient processing +processor = BatchProcessor(batch_size=500) + +# Large dataset processing +large_dataset = [{'value': i, 'category': f'cat_{i%10}'} for i in range(10000)] + +# Process in batches to manage memory +batch_results = processor.process_conditions_in_batches( + ConditionParser("high:value > 5000;low:value <= 5000"), + large_dataset +) +``` + +### Caching Strategies + +```python +import pickle +import hashlib +from functools import wraps + +class ResultCache: + """Cache processing results for better performance.""" + + def __init__(self, cache_dir=None): + self.cache_dir = cache_dir or '/tmp/ooui_cache' + self.memory_cache = {} + self.max_memory_items = 100 + + def _generate_key(self, *args, **kwargs): + """Generate cache key from arguments.""" + content = str(args) + str(sorted(kwargs.items())) + return hashlib.md5(content.encode()).hexdigest() + + def cache_result(self, key, result): + """Cache result in memory.""" + if len(self.memory_cache) >= self.max_memory_items: + # Remove oldest item + oldest_key = next(iter(self.memory_cache)) + del self.memory_cache[oldest_key] + + self.memory_cache[key] = result + + def get_cached_result(self, key): + """Get cached result.""" + return self.memory_cache.get(key) + + def cached_process(self, func): + """Decorator for caching function results.""" + @wraps(func) + def wrapper(*args, **kwargs): + key = self._generate_key(*args, **kwargs) + + # Check memory cache + result = self.get_cached_result(key) + if result is not None: + return result + + # Execute function and cache result + result = func(*args, **kwargs) + self.cache_result(key, result) + + return result + + return wrapper + +# Usage +cache = ResultCache() + +@cache.cached_process +def expensive_graph_processing(graph, data, fields): + """Expensive processing that benefits from caching.""" + return graph.process(data, fields) + +# Subsequent calls with same parameters will use cache +result1 = expensive_graph_processing(my_graph, my_data, my_fields) +result2 = expensive_graph_processing(my_graph, my_data, my_fields) # From cache +``` + +## Integration Patterns + +### Plugin Architecture + +```python +class OOUIPlugin: + """Base class for OOUI plugins.""" + + def __init__(self, name): + self.name = name + + def process_graph_data(self, graph, data, fields): + """Override to add custom graph processing.""" + return graph.process(data, fields) + + def process_tree_data(self, tree, data): + """Override to add custom tree processing.""" + return data + + def enhance_conditions(self, condition_string): + """Override to add custom condition enhancements.""" + return condition_string + +class ExportPlugin(OOUIPlugin): + """Plugin for data export functionality.""" + + def __init__(self): + super().__init__('export') + self.formats = ['csv', 'json', 'xlsx'] + + def export_graph_data(self, graph_data, format='json'): + """Export graph data to various formats.""" + if format == 'json': + return json.dumps(graph_data, indent=2) + elif format == 'csv': + return self._to_csv(graph_data) + else: + raise ValueError(f"Unsupported format: {format}") + + def _to_csv(self, data): + """Convert data to CSV format.""" + # Implementation depends on data structure + return "CSV data here" + +class ValidationPlugin(OOUIPlugin): + """Plugin for data validation.""" + + def __init__(self): + super().__init__('validation') + self.rules = {} + + def add_validation_rule(self, field, rule): + """Add validation rule for field.""" + self.rules[field] = rule + + def validate_data(self, data): + """Validate data against rules.""" + errors = [] + + for record in data: + for field, rule in self.rules.items(): + if field in record: + if not rule(record[field]): + errors.append(f"Validation failed for {field}: {record[field]}") + + return errors + +class PluginManager: + """Manager for OOUI plugins.""" + + def __init__(self): + self.plugins = {} + + def register_plugin(self, plugin): + """Register a plugin.""" + self.plugins[plugin.name] = plugin + + def get_plugin(self, name): + """Get plugin by name.""" + return self.plugins.get(name) + + def process_with_plugins(self, operation, *args, **kwargs): + """Process operation through relevant plugins.""" + results = {} + + for name, plugin in self.plugins.items(): + if hasattr(plugin, operation): + method = getattr(plugin, operation) + results[name] = method(*args, **kwargs) + + return results + +# Usage +manager = PluginManager() +manager.register_plugin(ExportPlugin()) +manager.register_plugin(ValidationPlugin()) + +# Use plugins +export_plugin = manager.get_plugin('export') +validation_plugin = manager.get_plugin('validation') + +# Add validation rules +validation_plugin.add_validation_rule('age', lambda x: 0 <= x <= 150) +validation_plugin.add_validation_rule('email', lambda x: '@' in x) +``` + +This advanced usage guide demonstrates sophisticated patterns and techniques for working with Python OOUI in complex scenarios, including performance optimization, extensibility, and integration patterns. \ No newline at end of file diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..ea29cd6 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,473 @@ +# API Reference + +Complete API documentation for Python OOUI. + +## Module Structure + +``` +ooui/ +├── graph/ # Graph processing components +│ ├── __init__.py # parse_graph() +│ ├── base.py # Graph base class +│ ├── chart.py # GraphChart class +│ ├── indicator.py # GraphIndicator classes +│ ├── axis.py # Axis processing +│ ├── fields.py # Field operations +│ ├── processor.py # Data processing utilities +│ └── timerange.py # Time range handling +├── tree/ # Tree view components +│ ├── __init__.py # parse_tree() +│ └── base.py # Tree class +└── helpers/ # Utility modules + ├── __init__.py # Common utilities + ├── conditions.py # ConditionParser + ├── domain.py # Domain class + ├── aggregated.py # Aggregator class + ├── dates.py # Date utilities + └── features.py # Feature detection +``` + +## Graph Module (`ooui.graph`) + +### parse_graph(xml) + +Parse a graph definition from XML string. + +**Parameters:** +- `xml` (str): XML string containing graph definition + +**Returns:** +- Graph object (GraphChart, GraphIndicator, or GraphIndicatorField) + +**Raises:** +- `ValueError`: If graph type is invalid or unsupported + +**Example:** +```python +from ooui.graph import parse_graph + +xml = ''' + + + + +''' +graph = parse_graph(xml) +``` + +### Graph Base Class + +Base class for all graph types. + +#### Properties + +- `string`: Graph title/label +- `type`: Graph type (line, bar, pie, indicator, indicatorField) +- `fields`: List of field names used in the graph + +#### Methods + +##### process(values, fields, options=None) + +Process data for the graph. + +**Parameters:** +- `values` (list): List of data dictionaries +- `fields` (dict): Field definitions +- `options` (dict, optional): Processing options + +**Returns:** +- Processed data structure ready for visualization + +### GraphChart Class + +Chart-type graphs (line, bar, pie). + +#### Properties + +- `x`: X-axis configuration +- `y`: Y-axis configuration (list of axis objects) + +#### Methods + +Same as Graph base class plus chart-specific processing. + +**Example:** +```python +# Sample data processing +data = [ + {'date': '2023-01-01', 'sales': 1000}, + {'date': '2023-01-02', 'sales': 1200} +] +fields = { + 'date': {'type': 'date'}, + 'sales': {'type': 'float'} +} +result = chart.process(data, fields) +``` + +### GraphIndicator Class + +Single-value indicator graphs. + +#### Properties + +- `field`: Main field configuration +- `compare_field`: Optional comparison field + +#### Methods + +##### get_indicator_value(values, fields) + +Calculate the indicator value. + +**Example:** +```python +xml = ''' + + + +''' +indicator = parse_graph(xml) +``` + +### GraphIndicatorField Class + +Field-based indicators with multiple values. + +Similar to GraphIndicator but handles multiple field indicators. + +## Tree Module (`ooui.tree`) + +### parse_tree(xml) + +Parse a tree view definition from XML. + +**Parameters:** +- `xml` (str): XML string containing tree definition + +**Returns:** +- Tree object + +**Example:** +```python +from ooui.tree import parse_tree + +xml = ''' + + + + +''' +tree = parse_tree(xml) +``` + +### Tree Class + +Represents a tree view configuration. + +#### Properties + +- `string`: Tree title/label +- `infinite`: Whether tree supports infinite scrolling +- `colors`: Color condition string +- `status`: Status condition string +- `editable`: Edit mode (top, bottom, etc.) +- `fields`: List of field elements +- `fields_in_conditions`: Dict of fields used in color/status conditions + +#### Methods + +Tree objects are primarily data containers. Field processing is handled by the parsing system. + +**Example:** +```python +# Access tree properties +print(tree.string) # "Customer List" +print(tree.editable) # "top" +print(len(tree.fields)) # 2 + +# Check conditional fields +if tree.colors: + conditional = tree.fields_in_conditions + print(conditional.get('colors', [])) # Fields used in color conditions +``` + +## Helpers Module (`ooui.helpers`) + +### Utility Functions + +#### parse_bool_attribute(attribute) + +Parse boolean values from string representations. + +**Parameters:** +- `attribute`: Value to parse (string, int, or bool) + +**Returns:** +- `True` for "1", "true" (case-insensitive) +- `False` otherwise + +**Example:** +```python +from ooui.helpers import parse_bool_attribute + +print(parse_bool_attribute('1')) # True +print(parse_bool_attribute('True')) # True +print(parse_bool_attribute('0')) # False +``` + +#### replace_entities(text) + +Replace HTML entities with Unicode characters. + +**Parameters:** +- `text` (str): Text containing HTML entities + +**Returns:** +- Cleaned text string + +**Example:** +```python +from ooui.helpers import replace_entities + +text = "Price > $100 & < $200" +clean = replace_entities(text) +print(clean) # "Price > $100 & < $200" +``` + +### ConditionParser Class (`ooui.helpers.conditions`) + +Parse and evaluate conditional expressions. + +#### Constructor + +```python +ConditionParser(condition) +``` + +**Parameters:** +- `condition` (str): Condition string in format "result1:condition1;result2:condition2" + +#### Properties + +- `involved_fields`: Set of field names used in conditions +- `raw_condition`: Original condition string + +#### Methods + +##### eval(values) + +Evaluate conditions against provided values. + +**Parameters:** +- `values` (dict): Field values to evaluate against + +**Returns:** +- Result value if condition matches, None otherwise + +**Example:** +```python +from ooui.helpers import ConditionParser + +parser = ConditionParser("red:amount < 100;green:amount >= 100") +print(parser.involved_fields) # {'amount'} + +result = parser.eval({'amount': 50}) +print(result) # "red" + +result = parser.eval({'amount': 150}) +print(result) # "green" +``` + +### Domain Class (`ooui.helpers.domain`) + +Parse and evaluate domain expressions (query filters). + +#### Constructor + +```python +Domain(domain) +``` + +**Parameters:** +- `domain` (str): Domain expression string + +#### Methods + +##### parse(values=None) + +Parse domain with optional variable substitution. + +**Parameters:** +- `values` (dict, optional): Variable values for substitution + +**Returns:** +- Parsed domain structure + +**Example:** +```python +from ooui.helpers import Domain + +# Simple domain +domain = Domain("[('active', '=', True)]") +result = domain.parse() + +# Domain with variables +domain = Domain("[('user_id', '=', user)]") +result = domain.parse({'user': 42}) +``` + +### Aggregator Class (`ooui.helpers.aggregated`) + +Aggregate data with various operations. + +#### Constructor + +```python +Aggregator(rules) +``` + +**Parameters:** +- `rules` (dict): Aggregation rules mapping + +#### Methods + +##### aggregate(data, group_by=None) + +Aggregate data according to rules. + +**Parameters:** +- `data` (list): List of data dictionaries +- `group_by` (str, optional): Field name to group by + +**Returns:** +- Aggregated data structure + +**Example:** +```python +from ooui.helpers.aggregated import Aggregator + +aggregator = Aggregator({ + 'total': {'operator': 'sum', 'field': 'amount'}, + 'count': {'operator': 'count', 'field': 'id'} +}) + +data = [ + {'amount': 100, 'id': 1, 'category': 'A'}, + {'amount': 200, 'id': 2, 'category': 'A'}, + {'amount': 150, 'id': 3, 'category': 'B'} +] + +result = aggregator.aggregate(data, group_by='category') +# Result: {'A': {'total': 300, 'count': 2}, 'B': {'total': 150, 'count': 1}} +``` + +## Field Processing (`ooui.graph.fields`) + +### get_value_for_operator(values, operator) + +Apply aggregation operator to list of values. + +**Parameters:** +- `values` (list): Numeric values +- `operator` (str): Operation ('sum', 'avg', 'max', 'min', 'count') + +**Returns:** +- Aggregated result + +**Example:** +```python +from ooui.graph.fields import get_value_for_operator + +values = [10, 20, 30, 40, 50] +print(get_value_for_operator(values, 'sum')) # 150 +print(get_value_for_operator(values, 'avg')) # 30 +print(get_value_for_operator(values, 'max')) # 50 +print(get_value_for_operator(values, 'count')) # 5 +``` + +## Date Processing (`ooui.helpers.dates`) + +### DateRange Class + +Represents a date range with start and end dates. + +#### Constructor + +```python +DateRange(start, end) +``` + +**Parameters:** +- `start`: Start date (string or datetime) +- `end`: End date (string or datetime) + +#### Properties + +- `start`: Start datetime object +- `end`: End datetime object + +### get_date_range(period) + +Get predefined date ranges. + +**Parameters:** +- `period` (str): Period identifier ('today', 'this_week', 'this_month', etc.) + +**Returns:** +- DateRange object + +**Example:** +```python +from ooui.helpers.dates import get_date_range, DateRange + +# Predefined ranges +this_month = get_date_range('this_month') +print(this_month.start) +print(this_month.end) + +# Custom range +custom = DateRange('2023-01-01', '2023-12-31') +``` + +## Error Handling + +### Common Exceptions + +- `ValueError`: Invalid graph types, malformed conditions +- `AttributeError`: Missing required attributes +- `KeyError`: Missing field references +- `XMLSyntaxError`: Malformed XML (from lxml) + +### Error Handling Example + +```python +try: + graph = parse_graph(xml_string) + result = graph.process(data, fields) +except ValueError as e: + print(f"Configuration error: {e}") +except Exception as e: + print(f"Processing error: {e}") +``` + +## Type Annotations + +Python OOUI supports both Python 2 and 3, so type annotations are not used in the source code. However, for modern development, expected types are: + +```python +# Function signatures (for reference) +def parse_graph(xml: str) -> Graph +def parse_tree(xml: str) -> Tree +def parse_bool_attribute(attribute: Union[str, int, bool]) -> bool +def replace_entities(text: str) -> str + +class ConditionParser: + def __init__(self, condition: str) -> None + def eval(self, values: Dict[str, Any]) -> Optional[str] + +class Domain: + def __init__(self, domain: str) -> None + def parse(self, values: Optional[Dict[str, Any]] = None) -> Any +``` \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..f309347 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,580 @@ +# Examples + +This page provides practical examples of using Python OOUI for various scenarios. + +## Graph Examples + +### Line Chart for Time Series Data + +```python +from ooui.graph import parse_graph + +# Define a line chart for sales over time +sales_chart_xml = ''' + + + + +''' + +# Parse the graph +sales_graph = parse_graph(sales_chart_xml) + +# Sample data +sales_data = [ + {'month': '2023-01-01', 'revenue': 15000.0, 'sales_rep': 'John'}, + {'month': '2023-01-01', 'revenue': 12000.0, 'sales_rep': 'Jane'}, + {'month': '2023-02-01', 'revenue': 18000.0, 'sales_rep': 'John'}, + {'month': '2023-02-01', 'revenue': 16000.0, 'sales_rep': 'Jane'}, + {'month': '2023-03-01', 'revenue': 21000.0, 'sales_rep': 'John'}, +] + +# Field definitions +fields = { + 'month': {'type': 'date', 'string': 'Month'}, + 'revenue': {'type': 'float', 'string': 'Revenue'}, + 'sales_rep': {'type': 'char', 'string': 'Sales Rep'} +} + +# Process the data +chart_data = sales_graph.process(sales_data, fields) +print("Processed chart data:", chart_data) +``` + +### Bar Chart with Multiple Series + +```python +# Bar chart comparing performance across categories +performance_xml = ''' + + + + + +''' + +performance_graph = parse_graph(performance_xml) + +performance_data = [ + {'department': 'Sales', 'target': 100000, 'actual': 95000}, + {'department': 'Marketing', 'target': 50000, 'actual': 52000}, + {'department': 'Support', 'target': 30000, 'actual': 28000}, +] + +fields = { + 'department': {'type': 'char'}, + 'target': {'type': 'float'}, + 'actual': {'type': 'float'} +} + +result = performance_graph.process(performance_data, fields) +``` + +### Pie Chart for Distribution Analysis + +```python +# Pie chart showing market share +market_share_xml = ''' + + + + +''' + +market_graph = parse_graph(market_share_xml) + +market_data = [ + {'region': 'North America', 'sales': 450000}, + {'region': 'Europe', 'sales': 380000}, + {'region': 'Asia Pacific', 'sales': 290000}, + {'region': 'Latin America', 'sales': 125000}, + {'region': 'Africa', 'sales': 75000}, +] + +fields = { + 'region': {'type': 'char'}, + 'sales': {'type': 'float'} +} + +pie_data = market_graph.process(market_data, fields) +``` + +### Indicator for KPI Dashboard + +```python +# Single KPI indicator +kpi_xml = ''' + + + +''' + +kpi_graph = parse_graph(kpi_xml) + +revenue_data = [ + {'revenue': 15000}, + {'revenue': 22000}, + {'revenue': 18500}, + {'revenue': 31000}, +] + +kpi_result = kpi_graph.process(revenue_data, {'revenue': {'type': 'float'}}) +print(f"Total Revenue: {kpi_result}") +``` + +## Tree View Examples + +### Basic Employee List + +```python +from ooui.tree import parse_tree + +# Simple employee tree view +employee_tree_xml = ''' + + + + + + +''' + +employee_tree = parse_tree(employee_tree_xml) + +print(f"Tree title: {employee_tree.string}") +print(f"Editable: {employee_tree.editable}") +print(f"Number of fields: {len(employee_tree.fields)}") +``` + +### Tree with Conditional Colors + +```python +# Order list with status-based coloring +order_tree_xml = ''' + + + + + + + +''' + +order_tree = parse_tree(order_tree_xml) + +# Check which fields are used in conditions +conditional_fields = order_tree.fields_in_conditions +print(f"Fields in color conditions: {conditional_fields['colors']}") +# Output: ['status'] +``` + +### Tree with Status Indicators + +```python +# Project list with status indicators +project_tree_xml = ''' + + + + + + + +''' + +project_tree = parse_tree(project_tree_xml) + +# Get all conditional fields +all_conditions = project_tree.fields_in_conditions +print("Color fields:", all_conditions.get('colors', [])) +print("Status fields:", all_conditions.get('status', [])) +``` + +## Condition Parser Examples + +### Simple Status Conditions + +```python +from ooui.helpers import ConditionParser + +# Define status-based conditions +status_condition = "red:status=='error';yellow:status=='warning';green:status=='ok'" +parser = ConditionParser(status_condition) + +# Test with different statuses +test_cases = [ + {'status': 'error'}, + {'status': 'warning'}, + {'status': 'ok'}, + {'status': 'unknown'} +] + +for case in test_cases: + result = parser.eval(case) + print(f"Status {case['status']} -> Color {result}") + +# Output: +# Status error -> Color red +# Status warning -> Color yellow +# Status ok -> Color green +# Status unknown -> Color None +``` + +### Numeric Range Conditions + +```python +# Score-based color coding +score_condition = "red:score < 60;yellow:score < 80;green:score >= 80" +score_parser = ConditionParser(score_condition) + +scores = [45, 65, 72, 85, 92] +for score in scores: + color = score_parser.eval({'score': score}) + print(f"Score {score} -> {color}") +``` + +### Complex Multi-Field Conditions + +```python +# Complex business logic +complex_condition = """ +urgent:priority=='high' and days_remaining <= 1; +warning:priority=='medium' and days_remaining <= 3; +normal:priority=='low' or days_remaining > 7 +""" + +complex_parser = ConditionParser(complex_condition) + +test_scenarios = [ + {'priority': 'high', 'days_remaining': 0}, + {'priority': 'medium', 'days_remaining': 2}, + {'priority': 'low', 'days_remaining': 5}, + {'priority': 'medium', 'days_remaining': 10}, +] + +for scenario in test_scenarios: + result = complex_parser.eval(scenario) + print(f"{scenario} -> {result}") +``` + +## Domain Parser Examples + +### Basic Query Filters + +```python +from ooui.helpers import Domain + +# Simple equality filter +basic_domain = Domain("[('active', '=', True), ('type', '=', 'customer')]") +result = basic_domain.parse() +print("Basic domain:", result) + +# Range filter +range_domain = Domain("[('age', '>=', 18), ('age', '<=', 65)]") +result = range_domain.parse() +print("Range domain:", result) +``` + +### Dynamic Domains with Variables + +```python +# Domain with user-provided values +user_domain = Domain("[('created_by', '=', user_id), ('date', '>=', start_date)]") + +# Provide values at runtime +context = { + 'user_id': 42, + 'start_date': '2023-01-01' +} + +parsed_domain = user_domain.parse(context) +print("User domain:", parsed_domain) +``` + +### Complex Logical Operations + +```python +# OR conditions +or_domain = Domain(""" +[ + '|', + ('state', '=', 'active'), + ('state', '=', 'pending'), + ('priority', '=', 'high') +] +""") + +result = or_domain.parse() +print("OR domain:", result) +``` + +## Data Aggregation Examples + +### Sales Reporting + +```python +from ooui.helpers.aggregated import Aggregator + +# Sales aggregation rules +sales_aggregator = Aggregator({ + 'total_sales': {'operator': 'sum', 'field': 'amount'}, + 'avg_deal_size': {'operator': 'avg', 'field': 'amount'}, + 'deal_count': {'operator': 'count', 'field': 'deal_id'}, + 'largest_deal': {'operator': 'max', 'field': 'amount'}, + 'smallest_deal': {'operator': 'min', 'field': 'amount'} +}) + +# Sample sales data +sales_data = [ + {'amount': 5000, 'deal_id': 1, 'rep': 'Alice', 'region': 'North'}, + {'amount': 7500, 'deal_id': 2, 'rep': 'Bob', 'region': 'North'}, + {'amount': 3200, 'deal_id': 3, 'rep': 'Charlie', 'region': 'South'}, + {'amount': 9800, 'deal_id': 4, 'rep': 'Diana', 'region': 'South'}, +] + +# Aggregate by region +regional_sales = sales_aggregator.aggregate(sales_data, group_by='region') +print("Regional Sales:", regional_sales) + +# Aggregate by sales rep +rep_sales = sales_aggregator.aggregate(sales_data, group_by='rep') +print("Rep Sales:", rep_sales) + +# Overall aggregation (no grouping) +total_sales = sales_aggregator.aggregate(sales_data) +print("Total Sales:", total_sales) +``` + +### Performance Metrics + +```python +# Website performance metrics +perf_aggregator = Aggregator({ + 'total_visits': {'operator': 'sum', 'field': 'visits'}, + 'avg_load_time': {'operator': 'avg', 'field': 'load_time'}, + 'bounce_rate': {'operator': 'avg', 'field': 'bounce_rate'}, + 'peak_concurrent': {'operator': 'max', 'field': 'concurrent_users'} +}) + +performance_data = [ + {'visits': 1200, 'load_time': 2.3, 'bounce_rate': 0.35, 'concurrent_users': 45, 'page': 'home'}, + {'visits': 800, 'load_time': 1.8, 'bounce_rate': 0.28, 'concurrent_users': 32, 'page': 'products'}, + {'visits': 600, 'load_time': 3.1, 'bounce_rate': 0.42, 'concurrent_users': 28, 'page': 'contact'}, +] + +page_metrics = perf_aggregator.aggregate(performance_data, group_by='page') +print("Page Performance:", page_metrics) +``` + +## Date Range Examples + +### Time-based Analysis + +```python +from ooui.helpers.dates import get_date_range, DateRange +from datetime import datetime + +# Predefined ranges +today = get_date_range('today') +this_week = get_date_range('this_week') +this_month = get_date_range('this_month') + +print(f"Today: {today.start} to {today.end}") +print(f"This week: {this_week.start} to {this_week.end}") +print(f"This month: {this_month.start} to {this_month.end}") + +# Custom date range +quarter_start = DateRange('2023-01-01', '2023-03-31') +print(f"Q1 2023: {quarter_start.start} to {quarter_start.end}") +``` + +### Filtering Data by Date Range + +```python +# Filter sales data by date range +def filter_by_date_range(data, date_field, date_range): + """Filter data within a date range.""" + filtered = [] + for record in data: + record_date = datetime.strptime(record[date_field], '%Y-%m-%d') + if date_range.start <= record_date <= date_range.end: + filtered.append(record) + return filtered + +# Sample sales data +sales_records = [ + {'date': '2023-01-15', 'amount': 1500}, + {'date': '2023-02-20', 'amount': 2200}, + {'date': '2023-03-10', 'amount': 1800}, + {'date': '2023-04-05', 'amount': 2500}, +] + +# Filter for Q1 +q1_range = DateRange('2023-01-01', '2023-03-31') +q1_sales = filter_by_date_range(sales_records, 'date', q1_range) +print("Q1 Sales:", q1_sales) +``` + +## Field Operations Examples + +### Data Transformation + +```python +from ooui.graph.fields import get_value_for_operator + +# Sample monthly sales figures +monthly_sales = [12000, 15000, 11000, 18000, 22000, 19000] + +# Calculate various metrics +total = get_value_for_operator(monthly_sales, 'sum') +average = get_value_for_operator(monthly_sales, 'avg') +best_month = get_value_for_operator(monthly_sales, 'max') +worst_month = get_value_for_operator(monthly_sales, 'min') +months_count = get_value_for_operator(monthly_sales, 'count') + +print(f"Total Sales: ${total:,.2f}") +print(f"Average Monthly: ${average:,.2f}") +print(f"Best Month: ${best_month:,.2f}") +print(f"Worst Month: ${worst_month:,.2f}") +print(f"Months Tracked: {months_count}") +``` + +## Utility Functions Examples + +### Boolean Parsing + +```python +from ooui.helpers import parse_bool_attribute + +# Parse various boolean representations +config_values = ['1', '0', 'true', 'false', 'True', 'False', 'yes', 'no'] + +for value in config_values: + parsed = parse_bool_attribute(value) + print(f"'{value}' -> {parsed}") +``` + +### HTML Entity Cleanup + +```python +from ooui.helpers import replace_entities + +# Clean HTML entities from text +html_texts = [ + "Price > $100", + "Q&A Section", + "<tag> content </tag>", + "R&D Department", + "50% < target < 75%" +] + +for text in html_texts: + clean = replace_entities(text) + print(f"Original: {text}") + print(f"Cleaned: {clean}\n") +``` + +## Complete Integration Example + +### Dashboard Data Processing + +```python +from ooui.graph import parse_graph +from ooui.tree import parse_tree +from ooui.helpers import ConditionParser, Aggregator +from ooui.helpers.dates import get_date_range + +# Complete dashboard setup +class SalesDashboard: + def __init__(self): + # Define graphs + self.sales_trend = parse_graph(''' + + + + + ''') + + self.top_products = parse_graph(''' + + + + + ''') + + # Define tree view + self.sales_list = parse_tree(''' + + + + + + + ''') + + # Setup aggregation + self.aggregator = Aggregator({ + 'total_revenue': {'operator': 'sum', 'field': 'amount'}, + 'avg_deal_size': {'operator': 'avg', 'field': 'amount'}, + 'total_deals': {'operator': 'count', 'field': 'sale_id'} + }) + + def process_dashboard_data(self, raw_data): + """Process raw sales data for dashboard display.""" + fields = { + 'date': {'type': 'date'}, + 'amount': {'type': 'float'}, + 'product': {'type': 'char'}, + 'customer': {'type': 'char'}, + 'sale_id': {'type': 'integer'} + } + + # Filter for current month + current_month = get_date_range('this_month') + + # Process trend data + trend_data = self.sales_trend.process(raw_data, fields) + + # Aggregate by product + product_data = [] + product_totals = {} + for record in raw_data: + product = record['product'] + if product not in product_totals: + product_totals[product] = 0 + product_totals[product] += record['amount'] + + for product, revenue in product_totals.items(): + product_data.append({'product': product, 'revenue': revenue}) + + top_products_data = self.top_products.process(product_data, fields) + + # Overall metrics + metrics = self.aggregator.aggregate(raw_data) + + return { + 'sales_trend': trend_data, + 'top_products': top_products_data, + 'metrics': metrics, + 'tree_config': self.sales_list + } + +# Usage +dashboard = SalesDashboard() + +sample_data = [ + {'date': '2023-10-01', 'amount': 1500, 'product': 'Widget A', 'customer': 'Acme Corp', 'sale_id': 1}, + {'date': '2023-10-02', 'amount': 750, 'product': 'Widget B', 'customer': 'Tech Inc', 'sale_id': 2}, + {'date': '2023-10-03', 'amount': 2200, 'product': 'Widget A', 'customer': 'Global Ltd', 'sale_id': 3}, +] + +dashboard_data = dashboard.process_dashboard_data(sample_data) +print("Dashboard processed successfully!") +print("Metrics:", dashboard_data['metrics']) +``` + +This comprehensive set of examples demonstrates the full capabilities of Python OOUI across all its major components. Each example is practical and can be adapted for real-world use cases. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..8a6ac5e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,75 @@ +# Python OOUI Documentation + +Welcome to the Python OOUI documentation! This package is a Python port of ooui.js, providing Object-Oriented User Interface components for data visualization and processing. + +## What is Python OOUI? + +Python OOUI (Open Object User Interface) is a Python library that provides tools for: + +- **Graph Processing**: Create and manipulate various types of charts (line, bar, pie, indicators) +- **Tree Views**: Parse and process tree-structured data with advanced filtering +- **Data Helpers**: Utilities for domain parsing, condition evaluation, date handling, and data aggregation +- **Field Processing**: Handle complex field definitions and relationships + +## Key Features + +- **Graph Types**: Support for line charts, bar charts, pie charts, and indicator displays +- **XML Parsing**: Parse XML-based graph and tree definitions +- **Condition Evaluation**: Advanced condition parsing and evaluation system +- **Data Aggregation**: Built-in aggregation functions for data analysis +- **Domain Handling**: Powerful domain parsing with support for complex expressions +- **Date Utilities**: Comprehensive date and time range processing + +## Quick Start + +### Installation + +```bash +pip install ooui +``` + +### Basic Usage + +```python +from ooui.graph import parse_graph +from ooui.tree import parse_tree + +# Parse a graph from XML +xml_graph = ''' + + + + +''' +graph = parse_graph(xml_graph) + +# Parse a tree view +xml_tree = ''' + + + + +''' +tree = parse_tree(xml_tree) +``` + +## Documentation Structure + +- **[Installation](installation.md)** - Detailed installation and setup instructions +- **[Usage Guide](usage.md)** - Core concepts and basic usage patterns +- **[API Reference](api-reference.md)** - Complete API documentation +- **[Examples](examples.md)** - Practical code examples and use cases +- **[Advanced Usage](advanced-usage.md)** - Complex scenarios and advanced features + +## Getting Help + +If you encounter issues or have questions: + +1. Check the [Usage Guide](usage.md) for common patterns +2. Browse the [Examples](examples.md) for practical code samples +3. Consult the [API Reference](api-reference.md) for detailed method documentation +4. Review the source code on [GitHub](https://github.com/gisce/python-ooui) + +## Contributing + +Python OOUI is developed by GISCE. Contributions are welcome! Please visit the [GitHub repository](https://github.com/gisce/python-ooui) for more information. \ No newline at end of file diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..8d86123 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,137 @@ +# Installation Guide + +This guide covers the installation and setup of Python OOUI. + +## Requirements + +Python OOUI requires: + +- Python 2.7 or Python 3.x +- lxml +- python-dateutil +- six +- simpleeval < 0.9.12 + +## Installation Methods + +### Using pip (Recommended) + +Install the latest stable version from PyPI: + +```bash +pip install ooui +``` + +### Development Installation + +For development or to get the latest features: + +```bash +# Clone the repository +git clone https://github.com/gisce/python-ooui.git +cd python-ooui + +# Install in development mode +pip install -e . + +# Install development dependencies for testing +pip install -r requirements-dev.txt +``` + +## Verifying Installation + +To verify your installation is working correctly: + +```python +import ooui +from ooui.graph import parse_graph +from ooui.tree import parse_tree +from ooui.helpers import ConditionParser, Domain + +# Test basic imports +print("Python OOUI installed successfully!") +``` + +## Dependencies Explained + +### Core Dependencies + +- **lxml**: Used for XML parsing and manipulation of graph and tree definitions +- **python-dateutil**: Provides enhanced date/time parsing and manipulation +- **six**: Ensures Python 2/3 compatibility +- **simpleeval**: Safe evaluation of Python expressions in conditions and domains + +### Development Dependencies + +- **mamba**: Testing framework for behavior-driven development +- **expects**: Assertion library for testing + +## Troubleshooting + +### Common Installation Issues + +#### lxml Installation Problems + +If you encounter issues installing lxml: + +**On Ubuntu/Debian:** +```bash +sudo apt-get install libxml2-dev libxslt-dev python-dev +pip install lxml +``` + +**On CentOS/RHEL:** +```bash +sudo yum install libxml2-devel libxslt-devel python-devel +pip install lxml +``` + +**On macOS:** +```bash +# Using Homebrew +brew install libxml2 libxslt +pip install lxml + +# Or using conda +conda install lxml +``` + +#### Permission Issues + +If you encounter permission errors: + +```bash +# Install for current user only +pip install --user ooui + +# Or use virtual environment (recommended) +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +pip install ooui +``` + +### Virtual Environment Setup (Recommended) + +Using a virtual environment is the recommended approach: + +```bash +# Create virtual environment +python -m venv ooui-env + +# Activate it +source ooui-env/bin/activate # On Windows: ooui-env\Scripts\activate + +# Install ooui +pip install ooui + +# When done, deactivate +deactivate +``` + +## Next Steps + +Once installed, check out: + +- [Usage Guide](usage.md) to learn the basic concepts +- [Examples](examples.md) for practical code samples +- [API Reference](api-reference.md) for detailed documentation \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..93ea032 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,292 @@ +# Usage Guide + +This guide covers the core concepts and basic usage patterns of Python OOUI. + +## Core Concepts + +Python OOUI is built around three main components: + +1. **Graph Processing** - For creating and manipulating charts and indicators +2. **Tree Views** - For handling structured data displays +3. **Helper Utilities** - For data processing, conditions, and domain handling + +## Graph Processing + +### Graph Types + +Python OOUI supports several graph types: + +- **line**: Line charts for trend visualization +- **bar**: Bar charts for comparative data +- **pie**: Pie charts for proportional data +- **indicator**: Single-value indicators +- **indicatorField**: Field-based indicators + +### Basic Graph Usage + +```python +from ooui.graph import parse_graph + +# Define a line chart +xml_definition = ''' + + + + +''' + +# Parse the graph +graph = parse_graph(xml_definition) + +# Access graph properties +print(graph.string) # "Sales Over Time" +print(graph.type) # "line" +print(graph.fields) # ['date', 'sales'] +``` + +### Processing Graph Data + +```python +# Sample data +data = [ + {'date': '2023-01-01', 'sales': 1000.0}, + {'date': '2023-01-02', 'sales': 1200.0}, + {'date': '2023-01-03', 'sales': 950.0}, +] + +# Fields definition (typically from your data model) +fields = { + 'date': {'type': 'date'}, + 'sales': {'type': 'float'} +} + +# Process the data +result = graph.process(data, fields) +print(result) # Processed graph data ready for visualization +``` + +### Indicator Graphs + +```python +# Simple indicator +indicator_xml = ''' + + + +''' + +indicator = parse_graph(indicator_xml) +``` + +## Tree Views + +Tree views handle structured data displays with filtering capabilities. + +### Basic Tree Usage + +```python +from ooui.tree import parse_tree + +# Define a tree view +tree_xml = ''' + + + + + +''' + +# Parse the tree +tree = parse_tree(tree_xml) + +# Access tree properties +print(tree.string) # "Customer List" +print(tree.editable) # "top" +print(len(tree.fields)) # 3 +``` + +### Tree with Conditional Formatting + +```python +# Tree with colors based on conditions +tree_with_colors = ''' + + + + + + +''' + +tree = parse_tree(tree_with_colors) + +# Get fields used in conditions +conditional_fields = tree.fields_in_conditions +print(conditional_fields) # {'colors': ['state']} +``` + +## Helper Utilities + +### Condition Parser + +The `ConditionParser` evaluates conditional expressions: + +```python +from ooui.helpers import ConditionParser + +# Define conditions +condition = "red:amount < 100;yellow:amount < 500;green:amount >= 500" +parser = ConditionParser(condition) + +# Check which fields are involved +print(parser.involved_fields) # ['amount'] + +# Evaluate conditions with data +result = parser.eval({'amount': 150}) +print(result) # "red" + +result = parser.eval({'amount': 300}) +print(result) # "yellow" + +result = parser.eval({'amount': 600}) +print(result) # "green" +``` + +### Domain Parsing + +The `Domain` class handles complex query expressions: + +```python +from ooui.helpers import Domain + +# Simple domain +domain = Domain("[('active', '=', True), ('age', '>', 18)]") +parsed = domain.parse() +print(parsed) + +# Domain with variables +domain_with_vars = Domain("[('date', '>=', start_date), ('user_id', '=', user)]") +values = { + 'start_date': '2023-01-01', + 'user': 42 +} +parsed = domain_with_vars.parse(values) +``` + +### Date Utilities + +```python +from ooui.helpers.dates import get_date_range, DateRange + +# Create date ranges +date_range = DateRange('2023-01-01', '2023-12-31') +print(date_range.start) # datetime object +print(date_range.end) # datetime object + +# Get relative date ranges +ranges = get_date_range('this_month') +print(ranges) # Current month start and end dates +``` + +### Data Aggregation + +```python +from ooui.helpers.aggregated import Aggregator + +# Define aggregation rules +aggregator = Aggregator({ + 'sales': {'operator': 'sum', 'field': 'amount'}, + 'count': {'operator': 'count', 'field': 'id'}, + 'avg_age': {'operator': 'avg', 'field': 'age'} +}) + +# Sample data +data = [ + {'amount': 100, 'id': 1, 'age': 25, 'region': 'north'}, + {'amount': 200, 'id': 2, 'age': 30, 'region': 'north'}, + {'amount': 150, 'id': 3, 'age': 28, 'region': 'south'}, +] + +# Aggregate data +result = aggregator.aggregate(data, group_by='region') +print(result) +# { +# 'north': {'sales': 300, 'count': 2, 'avg_age': 27.5}, +# 'south': {'sales': 150, 'count': 1, 'avg_age': 28} +# } +``` + +## Advanced Features + +### Custom Field Processing + +```python +from ooui.graph.fields import get_value_for_operator + +# Apply different operators to field values +values = [10, 20, 30, 40, 50] + +sum_result = get_value_for_operator(values, 'sum') # 150 +avg_result = get_value_for_operator(values, 'avg') # 30 +max_result = get_value_for_operator(values, 'max') # 50 +min_result = get_value_for_operator(values, 'min') # 10 +count_result = get_value_for_operator(values, 'count') # 5 +``` + +### Boolean Attribute Parsing + +```python +from ooui.helpers import parse_bool_attribute + +# Parse various boolean representations +print(parse_bool_attribute('1')) # True +print(parse_bool_attribute('true')) # True +print(parse_bool_attribute('True')) # True +print(parse_bool_attribute('0')) # False +print(parse_bool_attribute('false')) # False +``` + +### HTML Entity Replacement + +```python +from ooui.helpers import replace_entities + +# Clean HTML entities +text = "Price > $100 & < $200" +clean_text = replace_entities(text) +print(clean_text) # "Price > $100 & < $200" +``` + +## Best Practices + +1. **Always validate XML**: Ensure your XML definitions are well-formed +2. **Handle missing data**: Check for None values in your data processing +3. **Use appropriate operators**: Choose the right aggregation operator for your data type +4. **Test conditions**: Verify conditional expressions with sample data +5. **Cache parsed objects**: Reuse parsed graphs and trees when possible + +## Error Handling + +```python +from ooui.graph import parse_graph + +try: + # Invalid graph type + invalid_xml = '' + graph = parse_graph(invalid_xml) +except ValueError as e: + print(f"Error: {e}") # "invalid_type is not a valid graph" + +try: + # Malformed XML + malformed_xml = '' # Missing closing tag + graph = parse_graph(malformed_xml) +except Exception as e: + print(f"XML Error: {e}") +``` + +## Next Steps + +- Check out [Examples](examples.md) for more practical use cases +- Explore the [API Reference](api-reference.md) for detailed method documentation +- Learn about [Advanced Usage](advanced-usage.md) for complex scenarios \ No newline at end of file