diff --git a/mitm_tooling/definition/definition_representation.py b/mitm_tooling/definition/definition_representation.py
index 09903f94bd6c052471b6a02df082217646e78d28..d2000a67d371ef7b8e52eb979921fd76a662bee6 100644
--- a/mitm_tooling/definition/definition_representation.py
+++ b/mitm_tooling/definition/definition_representation.py
@@ -8,6 +8,7 @@ import pydantic
 from pydantic import Field
 
 from mitm_tooling.data_types.data_types import MITMDataType
+from mitm_tooling.utilities.identifiers import naive_pluralize
 from mitm_tooling.utilities.python_utils import unpack_singleton, map_vals, normalize_list_of_mixed, combine_dicts
 
 COLUMN_GROUPS = Literal['kind', 'type', 'identity-relations', 'inline-relations', 'foreign-relations', 'attributes']
@@ -52,6 +53,9 @@ class OwnedRelations(pydantic.BaseModel):
         return {n: fk_rel_info.fk_relations for n, fk_rel_info in self.foreign.items() if
                 fk_rel_info.target_concept == concept}
 
+    @property
+    def relation_names(self) -> tuple[RelationName, ...]:
+        return tuple(itertools.chain(self.identity.keys(), self.inline.keys(), self.foreign.keys()))
 
 class ConceptProperties(pydantic.BaseModel):
     nature: tuple[ConceptLevel, ConceptKind]
@@ -208,7 +212,7 @@ class ConceptPropertiesFile(pydantic.BaseModel):
 
         kwargs = combine_dicts(inherited_kwargs,
                                {'typing_concept': self.typing_concept, 'permit_attributes': self.permit_attributes,
-                                'plural': self.plural}, {'plural': concept + 's'})
+                                'plural': self.plural}, {'plural': naive_pluralize(concept)})
 
         opt_kwargs = {}
         if self.override_column_group_ordering is not None:
diff --git a/mitm_tooling/transformation/superset/__init__.py b/mitm_tooling/transformation/superset/__init__.py
index 03c0f2a80d338d27df50dd97e9e2070005eee786..51faa41b213ab6b28b70071ea559bb62a56dbd74 100644
--- a/mitm_tooling/transformation/superset/__init__.py
+++ b/mitm_tooling/transformation/superset/__init__.py
@@ -1,5 +1,7 @@
-from . import definitions, factories, mitm_specific
+from . import definitions, factories, asset_bundles, visualizations
 from . import exporting, from_sql, from_intermediate
 from . import interface
 from .exporting import write_superset_import_as_zip
 from .interface import mk_superset_datasource_bundle, mk_superset_visualization_bundle, mk_superset_mitm_dataset_bundle
+from .visualizations.registry import VisualizationType, MAEDVisualizationType
+from .visualizations.registry import mk_visualization, get_mitm_visualization_creator
diff --git a/mitm_tooling/transformation/superset/asset_bundles/__init__.py b/mitm_tooling/transformation/superset/asset_bundles/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1ab5d6189c35a161470a1c10c490cb8ce41aa79
--- /dev/null
+++ b/mitm_tooling/transformation/superset/asset_bundles/__init__.py
@@ -0,0 +1 @@
+from .asset_bundles import *
diff --git a/mitm_tooling/transformation/superset/definition_bundles.py b/mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py
similarity index 84%
rename from mitm_tooling/transformation/superset/definition_bundles.py
rename to mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py
index bbea4c0c928640f7dbad01b321ad95f77721403f..5a9ef91f5e64dc95605f50c765124f2ec942f511 100644
--- a/mitm_tooling/transformation/superset/definition_bundles.py
+++ b/mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py
@@ -1,14 +1,15 @@
+import itertools
 from abc import ABC, abstractmethod
 from typing import Any, Self
 from uuid import UUID
 
 import pydantic
 
-from .definitions import SupersetDatabaseDef, SupersetMitMDatasetDef, \
+from mitm_tooling.representation import TableName
+from ..definitions import SupersetDatabaseDef, SupersetMitMDatasetDef, \
     SupersetChartDef, SupersetDashboardDef, SupersetAssetsImport, SupersetDatasetDef, \
     SupersetMitMDatasetImport, SupersetDefFolder, DatasourceIdentifier
-from .factories.importable import mk_assets_import, mk_mitm_dataset_import
-from ...representation import TableName
+from ..factories.importable import mk_assets_import, mk_mitm_dataset_import
 
 
 class SupersetAssetBundle(SupersetDefFolder, ABC):
@@ -53,6 +54,15 @@ class SupersetVisualizationBundle(SupersetAssetBundle):
     def dashboard_uuids(self) -> list[UUID]:
         return [da.uuid for da in self.dashboards]
 
+    @classmethod
+    def combine(cls, *bundles: Self) -> Self:
+        if not bundles or len(bundles) == 0:
+            return cls()
+
+        charts, dashboards = itertools.chain(*(b.charts for b in bundles)), itertools.chain(*(b.dashboards for b in
+                                                                                              bundles))
+        return cls(charts=list(charts), dashboards=list(dashboards))
+
     def to_import(self) -> SupersetAssetsImport:
         return mk_assets_import(charts=self.charts, dashboards=self.dashboards)
 
diff --git a/mitm_tooling/transformation/superset/common.py b/mitm_tooling/transformation/superset/common.py
index 00af82b3c1ca1c041077f797675a655ec8e327e0..9224964305c0809fbd2d29903ed323dc5a2d2200 100644
--- a/mitm_tooling/transformation/superset/common.py
+++ b/mitm_tooling/transformation/superset/common.py
@@ -1,25 +1,25 @@
-from uuid import UUID
+from typing import Type
 
 import pydantic
 import sqlalchemy as sa
+from pydantic import AnyUrl, ConfigDict
+
 from mitm_tooling.representation import SchemaName
 from mitm_tooling.representation.sql_representation import SQL_REPRESENTATION_DEFAULT_SCHEMA
-from .definitions import StrUrl
-from .factories.utils import mk_short_uuid_str
 from mitm_tooling.utilities.io_utils import FilePath
 from mitm_tooling.utilities.sql_utils import create_sa_engine, dialect_cls_from_url, any_url_into_sa_url
-from pydantic import AnyUrl, ConfigDict
-from typing import Type
+from .definitions import StrUrl
 
 SQLiteFileOrEngine = FilePath | sa.Engine
 
 
-class SupersetDBConnectionInfo(pydantic.BaseModel):
+class DBConnectionInfo(pydantic.BaseModel):
     model_config = ConfigDict(arbitrary_types_allowed=True)
 
     sql_alchemy_uri: StrUrl
     explicit_db_name: str | None = None
     schema_name: SchemaName = SQL_REPRESENTATION_DEFAULT_SCHEMA
+    catalog: str | None = None  # for future use
 
     @property
     def sa_url(self) -> sa.URL:
@@ -38,10 +38,6 @@ class SupersetDBConnectionInfo(pydantic.BaseModel):
         return dialect_cls_from_url(self.sql_alchemy_uri)
 
 
-def name_plus_uuid(name: str, uuid: UUID | None = None, sep: str = '-') -> str:
-    return f'{name}{sep}{mk_short_uuid_str(uuid)}'
-
-
 def _mk_engine(arg: SQLiteFileOrEngine) -> sa.Engine:
     if isinstance(arg, sa.Engine):
         return arg
diff --git a/mitm_tooling/transformation/superset/definitions/__init__.py b/mitm_tooling/transformation/superset/definitions/__init__.py
index 77e9f60906d7b569ae6bc8268f5ad714c6ffbcdc..e8bcb327683c31552434c8e0cfcfb7e9fe3bf573 100644
--- a/mitm_tooling/transformation/superset/definitions/__init__.py
+++ b/mitm_tooling/transformation/superset/definitions/__init__.py
@@ -1,8 +1,8 @@
+from .chart import *
 from .constants import *
 from .core import *
+from .dashboard import *
 from .database import *
 from .dataset import *
-from .chart import *
-from .dashboard import *
 from .importable import *
 from .post_processing import *
diff --git a/mitm_tooling/transformation/superset/definitions/chart.py b/mitm_tooling/transformation/superset/definitions/chart.py
index d8bc00d8737f4fd81dbf014a429aa614902c2709..442e4f218eec66ba054562f109352be859471e27 100644
--- a/mitm_tooling/transformation/superset/definitions/chart.py
+++ b/mitm_tooling/transformation/superset/definitions/chart.py
@@ -1,13 +1,6 @@
-from typing import Annotated
-
-import pydantic
-
-from . import SupersetDefFile, StrUUID, SupersetVizType, QueryContext, StrUrl
 from .core import *
 
 
