{"id":35398,"date":"2026-03-30T09:07:55","date_gmt":"2026-03-30T09:07:55","guid":{"rendered":"https:\/\/streamhub.co.uk\/?p=35398"},"modified":"2026-03-30T09:28:43","modified_gmt":"2026-03-30T09:28:43","slug":"architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns","status":"publish","type":"post","link":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/","title":{"rendered":"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &#038; Factory Patterns"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 4<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span><p>[et_pb_section fb_built=&#8221;1&#8243; _builder_version=&#8221;4.16&#8243; da_disable_devices=&#8221;off|off|off&#8221; global_colors_info=&#8221;{}&#8221; da_is_popup=&#8221;off&#8221; da_exit_intent=&#8221;off&#8221; da_has_close=&#8221;on&#8221; da_alt_close=&#8221;off&#8221; da_dark_close=&#8221;off&#8221; da_not_modal=&#8221;on&#8221; da_is_singular=&#8221;off&#8221; da_with_loader=&#8221;off&#8221; da_has_shadow=&#8221;on&#8221;][et_pb_row _builder_version=&#8221;4.16&#8243; background_size=&#8221;initial&#8221; background_position=&#8221;top_left&#8221; background_repeat=&#8221;repeat&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_column type=&#8221;4_4&#8243; _builder_version=&#8221;4.16&#8243; custom_padding=&#8221;|||&#8221; global_colors_info=&#8221;{}&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_text _builder_version=&#8221;4.27.0&#8243; background_size=&#8221;initial&#8221; background_position=&#8221;top_left&#8221; background_repeat=&#8221;repeat&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"_n7zlglyw _4t3i8vuz _1pby1ial _p12f18ua _1bsb1osq _1e0c1txw _4cvr1h6o _vchhusvi _1ltv1d4h _1bah1vxp _kqsw1n9t _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2lx21bp4 _bfhkvuon\" data-testid=\"content-header-container\">\n<div class=\"css-lxgc1w\">\n<div class=\"_bfhk1j28 _kqswlr7h _lcxv12je _1bsb1osq _1e0c11p5\">\n<div class=\"css-81kfic\">\n<div id=\"object-header-container-id\" class=\"css-tikxu8\" data-testid=\"object-header-container\">\n<div class=\"css-1lbnbxa\" data-testid=\"object-header-actions-container\">\n<div class=\"_1e0c1txw _4cvr1h6o\" data-testid=\"lastEdited-action-container-without-separator\" data-vc=\"lastEdited-action-container-without-separator\">\n<div class=\"css-1b74e0e\">In the <a href=\"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics\/\">previous post<\/a>, we introduced the architectural challenges behind supporting multiple chart types within a scalable dashboard solution. We established that different visualisation libraries require data in different structural formats. This mismatch makes the transformation layer a critical part of the architecture.<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div data-testid=\"content-container-component\">\n<div class=\"_16jlcs5v _1o9zkb7n _i0dlf1ug _1e0c1ule _1bsb1osq\">\n<article>\n<div class=\"_19itglyw _vchhusvi _r06hglyw\" data-vc=\"view-page-main-content-container\" data-testid=\"view-page-main-content-container\">\n<div id=\"content\" class=\"highlighter-context page view\" data-inline-comments-target=\"true\" data-testid=\"page-content-only\">\n<div class=\"_19itglyw _vchhusvi _r06hglyw _19pkidpf _2hwx1wug _otyr1epz _18u01wug _1bsb1osq\">\n<div id=\"main-content\" class=\"wiki-content css-bm2ef2 e5xcnr80\" data-testid=\"pageContentRendererTestId\" data-vc=\"pageContentRendererTestId\" data-test-appearance=\"full-page\">\n<div class=\"renderer-overrides\">\n<div class=\"css-3qfej8\">\n<div class=\"ak-renderer-wrapper is-full-page css-pw7jst\">\n<div class=\"css-45aswx\" role=\"none\">\n<div class=\"ak-renderer-document\">\n<p data-renderer-start-pos=\"404\" data-local-id=\"ccabb01817b0\">In this post, we focus on how the system decides <em data-renderer-mark=\"true\">which<\/em> transformation logic to apply for a given chart type. Two design patterns make this possible: <strong data-renderer-mark=\"true\">Strategy<\/strong> and <strong data-renderer-mark=\"true\">Factory<\/strong>. Together, they allow us to select and execute the correct transformation algorithm at runtime without introducing tight coupling or complex conditionals.<\/p>\n<h2 data-local-id=\"530549f223ec\" data-renderer-start-pos=\"731\">\u00a0<\/h2>\n<h2 id=\"Strategy-Pattern:-Chart-Specific-Data-Transformations\" data-local-id=\"530549f223ec\" data-renderer-start-pos=\"731\"><strong>Strategy Pattern: Chart-Specific Data Transformations<\/strong><\/h2>\n<p data-renderer-start-pos=\"786\" data-local-id=\"82c9e9062d20\">The Strategy pattern allows us to define a family of algorithms (transformations), encapsulate each one, and make them interchangeable. In our chart module, each chart type has its own transformation strategy responsible for converting raw report data into a format suitable for the rendering library.<\/p>\n<h3 data-local-id=\"073c55767e02\" data-renderer-start-pos=\"1089\">\u00a0<\/h3>\n<h3 id=\"Structure\" data-local-id=\"073c55767e02\" data-renderer-start-pos=\"1089\"><strong>Structure<\/strong><\/h3>\n<p data-renderer-start-pos=\"1100\" data-local-id=\"82758c600e95\">The implementation revolves around a contract interface that defines two key methods:<\/p>\n<pre data-renderer-start-pos=\"1100\" data-local-id=\"82758c600e95\"><span class=\"\" data-testid=\"renderer-code-block-line-1\" data-ds--code--row=\"\"><span class=\"token comment\">\/\/ Interface defining the strategy contract<\/span> \n<\/span><span class=\"\" data-testid=\"renderer-code-block-line-2\" data-ds--code--row=\"\"><span class=\"token keyword module\">export<\/span> <span class=\"token keyword\">interface<\/span> <span class=\"token class-name token maybe-class-name\">IChartDataTransformer<\/span> \n<span class=\"token punctuation\">{<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-3\" data-ds--code--row=\"\"> \n<span class=\"token doc-comment comment\">  \/** <\/span><\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-4\" data-ds--code--row=\"\"> \n  * Determines if this transformer supports the given chart type. <\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-5\" data-ds--code--row=\"\"> \n  * Each transformer checks if it can handle the visualization type. <\/span><span class=\"\" data-testid=\"renderer-code-block-line-6\" data-ds--code--row=\"\"><span class=\"token doc-comment comment\"> \n  *\/<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-7\" data-ds--code--row=\"\"> \n<span class=\"token function\">  supports<\/span><span class=\"token punctuation\">(<\/span>chartType<span class=\"token operator\">:<\/span> <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">boolean<\/span><span class=\"token punctuation\">;<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-9\" data-ds--code--row=\"\"> \n<span class=\"token doc-comment comment\"> \n  \/** <\/span><\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-10\" data-ds--code--row=\"\"> \n  * Transforms raw report data into chart-specific format. <\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-11\" data-ds--code--row=\"\"> \n  * Each transformer implements its own transformation algorithm. <\/span><span class=\"\" data-testid=\"renderer-code-block-line-12\" data-ds--code--row=\"\"><span class=\"token doc-comment comment\"> \n  *\/<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-13\" data-ds--code--row=\"\"> \n<span class=\"token function\">  transform<\/span><span class=\"token punctuation\">(<\/span>reportData<span class=\"token operator\">:<\/span> <span class=\"token builtin\">any<\/span><span class=\"token punctuation\">,<\/span> chartType<span class=\"token operator\">?<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> report<span class=\"token operator\">?<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">any<\/span><span class=\"token punctuation\">)<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">any<\/span><span class=\"token punctuation\">;<\/span> \n<\/span><span class=\"\" data-testid=\"renderer-code-block-line-14\" data-ds--code--row=\"\"><span class=\"token punctuation\">}<\/span><\/span><\/pre>\n<p data-renderer-start-pos=\"1693\" data-local-id=\"af3f1adf56e1\">Each chart transformer implements this interface:<\/p>\n<pre>@Injectable({ providedIn: 'root' })\nexport class BarChartTransformer implements IChartDataTransformer {\n  supports(chartType: string): boolean {\n    \/\/ Returns true for bar chart type identifiers\n  }\n\n  transform(reportData: any): any[] {\n    \/\/ Analyzes dimensions and metrics\n    \/\/ Normalizes data structure\n    \/\/ Applies color mapping\n    \/\/ Enriches with metadata\n    \/\/ Returns chart library-compatible format\n  }\n}<\/pre>\n<h3 data-local-id=\"e67c41cfef31\" data-renderer-start-pos=\"2168\">\u00a0<\/h3>\n<h3 id=\"What-Each-Strategy-Handles\" data-local-id=\"e67c41cfef31\" data-renderer-start-pos=\"2168\"><strong>What Each Strategy Handles<\/strong><\/h3>\n<p data-renderer-start-pos=\"2196\" data-local-id=\"a022bc4bc95c\">Each transformation strategy encapsulates logic specific to a chart type. This includes:<\/p>\n<ul class=\"ak-ul\" data-local-id=\"ffe16b8e-b450-4f28-be3b-004eb97f39ed\" data-indent-level=\"1\">\n<li>\n<p data-renderer-start-pos=\"2288\" data-local-id=\"9e866dbe518d\"><strong data-renderer-mark=\"true\">Dimensional Analysis<\/strong>: Understanding how many dimensions and metrics are present, which affects grouping and series construction.<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"2420\" data-local-id=\"3a70933e85fe\"><strong data-renderer-mark=\"true\">Data Normalisation<\/strong>: Converting the consistent API response into the structure expected by the specific visualisation library (e.g., row-based data for ag-Grid vs. series-based arrays for Highcharts).<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"2623\" data-local-id=\"1ad48e333646\"><strong data-renderer-mark=\"true\">Metadata Enrichment<\/strong>: Adding display labels, tooltip formatting, and contextual information derived from report metadata.<\/p>\n<\/li>\n<\/ul>\n<p data-renderer-start-pos=\"2747\" data-local-id=\"570d0141ee36\"><em data-renderer-mark=\"true\"><strong data-renderer-mark=\"true\"><sub data-renderer-mark=\"true\">*Quick note: <\/sub><\/strong><sub data-renderer-mark=\"true\">At first glance, this example may appear to violate the Single Responsibility Principle. However, in practice, the transformer acts as an orchestration layer, delegating specific responsibilities to dedicated services. The example is simplified to keep the focus on the pattern rather than internal service composition.<\/sub><\/em><\/p>\n<h3 data-local-id=\"a01d9ce39418\" data-renderer-start-pos=\"3075\">\u00a0<\/h3>\n<h3 id=\"Benefits\" data-local-id=\"a01d9ce39418\" data-renderer-start-pos=\"3075\"><strong>Benefits<\/strong><\/h3>\n<ul class=\"ak-ul\" data-local-id=\"e4a993de-8a97-41a6-ba1c-17fee495bd86\" data-indent-level=\"1\">\n<li>\n<p data-renderer-start-pos=\"3087\" data-local-id=\"e4ab746f369c\">Each chart type can evolve independently without impacting others.<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"3157\" data-local-id=\"ea4959bbbcac\">Adding support for a new chart only requires implementing the interface\u2014no changes to existing code.<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"3261\" data-local-id=\"415fa09266fd\">Clear separation of concerns between transformation and rendering logic.<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"3337\" data-local-id=\"1e1e9466e0f5\">High testability: each strategy can be unit-tested in isolation using mock datasets.<\/p>\n<\/li>\n<\/ul>\n<h2 data-local-id=\"beabc8041b73\" data-renderer-start-pos=\"3426\">\u00a0<\/h2>\n<h2 id=\"Factory-Pattern:-Selecting-the-Right-Transformer\" data-local-id=\"beabc8041b73\" data-renderer-start-pos=\"3426\"><strong>Factory Pattern: Selecting the Right Transformer<\/strong><\/h2>\n<p data-renderer-start-pos=\"3476\" data-local-id=\"43f16b6c9f94\">While strategies encapsulate transformation logic, we still need a mechanism to select the correct strategy at runtime. This is where the Factory pattern comes into play.<\/p>\n<p data-renderer-start-pos=\"3648\" data-local-id=\"075ef0fbc53d\">The factory acts as a lookup mechanism that returns the appropriate transformer based on the chart type, without consumers needing to know about concrete implementations.<\/p>\n<h3 data-local-id=\"2d5dbbb9b494\" data-renderer-start-pos=\"3820\">\u00a0<\/h3>\n<h3 id=\"Structure.1\" data-local-id=\"2d5dbbb9b494\" data-renderer-start-pos=\"3820\"><strong>Structure<\/strong><\/h3>\n<p data-renderer-start-pos=\"3831\" data-local-id=\"57936ac4e4ca\">We use an injection token to collect all chart transformers, and a factory that receives them via dependency injection:<\/p>\n<div class=\"ak-renderer-sticky-safe-breakout-wrapper ak-renderer-flex-center-wrapper css-l5clsc\">\n<div class=\"ak-renderer-sticky-safe-breakout-inner fabric-editor-breakout-mark fabric-editor-block-mark css-ozd7xs\" data-mode=\"wide\" data-has-width=\"true\" data-width=\"760\">\n<div class=\"code-block css-1l9rc3g\" data-local-id=\"49c78b5da0cb\">\n<div class=\"css-1sws8jd\">\n<div class=\"css-1u47mc9\">\n<div role=\"presentation\">\n<pre>\/\/ Injection token for collecting all chart transformers\nexport const CHART_TRANSFORMERS = new InjectionToken&lt;IChartDataTransformer[]&gt;(\n  'CHART_TRANSFORMERS'\n);\n\n@Injectable({ providedIn: 'root' })\nexport class ChartDataTransformerFactory {\n  constructor(@Inject(CHART_TRANSFORMERS) private transformers: IChartDataTransformer[]) {}\n\n  getTransformer(chartType: string): IChartDataTransformer | null {\n    \/\/ Searches for transformer where supports() returns true\n    \/\/ This is the common method enforced by the interface defined earlier.\n    return this.transformers.find(t =&gt; t.supports(chartType)) || null;\n    \/\/ Returns null if no transformer found\n  }\n}<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p data-renderer-start-pos=\"4615\" data-local-id=\"36da5488bd92\">The factory is used by the main chart data transformer (which participates in the pipeline we&#8217;ll see in the next post):<\/p>\n<div class=\"ak-renderer-sticky-safe-breakout-wrapper ak-renderer-flex-center-wrapper css-l5clsc\">\n<div class=\"ak-renderer-sticky-safe-breakout-inner fabric-editor-breakout-mark fabric-editor-block-mark css-ozd7xs\" data-mode=\"wide\" data-has-width=\"true\" data-width=\"760\">\n<div class=\"code-block css-1l9rc3g\" data-local-id=\"824a1bbc604e\">\n<div class=\"css-1sws8jd\">\n<div class=\"css-1u47mc9\">\n<div role=\"presentation\">\n<pre>@Injectable({ providedIn: 'root' })\nexport class ChartDataTransformer implements ReportTransformer {\n  constructor(private chartFactory: ChartDataTransformerFactory) {}\n\n  isApplicable(reportData: any): boolean {\n    \/\/ Checks for visualization type presence\n  }\n\n  apply(reportData: any): any[] {\n    \/\/ Gets transformer from factory\n    const transformer = this.chartFactory.getTransformer(..);\n    if (!transformer) {\n      \/\/ Throws error if no transformer found\n    }\n    \/\/ Delegates to transformer's transform method\n    \/\/ Remember, this is part of the contract defined earlier in strategy pattern.\n    return transformer.transform(..);\n  }\n}<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h3 data-local-id=\"b5af5d52f748\" data-renderer-start-pos=\"5388\">\u00a0<\/h3>\n<h3 id=\"How-It-Fits-Together\" data-local-id=\"b5af5d52f748\" data-renderer-start-pos=\"5388\"><strong>How It Fits Together<\/strong><\/h3>\n<p data-renderer-start-pos=\"5410\" data-local-id=\"4957761eb864\">The factory leverages Angular&#8217;s dependency injection to receive all registered transformers via the custom token. It acts as a <strong data-renderer-mark=\"true\">registry and lookup<\/strong>: it doesn&#8217;t create transformers itself\u2014they&#8217;re registered elsewhere (we\u2019ll cover that in a future post). The factory is completely decoupled from specific transformer implementations.<\/p>\n<p data-renderer-start-pos=\"5738\" data-local-id=\"dc428d579675\">When no transformer is found for a given chart type, the system throws a descriptive error rather than returning null. This fail-fast approach ensures bugs are caught early and production errors are clearly identifiable.<\/p>\n<h3 data-local-id=\"e1e266f1a158\" data-renderer-start-pos=\"5960\">\u00a0<\/h3>\n<h3 id=\"Benefits.1\" data-local-id=\"e1e266f1a158\" data-renderer-start-pos=\"5960\"><strong>Benefits<\/strong><\/h3>\n<ul class=\"ak-ul\" data-local-id=\"232d237aa9f0\" data-indent-level=\"1\">\n<li>\n<p data-renderer-start-pos=\"5972\" data-local-id=\"31eb25781992\">Decouples transformer selection from usage\u2014consumers don&#8217;t need to know which transformer to use<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"6072\" data-local-id=\"1eac0b7457f9\">Centralised logic for transformer selection\u2014single point of control<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"6143\" data-local-id=\"a4dec730abd1\">Easy to extend with new chart types\u2014just register a new transformer<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"6214\" data-local-id=\"0e8b70bfddcd\">Type-safe transformer selection through TypeScript interfaces<\/p>\n<\/li>\n<li>\n<p data-renderer-start-pos=\"6279\" data-local-id=\"4247f3994b02\">Strategies are unaware of each other; the factory is unaware of strategy implementation details<\/p>\n<\/li>\n<\/ul>\n<h2 data-local-id=\"15ff541504eb\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"heading\" data-prosemirror-node-block=\"true\" data-pm-slice=\"1 1 []\">\u00a0<\/h2>\n<h2 data-local-id=\"15ff541504eb\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"heading\" data-prosemirror-node-block=\"true\" data-pm-slice=\"1 1 []\"><strong>Conclusion<\/strong><\/h2>\n<p data-local-id=\"668005f0118c\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"paragraph\" data-prosemirror-node-block=\"true\">Pairing Strategy with a simple Factory keeps the chart transformation layer flexible and disciplined. Each chart type encapsulates its transformation logic behind a common interface, while the factory selects the appropriate strategy at runtime. This approach creates a fail-fast system that detects misconfigurations early and allows new visualisations to be added without modifying existing code.<\/p>\n<hr role=\"presentation\" \/>\n<p data-local-id=\"668005f0118c\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"paragraph\" data-prosemirror-node-block=\"true\">\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<\/div>\n<\/div>\n<p>[\/et_pb_text][et_pb_team_member name=&#8221;Ravindra Soman&#8221; position=&#8221;Senior Full Stack Data Engineer&#8221; image_url=&#8221;https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/07\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-07-27-16.02.20-150&#215;150.png&#8221; linkedin_url=&#8221;https:\/\/www.linkedin.com\/in\/somanravindra\/&#8221; _builder_version=&#8221;4.27.0&#8243; _module_preset=&#8221;default&#8221; border_radii_image=&#8221;off||||&#8221; global_colors_info=&#8221;{}&#8221;]Ravindra is a Senior Full Stack Data Engineer specialising in frontend architecture and complex data visualisation systems. In his spare time, he likes to cook new dishes for his family, read fiction and travel the world.<br \/>\n[\/et_pb_team_member][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 4<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span><\/p>\n<p>In the previous post, we introduced the architectural challenges behind supporting multiple chart types within a scalable dashboard solution. We established that different visualisation libraries require data in different structural formats. This mismatch makes the transformation layer a critical part of the architecture. In this post, we focus on how the system decides which transformation [&hellip;]<\/p>\n","protected":false},"author":21,"featured_media":34720,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"on","_et_pb_old_content":"<div class=\"_n7zlglyw _4t3i8vuz _1pby1ial _p12f18ua _1bsb1osq _1e0c1txw _4cvr1h6o _vchhusvi _1ltv1d4h _1bah1vxp _kqsw1n9t _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2lx21bp4 _bfhkvuon\" data-testid=\"content-header-container\"><div class=\"css-lxgc1w\"><div class=\"_bfhk1j28 _kqswlr7h _lcxv12je _1bsb1osq _1e0c11p5\"><div class=\"css-81kfic\"><div id=\"object-header-container-id\" class=\"css-tikxu8\" data-testid=\"object-header-container\"><div class=\"css-1lbnbxa\" data-testid=\"object-header-actions-container\"><div class=\"_1e0c1txw _4cvr1h6o\" data-testid=\"lastEdited-action-container-without-separator\" data-vc=\"lastEdited-action-container-without-separator\"><div class=\"css-1b74e0e\">\u00a0<\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><div data-testid=\"content-container-component\"><div class=\"_16jlcs5v _1o9zkb7n _i0dlf1ug _1e0c1ule _1bsb1osq\"><article><div class=\"_19itglyw _vchhusvi _r06hglyw\" data-vc=\"view-page-main-content-container\" data-testid=\"view-page-main-content-container\"><div id=\"content\" class=\"highlighter-context page view\" data-inline-comments-target=\"true\" data-testid=\"page-content-only\"><div class=\"_19itglyw _vchhusvi _r06hglyw _19pkidpf _2hwx1wug _otyr1epz _18u01wug _1bsb1osq\"><div id=\"main-content\" class=\"wiki-content css-bm2ef2 e5xcnr80\" data-testid=\"pageContentRendererTestId\" data-vc=\"pageContentRendererTestId\" data-test-appearance=\"full-page\"><div class=\"renderer-overrides\"><div class=\"css-3qfej8\"><div class=\"ak-renderer-wrapper is-full-page css-pw7jst\"><div class=\"css-45aswx\" role=\"none\"><div class=\"ak-renderer-document\"><p data-renderer-start-pos=\"79\" data-local-id=\"1eb9ec9e2e1e\">In the <a href=\"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics\/\">previous post<\/a>, we introduced the architectural challenges behind supporting multiple chart types within a scalable dashboard solution. We established that different visualisation libraries require data in different structural formats. This mismatch makes the transformation layer a critical part of the architecture.<\/p><p data-renderer-start-pos=\"404\" data-local-id=\"ccabb01817b0\">In this post, we focus on how the system decides <em data-renderer-mark=\"true\">which<\/em> transformation logic to apply for a given chart type. Two design patterns make this possible: <strong data-renderer-mark=\"true\">Strategy<\/strong> and <strong data-renderer-mark=\"true\">Factory<\/strong>. Together, they allow us to select and execute the correct transformation algorithm at runtime without introducing tight coupling or complex conditionals.<\/p><hr role=\"presentation\" \/><h2 id=\"Strategy-Pattern:-Chart-Specific-Data-Transformations\" data-local-id=\"530549f223ec\" data-renderer-start-pos=\"731\">Strategy Pattern: Chart-Specific Data Transformations<\/h2><p data-renderer-start-pos=\"786\" data-local-id=\"82c9e9062d20\">The Strategy pattern allows us to define a family of algorithms (transformations), encapsulate each one, and make them interchangeable. In our chart module, each chart type has its own transformation strategy responsible for converting raw report data into a format suitable for the rendering library.<\/p><h3 id=\"Structure\" data-local-id=\"073c55767e02\" data-renderer-start-pos=\"1089\">Structure<\/h3><p data-renderer-start-pos=\"1100\" data-local-id=\"82758c600e95\">The implementation revolves around a contract interface that defines two key methods:<\/p><pre data-renderer-start-pos=\"1100\" data-local-id=\"82758c600e95\"><span class=\"\" data-testid=\"renderer-code-block-line-1\" data-ds--code--row=\"\"><span class=\"token comment\">\/\/ Interface defining the strategy contract<\/span> <br \/><\/span><span class=\"\" data-testid=\"renderer-code-block-line-2\" data-ds--code--row=\"\"><span class=\"token keyword module\">export<\/span> <span class=\"token keyword\">interface<\/span> <span class=\"token class-name token maybe-class-name\">IChartDataTransformer<\/span> <br \/><span class=\"token punctuation\">{<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-3\" data-ds--code--row=\"\"> <br \/><span class=\"token doc-comment comment\">  \/** <\/span><\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-4\" data-ds--code--row=\"\"> <br \/>  * Determines if this transformer supports the given chart type. <\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-5\" data-ds--code--row=\"\"> <br \/>  * Each transformer checks if it can handle the visualization type. <\/span><span class=\"\" data-testid=\"renderer-code-block-line-6\" data-ds--code--row=\"\"><span class=\"token doc-comment comment\"> <br \/>  *\/<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-7\" data-ds--code--row=\"\"> <br \/><span class=\"token function\">  supports<\/span><span class=\"token punctuation\">(<\/span>chartType<span class=\"token operator\">:<\/span> <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">boolean<\/span><span class=\"token punctuation\">;<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-9\" data-ds--code--row=\"\"> <br \/><span class=\"token doc-comment comment\"> <br \/>  \/** <\/span><\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-10\" data-ds--code--row=\"\"> <br \/>  * Transforms raw report data into chart-specific format. <\/span><span class=\"token doc-comment comment\" data-testid=\"renderer-code-block-line-11\" data-ds--code--row=\"\"> <br \/>  * Each transformer implements its own transformation algorithm. <\/span><span class=\"\" data-testid=\"renderer-code-block-line-12\" data-ds--code--row=\"\"><span class=\"token doc-comment comment\"> <br \/>  *\/<\/span> <\/span><span class=\"\" data-testid=\"renderer-code-block-line-13\" data-ds--code--row=\"\"> <br \/><span class=\"token function\">  transform<\/span><span class=\"token punctuation\">(<\/span>reportData<span class=\"token operator\">:<\/span> <span class=\"token builtin\">any<\/span><span class=\"token punctuation\">,<\/span> chartType<span class=\"token operator\">?<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> report<span class=\"token operator\">?<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">any<\/span><span class=\"token punctuation\">)<\/span><span class=\"token operator\">:<\/span> <span class=\"token builtin\">any<\/span><span class=\"token punctuation\">;<\/span> <br \/><\/span><span class=\"\" data-testid=\"renderer-code-block-line-14\" data-ds--code--row=\"\"><span class=\"token punctuation\">}<\/span><\/span><\/pre><p data-renderer-start-pos=\"1693\" data-local-id=\"af3f1adf56e1\">Each chart transformer implements this interface:<\/p><pre>@Injectable({ providedIn: 'root' })<br \/>export class BarChartTransformer implements IChartDataTransformer {<br \/>  supports(chartType: string): boolean {<br \/>    \/\/ Returns true for bar chart type identifiers<br \/>  }<br \/><br \/>  transform(reportData: any): any[] {<br \/>    \/\/ Analyzes dimensions and metrics<br \/>    \/\/ Normalizes data structure<br \/>    \/\/ Applies color mapping<br \/>    \/\/ Enriches with metadata<br \/>    \/\/ Returns chart library-compatible format<br \/>  }<br \/>}<\/pre><h3 id=\"What-Each-Strategy-Handles\" data-local-id=\"e67c41cfef31\" data-renderer-start-pos=\"2168\">What Each Strategy Handles<\/h3><p data-renderer-start-pos=\"2196\" data-local-id=\"a022bc4bc95c\">Each transformation strategy encapsulates logic specific to a chart type. This includes:<\/p><ul class=\"ak-ul\" data-local-id=\"ffe16b8e-b450-4f28-be3b-004eb97f39ed\" data-indent-level=\"1\"><li><p data-renderer-start-pos=\"2288\" data-local-id=\"9e866dbe518d\"><strong data-renderer-mark=\"true\">Dimensional Analysis<\/strong>: Understanding how many dimensions and metrics are present, which affects grouping and series construction.<\/p><\/li><li><p data-renderer-start-pos=\"2420\" data-local-id=\"3a70933e85fe\"><strong data-renderer-mark=\"true\">Data Normalisation<\/strong>: Converting the consistent API response into the structure expected by the specific visualization library (e.g., row-based data for ag-Grid vs. series-based arrays for Highcharts).<\/p><\/li><li><p data-renderer-start-pos=\"2623\" data-local-id=\"1ad48e333646\"><strong data-renderer-mark=\"true\">Metadata Enrichment<\/strong>: Adding display labels, tooltip formatting, and contextual information derived from report metadata.<\/p><\/li><\/ul><p data-renderer-start-pos=\"2747\" data-local-id=\"570d0141ee36\"><em data-renderer-mark=\"true\"><strong data-renderer-mark=\"true\"><sub data-renderer-mark=\"true\">*Quick note: <\/sub><\/strong><sub data-renderer-mark=\"true\">At first glance this example may appear to violate the Single Responsibility Principle. But in practice, the transformer acts as an orchestration layer, delegating specific responsibilities to dedicated services. The example is simplified to keep the focus on the pattern rather than internal service composition.<\/sub><\/em><\/p><h3 id=\"Benefits\" data-local-id=\"a01d9ce39418\" data-renderer-start-pos=\"3075\">Benefits<\/h3><ul class=\"ak-ul\" data-local-id=\"e4a993de-8a97-41a6-ba1c-17fee495bd86\" data-indent-level=\"1\"><li><p data-renderer-start-pos=\"3087\" data-local-id=\"e4ab746f369c\">Each chart type can evolve independently without impacting others.<\/p><\/li><li><p data-renderer-start-pos=\"3157\" data-local-id=\"ea4959bbbcac\">Adding support for a new chart only requires implementing the interface\u2014no changes to existing code.<\/p><\/li><li><p data-renderer-start-pos=\"3261\" data-local-id=\"415fa09266fd\">Clear separation of concerns between transformation and rendering logic.<\/p><\/li><li><p data-renderer-start-pos=\"3337\" data-local-id=\"1e1e9466e0f5\">High testability: each strategy can be unit-tested in isolation using mock datasets.<\/p><\/li><\/ul><hr role=\"presentation\" \/><h2 id=\"Factory-Pattern:-Selecting-the-Right-Transformer\" data-local-id=\"beabc8041b73\" data-renderer-start-pos=\"3426\">Factory Pattern: Selecting the Right Transformer<\/h2><p data-renderer-start-pos=\"3476\" data-local-id=\"43f16b6c9f94\">While strategies encapsulate transformation logic, we still need a mechanism to select the correct strategy at runtime. This is where the Factory pattern comes into play.<\/p><p data-renderer-start-pos=\"3648\" data-local-id=\"075ef0fbc53d\">The factory acts as a lookup mechanism that returns the appropriate transformer based on the chart type, without consumers needing to know about concrete implementations.<\/p><h3 id=\"Structure.1\" data-local-id=\"2d5dbbb9b494\" data-renderer-start-pos=\"3820\">Structure<\/h3><p data-renderer-start-pos=\"3831\" data-local-id=\"57936ac4e4ca\">We use an injection token to collect all chart transformers, and a factory that receives them via dependency injection:<\/p><div class=\"ak-renderer-sticky-safe-breakout-wrapper ak-renderer-flex-center-wrapper css-l5clsc\"><div class=\"ak-renderer-sticky-safe-breakout-inner fabric-editor-breakout-mark fabric-editor-block-mark css-ozd7xs\" data-mode=\"wide\" data-has-width=\"true\" data-width=\"760\"><div class=\"code-block css-1l9rc3g\" data-local-id=\"49c78b5da0cb\"><div class=\"css-1sws8jd\"><div class=\"css-1u47mc9\"><div role=\"presentation\"><pre>\/\/ Injection token for collecting all chart transformers<br \/>export const CHART_TRANSFORMERS = new InjectionToken<IChartDataTransformer[]>(<br \/>  'CHART_TRANSFORMERS'<br \/>);<br \/><br \/>@Injectable({ providedIn: 'root' })<br \/>export class ChartDataTransformerFactory {<br \/>  constructor(@Inject(CHART_TRANSFORMERS) private transformers: IChartDataTransformer[]) {}<br \/><br \/>  getTransformer(chartType: string): IChartDataTransformer | null {<br \/>    \/\/ Searches for transformer where supports() returns true<br \/>    \/\/ This is the common method enforced by the interface defined earlier.<br \/>    return this.transformers.find(t => t.supports(chartType)) || null;<br \/>    \/\/ Returns null if no transformer found<br \/>  }<br \/>}<\/pre><\/div><\/div><\/div><\/div><\/div><\/div><p data-renderer-start-pos=\"4615\" data-local-id=\"36da5488bd92\">The factory is used by the main chart data transformer (which participates in the pipeline we'll see in the next post):<\/p><div class=\"ak-renderer-sticky-safe-breakout-wrapper ak-renderer-flex-center-wrapper css-l5clsc\"><div class=\"ak-renderer-sticky-safe-breakout-inner fabric-editor-breakout-mark fabric-editor-block-mark css-ozd7xs\" data-mode=\"wide\" data-has-width=\"true\" data-width=\"760\"><div class=\"code-block css-1l9rc3g\" data-local-id=\"824a1bbc604e\"><div class=\"css-1sws8jd\"><div class=\"css-1u47mc9\"><div role=\"presentation\"><pre>@Injectable({ providedIn: 'root' })<br \/>export class ChartDataTransformer implements ReportTransformer {<br \/>  constructor(private chartFactory: ChartDataTransformerFactory) {}<br \/><br \/>  isApplicable(reportData: any): boolean {<br \/>    \/\/ Checks for visualization type presence<br \/>  }<br \/><br \/>  apply(reportData: any): any[] {<br \/>    \/\/ Gets transformer from factory<br \/>    const transformer = this.chartFactory.getTransformer(..);<br \/>    if (!transformer) {<br \/>      \/\/ Throws error if no transformer found<br \/>    }<br \/>    \/\/ Delegates to transformer's transform method<br \/>    \/\/ Remember, this is part of the contract defined earlier in strategy pattern.<br \/>    return transformer.transform(..);<br \/>  }<br \/>}<\/pre><\/div><\/div><\/div><\/div><\/div><\/div><h3 id=\"How-It-Fits-Together\" data-local-id=\"b5af5d52f748\" data-renderer-start-pos=\"5388\">How It Fits Together<\/h3><p data-renderer-start-pos=\"5410\" data-local-id=\"4957761eb864\">The factory leverages Angular's dependency injection to receive all registered transformers via the custom token. It acts as a <strong data-renderer-mark=\"true\">registry and lookup<\/strong>: it doesn't create transformers itself\u2014they're registered elsewhere (we'll see that in future post). The factory is completely decoupled from specific transformer implementations.<\/p><p data-renderer-start-pos=\"5738\" data-local-id=\"dc428d579675\">When no transformer is found for a given chart type, the system throws a descriptive error rather than returning null. This fail-fast approach ensures bugs are caught early and production errors are clearly identifiable.<\/p><h3 id=\"Benefits.1\" data-local-id=\"e1e266f1a158\" data-renderer-start-pos=\"5960\">Benefits<\/h3><ul class=\"ak-ul\" data-local-id=\"232d237aa9f0\" data-indent-level=\"1\"><li><p data-renderer-start-pos=\"5972\" data-local-id=\"31eb25781992\">Decouples transformer selection from usage\u2014consumers don't need to know which transformer to use<\/p><\/li><li><p data-renderer-start-pos=\"6072\" data-local-id=\"1eac0b7457f9\">Centralised logic for transformer selection\u2014single point of control<\/p><\/li><li><p data-renderer-start-pos=\"6143\" data-local-id=\"a4dec730abd1\">Easy to extend with new chart types\u2014just register a new transformer<\/p><\/li><li><p data-renderer-start-pos=\"6214\" data-local-id=\"0e8b70bfddcd\">Type-safe transformer selection through TypeScript interfaces<\/p><\/li><li><p data-renderer-start-pos=\"6279\" data-local-id=\"4247f3994b02\">Strategies are unaware of each other; the factory is unaware of strategy implementation details<\/p><\/li><\/ul><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/article><\/div><\/div>","_et_gb_content_width":"","content-type":"","_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[11,13,75],"tags":[],"class_list":["post-35398","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blogs","category-news","category-tech"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &amp; Factory Patterns - Streamhub.co.uk<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &amp; Factory Patterns - Streamhub.co.uk\" \/>\n<meta property=\"og:description\" content=\"Reading Time:  4 minutesIn the previous post, we introduced the architectural challenges behind supporting multiple chart types within a scalable dashboard solution. We established that different visualisation libraries require data in different structural formats. This mismatch makes the transformation layer a critical part of the architecture. In this post, we focus on how the system decides which transformation [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/\" \/>\n<meta property=\"og:site_name\" content=\"Streamhub.co.uk\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-30T09:07:55+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-30T09:28:43+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2336\" \/>\n\t<meta property=\"og:image:height\" content=\"1512\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Ravindra Soman\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ravindra Soman\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/\"},\"author\":{\"name\":\"Ravindra Soman\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#\\\/schema\\\/person\\\/337c25120a9db280c15bf34b2faec85a\"},\"headline\":\"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &#038; Factory Patterns\",\"datePublished\":\"2026-03-30T09:07:55+00:00\",\"dateModified\":\"2026-03-30T09:28:43+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/\"},\"wordCount\":838,\"publisher\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/streamhub.co.uk\\\/wp-content\\\/uploads\\\/2023\\\/12\\\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png\",\"articleSection\":[\"Blogs\",\"News\",\"Tech\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/\",\"url\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/\",\"name\":\"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy & Factory Patterns - Streamhub.co.uk\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/streamhub.co.uk\\\/wp-content\\\/uploads\\\/2023\\\/12\\\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png\",\"datePublished\":\"2026-03-30T09:07:55+00:00\",\"dateModified\":\"2026-03-30T09:28:43+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#primaryimage\",\"url\":\"https:\\\/\\\/streamhub.co.uk\\\/wp-content\\\/uploads\\\/2023\\\/12\\\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png\",\"contentUrl\":\"https:\\\/\\\/streamhub.co.uk\\\/wp-content\\\/uploads\\\/2023\\\/12\\\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png\",\"width\":2336,\"height\":1512},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/streamhub.co.uk\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &#038; Factory Patterns\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#website\",\"url\":\"https:\\\/\\\/streamhub.co.uk\\\/\",\"name\":\"Streamhub.co.uk\",\"description\":\"Streamhub.co.uk\",\"publisher\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/streamhub.co.uk\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#organization\",\"name\":\"Streamhub.co.uk\",\"url\":\"https:\\\/\\\/streamhub.co.uk\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/streamhub.co.uk\\\/wp-content\\\/uploads\\\/2020\\\/05\\\/SH-Logo.png\",\"contentUrl\":\"https:\\\/\\\/streamhub.co.uk\\\/wp-content\\\/uploads\\\/2020\\\/05\\\/SH-Logo.png\",\"width\":1397,\"height\":361,\"caption\":\"Streamhub.co.uk\"},\"image\":{\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.linkedin.com\\\/company\\\/3006156\\\/admin\\\/feed\\\/posts\\\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/streamhub.co.uk\\\/#\\\/schema\\\/person\\\/337c25120a9db280c15bf34b2faec85a\",\"name\":\"Ravindra Soman\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a071c552a8b71da6cc43160899d11d9ebd32c357f7b566d4df2179d088c43704?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a071c552a8b71da6cc43160899d11d9ebd32c357f7b566d4df2179d088c43704?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a071c552a8b71da6cc43160899d11d9ebd32c357f7b566d4df2179d088c43704?s=96&d=mm&r=g\",\"caption\":\"Ravindra Soman\"},\"description\":\"Senior Full Stack Engineer specialising in frontend architecture and complex data visualisation systems. He currently works on architecting visualisation solutions at Streamhub, focusing on modular frontend design.\",\"sameAs\":[\"https:\\\/\\\/www.linkedin.com\\\/in\\\/somanravindra\\\/\"],\"url\":false}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy & Factory Patterns - Streamhub.co.uk","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/","og_locale":"en_US","og_type":"article","og_title":"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy & Factory Patterns - Streamhub.co.uk","og_description":"Reading Time:  4 minutesIn the previous post, we introduced the architectural challenges behind supporting multiple chart types within a scalable dashboard solution. We established that different visualisation libraries require data in different structural formats. This mismatch makes the transformation layer a critical part of the architecture. In this post, we focus on how the system decides which transformation [&hellip;]","og_url":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/","og_site_name":"Streamhub.co.uk","article_published_time":"2026-03-30T09:07:55+00:00","article_modified_time":"2026-03-30T09:28:43+00:00","og_image":[{"width":2336,"height":1512,"url":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png","type":"image\/png"}],"author":"Ravindra Soman","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Ravindra Soman","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#article","isPartOf":{"@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/"},"author":{"name":"Ravindra Soman","@id":"https:\/\/streamhub.co.uk\/#\/schema\/person\/337c25120a9db280c15bf34b2faec85a"},"headline":"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &#038; Factory Patterns","datePublished":"2026-03-30T09:07:55+00:00","dateModified":"2026-03-30T09:28:43+00:00","mainEntityOfPage":{"@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/"},"wordCount":838,"publisher":{"@id":"https:\/\/streamhub.co.uk\/#organization"},"image":{"@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#primaryimage"},"thumbnailUrl":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png","articleSection":["Blogs","News","Tech"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/","url":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/","name":"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy & Factory Patterns - Streamhub.co.uk","isPartOf":{"@id":"https:\/\/streamhub.co.uk\/#website"},"primaryImageOfPage":{"@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#primaryimage"},"image":{"@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#primaryimage"},"thumbnailUrl":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png","datePublished":"2026-03-30T09:07:55+00:00","dateModified":"2026-03-30T09:28:43+00:00","breadcrumb":{"@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#primaryimage","url":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png","contentUrl":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png","width":2336,"height":1512},{"@type":"BreadcrumbList","@id":"https:\/\/streamhub.co.uk\/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/streamhub.co.uk\/"},{"@type":"ListItem","position":2,"name":"Architecting Scalable Chart Modules in Streamhub Analytics: Part II \u2013 Strategy &#038; Factory Patterns"}]},{"@type":"WebSite","@id":"https:\/\/streamhub.co.uk\/#website","url":"https:\/\/streamhub.co.uk\/","name":"Streamhub.co.uk","description":"Streamhub.co.uk","publisher":{"@id":"https:\/\/streamhub.co.uk\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/streamhub.co.uk\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/streamhub.co.uk\/#organization","name":"Streamhub.co.uk","url":"https:\/\/streamhub.co.uk\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/streamhub.co.uk\/#\/schema\/logo\/image\/","url":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2020\/05\/SH-Logo.png","contentUrl":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2020\/05\/SH-Logo.png","width":1397,"height":361,"caption":"Streamhub.co.uk"},"image":{"@id":"https:\/\/streamhub.co.uk\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.linkedin.com\/company\/3006156\/admin\/feed\/posts\/"]},{"@type":"Person","@id":"https:\/\/streamhub.co.uk\/#\/schema\/person\/337c25120a9db280c15bf34b2faec85a","name":"Ravindra Soman","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/a071c552a8b71da6cc43160899d11d9ebd32c357f7b566d4df2179d088c43704?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/a071c552a8b71da6cc43160899d11d9ebd32c357f7b566d4df2179d088c43704?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a071c552a8b71da6cc43160899d11d9ebd32c357f7b566d4df2179d088c43704?s=96&d=mm&r=g","caption":"Ravindra Soman"},"description":"Senior Full Stack Engineer specialising in frontend architecture and complex data visualisation systems. He currently works on architecting visualisation solutions at Streamhub, focusing on modular frontend design.","sameAs":["https:\/\/www.linkedin.com\/in\/somanravindra\/"],"url":false}]}},"views":14,"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/streamhub.co.uk\/wp-content\/uploads\/2023\/12\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2023-12-07-15.40.23.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/posts\/35398","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/comments?post=35398"}],"version-history":[{"count":17,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/posts\/35398\/revisions"}],"predecessor-version":[{"id":35425,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/posts\/35398\/revisions\/35425"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/media\/34720"}],"wp:attachment":[{"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/media?parent=35398"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/categories?post=35398"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/streamhub.co.uk\/wp-json\/wp\/v2\/tags?post=35398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}