{"id":42490870,"url":"https://github.com/lguillaud/osd_transform_vis","last_synced_at":"2026-01-28T12:14:34.430Z","repository":{"id":45101809,"uuid":"416247693","full_name":"lguillaud/osd_transform_vis","owner":"lguillaud","description":"OpenSearch-Dashboards plugin to create custom visualisations","archived":false,"fork":false,"pushed_at":"2025-11-26T14:37:39.000Z","size":4107,"stargazers_count":25,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"2.x","last_synced_at":"2025-11-29T11:48:33.603Z","etag":null,"topics":["javascript","opensearch","opensearch-dashboards","plugin","visualization"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lguillaud.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-10-12T08:23:48.000Z","updated_at":"2025-08-13T14:25:10.000Z","dependencies_parsed_at":"2024-04-25T14:56:37.487Z","dependency_job_id":"13d11487-094b-40d6-b7e0-e03e3c902c24","html_url":"https://github.com/lguillaud/osd_transform_vis","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/lguillaud/osd_transform_vis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lguillaud%2Fosd_transform_vis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lguillaud%2Fosd_transform_vis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lguillaud%2Fosd_transform_vis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lguillaud%2Fosd_transform_vis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lguillaud","download_url":"https://codeload.github.com/lguillaud/osd_transform_vis/tar.gz/refs/heads/2.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lguillaud%2Fosd_transform_vis/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28845107,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T10:53:21.605Z","status":"ssl_error","status_checked_at":"2026-01-28T10:53:20.789Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","opensearch","opensearch-dashboards","plugin","visualization"],"created_at":"2026-01-28T12:14:33.825Z","updated_at":"2026-01-28T12:14:34.421Z","avatar_url":"https://github.com/lguillaud.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Credits\n\u003e Forked (ported) from https://github.com/gwintzer/transform_vis and https://github.com/bjacomy/transform_vis* - Special thanks to these contributors who kept and are still keeping this plugin working for all Kibana versions.\n\n## Transform plugin\n\nAn Opensearch visualization plugin that allows arbitrary queries results to be processed by a [Mustache](https://mustache.github.io/) transform.\nYou can also call any external JS library to build new visualisations: Google Chart, d3js, Apache Echart...\n\n**[See OpenSearch Community Projects](https://opensearch.org/community_projects)**\n\n* [Prerequisites](#prerequisites)\n* [Installation](#installation)\n* [Plugin presentation](#plugin-presentation)\n* [Visualisation samples](#visualisation-samples)\n\n## Prerequisites\n\n### OpenSearch Dashboards\n\n#### Client side\n\nOn the OpenSearch Dashboards client side (**Stack Management \u003e Advanced Settings**), enable the `state:storeInSessionStorage` option in order to avoid the following error message: *\"The URL for this object is too long, and we can't display it\"*.\n\n#### Server side\n\nBy default, no unsafe HTML (such as `\u003cstyle\u003e` tags) will be allowed, but Javascript processing can be achieved by acknowledging the client-side security risk in `opensearch_dashboards.yml`. \nMorevover, access to external libraries can also be allowed by adding the required root URLs in the configuration (the list given below is not exhaustive). \n\nAdd the following lines into your `opensearch_dashboards.yml` file: \n\n```sh\n# server.cors: true is normally the default value but required in case you you need to access Opensearch from JS code (e.g. XHR requests) along with the opensearch configuration next section.\nserver.cors: true\n\ncsp.strict: false\ncsp.warnLegacyBrowsers: false\ncsp.rules:\n  - \"script-src 'unsafe-eval' 'unsafe-inline' https://www.gstatic.com/ https://d3js.org/ https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://cdn.datatables.net/ 'self'\n  - \"worker-src blob: *\"\n  - \"child-src data: * blob: *\"\n```\n\n### OpenSearch (optional)\n\nThe configuration for Opensearch (server side) in only required in case you need to access Opensearch from JS code (e.g. XHR requests).\n\nAdd the following lines into your `opensearch.yml` file:\n\n```sh\nhttp.cors.enabled : true\nhttp.cors.allow-origin: \"*\"\nhttp.cors.allow-methods : OPTIONS, HEAD, GET, POST, PUT, DELETE\nhttp.cors.allow-headers: \"kbn-version, Origin, X-Requested-With, Content-Type, Accept, Engaged-Auth-Token Authorization\"\n```\n\n## Installation\n\nThe installation command is given for version `1.1.0`:\n\n```sh\nbin/opensearch-dashboards-plugin install https://github.com/lguillaud/osd_transform_vis/releases/download/1.1.0/transformVis-1.1.0.zip\n```\n\nBy running this command, a new visualization `transform` will be available when using the Visualize module in OpenSearch Dashboards.\n\n![Transform visualisation](./images/visualize_transform.png)\n\n## Plugin presentation\n\nThe plugin is split into 2 parts:\n\n* a development window with 3 different tabs: Multi Query DSL, Javascript and Template\n* a visualisation window\n\n![Plugin aspect](./images/transform_plugin.png)\n\n### Basic usage\n\nThe object passed to the mustache template looks like this:\n\n```sh\n{\n  response: { .. } // The Query Response exactly as returned from OpenSearch\n  context: { .. } // The dashboard context (contains filters, query string, time range)\n  meta: { .. } // Your Javascript Object\n}\n```\n\nFrom mustache, you can access these variables directly.\nFrom the Javascript object, they are available via `this`, e.g., `this.response`.\n\n`\u003cscript\u003e` tags will not be evaluated.  \n\nAny Javascript given will be executed by the web browser, however in order to be merged with the query response object for processing by Mustache, you must prepare an Object, enclosed by parentheses.\n\n\nFunctions called by mustache are executed before the actual render on the page, so no DOM manipulation can be done.   \nThe `before_render` and `after_render` lifecycle hooks will be called automatically. The former can be used for any pre-processing that might be required before rendering, and the latter should be used for anything that expects the HTML to be rendered.\n\n\n### Example to display single value\n\nJavascript\n```sh\n({\n some_var: 42,\n count_hits: function() {\n  return this.response.hits.total;\n }\n})\n```\n\nTemplate\nNamed functions can then be called by mustache, like:\n```\n\u003chr\u003e{{meta.count_hits}} total hits\u003chr\u003e\n```\n\n### Example to display Bootstrap table\n\nMulti Query DSL\n```\n{\n  \"sample\": {\n    \"index\": \"opensearch_dashboards_sample_data_ecommerce\",\n    \"query\": {\n      \"bool\": {\n        \"must\": [\n          \"_DASHBOARD_CONTEXT_\",\n          \"_TIME_RANGE_[order_date]\"\n        ]\n      }\n    },\n    \"aggs\": {\n      \"customer\": {\n        \"terms\": {\n          \"field\": \"customer_full_name.keyword\",\n            \"size\": 5\n        }\n      }\n    }\n  }\n}\n```\n\nJavascript \n```\n({\n  ecommerce: function() {\n    return this.response.sample.aggregations.customer.buckets;\n  }\n})\n```\n\nTemplate\n```\n\u003clink rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css\" integrity=\"sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm\" crossorigin=\"anonymous\"\u003e\n\n\u003ch2\u003eList of best buyers from response\u003c/h2\u003e\n\u003ctable class=\"table caption-top\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth scope=\"col\"\u003eFull name\u003c/th\u003e\n      \u003cth scope=\"col\"\u003eNumber or orders\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    {{#response.sample.aggregations.customer.buckets}} \n    \u003ctr\u003e\n      \u003cth scope=\"row\"\u003e{{key}}\u003c/th\u003e\n      \u003ctd\u003e{{doc_count}}\u003c/td\u003e\n    \u003c/tr\u003e\n    {{/response.sample.aggregations.customer.buckets}} \n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2\u003eList of best buyers from function\u003c/h2\u003e\n\u003ctable class=\"table caption-top\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth scope=\"col\"\u003eFull name\u003c/th\u003e\n      \u003cth scope=\"col\"\u003eNumber or orders\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e \n  \u003ctbody\u003e\n    {{#meta.ecommerce}} \n    \u003ctr\u003e\n      \u003cth scope=\"row\"\u003e{{key}}\u003c/th\u003e\n      \u003ctd\u003e{{doc_count}}\u003c/td\u003e\n    \u003c/tr\u003e\n    {{/meta.ecommerce}} \n    \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\nResult\n![table](./images/sample_table.png)\n\n#### Support for CSS\n\n`\u003cstyle\u003e` tags can be embedded in the mustache template.\n\nAnother to load css files is to use a JS function (e.g. for FontAwesome):\n\nJavascript\n\n```sh\ncss: function() {  \n      const css_list = [\"https://use.fontawesome.com/releases/v5.6.3/css/all.css\"];  \n      for (let css_file of css_list) {\n          const links = document.head.querySelectorAll(\"link\");\n\n          // Already been injected\n          for(let l in links)\n            if(links[l].href == css_file) return;\n            const link = document.createElement('link');\n            link.rel = \"stylesheet\";\n            link.href = css_file;\n            document.head.appendChild(link);\n        } \n}\n```\n\n#### Query DSL\n\n`\"_DASHBOARD_CONTEXT_\"`, including the surrounding quotes, will be replaced by a bool query that represents the filter/search state of the dashboard in context.  \n`\"_TIME_RANGE_[\u003cdate field name\u003e]\" ` , including the surrounding quotes, will be replaced by a date range query that represents the date/time filter state of the dashboard in context.\n\nIt is allowed to use multiple named queries.\n\nThis example is given to demonstrate how the dashboard context can co-exist with your own query clauses, but you are not restricted to this format.\n\nThe following code produces multi-level bool statement:\n\nQuery DSL\n```\n{\n  \t\"ordersPerDay\": {\n   \t\t\"index\": \"opensearch_dashboards_sample_data_ecommerce\",\n \t\t\"query\": {\n\t\t\t\"bool\": {\n   \t\t\t\t\"must\": [\n\t    \t\t\t\"_DASHBOARD_CONTEXT_\",\n     \t\t\t\t\"_TIME_RANGE_[order_date]\"\n     \t\t\t\t.. your must clause(s) can go here ..\n   \t\t\t\t], \n   \t\t\t\t\"should\": [\n\t     \t\t.. your should clause(s) can go here ..\n   \t\t\t\t]\n  \t\t\t}\n \t\t}\n\t}\n```\n\n#### Debugging\n\nYou can dump the response object to a `\u003cpre\u003e` tag in your visualization output while testing, for convenience.\n\nJavascript\n```\n({\n count_hits: function() {\n  return this.response.hits.hits.length;\n },\n debug: function() {\n  return JSON.stringify(this, null, ' ');\n } \n})\n```\n\nTemplate\n\n```\n\u003chr\u003e\n  {{response.hits.total}} total hits\u003cBR\u003e\n  {{meta.count_hits}} returned hits\u003cBR\u003e\n\u003chr\u003e\n\n\u003cpre\u003e\n{{meta.debug}}\n\u003c/pre\u003e\n```\n### Advanced usage\n\nThe transform plugin makes use of a shadow DOM for the visualisation. This DOM is located below the div of which id is `output-vis`. \nShadow DOM is a DOM feature that helps you build components. You can think of shadow DOM as a scoped subtree inside your element. See [here](https://polymer-library.polymer-project.org/2.0/docs/devguide/shadow-dom) for more details.\nWe then use [the shadowRoot command](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot) in order to access the shadow DOM and manipulate it.\n\nThis DOM will be used when you need to change the DOM from the javascript window which will be the case when using external librairies for visualisations (e.g. Google Chart): in that specific case, you should be using the `after_render` lifecycle hook.\nWhen used in an OpenSearch Dashboard dashboard, we need to make sure we access the right shadow DOM in the case we have multiple transform visualisations. Make sure to always use unique IDs for HTML tag.\n\nTo do so, here are the javascript lines in charge of getting the right DOM. In this example, we need to find out where the `viz` ID is located in the shadow DOM in order to pass the location to the D3js function.\nA dedicated function will be used to retrieve the shadow DOM information.\n\nJavascript\n```sh\n({\ngetShadowDomLocation: function(selector) {\n  let vizLocation;\n  // output-viz being the top selector used by the Transform plugin\n  // Get all the output-viz elements (can have mutliple selector if multiple transfrm vizs in a dashboard)\n  // selector parameter value must be unque within the DOM\n  const elements = $('.output-vis');\n  let shadow;\n  for (let elem of elements) {\n      shadow = elem.shadowRoot;\n      vizLocation = $(shadow).find(selector);\n      if (vizLocation.length \u003e 0) {\n      // selector found, exiting\n        break;\n      } else {\n        vizLocation = '.notFound';\n      }\n  } \n  const obj = {\n    vizLocation: vizLocation,\n    shadowRoot: shadow\n    }\n// obj object contains the shadowRoot element and and the location of the selector within the shadowRoot\n  return obj;\n},\n\nafter_render: function() {\n  // Save this.meta Object for further use\n  // Will be used to call the getShadowDomLocation function\n  const meta = this.meta;\n  \n  function displayCircle() {\n    // Get shadow DOM\n    let ctxRadar = meta.getShadowDomLocation(\"#viz\").vizLocation[0];\n    \n    // Clear ctxRadar to avoid multiple circles\n    $(ctxRadar).empty();\n    \n    let sampleSVG = d3.select(ctxRadar)\n      .append(\"svg\")\n      .attr(\"width\", 100)\n      .attr(\"height\", 100);    \n    \n    sampleSVG.append(\"circle\") \n      .style(\"stroke\", \"gray\")  \n      .style(\"fill\", \"white\")\n      .attr(\"r\", 40)\n      .attr(\"cx\", 50)\n      .attr(\"cy\", 50)\n      .on(\"mouseover\", function(){d3.select(this).style(\"fill\", \"aliceblue\");})\n      .on(\"mouseout\", function(){d3.select(this).style(\"fill\", \"white\");});\n  }\n  \n  $.getScript(\"https://d3js.org/d3.v3.min.js\")\n    .done(function(script, textStatus) {\n      displayCircle();\n  });\n\n}\n}) \n```\n\nTemplate\n```\n\u003cdiv id=\"viz\"\u003e \u003c/div\u003e\n```\n\n\n\n\n## Visualisation samples\n\nA new directory sample_vizs is now available with the following visualisations:\n\n* sample_decisionTree.ndjson: Decision tree with D3js lib (https://fractalytics.io/visualization-scikit-learn-decision-trees-d3-js)\n* sample_googleChartGauge.ndjson: Gauge with Google Chart (requires sample data `opensearch_dashboards_sample_data_ecommerce`). Also need to change time filter to \"Last 1 year\" as set to \"last 15 minutes\" by default, otherwise you will get an *Error (See Console)* on the visualisation window. \n* sample_googleChartScatterAndControlIframe.ndjson: Scatter chart with Goofle Chart byt using Iframe (might be required in a few cases when there are JS librairies conflicts)\n* sample_sankey.ndjson: Sankey with D3js lib (https://www.d3-graph-gallery.com/graph/sankey_basic.html)\n* sample_saveCSVToOpensearch.ndjson: Import CSV file into an OpenSearch index \n* sample_map_openstreetmap_leaflet.ndjson: Openstreetmap with leaflet\n* sample_map_openstreetmap_leaflet_custom_geojson.ndjson: Openstreetmap with leaflet and custom GeoJSON layer (french departments)\n* sample_boostedOrangeSampleIframe.ndjson: sample accordion component from Orange Boosted (within Iframe) (https://boosted.orange.com/)\n* sample_sankey_with_apache_echart.ndjson: sample sankey using the Apache Echart library (https://echarts.apache.org/examples/en/editor.html?c=sankey-levels)\n* sample_tabulator.ndjson: sample table with enriched content (https://tabulator.info/)\n\n**How to load ndjson files ?** Open OpenSearch Dashboards \u003e Stack Management \u003e Saved Objects \u003e Import \u003e Select ndjson file to import \u003e Click Import\n\n**How to open the samples once loaded ?** Open OpenSearch Dashboards \u003e Visualize \u003e Click on the visualization you just imported\n\n\u003e Note: Most of the examples do not have a query to get data from Opensearch as data used are hardcoded in the Javascript part but you might need to import sample data for some visualisations.\n\nTo import sample data: go to OpenSearch Dashboards \u003e Click on the logo on the top left corner \u003e Ingest your data \u003e Add data \u003e Choose sample data to add and click `Add Data`.\n\n###  Decision tree\n\n![Decision tree](./images/decision_tree.png)\n\n### Gauge\n\n![Gauge](./images/gauge.png)\n\n### Scatter chart and control\n\n![Scatter chart](./images/scatter.png)\n\n### Sankey\n\n![Sankey](./images/sankey.png)\n\n### CSV import (pure JS)\n![CSV import](./images/importcsv.png)\n\n### Openstreetmap\n![Openstreetmap with leaflet](./images/openstreetmap.png)\n\n### Openstreetmap with custom GeoJSON layer\n![Openstreetmap with custom GeoJSON layer with leaflet](./images/openstreetmap_custom_geojson.png)\n\n### Boosted sample\n![OrangeBoosted accordion sample](./images/boosted.png)\n\n### Apache Echart Sankey sample\n![Apache Echart Sankey sample](./images/apacheEchartSankey.png)\n\n### Tabulator sample\n![Tabulator sample](./images/tabulator.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flguillaud%2Fosd_transform_vis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flguillaud%2Fosd_transform_vis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flguillaud%2Fosd_transform_vis/lists"}