-
-
 class ChartParams(FormData):
     datasource: str | DatasourceIdentifier
     viz_type: SupersetVizType
diff --git a/mitm_tooling/transformation/superset/definitions/constants.py b/mitm_tooling/transformation/superset/definitions/constants.py
index 786d525a0885a0a0e2b6030072db164687ec7324..6df8f1e1e5edf9daf631265ef7bf2d4dc757fdcc 100644
--- a/mitm_tooling/transformation/superset/definitions/constants.py
+++ b/mitm_tooling/transformation/superset/definitions/constants.py
@@ -1,6 +1,5 @@
 from abc import ABC, abstractmethod
 from datetime import datetime
-from datetime import datetime
 from enum import StrEnum, IntEnum
 from typing import Annotated, Literal, Self, Union
 from uuid import UUID
@@ -81,6 +80,7 @@ class SupersetVizType(StrEnum):
     PIE = 'pie'
     TIMESERIES_BAR = 'echarts_timeseries_bar'
     TIMESERIES_LINE = 'echarts_timeseries_line'
+    MAED_CUSTOM = 'maed_custom'
 
 
 class ExpressionType(StrEnum):
diff --git a/mitm_tooling/transformation/superset/definitions/core.py b/mitm_tooling/transformation/superset/definitions/core.py
index 7fad78fdad8c5df553553a1f3accbfcd0435f8e1..21e175a83556fd8e4080c2ba52768f4516eae551 100644
--- a/mitm_tooling/transformation/superset/definitions/core.py
+++ b/mitm_tooling/transformation/superset/definitions/core.py
@@ -5,7 +5,7 @@ from .constants import *
 
 
 class DatasourceIdentifier(FrozenSupersetDefinition):
-    id: SupersetId = -1 # -1 as a placeholder
+    id: SupersetId = -1  # -1 as a placeholder
     type: Literal['table', 'annotation'] = 'table'
 
     uuid: StrUUID = pydantic.Field(exclude=False)
@@ -178,5 +178,3 @@ class QueryContext(BaseSupersetDefinition):
     result_format: ChartDataResultFormat = ChartDataResultFormat.JSON
     force: bool = False
     custom_cache_timeout: int | None = None
-
-
diff --git a/mitm_tooling/transformation/superset/definitions/dashboard.py b/mitm_tooling/transformation/superset/definitions/dashboard.py
index a2c6b18286c16030bb84de78667a21be61c15c0b..8bb9c1ebcf42cc28c439eb8abef2c83ba98b2b22 100644
--- a/mitm_tooling/transformation/superset/definitions/dashboard.py
+++ b/mitm_tooling/transformation/superset/definitions/dashboard.py
@@ -3,7 +3,7 @@ from typing import Literal
 
 import pydantic
 
-from . import SupersetDefFile, StrUUID, StrUrl
+from . import SupersetDefFile, StrUrl
 from .constants import StrUUID
 
 DashboardInternalID = str
diff --git a/mitm_tooling/transformation/superset/definitions/importable.py b/mitm_tooling/transformation/superset/definitions/importable.py
index c52f5a2ba659d17bc51cc55b894a81e652c97417..7ceb14858628cd9eab9a317106409b71150bc78a 100644
--- a/mitm_tooling/transformation/superset/definitions/importable.py
+++ b/mitm_tooling/transformation/superset/definitions/importable.py
@@ -66,7 +66,7 @@ class SupersetAssetsImport(SupersetDefFolder):
 class SupersetMitMDatasetImport(SupersetDefFolder):
     mitm_datasets: list[SupersetMitMDatasetDef] | None
     base_assets: SupersetAssetsImport | None
-    metadata: SupersetMetadataDef = pydantic.Field(default_factory=lambda : SupersetMetadataDef(type=MetadataType.MitMDataset))
+    metadata: SupersetMetadataDef = pydantic.Field(default_factory=lambda: SupersetMetadataDef(type=MetadataType.MitMDataset))
 
     @property
     def folder_dict(self) -> dict[str, Any]:
diff --git a/mitm_tooling/transformation/superset/definitions/mitm_dataset.py b/mitm_tooling/transformation/superset/definitions/mitm_dataset.py
index 83d4d7ed395df33401bc32f6f79d7031f28f5fdf..c215b6c77ab73eb5a74d01c9683e268c309282b0 100644
--- a/mitm_tooling/transformation/superset/definitions/mitm_dataset.py
+++ b/mitm_tooling/transformation/superset/definitions/mitm_dataset.py
@@ -16,15 +16,15 @@ class RelatedObjectIdentifier(BaseSupersetDefinition):
 
 
 class RelatedTable(RelatedObjectIdentifier):
-    pass
+    table_name: str | None = None
 
 
 class RelatedSlice(RelatedObjectIdentifier):
-    pass
+    slice_name: str | None = None
 
 
 class RelatedDashboard(RelatedObjectIdentifier):
-    pass
+    dashboard_title: str | None = None
 
 
 class SupersetMitMDatasetDef(SupersetDefFile):
@@ -38,6 +38,10 @@ class SupersetMitMDatasetDef(SupersetDefFile):
     dashboards: list[RelatedDashboard] | None = None
     version: str = '1.0.0'
 
+    @property
+    def identifier(self) -> MitMDatasetIdentifier:
+        return MitMDatasetIdentifier(dataset_name=self.dataset_name, uuid=self.uuid)
+
     @property
     def filename(self) -> str:
         return self.dataset_name
diff --git a/mitm_tooling/transformation/superset/definitions/post_processing.py b/mitm_tooling/transformation/superset/definitions/post_processing.py
index 6c00e407a33a563e3853cc878d0a8b313e1ef15b..733cad4a6f2a5368a890491d88f2d0136a6b9499 100644
--- a/mitm_tooling/transformation/superset/definitions/post_processing.py
+++ b/mitm_tooling/transformation/superset/definitions/post_processing.py
@@ -32,4 +32,3 @@ class Rename(SupersetPostProcessing):
 
 class Flatten(SupersetPostProcessing):
     operation: Literal['flatten'] = 'flatten'
-
diff --git a/mitm_tooling/transformation/superset/exporting.py b/mitm_tooling/transformation/superset/exporting.py
index 98e1b554b72ea4e20f9f9f10f4aa1c7fda284da6..8eb702b90e4c6bbbb2b4ddb99fe60a801c398bc2 100644
--- a/mitm_tooling/transformation/superset/exporting.py
+++ b/mitm_tooling/transformation/superset/exporting.py
@@ -3,11 +3,11 @@ import zipfile
 
 import yaml
 
-from mitm_tooling.utilities.io_utils import FilePath, ByteSink, use_bytes_io
+from mitm_tooling.utilities.io_utils import ByteSink, use_bytes_io
 from .definitions import SupersetDefFile, SupersetDefFolder
 
 
-def write_superset_import_as_zip(target: ByteSink, superset_def: SupersetDefFolder):
+def write_superset_import_as_zip(target: ByteSink, superset_def: SupersetDefFolder) -> None:
     folder_structure = superset_def.folder_dict
     with use_bytes_io(target, expected_file_ext='.zip', mode='wb', create_file_if_necessary=True) as f:
         with zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED) as zf:
@@ -38,4 +38,4 @@ def write_superset_import_as_zip(target: ByteSink, superset_def: SupersetDefFold
                             zf.mkdir(path)
                         mk_node(folder_content, prefix=path)
 
-            mk_node(folder_structure)
\ No newline at end of file
+            mk_node(folder_structure)
diff --git a/mitm_tooling/transformation/superset/factories/chart.py b/mitm_tooling/transformation/superset/factories/chart.py
index eef34774f84d1046860e32bf53285fca744b0cac..b397b3254a08abbbb5e3d52a814dfd4799c57427 100644
--- a/mitm_tooling/transformation/superset/factories/chart.py
+++ b/mitm_tooling/transformation/superset/factories/chart.py
@@ -1,6 +1,6 @@
 from uuid import UUID
 
-from .utils import mk_uuid
+from mitm_tooling.utilities.identifiers import mk_uuid
 from ..definitions import SupersetChartDef, SupersetVizType, ChartParams, QueryContext
 
 
@@ -15,5 +15,3 @@ def mk_chart_def(name: str, dataset_uuid: UUID, viz_type: SupersetVizType, param
         uuid=uuid or mk_uuid(),
         **kwargs
     )
-
-
diff --git a/mitm_tooling/transformation/superset/factories/custom_charts.py b/mitm_tooling/transformation/superset/factories/custom_charts.py
new file mode 100644
index 0000000000000000000000000000000000000000..54865d3baf977300f3928c483e4da259d0bb1bbc
--- /dev/null
+++ b/mitm_tooling/transformation/superset/factories/custom_charts.py
@@ -0,0 +1,28 @@
+from typing import Literal
+
+from mitm_tooling.transformation.superset.definitions import SupersetChartDef, SupersetVizType, ChartParams, \
+    DatasourceIdentifier
+from mitm_tooling.transformation.superset.definitions.mitm_dataset import MitMDatasetIdentifier
+from mitm_tooling.transformation.superset.factories.chart import mk_chart_def
+from mitm_tooling.transformation.superset.factories.query import mk_query_object, mk_query_context
+
+
+class MAEDCustomChartParams(ChartParams):
+    viz_type: Literal[SupersetVizType.MAED_CUSTOM] = SupersetVizType.MAED_CUSTOM
+    mitm_dataset: MitMDatasetIdentifier
+
+
+def mk_maed_custom_chart(name: str,
+                         mitm_dataset_identifier: MitMDatasetIdentifier,
+                         datasource_identifier: DatasourceIdentifier,
+                         **kwargs) -> SupersetChartDef:
+    params = MAEDCustomChartParams(datasource=datasource_identifier, mitm_dataset=mitm_dataset_identifier)
+    qo = mk_query_object()
+    qc = mk_query_context(datasource=datasource_identifier, queries=[qo], form_data=params)
+
+    return mk_chart_def(name,
+                        dataset_uuid=datasource_identifier.uuid,
+                        viz_type=SupersetVizType.MAED_CUSTOM,
+                        params=params,
+                        qc=qc,
+                        **kwargs)
diff --git a/mitm_tooling/transformation/superset/factories/dashboard.py b/mitm_tooling/transformation/superset/factories/dashboard.py
index 3bb6542993323af9d653de906626ea29c8263da8..c1d14d5632d585e6380602cf10f5977e40973f30 100644
--- a/mitm_tooling/transformation/superset/factories/dashboard.py
+++ b/mitm_tooling/transformation/superset/factories/dashboard.py
@@ -2,9 +2,11 @@ from uuid import UUID
 
 from mitm_tooling.representation import ColumnName
 from mitm_tooling.transformation.superset.definitions import SupersetChartDef
-from mitm_tooling.transformation.superset.factories.utils import mk_uuid, mk_short_uuid_str
+from mitm_tooling.utilities.identifiers import mk_uuid, mk_short_uuid_str
 from ..definitions.dashboard import *
 
+ChartGrid = list[list[DashboardChart]]
+
 
 def mk_filter_config(name: str, target_cols: list[tuple[ColumnName, UUID]], ft: FilterType = FilterType.FILTER_SELECT,
                      control_values: ControlValues = ControlValues()) -> NativeFilterConfig:
@@ -42,7 +44,7 @@ def chart_def_into_dashboard_chart(chart_def: SupersetChartDef, width: int, heig
     return mk_dashboard_chart(chart_uuid=chart_def.uuid, width=width, height=height)
 
 
-def mk_dashboard_position_data(header_text: str, chart_grid: list[list[DashboardChart]]) -> DashboardPositionData:
+def mk_dashboard_position_data(header_text: str, chart_grid: ChartGrid) -> DashboardPositionData:
     row_ids = []
     elements = {}
     for row in chart_grid:
@@ -60,12 +62,15 @@ def mk_dashboard_position_data(header_text: str, chart_grid: list[list[Dashboard
     return res
 
 
-def mk_dashboard_metadata(native_filters: list[NativeFilterConfig]) -> DashboardMetadata:
-    return DashboardMetadata(native_filter_configuration=native_filters)
+def mk_dashboard_metadata(native_filters: list[NativeFilterConfig] | None = None) -> DashboardMetadata:
+    return DashboardMetadata(native_filter_configuration=native_filters or [])
 
 
-def mk_dashboard_def(dashboard_title, chart_grid: list[list[DashboardChart]], native_filters: list[NativeFilterConfig],
-                     description: str | None = None, uuid: UUID | None = None) -> SupersetDashboardDef:
+def mk_dashboard_def(dashboard_title: str,
+                     chart_grid: ChartGrid,
+                     native_filters: list[NativeFilterConfig] | None = None,
+                     description: str | None = None,
+                     uuid: UUID | None = None) -> SupersetDashboardDef:
     position_data = mk_dashboard_position_data(dashboard_title, chart_grid)
     dashboard_metadata = mk_dashboard_metadata(native_filters=native_filters)
     return SupersetDashboardDef(
diff --git a/mitm_tooling/transformation/superset/factories/database.py b/mitm_tooling/transformation/superset/factories/database.py
index f4befd77ef4e8245f62ecb6429f3a79ce99f11df..1c18f395e37f15e1686d0ceeb404439147f21e8a 100644
--- a/mitm_tooling/transformation/superset/factories/database.py
+++ b/mitm_tooling/transformation/superset/factories/database.py
@@ -2,8 +2,7 @@ from uuid import UUID
 
 from pydantic import AnyUrl
 
-from .utils import mk_uuid
-from ..common import name_plus_uuid
+from mitm_tooling.utilities.identifiers import mk_uuid, name_plus_uuid
 from ..definitions import SupersetDatabaseDef
 
 
diff --git a/mitm_tooling/transformation/superset/factories/dataset.py b/mitm_tooling/transformation/superset/factories/dataset.py
index 477fe20db5bc34618e23a9392674b606de44a804..0cfcab0f1cb2576c4828eda66e225d2b26802cf2 100644
--- a/mitm_tooling/transformation/superset/factories/dataset.py
+++ b/mitm_tooling/transformation/superset/factories/dataset.py
@@ -1,12 +1,11 @@
 from uuid import UUID
 
-import pydantic
 import sqlalchemy as sa
 
 from mitm_tooling.data_types import MITMDataType
 from mitm_tooling.extraction.sql.data_models import TableMetaInfo
+from mitm_tooling.utilities.identifiers import mk_uuid
 from .core import mk_column, mk_metric
-from .utils import mk_uuid
 from ..definitions import SupersetAggregate, SupersetDatasetDef
 
 
diff --git a/mitm_tooling/transformation/superset/factories/mitm_dataset.py b/mitm_tooling/transformation/superset/factories/mitm_dataset.py
index 2b15b05a1bf79de0ba6f41176b3e97fa9f071901..6d554bb3ac7dbe3432b30414a971cfb7500476e7 100644
--- a/mitm_tooling/transformation/superset/factories/mitm_dataset.py
+++ b/mitm_tooling/transformation/superset/factories/mitm_dataset.py
@@ -2,10 +2,9 @@ from typing import Sequence, Literal, Iterable
 from uuid import UUID
 
 from mitm_tooling.definition import MITM
-from ..common import name_plus_uuid
+from mitm_tooling.utilities.identifiers import mk_uuid, name_plus_uuid
 from ..definitions import SupersetMitMDatasetDef
 from ..definitions.mitm_dataset import RelatedTable, RelatedSlice, RelatedDashboard
-from ..factories.utils import mk_uuid
 
 
 def mk_related_obj(kind: Literal['table', 'slice', 'dashboard'],
diff --git a/mitm_tooling/transformation/superset/factories/query.py b/mitm_tooling/transformation/superset/factories/query.py
index 2d51203aea0ac16c416360b63057655534fb1ef5..d70875ee59e5314d9522f89e1f540e6f9ffc7a1c 100644
--- a/mitm_tooling/transformation/superset/factories/query.py
+++ b/mitm_tooling/transformation/superset/factories/query.py
@@ -1,7 +1,7 @@
+from mitm_tooling.representation import ColumnName
 from ..definitions import QueryObject, QueryContext, DatasourceIdentifier, SupersetAdhocMetric, \
     QueryObjectFilterClause, FormData, FilterValues, FilterOperator, SupersetPostProcessing, \
     SupersetAdhocColumn
-from mitm_tooling.representation import ColumnName
 
 
 def mk_query_object_filter_clause(col: ColumnName, op: FilterOperator,
@@ -13,14 +13,17 @@ def mk_empty_query_object_time_filter_clause() -> QueryObjectFilterClause:
     return mk_query_object_filter_clause('time', FilterOperator.TEMPORAL_RANGE)
 
 
-def mk_query_object(columns: list[ColumnName | SupersetAdhocColumn],
-                    metrics: list[SupersetAdhocMetric],
-                    filters: list[QueryObjectFilterClause],
+def mk_query_object(columns: list[ColumnName | SupersetAdhocColumn] | None = None,
+                    metrics: list[SupersetAdhocMetric] | None = None,
+                    filters: list[QueryObjectFilterClause] | None = None,
                     orderby: list[tuple[SupersetAdhocMetric, bool]] | None = None,
                     post_processing: list[SupersetPostProcessing] | None = None,
                     row_limit: int | None = 10_000,
                     **kwargs) -> QueryObject:
-    if orderby is None:
+    columns = columns or []
+    metrics = metrics or []
+    filters = filters or []
+    if orderby is None and len(metrics) > 0:
         orderby = [(metrics[0], 0)]
     if post_processing is None:
         post_processing = []
diff --git a/mitm_tooling/transformation/superset/factories/utils.py b/mitm_tooling/transformation/superset/factories/utils.py
index 86ebcab95db6f3227a0688c542f03a68c7543287..b28b04f643122b019e912540f228c8ed20be9eeb 100644
--- a/mitm_tooling/transformation/superset/factories/utils.py
+++ b/mitm_tooling/transformation/superset/factories/utils.py
@@ -1,11 +1,3 @@
-import uuid
 
-import pydantic
 
 
-def mk_uuid() -> pydantic.UUID4:
-    return uuid.uuid4()
-
-
-def mk_short_uuid_str(existing_uuid: uuid.UUID | None = None) -> str:
-    return (existing_uuid or mk_uuid()).hex[:12]
diff --git a/mitm_tooling/transformation/superset/from_intermediate.py b/mitm_tooling/transformation/superset/from_intermediate.py
index 83bb7280d43b42c2fdc871ec5cb1e9db1c1bdecb..a315f08f0cf89560c8f372aff832a654f02e3a40 100644
--- a/mitm_tooling/transformation/superset/from_intermediate.py
+++ b/mitm_tooling/transformation/superset/from_intermediate.py
@@ -1,34 +1,22 @@
 from mitm_tooling.representation import Header
-from .common import SupersetDBConnectionInfo
-from .definition_bundles import SupersetDatasourceBundle, SupersetMitMDatasetBundle, SupersetVisualizationBundle
+from .asset_bundles.asset_bundles import SupersetDatasourceBundle, \
+    SupersetMitMDatasetBundle
+from .common import DBConnectionInfo
 from .definitions.mitm_dataset import MitMDatasetIdentifier
-from .mitm_specific import get_mitm_visualization_factory
 
 
 def header_into_superset_datasource_bundle(header: Header,
-                                           db_conn_info: SupersetDBConnectionInfo) -> SupersetDatasourceBundle:
+                                           db_conn_info: DBConnectionInfo) -> SupersetDatasourceBundle:
     from ..sql.from_intermediate import header_into_db_meta
     from .from_sql import db_meta_into_superset_datasource_bundle
     db_meta = header_into_db_meta(header)
     return db_meta_into_superset_datasource_bundle(db_meta, db_conn_info)
 
 
-def header_into_superset_visualization_bundle(header: Header,
-                                              datasource_bundle: SupersetDatasourceBundle) -> SupersetVisualizationBundle:
-    return get_mitm_visualization_factory(header.mitm)(header, datasource_bundle)
-
-
-def header_into_superset_mitm_dataset_bundle(header: Header,
-                                             db_conn_info: SupersetDBConnectionInfo,
-                                             dataset_identifier: MitMDatasetIdentifier,
-                                             include_visualizations: bool = False) -> SupersetMitMDatasetBundle:
+def header_into_mitm_dataset_bundle(header: Header,
+                                    db_conn_info: DBConnectionInfo,
+                                    dataset_identifier: MitMDatasetIdentifier) -> SupersetMitMDatasetBundle:
     from ..sql.from_intermediate import header_into_db_meta
     from .from_sql import db_meta_into_mitm_dataset_bundle
     db_meta = header_into_db_meta(header)
-    mitm_dataset_bundle = db_meta_into_mitm_dataset_bundle(db_meta, db_conn_info, dataset_identifier, header.mitm)
-    if include_visualizations:
-        mitm_dataset_bundle = mitm_dataset_bundle.with_visualization_bundle(
-            header_into_superset_visualization_bundle(header,
-                                                      mitm_dataset_bundle.datasource_bundle))
-
-    return mitm_dataset_bundle
+    return db_meta_into_mitm_dataset_bundle(db_meta, db_conn_info, dataset_identifier, header.mitm)
diff --git a/mitm_tooling/transformation/superset/from_sql.py b/mitm_tooling/transformation/superset/from_sql.py
index 359b16c73af5adfe1694b645ef5c99ebc5c62bc4..9cf7822aded53a067869bd69242f592fc8e10c02 100644
--- a/mitm_tooling/transformation/superset/from_sql.py
+++ b/mitm_tooling/transformation/superset/from_sql.py
@@ -1,9 +1,10 @@
 from mitm_tooling.extraction.sql.data_models import DBMetaInfo
 from mitm_tooling.extraction.sql.data_models.db_meta import DBMetaInfoBase
 from mitm_tooling.extraction.sql.db import connect_and_reflect
-from .common import SupersetDBConnectionInfo
+from .asset_bundles import SupersetDatasourceBundle, \
+    SupersetMitMDatasetBundle
+from .common import DBConnectionInfo
 from .common import _mk_engine, SQLiteFileOrEngine
-from .definition_bundles import SupersetDatasourceBundle, SupersetMitMDatasetBundle
 from .definitions.mitm_dataset import MitMDatasetIdentifier
 from .factories.database import mk_database
 from .factories.dataset import mk_dataset
@@ -12,7 +13,7 @@ from ...definition import MITM
 
 
 def db_meta_into_superset_datasource_bundle(db_meta: DBMetaInfoBase,
-                                            db_conn_info: SupersetDBConnectionInfo) -> SupersetDatasourceBundle:
+                                            db_conn_info: DBConnectionInfo) -> SupersetDatasourceBundle:
     sqlalchemy_uri = db_conn_info.sql_alchemy_uri
     db_name = db_conn_info.db_name
     dialect = db_conn_info.dialect_cls()
@@ -29,7 +30,7 @@ def db_meta_into_superset_datasource_bundle(db_meta: DBMetaInfoBase,
 
 
 def db_meta_into_mitm_dataset_bundle(db_meta: DBMetaInfoBase,
-                                     db_conn_info: SupersetDBConnectionInfo,
+                                     db_conn_info: DBConnectionInfo,
                                      dataset_identifier: MitMDatasetIdentifier,
                                      mitm: MITM) -> SupersetMitMDatasetBundle:
     datasource_bundle = db_meta_into_superset_datasource_bundle(db_meta, db_conn_info)
@@ -40,7 +41,7 @@ def db_meta_into_mitm_dataset_bundle(db_meta: DBMetaInfoBase,
 
 
 def db_into_superset_datasource_bundle(arg: SQLiteFileOrEngine,
-                                       db_conn_info: SupersetDBConnectionInfo) -> SupersetDatasourceBundle:
+                                       db_conn_info: DBConnectionInfo) -> SupersetDatasourceBundle:
     engine = _mk_engine(arg)
 
     meta, _ = connect_and_reflect(engine, allowed_schemas=[db_conn_info.schema_name])
@@ -50,7 +51,7 @@ def db_into_superset_datasource_bundle(arg: SQLiteFileOrEngine,
 
 
 def db_into_mitm_dataset_bundle(arg: SQLiteFileOrEngine,
-                                db_conn_info: SupersetDBConnectionInfo,
+                                db_conn_info: DBConnectionInfo,
                                 dataset_identifier: MitMDatasetIdentifier,
                                 mitm: MITM) -> SupersetMitMDatasetBundle:
     datasource_bundle = db_into_superset_datasource_bundle(arg, db_conn_info)
diff --git a/mitm_tooling/transformation/superset/interface.py b/mitm_tooling/transformation/superset/interface.py
index 85204be5e3028c6e88905cea145917040db7cd04..43c8de6896a3750575baece647b3552712c8271b 100644
--- a/mitm_tooling/transformation/superset/interface.py
+++ b/mitm_tooling/transformation/superset/interface.py
@@ -1,22 +1,40 @@
+from collections.abc import Iterable
+
 from mitm_tooling.representation import Header
-from mitm_tooling.transformation.superset.definition_bundles import SupersetDatasourceBundle, \
+from .asset_bundles.asset_bundles import SupersetDatasourceBundle, \
     SupersetVisualizationBundle, SupersetMitMDatasetBundle
-from mitm_tooling.transformation.superset.from_intermediate import header_into_superset_mitm_dataset_bundle
-from mitm_tooling.transformation.superset.from_intermediate import header_into_superset_visualization_bundle
-from .common import SupersetDBConnectionInfo
+from .common import DBConnectionInfo
 from .definitions.mitm_dataset import MitMDatasetIdentifier
-from .from_intermediate import header_into_superset_datasource_bundle
+from .from_intermediate import header_into_superset_datasource_bundle, header_into_mitm_dataset_bundle
+from .visualizations.abstract import DatasourceIdentifierMap
+from .visualizations.registry import VisualizationType, mk_visualization
 
 
-def mk_superset_datasource_bundle(header: Header, db_conn_info: SupersetDBConnectionInfo) -> SupersetDatasourceBundle:
+def mk_superset_datasource_bundle(header: Header, db_conn_info: DBConnectionInfo) -> SupersetDatasourceBundle:
     return header_into_superset_datasource_bundle(header, db_conn_info)
 
 
 def mk_superset_visualization_bundle(header: Header,
-                                     superset_datasource_bundle: SupersetDatasourceBundle) -> SupersetVisualizationBundle:
-    return header_into_superset_visualization_bundle(header, superset_datasource_bundle)
+                                     mitm_dataset_identifier: MitMDatasetIdentifier,
+                                     ds_id_map: DatasourceIdentifierMap,
+                                     visualization_types: Iterable[
+                                         VisualizationType]) -> SupersetVisualizationBundle:
+    return SupersetVisualizationBundle.combine(*(
+        mk_visualization(vzt, header, mitm_dataset_identifier, ds_id_map) for vzt
+        in set(visualization_types)
+    ))
+
 
+def mk_superset_mitm_dataset_bundle(header: Header,
+                                    db_conn_info: DBConnectionInfo,
+                                    mitm_dataset_identifier: MitMDatasetIdentifier,
+                                    visualization_types: Iterable[
+                                                             VisualizationType] | None = None) -> SupersetMitMDatasetBundle:
+    mitm_dataset_bundle = header_into_mitm_dataset_bundle(header, db_conn_info, mitm_dataset_identifier)
+    if visualization_types is not None:
+        mdi = mitm_dataset_bundle.mitm_dataset.identifier
+        ds_id_map = mitm_dataset_bundle.datasource_bundle.placeholder_dataset_identifiers
+        mitm_dataset_bundle = mitm_dataset_bundle.with_visualization_bundle(
+            mk_superset_visualization_bundle(header, mdi, ds_id_map, visualization_types))
 
-def mk_superset_mitm_dataset_bundle(header: Header, dataset_identifier: MitMDatasetIdentifier, db_conn_info: SupersetDBConnectionInfo,
-                                    include_visualizations: bool = False) -> SupersetMitMDatasetBundle:
-    return header_into_superset_mitm_dataset_bundle(header, db_conn_info, dataset_identifier, include_visualizations=include_visualizations)
+    return mitm_dataset_bundle
diff --git a/mitm_tooling/transformation/superset/mitm_specific/__init__.py b/mitm_tooling/transformation/superset/mitm_specific/__init__.py
deleted file mode 100644
index dbd365983a6f8f0ca9607639fca4777e42502377..0000000000000000000000000000000000000000
--- a/mitm_tooling/transformation/superset/mitm_specific/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .maed_visualization import mk_maed_visualization
-from .registry import get_mitm_visualization_factory
\ No newline at end of file
diff --git a/mitm_tooling/transformation/superset/mitm_specific/fallback.py b/mitm_tooling/transformation/superset/mitm_specific/fallback.py
deleted file mode 100644
index 4e076ab1b3c4669232ca5f953343fd5013c3d8c1..0000000000000000000000000000000000000000
--- a/mitm_tooling/transformation/superset/mitm_specific/fallback.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from mitm_tooling.representation import Header
-from mitm_tooling.transformation.superset.definition_bundles import SupersetDatasourceBundle, \
-    SupersetVisualizationBundle
-
-
-def mk_empty_visualization(header: Header, datasource_bundle: SupersetDatasourceBundle) -> SupersetVisualizationBundle:
-    return SupersetVisualizationBundle()
\ No newline at end of file
diff --git a/mitm_tooling/transformation/superset/mitm_specific/maed_charts.py b/mitm_tooling/transformation/superset/mitm_specific/maed_charts.py
deleted file mode 100644
index 523e3e175332309ea8f3a78225ce9d5e2ef276b3..0000000000000000000000000000000000000000
--- a/mitm_tooling/transformation/superset/mitm_specific/maed_charts.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from mitm_tooling.data_types import MITMDataType
-from mitm_tooling.representation import SQLRepresentationSchema, Header, mk_sql_rep_schema
-from mitm_tooling.utilities.python_utils import take_first
-from ..factories.generic_charts import mk_pie_chart, mk_time_series_bar_chart, mk_avg_count_time_series_chart
-from ..factories.core import mk_adhoc_filter
-from ..definition_bundles import SupersetDatasourceBundle
-from ..definitions import SupersetChartDef, FilterOperator
-
-
-def mk_maed_charts(header: Header, superset_datasource_bundle: SupersetDatasourceBundle,
-                   sql_rep_schema: SQLRepresentationSchema | None = None) -> dict[str,
-SupersetChartDef]:
-    sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header)
-    ds_ids = superset_datasource_bundle.placeholder_dataset_identifiers
-
-    event_counts_ts = mk_time_series_bar_chart('Event Counts',
-                                               ds_ids['observations'],
-                                               'type',
-                                               MITMDataType.Text,
-                                               'time',
-                                               groupby_cols=['object'],
-                                               filters=[
-                                                   mk_adhoc_filter('kind', FilterOperator.EQUALS, 'E')]
-                                               )
-    measurement_counts_ts = mk_time_series_bar_chart('Measurement Counts',
-                                                     ds_ids['observations'],
-                                                     'type',
-                                                     MITMDataType.Text,
-                                                     'time',
-                                                     groupby_cols=['object'],
-                                                     filters=[
-                                                         mk_adhoc_filter('kind', FilterOperator.EQUALS, 'M')]
-                                                     )
-    objects_pie = mk_pie_chart('Objects', ds_ids['observations'], 'object', MITMDataType.Text)
-
-    type_name, tbl = take_first(sql_rep_schema.type_tables['measurement'].items())  # TODO
-
-    ts = mk_avg_count_time_series_chart(f'{type_name} Time Series', ds_ids[tbl.name], groupby_cols=['object'],
-                                        filters=[mk_adhoc_filter('kind', FilterOperator.EQUALS, 'M')])
-    return {'event-count-ts': event_counts_ts, 'measurement-count-ts': measurement_counts_ts,
-            'objects-pie': objects_pie, 'ts': ts}
diff --git a/mitm_tooling/transformation/superset/mitm_specific/maed_dashboards.py b/mitm_tooling/transformation/superset/mitm_specific/maed_dashboards.py
deleted file mode 100644
index e83b5c46c7f4c578a2e98cec5c38057b6ba5f3e6..0000000000000000000000000000000000000000
--- a/mitm_tooling/transformation/superset/mitm_specific/maed_dashboards.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from mitm_tooling.representation import Header
-from ..definition_bundles import SupersetDatasourceBundle
-from ..definitions import SupersetDashboardDef, SupersetChartDef
-from ..factories.dashboard import mk_dashboard_def, mk_dashboard_chart
-from .maed_charts import mk_maed_charts
-
-
-def mk_maed_dashboard(header: Header, datasource_bundle: SupersetDatasourceBundle) -> tuple[SupersetDashboardDef, list[SupersetChartDef]]:
-    charts = mk_maed_charts(header, datasource_bundle)
-    chart_grid = [[mk_dashboard_chart(chart_uuid=charts['objects-pie'].uuid, width=4, height=50),
-                   mk_dashboard_chart(chart_uuid=charts['event-count-ts'].uuid, width=4, height=50),
-                   mk_dashboard_chart(chart_uuid=charts['measurement-count-ts'].uuid, width=4, height=50)],
-                  [mk_dashboard_chart(chart_uuid=charts['ts'].uuid, width=12, height=100)]]
-    return mk_dashboard_def('MAED Dashboard', chart_grid=chart_grid, native_filters=[],
-                            description='A rudimentary dashboard to view MAED data.'), list(charts.values())
diff --git a/mitm_tooling/transformation/superset/mitm_specific/maed_visualization.py b/mitm_tooling/transformation/superset/mitm_specific/maed_visualization.py
deleted file mode 100644
index 2ab8b7ebb51d30501ab25a6b125295787d000333..0000000000000000000000000000000000000000
--- a/mitm_tooling/transformation/superset/mitm_specific/maed_visualization.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from mitm_tooling.representation import Header
-from ..definition_bundles import SupersetVisualizationBundle, \
-    SupersetDatasourceBundle
-from .maed_dashboards import mk_maed_dashboard
-
-
-def mk_maed_visualization(header: Header,
-                          superset_datasource_bundle: SupersetDatasourceBundle) -> SupersetVisualizationBundle:
-    dashboard, charts = mk_maed_dashboard(header, superset_datasource_bundle)
-    return SupersetVisualizationBundle(charts=charts, dashboards=[dashboard])
diff --git a/mitm_tooling/transformation/superset/mitm_specific/registry.py b/mitm_tooling/transformation/superset/mitm_specific/registry.py
deleted file mode 100644
index 746062e8684048109424e975f12b211ad4cc3b88..0000000000000000000000000000000000000000
--- a/mitm_tooling/transformation/superset/mitm_specific/registry.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from typing import Callable
-
-from mitm_tooling.definition import MITM
-from mitm_tooling.representation import Header
-from .fallback import mk_empty_visualization
-from .maed_visualization import mk_maed_visualization
-from ..definition_bundles import SupersetDatasourceBundle, SupersetVisualizationBundle
-
-VisualizationBundleFactory = Callable[[Header, SupersetDatasourceBundle], SupersetVisualizationBundle]
-
-
-
-mitm_specific_visualization_factories: dict[
-    MITM, VisualizationBundleFactory] = {
-    MITM.MAED: mk_maed_visualization,
-}
-
-
-def get_mitm_visualization_factory(mitm: MITM) -> VisualizationBundleFactory:
-    if factory := mitm_specific_visualization_factories.get(mitm):
-        return factory
-    else:
-        return mk_empty_visualization
diff --git a/mitm_tooling/transformation/superset/visualizations/__init__.py b/mitm_tooling/transformation/superset/visualizations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/mitm_tooling/transformation/superset/visualizations/abstract.py b/mitm_tooling/transformation/superset/visualizations/abstract.py
new file mode 100644
index 0000000000000000000000000000000000000000..a04671ddf248781fcb8d695a8a9b8cf8346c5545
--- /dev/null
+++ b/mitm_tooling/transformation/superset/visualizations/abstract.py
@@ -0,0 +1,106 @@
+from abc import ABC, abstractmethod
+from typing import Self, Type, Callable
+
+from mitm_tooling.representation import TableName, Header
+from ..asset_bundles.asset_bundles import SupersetVisualizationBundle
+from ..definitions import DatasourceIdentifier, SupersetChartDef, \
+    SupersetDashboardDef
+from ..definitions.mitm_dataset import MitMDatasetIdentifier
+
+ChartDefCollection = dict[str, SupersetChartDef]
+DashboardDefCollection = dict[str, SupersetDashboardDef]
+DatasourceIdentifierMap = dict[TableName, DatasourceIdentifier]
+
+
+class ChartCreator(ABC):
+
+    @abstractmethod
+    def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef:
+        ...
+
+
+class ChartCollectionCreator(ABC):
+
+    @abstractmethod
+    def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection:
+        ...
+
+    @classmethod
+    def cls_from_dict(cls, chart_creators: dict[str, tuple[TableName, ChartCreator]]) -> Type[Self]:
+        chart_creators = dict(chart_creators)
+
+        class ConcreteChartCollectionCreator(cls):
+
+            def __init__(self, header: Header):
+                super().__init__(header)
+                self._chart_creators = chart_creators
+
+            def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection:
+                return {name: cc.mk_chart(ds_id_map[table_name]) for name, (table_name, cc) in
+                        self._chart_creators.items()}
+
+        return ConcreteChartCollectionCreator
+
+
+class DashboardCreator(ABC):
+
+    @property
+    @abstractmethod
+    def chart_collection_creator(self) -> ChartCollectionCreator:
+        ...
+
+    @abstractmethod
+    def mk_dashboard(self, chart_collection: ChartDefCollection) -> SupersetDashboardDef:
+        ...
+
+    def mk_bundle(self, ds_id_map: DatasourceIdentifierMap) -> SupersetVisualizationBundle:
+        chart_collection = self.chart_collection_creator.mk_chart_collection(ds_id_map)
+        return SupersetVisualizationBundle(charts=list(chart_collection.values()),
+                                           dashboards=[self.mk_dashboard(chart_collection)])
+
+
+class VisualizationCreator(ABC):
+
+    def __init__(self, header: Header, **kwargs):
+        self.header = header
+
+    @property
+    @abstractmethod
+    def dashboard_creator_constructors(self) -> dict[str, Callable[
+        [Header, MitMDatasetIdentifier], DashboardCreator]]:
+        ...
+
+    def mk_dashboard_bundles(self,
+                             mitm_dataset_identifier: MitMDatasetIdentifier,
+                             ds_id_map: DatasourceIdentifierMap) -> dict[str, SupersetVisualizationBundle]:
+        return {name: constr(self.header, mitm_dataset_identifier).mk_bundle(ds_id_map) for name, constr in
+                self.dashboard_creator_constructors.items()}
+
+    def mk_bundle(self,
+                  mitm_dataset_identifier: MitMDatasetIdentifier,
+                  ds_id_map: DatasourceIdentifierMap) -> SupersetVisualizationBundle:
+        bundle_map = self.mk_dashboard_bundles(mitm_dataset_identifier, ds_id_map)
+        return SupersetVisualizationBundle.combine(*bundle_map.values())
+
+    @classmethod
+    def wrap_single(cls, dashboard_creator_constr: Callable[
+        [Header, MitMDatasetIdentifier], DashboardCreator]) -> Type[Self]:
+        dashboard_creator_constr_ = dashboard_creator_constr
+
+        class ConcreteVisualizationCreator(cls):
+            @property
+            def dashboard_creator_constructors(self) -> dict[str, Callable[
+                [Header, MitMDatasetIdentifier], DashboardCreator]]:
+                return {'default': dashboard_creator_constr_}
+
+        return ConcreteVisualizationCreator
+
+    @classmethod
+    def empty(cls) -> Type[Self]:
+        class ConcreteVisualizationCreator(cls):
+            @property
+            def dashboard_creator_constructors(self) -> dict[str, Callable[
+                [Header, MitMDatasetIdentifier], DashboardCreator]]:
+                return {}
+
+        return ConcreteVisualizationCreator
diff --git a/mitm_tooling/transformation/superset/visualizations/maed/__init__.py b/mitm_tooling/transformation/superset/visualizations/maed/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/mitm_tooling/transformation/superset/visualizations/maed/charts.py b/mitm_tooling/transformation/superset/visualizations/maed/charts.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ff69590e0ee8fdd98689bd41668549231e28a34
--- /dev/null
+++ b/mitm_tooling/transformation/superset/visualizations/maed/charts.py
@@ -0,0 +1,116 @@
+from collections.abc import Sequence
+
+from mitm_tooling.data_types import MITMDataType
+from mitm_tooling.definition import get_mitm_def, ConceptName, MITM, RelationName, TypeName
+from mitm_tooling.representation import SQLRepresentationSchema, Header, mk_sql_rep_schema
+from mitm_tooling.utilities.identifiers import naive_pluralize
+from ..abstract import DatasourceIdentifierMap, ChartDefCollection, ChartCollectionCreator, ChartCreator
+from ...definitions import FilterOperator, DatasourceIdentifier, SupersetChartDef
+from ...factories.core import mk_adhoc_filter
+from ...factories.generic_charts import mk_pie_chart, mk_time_series_bar_chart, mk_avg_count_time_series_chart
+
+
+class ConceptCountTS(ChartCreator):
+
+    def __init__(self,
+                 concept: ConceptName,
+                 groupby_relations: Sequence[RelationName] = ('object',),
+                 time_relation: RelationName = 'time'):
+        self.concept = concept
+        self.groupby_relations = list(groupby_relations)
+        self.time_relation = time_relation
+        props, rels = get_mitm_def(MITM.MAED).get(concept)
+        self.props = props
+        self.relations = rels
+        defined_relations = set(self.relations.relation_names)
+        assert set(self.groupby_relations) <= defined_relations
+        assert self.time_relation in self.relations.relation_names
+
+    def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef:
+        filters = [mk_adhoc_filter('kind',
+                                   FilterOperator.EQUALS,
+                                   self.props.key)] if self.props.is_sub else None
+        return mk_time_series_bar_chart(f'{self.concept.title()} Counts',
+                                        datasource_identifier,
+                                        'type',
+                                        MITMDataType.Text,
+                                        x_col=self.time_relation,
+                                        groupby_cols=self.groupby_relations,
+                                        filters=filters
+                                        )
+
+
+class RelationPie(ChartCreator):
+
+    def __init__(self, concept: ConceptName, relation: RelationName):
+        self.relation = relation
+        assert relation in get_mitm_def(MITM.MAED).get_relations(concept).relation_names
+
+    def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef:
+        return mk_pie_chart(naive_pluralize(self.relation).title(),
+                            datasource_identifier,
+                            col=self.relation,
+                            dt=MITMDataType.Text)
+
+
+class ConceptTypeAvgCountTS(ChartCreator):
+
+    def __init__(self,
+                 concept: ConceptName,
+                 type_name: TypeName,
+                 groupby_relations: Sequence[RelationName] = ('object',),
+                 time_relation: RelationName = 'time'):
+        self.concept = concept
+        self.type_name = type_name
+        self.groupby_relations = list(groupby_relations)
+        self.time_relation = time_relation
+        props, rels = get_mitm_def(MITM.MAED).get(concept)
+        self.props = props
+        self.relations = rels
+        defined_relations = set(self.relations.relation_names)
+        assert set(self.groupby_relations) <= defined_relations
+        assert self.time_relation in self.relations.relation_names
+
+    def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef:
+        return mk_avg_count_time_series_chart(f'{self.type_name.title()} Time Series',
+                                              datasource_identifier,
+                                              groupby_cols=self.groupby_relations,
+                                              time_col=self.time_relation,
+                                              )
+
+
+class ConceptTypesAvgCountTSCollection(ChartCollectionCreator):
+
+    def __init__(self, concept: ConceptName, sql_rep_schema: SQLRepresentationSchema):
+        super().__init__()
+        self.sql_rep_schema = sql_rep_schema
+        self.concept = concept
+        assert self.concept in self.sql_rep_schema.type_tables
+
+    def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection:
+        charts: ChartDefCollection = {}
+        for type_name, tbl in self.sql_rep_schema.type_tables[self.concept].items():
+            ds_id = ds_id_map[tbl.name]
+            charts[f'{self.concept}-{type_name}-ts'] = ConceptTypeAvgCountTS(self.concept, type_name).mk_chart(ds_id)
+        return charts
+
+
+class BaselineMAEDCharts(ChartCollectionCreator):
+
+    def __init__(self, header: Header, sql_rep_schema: SQLRepresentationSchema | None = None):
+        super().__init__()
+        self.header = header
+        self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header)
+        self.mitm_def = header.mitm_def
+
+    def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection:
+        charts = {}
+        observation_table_name = self.sql_rep_schema.concept_tables['observation'].name
+        # alternatively, mk_table_name('observation')
+        for sub_concept in self.mitm_def.sub_concept_map['observation']:
+            charts[f'{sub_concept}-count-ts'] = ConceptCountTS(sub_concept).mk_chart(ds_id_map[observation_table_name])
+        charts['observation-objects-pie'] = RelationPie('observation', 'object').mk_chart(
+            ds_id_map[observation_table_name])
+        charts.update(ConceptTypesAvgCountTSCollection('measurement',
+                                                       self.sql_rep_schema).mk_chart_collection(ds_id_map))
+        return charts
diff --git a/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py b/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py
new file mode 100644
index 0000000000000000000000000000000000000000..5866f36a90537aca18537d496537bf79334d2ba8
--- /dev/null
+++ b/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py
@@ -0,0 +1,65 @@
+from mitm_tooling.representation import Header, SQLRepresentationSchema, mk_sql_rep_schema
+from mitm_tooling.transformation.superset.definitions import SupersetDashboardDef
+from mitm_tooling.transformation.superset.definitions.mitm_dataset import MitMDatasetIdentifier
+from mitm_tooling.transformation.superset.factories.custom_charts import mk_maed_custom_chart
+from mitm_tooling.transformation.superset.factories.dashboard import mk_dashboard_chart, mk_dashboard_def
+from mitm_tooling.transformation.superset.visualizations.abstract import DashboardCreator, \
+    ChartCollectionCreator, ChartDefCollection, DatasourceIdentifierMap
+from mitm_tooling.transformation.superset.visualizations.maed.charts import BaselineMAEDCharts
+from mitm_tooling.utilities.python_utils import take_first
+
+
+class BaselineMAEDDashboard(DashboardCreator):
+
+    def __init__(self, header: Header, sql_rep_schema: SQLRepresentationSchema | None = None):
+        super().__init__()
+        self.header = header
+        self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header)
+
+    @property
+    def chart_collection_creator(self) -> ChartCollectionCreator:
+        return BaselineMAEDCharts(self.header, self.sql_rep_schema)
+
+    def mk_dashboard(self, chart_collection: ChartDefCollection) -> SupersetDashboardDef:
+        x = take_first(self.header.as_dict['measurement'])
+
+        chart_grid = [
+            [mk_dashboard_chart(chart_uuid=chart_collection['observation-objects-pie'].uuid, width=4, height=50),
+             mk_dashboard_chart(chart_uuid=chart_collection['event-count-ts'].uuid, width=4, height=50),
+             mk_dashboard_chart(chart_uuid=chart_collection['measurement-count-ts'].uuid, width=4, height=50)],
+            [mk_dashboard_chart(chart_uuid=chart_collection[f'measurement-{x}-ts'].uuid, width=12, height=100)]]
+        return mk_dashboard_def('MAED Dashboard',
+                                chart_grid=chart_grid,
+                                description='A rudimentary dashboard to view MAED data.')
+
+
+class ExperimentalMAEDDashboard(DashboardCreator):
+
+    def __init__(self,
+                 header: Header,
+                 mitm_dataset_identifier: MitMDatasetIdentifier,
+                 sql_rep_schema: SQLRepresentationSchema | None = None):
+        super().__init__()
+        self.header = header
+        self.mitm_dataset_identifier = mitm_dataset_identifier
+        self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header)
+
+    @property
+    def chart_collection_creator(self) -> ChartCollectionCreator:
+        mitm_dataset_identifier = self.mitm_dataset_identifier
+
+        class CustomChartCollectionCreator(ChartCollectionCreator):
+
+            def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection:
+                return {
+                    'custom': mk_maed_custom_chart('Custom MAED Chart',
+                                                   mitm_dataset_identifier,
+                                                   ds_id_map['observations'])}
+
+        return CustomChartCollectionCreator()
+
+    def mk_dashboard(self, chart_collection: ChartDefCollection) -> SupersetDashboardDef:
+        chart_grid = [[mk_dashboard_chart(chart_uuid=chart_collection['custom'].uuid, width=12, height=400)]]
+        return mk_dashboard_def('Experimental MAED Dashboard',
+                                chart_grid,
+                                description='An experimental dashboard to view MAED data.')
diff --git a/mitm_tooling/transformation/superset/visualizations/maed/registry.py b/mitm_tooling/transformation/superset/visualizations/maed/registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f0602e06e1f4c4e27a38aa017f04b1b2e60ae91
--- /dev/null
+++ b/mitm_tooling/transformation/superset/visualizations/maed/registry.py
@@ -0,0 +1,18 @@
+from enum import StrEnum
+from typing import Type
+
+from mitm_tooling.transformation.superset.visualizations.abstract import VisualizationCreator
+from mitm_tooling.transformation.superset.visualizations.maed.dashboards import BaselineMAEDDashboard, \
+    ExperimentalMAEDDashboard
+
+
+class MAEDVisualizationType(StrEnum):
+    Baseline = 'baseline'
+    Experimental = 'experimental'
+
+
+maed_visualization_creators: dict[MAEDVisualizationType, Type[VisualizationCreator]] = {
+    MAEDVisualizationType.Baseline: VisualizationCreator.wrap_single(lambda h, mdi: BaselineMAEDDashboard(h)),
+    MAEDVisualizationType.Experimental: VisualizationCreator.wrap_single(lambda h, mdi: ExperimentalMAEDDashboard(h,
+                                                                                                                  mdi)),
+}
diff --git a/mitm_tooling/transformation/superset/visualizations/registry.py b/mitm_tooling/transformation/superset/visualizations/registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..ece717ca0b491db131333c6490fb7f690932c679
--- /dev/null
+++ b/mitm_tooling/transformation/superset/visualizations/registry.py
@@ -0,0 +1,28 @@
+from typing import Type
+
+from mitm_tooling.definition import MITM
+from mitm_tooling.representation import Header
+from .maed.registry import MAEDVisualizationType, maed_visualization_creators
+from ..definitions.mitm_dataset import MitMDatasetIdentifier
+from ..visualizations.abstract import VisualizationCreator, DatasourceIdentifierMap, SupersetVisualizationBundle
+
+VisualizationType = MAEDVisualizationType | None
+
+mitm_visualization_creators = {
+    MITM.MAED: maed_visualization_creators
+}
+
+
+def get_mitm_visualization_creator(mitm: MITM, visualization_type: VisualizationType) -> Type[
+                                                                                             VisualizationCreator] | None:
+    if creators := mitm_visualization_creators.get(mitm):
+        if (creator := creators.get(visualization_type)) is not None:
+            return creator
+
+
+def mk_visualization(visualization_type: VisualizationType,
+                     header: Header,
+                     mitm_dataset_identifier: MitMDatasetIdentifier,
+                     ds_id_map: DatasourceIdentifierMap) -> SupersetVisualizationBundle | None:
+    if (creator := get_mitm_visualization_creator(header.mitm, visualization_type)) is not None:
+        return creator(header).mk_bundle(mitm_dataset_identifier, ds_id_map)
diff --git a/mitm_tooling/utilities/identifiers.py b/mitm_tooling/utilities/identifiers.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b08011f6d6c4db885e1635d3ace2112d7bfc064
--- /dev/null
+++ b/mitm_tooling/utilities/identifiers.py
@@ -0,0 +1,19 @@
+import uuid
+from uuid import UUID
+
+import pydantic
+
+
+def mk_uuid() -> pydantic.UUID4:
+    return uuid.uuid4()
+
+
+def mk_short_uuid_str(existing_uuid: uuid.UUID | None = None) -> str:
+    return (existing_uuid or mk_uuid()).hex[:12]
+
+
+def name_plus_uuid(name: str, uuid: UUID | None = None, sep: str = '-') -> str:
+    return f'{name}{sep}{mk_short_uuid_str(uuid)}'
+
+def naive_pluralize(name: str) -> str:
+    return f'{name}s'
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 118d222a229dd673b134114ebe763000ab8a1f08..fde4501ba052aac727442cc359d90d6ce6753b35 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "mitm-tooling"
-version = "0.4.7"
+version = "0.4.8"
 description = ""
 authors = ["Leah Tacke genannt Unterberg <leah.tgu@pads.rwth-aachen.de>"]
 readme = "README.md"
diff --git a/test/something.py b/test/something.py
index e36b7394c8b39d8d6e3b0c040d8f8ef9e2a73473..f48825fe2fc0f27fd10b2bd3f242f71511896254 100644
--- a/test/something.py
+++ b/test/something.py
@@ -3,8 +3,10 @@ import unittest
 
 from pydantic import AnyUrl
 
-from mitm_tooling.transformation.superset.common import name_plus_uuid, SupersetDBConnectionInfo
+from mitm_tooling.transformation.superset.common import DBConnectionInfo
 from mitm_tooling.transformation.superset.definitions.mitm_dataset import MitMDatasetIdentifier
+from mitm_tooling.transformation.superset.visualizations.maed.registry import MAEDVisualizationType
+from mitm_tooling.utilities.identifiers import name_plus_uuid
 
 
 class MyTestCase(unittest.TestCase):
@@ -21,7 +23,7 @@ class MyTestCase(unittest.TestCase):
             HeaderEntry(concept='segment', kind='S', type_name='annotation', attributes=(),
                         attribute_dtypes=()),
             HeaderEntry(concept='segment_data', kind='SD', type_name='annotation_info', attributes=('y',),
-                        attribute_dtypes=(MITMDataType.Json, )),
+                        attribute_dtypes=(MITMDataType.Json,)),
         ))
         sql_rep = mk_sql_rep_schema(h)
         print(sql_rep.meta)
@@ -62,36 +64,39 @@ class MyTestCase(unittest.TestCase):
             os.remove('synthetic-variation.sqlite')
         mk_sqlite(syn, 'synthetic-variation.sqlite')
 
-    def test_superset_dataset_assets(self):
+    def test_mitm_datasource_assets(self):
         from mitm_tooling.io import importing, MITM
         syn = importing.read_zip('synthetic.maed', MITM.MAED, header_only=True)
         from mitm_tooling.transformation.superset import mk_superset_mitm_dataset_bundle, write_superset_import_as_zip
-        dataset_import = mk_superset_mitm_dataset_bundle(syn.header,
-                                                         MitMDatasetIdentifier(
-                                                             dataset_name=name_plus_uuid('SyntheticExampleDataset')),
-                                                         SupersetDBConnectionInfo(
+        dataset_bundle = mk_superset_mitm_dataset_bundle(syn.header,
+                                                         DBConnectionInfo(
                                                              sql_alchemy_uri=AnyUrl(
                                                                  'sqlite://synthetic-variation.sqlite'),
                                                              explicit_db_name='SyntheticExampleDB',
-                                                             schema_name='main'))
-        write_superset_import_as_zip('superset_dataset_import', dataset_import.to_import())
+                                                             schema_name='main'),
+                                                         MitMDatasetIdentifier(
+                                                             dataset_name=name_plus_uuid('SyntheticExampleDataset'))
+                                                         )
+
+        write_superset_import_as_zip('superset_datasource_import', dataset_bundle.datasource_bundle.to_import())
+        write_superset_import_as_zip('superset_mitm_dataset_import', dataset_bundle.to_import())
 
     def test_superset_viz_assets(self):
         from mitm_tooling.io import importing, MITM
         syn = importing.read_zip('synthetic.maed', MITM.MAED, header_only=True)
         from mitm_tooling.transformation.superset import mk_superset_mitm_dataset_bundle, write_superset_import_as_zip
-        dataset_import = mk_superset_mitm_dataset_bundle(syn.header,
-                                                         MitMDatasetIdentifier(
-                                                             dataset_name=name_plus_uuid('SyntheticExampleDataset')),
-                                                         SupersetDBConnectionInfo(
-                                                             sql_alchemy_uri=AnyUrl(
-                                                                 'sqlite://synthetic-variation.sqlite'),
-                                                             explicit_db_name='SyntheticExampleDB',
-                                                             schema_name='main'))
-
-        from mitm_tooling.transformation.superset import mk_superset_visualization_bundle
-        visualization_import = mk_superset_visualization_bundle(syn.header, dataset_import.datasource_bundle)
-        write_superset_import_as_zip('superset_viz_import', visualization_import.to_import())
+        full_bundle = mk_superset_mitm_dataset_bundle(syn.header,
+                                                      DBConnectionInfo(
+                                                          sql_alchemy_uri=AnyUrl(
+                                                              'sqlite://synthetic-variation.sqlite'),
+                                                          explicit_db_name='SyntheticExampleDB',
+                                                          schema_name='main'),
+                                                      MitMDatasetIdentifier(
+                                                          dataset_name=name_plus_uuid('SyntheticExampleDataset')),
+                                                      visualization_types=[MAEDVisualizationType.Baseline]
+                                                      )
+        write_superset_import_as_zip('superset_mitm_dataset_with_viz_import', full_bundle.to_import())
+        write_superset_import_as_zip('superset_viz_import', full_bundle.visualization_bundle.to_import())
 
 
 if __name__ == '__main__':